Skip to content

Commit f0aa705

Browse files
authored
Merge branch 'cloudfoundry-community:main' into main
2 parents 206b0f2 + 5427472 commit f0aa705

File tree

9 files changed

+412
-136
lines changed

9 files changed

+412
-136
lines changed

.github/workflows/pythonpackage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
1717

1818
steps:
19-
- uses: actions/checkout@v4
19+
- uses: actions/checkout@v5
2020

2121
- name: Set up Python ${{ matrix.python-version }}
2222
uses: actions/setup-python@v5

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/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
This module provides a client library for cloudfoundry_client v2/v3.
33
"""
44

5-
__version__ = "1.38.5"
5+
__version__ = "1.39.0"

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

poetry.lock

Lines changed: 132 additions & 130 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ exclude = '''
88

99
[tool.poetry]
1010
name = "cloudfoundry_client"
11-
version = "1.38.5"
11+
version = "1.39.0"
1212
description = "A client library for CloudFoundry"
1313
authors = ["Benjamin Einaudi <[email protected]>"]
1414
readme = "README.rst"
@@ -20,7 +20,7 @@ keywords = ["cloudfoundry", "cf"]
2020
[tool.poetry.dependencies]
2121
python = ">=3.9"
2222
aiohttp = ">=3.8.0"
23-
protobuf = "6.31.1"
23+
protobuf = "6.32.0"
2424
oauth2-client= "1.4.2"
2525
websocket-client= "~1.8.0"
2626
PyYAML = ">=6.0"
@@ -29,8 +29,8 @@ polling2= "0.5.0"
2929

3030
[tool.poetry.group.dev.dependencies]
3131
black= "25.1.0"
32-
flake8= "7.2.0"
33-
pytest = ">=8.2.2,<8.4.0"
32+
flake8= "7.3.0"
33+
pytest = ">=8.2.2,<8.5.0"
3434
twine = ">=6.0,<6.2"
3535

3636
[tool.poetry.scripts]
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)