Skip to content

Commit fc56dc4

Browse files
feat: add tenant parameter to list_instance_* methods (#73)
1 parent 1b502db commit fc56dc4

18 files changed

Lines changed: 230 additions & 16 deletions

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "sap-cloud-sdk"
3-
version = "0.11.2"
3+
version = "0.11.3"
44
description = "SAP Cloud SDK for Python"
55
readme = "README.md"
66
license = "Apache-2.0"

src/sap_cloud_sdk/destination/_local_client_base.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,23 @@ def find_provider() -> Optional[T]:
217217
return result
218218
return None
219219

220+
def _resolve_instance_list(
221+
self,
222+
tenant: Optional[str],
223+
instance_list: List[Dict[str, Any]],
224+
) -> List[T]:
225+
"""Resolve entities from the instance list filtered by tenant.
226+
227+
When tenant is provided, returns entries matching that tenant (subscriber context).
228+
When tenant is None, returns entries without a tenant field (provider context).
229+
"""
230+
if tenant is not None:
231+
return [
232+
self.from_dict(e) for e in instance_list if e.get("tenant") == tenant
233+
]
234+
235+
return [self.from_dict(e) for e in instance_list if not e.get("tenant")]
236+
220237
def _resolve_subaccount_list(
221238
self,
222239
access_strategy: AccessStrategy,

src/sap_cloud_sdk/destination/certificate_client.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,14 @@ def __init__(self, http: DestinationHttp) -> None:
7979
)
8080
def list_instance_certificates(
8181
self,
82+
tenant: Optional[str] = None,
8283
filter: Optional[ListOptions] = None,
8384
) -> PagedResult[Certificate]:
8485
"""List all certificates at the service instance level.
8586
8687
Args:
88+
tenant: Optional subscriber tenant subdomain. When provided, the request uses
89+
subscriber context; otherwise the provider context is used.
8790
filter: Optional filter configuration for query parameters.
8891
8992
Returns:
@@ -95,7 +98,9 @@ def list_instance_certificates(
9598
DestinationOperationError: If HTTP error occurs or response parsing fails.
9699
"""
97100
try:
98-
return self._list_certificates(level=Level.SERVICE_INSTANCE, filter=filter)
101+
return self._list_certificates(
102+
level=Level.SERVICE_INSTANCE, tenant_subdomain=tenant, filter=filter
103+
)
99104
except HttpError as e:
100105
raise DestinationOperationError(
101106
f"failed to list instance certificates: {e}"

src/sap_cloud_sdk/destination/client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,15 @@ def set_proxy(self, transparent_proxy: TransparentProxy) -> None:
127127
Module.DESTINATION, Operation.DESTINATION_LIST_INSTANCE_DESTINATIONS
128128
)
129129
def list_instance_destinations(
130-
self, filter: Optional[ListOptions] = None
130+
self,
131+
tenant: Optional[str] = None,
132+
filter: Optional[ListOptions] = None,
131133
) -> PagedResult[Destination]:
132134
"""List all destinations from the service instance scope.
133135
134136
Args:
137+
tenant: Optional subscriber tenant subdomain. When provided, the request uses
138+
subscriber context; otherwise the provider context is used.
135139
filter: Optional filter configuration for pagination, filtering, or metadata inclusion.
136140
137141
Returns:
@@ -144,7 +148,7 @@ def list_instance_destinations(
144148
"""
145149
try:
146150
return self._list_destinations(
147-
level=Level.SERVICE_INSTANCE, tenant_subdomain=None, filter=filter
151+
level=Level.SERVICE_INSTANCE, tenant_subdomain=tenant, filter=filter
148152
)
149153
except HttpError as e:
150154
raise DestinationOperationError(

src/sap_cloud_sdk/destination/fragment_client.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,14 @@ def get_subaccount_fragment(
130130
@record_metrics(Module.DESTINATION, Operation.FRAGMENT_LIST_INSTANCE_FRAGMENTS)
131131
def list_instance_fragments(
132132
self,
133+
tenant: Optional[str] = None,
133134
filter: Optional[ListOptions] = None,
134135
) -> List[Fragment]:
135136
"""List all fragments from the service instance scope.
136137
137138
Args:
139+
tenant: Optional subscriber tenant subdomain. When provided, the request uses
140+
subscriber context; otherwise the provider context is used.
138141
filter: Optional filter configuration for label filtering.
139142
140143
Returns:
@@ -144,7 +147,9 @@ def list_instance_fragments(
144147
DestinationOperationError: If an HTTP error occurs or response parsing fails.
145148
"""
146149
try:
147-
return self._list_fragments(level=Level.SERVICE_INSTANCE, filter=filter)
150+
return self._list_fragments(
151+
level=Level.SERVICE_INSTANCE, tenant_subdomain=tenant, filter=filter
152+
)
148153
except HttpError as e:
149154
raise DestinationOperationError(f"failed to list instance fragments: {e}")
150155

src/sap_cloud_sdk/destination/local_certificate_client.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,17 @@ def patch_certificate_labels(
246246
)
247247

248248
def list_instance_certificates(
249-
self, _filter: Optional[Any] = None
249+
self,
250+
tenant: Optional[str] = None,
251+
_filter: Optional[Any] = None,
250252
) -> PagedResult[Certificate]:
251253
"""List all certificates from the service instance scope.
252254
253255
Args:
254-
filter: Optional ListCertificatesFilter (ignored in local dev mode).
256+
tenant: Optional subscriber tenant subdomain. When provided, returns only entries
257+
matching that tenant (subscriber context); otherwise returns provider-level
258+
entries (no tenant field).
259+
_filter: Optional ListCertificatesFilter (ignored in local dev mode).
255260
256261
Returns:
257262
PagedResult[Certificate] containing certificates and pagination info.
@@ -263,7 +268,7 @@ def list_instance_certificates(
263268
"""
264269
try:
265270
data = self._read()
266-
items = [Certificate.from_dict(entry) for entry in data.get("instance", [])]
271+
items = self._resolve_instance_list(tenant, data.get("instance", []))
267272
return PagedResult(items=items)
268273
except DestinationOperationError:
269274
raise

src/sap_cloud_sdk/destination/local_client.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class LocalDevDestinationClient(LocalDevClientBase[Destination]):
4141
],
4242
"instance": [
4343
{
44+
"tenant": "t1", # optional: subscriber-specific entry
4445
"name": "destC",
4546
"type": "HTTP",
4647
"url": "https://provider.example.com"
@@ -243,12 +244,17 @@ def delete_destination(
243244
)
244245

245246
def list_instance_destinations(
246-
self, _filter: Optional[Any] = None
247+
self,
248+
tenant: Optional[str] = None,
249+
_filter: Optional[Any] = None,
247250
) -> PagedResult[Destination]:
248251
"""List all destinations from the service instance scope.
249252
250253
Args:
251-
filter: Optional ListDestinationsFilter (ignored in local dev mode).
254+
tenant: Optional subscriber tenant subdomain. When provided, returns only entries
255+
matching that tenant (subscriber context); otherwise returns provider-level
256+
entries (no tenant field).
257+
_filter: Optional ListDestinationsFilter (ignored in local dev mode).
252258
253259
Returns:
254260
PagedResult[Destination] containing destinations and pagination info.
@@ -260,7 +266,7 @@ def list_instance_destinations(
260266
"""
261267
try:
262268
data = self._read()
263-
items = [Destination.from_dict(entry) for entry in data.get("instance", [])]
269+
items = self._resolve_instance_list(tenant, data.get("instance", []))
264270
return PagedResult(items=items)
265271
except DestinationOperationError:
266272
raise

src/sap_cloud_sdk/destination/local_fragment_client.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,16 @@ def get_subaccount_fragment(
140140

141141
def list_instance_fragments(
142142
self,
143+
tenant: Optional[str] = None,
143144
_filter: Optional[Any] = None,
144145
) -> List[Fragment]:
145146
"""List all fragments from the service instance scope.
146147
148+
Args:
149+
tenant: Optional subscriber tenant subdomain. When provided, returns only entries
150+
matching that tenant (subscriber context); otherwise returns provider-level
151+
entries (no tenant field).
152+
147153
Returns:
148154
List of fragments. Returns empty list if no fragments exist.
149155
@@ -152,7 +158,7 @@ def list_instance_fragments(
152158
"""
153159
try:
154160
data = self._read()
155-
return [Fragment.from_dict(entry) for entry in data.get("instance", [])]
161+
return self._resolve_instance_list(tenant, data.get("instance", []))
156162
except DestinationOperationError:
157163
raise
158164
except Exception as e:

src/sap_cloud_sdk/destination/user-guide.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ dest = client.get_instance_destination("my-destination")
2727
fragment = fragment_client.get_instance_fragment("my-fragment")
2828
cert = certificate_client.get_instance_certificate("my-cert")
2929

30+
# Instance-level list: provider context (no tenant)
31+
destinations = client.list_instance_destinations()
32+
fragments = fragment_client.list_instance_fragments()
33+
certificates = certificate_client.list_instance_certificates()
34+
35+
# Instance-level list: subscriber context (tenant provided)
36+
destinations = client.list_instance_destinations(tenant="tenant-subdomain")
37+
fragments = fragment_client.list_instance_fragments(tenant="tenant-subdomain")
38+
certificates = certificate_client.list_instance_certificates(tenant="tenant-subdomain")
39+
3040
# Subaccount-level read: provider only (no tenant required)
3141
dest = client.get_subaccount_destination("my-destination", access_strategy=AccessStrategy.PROVIDER_ONLY)
3242
fragment = fragment_client.get_subaccount_fragment("my-fragment", access_strategy=AccessStrategy.PROVIDER_ONLY)
@@ -111,7 +121,7 @@ class DestinationClient:
111121
# V1 Admin API - Read operations for destinations
112122
def get_instance_destination(self, name: str, proxy_enabled: Optional[bool] = None) -> Optional[Destination | TransparentProxyDestination]: ...
113123
def get_subaccount_destination(self, name: str, access_strategy: AccessStrategy = AccessStrategy.SUBSCRIBER_FIRST, tenant: Optional[str] = None, proxy_enabled: Optional[bool] = None) -> Optional[Destination | TransparentProxyDestination]: ...
114-
def list_instance_destinations(self, filter: Optional[ListOptions] = None) -> PagedResult[Destination]: ...
124+
def list_instance_destinations(self, tenant: Optional[str] = None, filter: Optional[ListOptions] = None) -> PagedResult[Destination]: ...
115125
def list_subaccount_destinations(self, access_strategy: AccessStrategy = AccessStrategy.SUBSCRIBER_FIRST, tenant: Optional[str] = None, filter: Optional[ListOptions] = None) -> PagedResult[Destination]: ...
116126

117127
# V1 Admin API - Write operations
@@ -134,7 +144,7 @@ The fragment client produced by `create_fragment_client()` exposes the following
134144
class FragmentClient:
135145
def get_instance_fragment(self, name: str) -> Optional[Fragment]: ...
136146
def get_subaccount_fragment(self, name: str, access_strategy: AccessStrategy = AccessStrategy.SUBSCRIBER_FIRST, tenant: Optional[str] = None) -> Optional[Fragment]: ...
137-
def list_instance_fragments(self, filter: Optional[ListOptions] = None) -> List[Fragment]: ...
147+
def list_instance_fragments(self, tenant: Optional[str] = None, filter: Optional[ListOptions] = None) -> List[Fragment]: ...
138148
def list_subaccount_fragments(self, access_strategy: AccessStrategy = AccessStrategy.SUBSCRIBER_FIRST, tenant: Optional[str] = None, filter: Optional[ListOptions] = None) -> List[Fragment]: ...
139149
def create_fragment(self, fragment: Fragment, level: Optional[Level] = Level.SUB_ACCOUNT, tenant: Optional[str] = None) -> None: ...
140150
def update_fragment(self, fragment: Fragment, level: Optional[Level] = Level.SUB_ACCOUNT, tenant: Optional[str] = None) -> None: ...
@@ -152,7 +162,7 @@ The certificate client produced by `create_certificate_client()` exposes the fol
152162
class CertificateClient:
153163
def get_instance_certificate(self, name: str) -> Optional[Certificate]: ...
154164
def get_subaccount_certificate(self, name: str, access_strategy: AccessStrategy = AccessStrategy.SUBSCRIBER_FIRST, tenant: Optional[str] = None) -> Optional[Certificate]: ...
155-
def list_instance_certificates(self, filter: Optional[ListOptions] = None) -> PagedResult[Certificate]: ...
165+
def list_instance_certificates(self, tenant: Optional[str] = None, filter: Optional[ListOptions] = None) -> PagedResult[Certificate]: ...
156166
def list_subaccount_certificates(self, access_strategy: AccessStrategy = AccessStrategy.SUBSCRIBER_FIRST, tenant: Optional[str] = None, filter: Optional[ListOptions] = None) -> PagedResult[Certificate]: ...
157167
def create_certificate(self, certificate: Certificate, level: Optional[Level] = Level.SUB_ACCOUNT, tenant: Optional[str] = None) -> None: ...
158168
def update_certificate(self, certificate: Certificate, level: Optional[Level] = Level.SUB_ACCOUNT, tenant: Optional[str] = None) -> None: ...

tests/destination/integration/destination.feature

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@ Feature: Destination Service Integration
107107
And the destination "test-list-inst-3" should be in the list
108108
And I clean up all instance destinations
109109

110+
# Scenario: List instance destinations with tenant (subscriber context)
111+
# Given I have multiple instance destinations:
112+
# | name | type | url |
113+
# | test-list-inst-ten-1 | HTTP | https://api1.example.com |
114+
# | test-list-inst-ten-2 | HTTP | https://api2.example.com |
115+
# And I use tenant "1776453780"
116+
# When I create all instance destinations
117+
# Then all destination creations should be successful
118+
# When I list instance destinations with tenant
119+
# Then the list should contain at least 2 destinations
120+
# And the destination "test-list-inst-ten-1" should be in the list
121+
# And the destination "test-list-inst-ten-2" should be in the list
122+
# And I clean up all instance destinations
123+
110124
# Scenario: Create and list subaccount destinations (provider access)
111125
# Given I have multiple subaccount destinations:
112126
# | name | type | url |

0 commit comments

Comments
 (0)