Skip to content

Commit 686acc0

Browse files
krismarcKrzysztof MarciniakKrzysztof Marciniak (external)
authored
Fields support (#248)
* Update entities.py Fields support. https://v3-apidocs.cloudfoundry.org/version/3.197.0/index.html#fields As specified in the API description. Navigable object would contain only data from includes returned by fields query params. * tests + removed additional quotation in entities.py * readme update * poetry corrections --------- Co-authored-by: Krzysztof Marciniak <[email protected]> Co-authored-by: Krzysztof Marciniak (external) <[email protected]>
1 parent d463e7c commit 686acc0

File tree

5 files changed

+274
-0
lines changed

5 files changed

+274
-0
lines changed

README.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,16 @@ By changing the first line only, a single request fetches all the data. The navi
252252
253253
app = client.v3.apps.get("app-guid", include="space.organization")
254254
255+
.. code-block:: python
256+
257+
fields = {
258+
"space": ["guid,name,relationships.organization"],
259+
"space.organization": ["guid","name"]
260+
}
261+
services_instances = client.v3.service_instances.list(fields=fields)
262+
263+
Relationship object generated by `fields` will contain only attributes returned by the API (eg. name, guid). Please note relationship needs to be explicitly requested, otherwise it will be ignored and child object not created.
264+
255265
Available managers on API V3 are:
256266

257267
- ``apps``

cloudfoundry_client/v3/entities.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ def _append_encoded_parameter(parameters: List[str], args: Tuple[str, Any]) -> L
300300
parameter_name, parameter_value = args[0], args[1]
301301
if isinstance(parameter_value, (list, tuple)):
302302
parameters.append("%s=%s" % (parameter_name, quote(",".join(parameter_value))))
303+
elif isinstance(parameter_value, (dict)) and parameter_name == "fields":
304+
for resource, key in parameter_value.items():
305+
parameters.append("%s[%s]=%s" % (parameter_name, resource, ",".join(key)))
303306
else:
304307
parameters.append("%s=%s" % (parameter_name, quote(str(parameter_value))))
305308
return parameters
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
{
2+
"pagination": {
3+
"total_results": 2,
4+
"total_pages": 1,
5+
"first": {
6+
"href": "https://somewhere.com/v3/service_instances?fields%5Bspace%5D=guid%2Cname%2Crelationships.organization&fields%5Bspace.organization%5D=guid%2Cname&page=1&per_page=50"
7+
},
8+
"last": {
9+
"href": "https://somewhere.com/v3/service_instances?fields%5Bspace%5D=guid%2Cname%2Crelationships.organization&fields%5Bspace.organization%5D=guid%2Cname&page=1&per_page=50"
10+
},
11+
"next": null,
12+
"previous": null
13+
},
14+
"resources": [
15+
{
16+
"guid": "147119a3-53e7-41af-8d06-46806695ae1a",
17+
"created_at": "2025-07-30T07:55:53Z",
18+
"updated_at": "2025-07-30T07:55:53Z",
19+
"name": "my-user-provided-service",
20+
"tags": [],
21+
"last_operation": {
22+
"type": "create",
23+
"state": "succeeded",
24+
"description": "Operation succeeded",
25+
"updated_at": "2025-07-30T07:55:53Z",
26+
"created_at": "2025-07-30T07:55:53Z"
27+
},
28+
"type": "user-provided",
29+
"syslog_drain_url": null,
30+
"route_service_url": null,
31+
"relationships": {
32+
"space": {
33+
"data": {
34+
"guid": "aa3c5cfd-3f75-43f3-aac8-216fec6b3be5"
35+
}
36+
}
37+
},
38+
"metadata": {
39+
"labels": {},
40+
"annotations": {}
41+
},
42+
"links": {
43+
"self": {
44+
"href": "https://somewhere.com/v3/service_instances/147119a3-53e7-41af-8d06-46806695ae1a"
45+
},
46+
"space": {
47+
"href": "https://somewhere.com/v3/spaces/aa3c5cfd-3f75-43f3-aac8-216fec6b3be5"
48+
},
49+
"service_credential_bindings": {
50+
"href": "https://somewhere.com/v3/service_credential_bindings?service_instance_guids=147119a3-53e7-41af-8d06-46806695ae1a"
51+
},
52+
"service_route_bindings": {
53+
"href": "https://somewhere.com/v3/service_route_bindings?service_instance_guids=147119a3-53e7-41af-8d06-46806695ae1a"
54+
},
55+
"credentials": {
56+
"href": "https://somewhere.com/v3/service_instances/147119a3-53e7-41af-8d06-46806695ae1a/credentials"
57+
}
58+
}
59+
},
60+
{
61+
"guid": "858e2101-ebb3-4c62-af6d-06e26bae744c",
62+
"created_at": "2025-07-30T07:57:04Z",
63+
"updated_at": "2025-07-30T07:57:05Z",
64+
"name": "my-managed-service",
65+
"tags": [],
66+
"last_operation": {
67+
"type": "create",
68+
"state": "succeeded",
69+
"description": "",
70+
"updated_at": "2025-07-30T07:57:05Z",
71+
"created_at": "2025-07-30T07:57:05Z"
72+
},
73+
"type": "managed",
74+
"maintenance_info": {},
75+
"upgrade_available": false,
76+
"dashboard_url": null,
77+
"relationships": {
78+
"space": {
79+
"data": {
80+
"guid": "aa3c5cfd-3f75-43f3-aac8-216fec6b3be5"
81+
}
82+
},
83+
"service_plan": {
84+
"data": {
85+
"guid": "a73e54c2-cc12-4fc5-8f8d-4eec3e6c383c"
86+
}
87+
}
88+
},
89+
"metadata": {
90+
"labels": {},
91+
"annotations": {}
92+
},
93+
"links": {
94+
"self": {
95+
"href": "https://somewhere.com/v3/service_instances/858e2101-ebb3-4c62-af6d-06e26bae744c"
96+
},
97+
"space": {
98+
"href": "https://somewhere.com/v3/spaces/aa3c5cfd-3f75-43f3-aac8-216fec6b3be5"
99+
},
100+
"service_credential_bindings": {
101+
"href": "https://somewhere.com/v3/service_credential_bindings?service_instance_guids=858e2101-ebb3-4c62-af6d-06e26bae744c"
102+
},
103+
"service_route_bindings": {
104+
"href": "https://somewhere.com/v3/service_route_bindings?service_instance_guids=858e2101-ebb3-4c62-af6d-06e26bae744c"
105+
},
106+
"service_plan": {
107+
"href": "https://somewhere.com/v3/service_plans/a73e54c2-cc12-4fc5-8f8d-4eec3e6c383c"
108+
},
109+
"parameters": {
110+
"href": "https://somewhere.com/v3/service_instances/858e2101-ebb3-4c62-af6d-06e26bae744c/parameters"
111+
},
112+
"shared_spaces": {
113+
"href": "https://somewhere.com/v3/service_instances/858e2101-ebb3-4c62-af6d-06e26bae744c/relationships/shared_spaces"
114+
}
115+
}
116+
}
117+
],
118+
"included": {
119+
"spaces": [
120+
{
121+
"guid": "aa3c5cfd-3f75-43f3-aac8-216fec6b3be5",
122+
"name": "my_space",
123+
"relationships": {
124+
"organization": {
125+
"data": {
126+
"guid": "24ae9e5a-3f0c-4347-8d82-610877534c74"
127+
}
128+
}
129+
}
130+
}
131+
],
132+
"organizations": [
133+
{
134+
"guid": "24ae9e5a-3f0c-4347-8d82-610877534c74",
135+
"name": "my_organization"
136+
}
137+
]
138+
}
139+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"guid": "858e2101-ebb3-4c62-af6d-06e26bae744c",
3+
"created_at": "2025-07-30T07:57:04Z",
4+
"updated_at": "2025-07-30T07:57:05Z",
5+
"name": "my-managed-service",
6+
"tags": [],
7+
"last_operation": {
8+
"type": "create",
9+
"state": "succeeded",
10+
"description": "",
11+
"updated_at": "2025-07-30T07:57:05Z",
12+
"created_at": "2025-07-30T07:57:05Z"
13+
},
14+
"type": "managed",
15+
"maintenance_info": {},
16+
"upgrade_available": false,
17+
"dashboard_url": null,
18+
"relationships": {
19+
"space": {
20+
"data": {
21+
"guid": "aa3c5cfd-3f75-43f3-aac8-216fec6b3be5"
22+
}
23+
},
24+
"service_plan": {
25+
"data": {
26+
"guid": "a73e54c2-cc12-4fc5-8f8d-4eec3e6c383c"
27+
}
28+
}
29+
},
30+
"metadata": {
31+
"labels": {},
32+
"annotations": {}
33+
},
34+
"links": {
35+
"self": {
36+
"href": "https://somewhere.com/v3/service_instances/858e2101-ebb3-4c62-af6d-06e26bae744c"
37+
},
38+
"space": {
39+
"href": "https://somewhere.com/v3/spaces/aa3c5cfd-3f75-43f3-aac8-216fec6b3be5"
40+
},
41+
"service_credential_bindings": {
42+
"href": "https://somewhere.com/v3/service_credential_bindings?service_instance_guids=858e2101-ebb3-4c62-af6d-06e26bae744c"
43+
},
44+
"service_route_bindings": {
45+
"href": "https://somewhere.com/v3/service_route_bindings?service_instance_guids=858e2101-ebb3-4c62-af6d-06e26bae744c"
46+
},
47+
"service_plan": {
48+
"href": "https://somewhere.com/v3/service_plans/a73e54c2-cc12-4fc5-8f8d-4eec3e6c383c"
49+
},
50+
"parameters": {
51+
"href": "https://somewhere.com/v3/service_instances/858e2101-ebb3-4c62-af6d-06e26bae744c/parameters"
52+
},
53+
"shared_spaces": {
54+
"href": "https://somewhere.com/v3/service_instances/858e2101-ebb3-4c62-af6d-06e26bae744c/relationships/shared_spaces"
55+
}
56+
},
57+
"included": {
58+
"spaces": [
59+
{
60+
"guid": "aa3c5cfd-3f75-43f3-aac8-216fec6b3be5",
61+
"name": "my_space"
62+
}
63+
],
64+
"organizations": [
65+
{
66+
"guid": "24ae9e5a-3f0c-4347-8d82-610877534c74",
67+
"name": "my_organization"
68+
}
69+
]
70+
}
71+
}

tests/v3/test_service_instances.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,57 @@ def test_get(self):
9999
self.assertEqual("service_instance_id", service_instance["guid"])
100100
self.assertIsInstance(service_instance, Entity)
101101

102+
def test_get_fields_space(self):
103+
self.client.get.return_value = self.mock_response(
104+
"/v3/service_instances/service_instance_id"
105+
"?fields[space]=guid,name,relationships.organization"
106+
"&fields[space.organization]=guid,name",
107+
HTTPStatus.OK,
108+
None,
109+
"v3",
110+
"service_instances",
111+
"GET_{id}_response_fields_space.json"
112+
)
113+
fields = {
114+
"space": ["guid,name,relationships.organization"],
115+
"space.organization": ["guid", "name"],
116+
}
117+
space = self.client.v3.service_instances.get("service_instance_id", fields=fields).space()
118+
self.client.get.assert_called_with(self.client.get.return_value.url)
119+
self.assertEqual("my_space", space["name"])
120+
self.assertIsInstance(space, Entity)
121+
122+
def test_list_fields_space_and_org(self):
123+
self.client.get.return_value = self.mock_response(
124+
"/v3/service_instances"
125+
"?fields[space]=guid,name,relationships.organization"
126+
"&fields[space.organization]=guid,name",
127+
HTTPStatus.OK,
128+
None,
129+
"v3",
130+
"service_instances",
131+
"GET_response_fields_space_and_org.json"
132+
)
133+
fields = {
134+
"space": ["guid,name,relationships.organization"],
135+
"space.organization": ["guid", "name"]
136+
}
137+
all_spaces = [app.space() for app in self.client.v3.service_instances.list(fields=fields)]
138+
self.client.get.assert_called_with(self.client.get.return_value.url)
139+
self.assertEqual(2, len(all_spaces))
140+
space1 = all_spaces[0]
141+
self.assertEqual(space1["name"], "my_space")
142+
space1_org = space1.organization()
143+
self.assertEqual(space1_org["name"], "my_organization")
144+
self.assertIsInstance(space1, Entity)
145+
self.assertIsInstance(space1_org, Entity)
146+
space2 = all_spaces[1]
147+
self.assertEqual(space2["name"], "my_space")
148+
space2_org = space2.organization()
149+
self.assertEqual(space2_org["name"], "my_organization")
150+
self.assertIsInstance(space2, Entity)
151+
self.assertIsInstance(space2_org, Entity)
152+
102153
def test_get_then_credentials(self):
103154
get_service_instance = self.mock_response(
104155
"/v3/service_instances/service_instance_id", HTTPStatus.OK, None, "v3", "service_instances", "GET_{id}_response.json")

0 commit comments

Comments
 (0)