Skip to content

Commit 1567581

Browse files
author
Francesco Faraone
authored
Merge pull request #39 from cloudblue/bulk_operations_support
LITE-23640: Add support for bulk update, delete and create of resources
2 parents d9b0a03 + c68e82b commit 1567581

File tree

9 files changed

+444
-18
lines changed

9 files changed

+444
-18
lines changed

connect/client/mixins.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#
22
# This file is part of the Ingram Micro CloudBlue Connect Python OpenAPI Client.
33
#
4-
# Copyright (c) 2021 Ingram Micro. All Rights Reserved.
4+
# Copyright (c) 2022 Ingram Micro. All Rights Reserved.
55
#
66
import time
77

@@ -37,7 +37,12 @@ def update(self, url, payload=None, **kwargs):
3737

3838
return self.execute('put', url, **kwargs)
3939

40-
def delete(self, url, **kwargs):
40+
def delete(self, url, payload=None, **kwargs):
41+
kwargs = kwargs or {}
42+
43+
if payload:
44+
kwargs['json'] = payload
45+
4146
return self.execute('delete', url, **kwargs)
4247

4348
def execute(self, method, path, **kwargs):
@@ -119,7 +124,12 @@ async def update(self, url, payload=None, **kwargs):
119124

120125
return await self.execute('put', url, **kwargs)
121126

122-
async def delete(self, url, **kwargs):
127+
async def delete(self, url, payload=None, **kwargs):
128+
kwargs = kwargs or {}
129+
130+
if payload:
131+
kwargs['json'] = payload
132+
123133
return await self.execute('delete', url, **kwargs)
124134

125135
async def execute(self, method, path, **kwargs):

connect/client/models/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,10 @@ def help(self):
269269
return self
270270

271271
def _get_resource_class(self):
272-
return NotImplementedError()
272+
return NotImplementedError() # pragma: no cover
273273

274274
def _get_resourceset_class(self):
275-
return NotImplementedError()
275+
return NotImplementedError() # pragma: no cover
276276

277277

278278
class Collection(_CollectionBase, CollectionMixin):

connect/client/models/mixins.py

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#
22
# This file is part of the Ingram Micro CloudBlue Connect Python OpenAPI Client.
33
#
4-
# Copyright (c) 2021 Ingram Micro. All Rights Reserved.
4+
# Copyright (c) 2022 Ingram Micro. All Rights Reserved.
55
#
66
from connect.client.exceptions import ClientError
77
from connect.client.utils import resolve_attribute
@@ -17,12 +17,70 @@ def create(self, payload=None, **kwargs):
1717
:return: The newly created resource.
1818
:rtype: dict
1919
"""
20+
if payload is not None and not isinstance(payload, dict):
21+
raise TypeError('`payload` must be a dict.')
22+
23+
return self._client.create(
24+
self._path,
25+
payload=payload,
26+
**kwargs,
27+
)
28+
29+
def bulk_create(self, payload, **kwargs):
30+
"""
31+
Create new resources within this collection. This operation may not be supported
32+
by the API.
33+
34+
:param payload: JSON payload of the list of resources to create.
35+
:type payload: list
36+
:return: The newly created resources.
37+
:rtype: list
38+
"""
39+
if not isinstance(payload, (list, tuple)):
40+
raise TypeError('`payload` must be a list or tuple.')
41+
2042
return self._client.create(
2143
self._path,
2244
payload=payload,
2345
**kwargs,
2446
)
2547

48+
def bulk_update(self, payload, **kwargs):
49+
"""
50+
Update a list of resources in this collection. This operation may not be supported
51+
by the API.
52+
53+
:param payload: JSON payload of the list of resources to update.
54+
:type payload: list
55+
:return: A list of the updated resources.
56+
:rtype: list
57+
"""
58+
if not isinstance(payload, (list, tuple)):
59+
raise TypeError('`payload` must be a list or tuple.')
60+
61+
return self._client.update(
62+
self._path,
63+
payload=payload,
64+
**kwargs,
65+
)
66+
67+
def bulk_delete(self, payload, **kwargs):
68+
"""
69+
Delete a list of resources from this collection. This operation may not be supported
70+
by the API.
71+
72+
:param payload: JSON payload of the list of resources to delete.
73+
:type payload: list
74+
"""
75+
if not isinstance(payload, (list, tuple)):
76+
raise TypeError('`payload` must be a list or tuple.')
77+
78+
self._client.delete(
79+
self._path,
80+
payload=payload,
81+
**kwargs,
82+
)
83+
2684

2785
class AsyncCollectionMixin:
2886
async def create(self, payload=None, **kwargs):
@@ -34,12 +92,70 @@ async def create(self, payload=None, **kwargs):
3492
:return: The newly created resource.
3593
:rtype: dict
3694
"""
95+
if payload is not None and not isinstance(payload, dict):
96+
raise TypeError('`payload` must be a dict.')
97+
3798
return await self._client.create(
3899
self._path,
39100
payload=payload,
40101
**kwargs,
41102
)
42103

104+
async def bulk_create(self, payload, **kwargs):
105+
"""
106+
Create new resources within this collection. This operation may not be supported
107+
by the API.
108+
109+
:param payload: JSON payload of the list of resources to create.
110+
:type payload: list
111+
:return: The newly created resources.
112+
:rtype: list
113+
"""
114+
if not isinstance(payload, (list, tuple)):
115+
raise TypeError('`payload` must be a list or tuple.')
116+
117+
return await self._client.create(
118+
self._path,
119+
payload=payload,
120+
**kwargs,
121+
)
122+
123+
async def bulk_update(self, payload, **kwargs):
124+
"""
125+
Update a list of resources in this collection. This operation may not be supported
126+
by the API.
127+
128+
:param payload: JSON payload of the list of resources to update.
129+
:type payload: list
130+
:return: A list of the updated resources.
131+
:rtype: list
132+
"""
133+
if not isinstance(payload, (list, tuple)):
134+
raise TypeError('`payload` must be a list or tuple.')
135+
136+
return await self._client.update(
137+
self._path,
138+
payload=payload,
139+
**kwargs,
140+
)
141+
142+
async def bulk_delete(self, payload, **kwargs):
143+
"""
144+
Delete a list of resources from this collection. This operation may not be supported
145+
by the API.
146+
147+
:param payload: JSON payload of the list of resources to delete.
148+
:type payload: list
149+
"""
150+
if not isinstance(payload, (list, tuple)):
151+
raise TypeError('`payload` must be a list or tuple.')
152+
153+
return await self._client.delete(
154+
self._path,
155+
payload=payload,
156+
**kwargs,
157+
)
158+
43159

44160
class ResourceMixin:
45161
def exists(self):

connect/client/models/resourceset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ async def _execute_request(self, url, kwargs):
510510
return results
511511

512512
async def _fetch_all(self):
513-
if self._results is None:
513+
if self._results is None: # pragma: no branch
514514
self._results = await self._execute_request(
515515
self._get_request_url(),
516516
self._get_request_kwargs(),

docs/guide.rst

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ Working with resources
9191
----------------------
9292

9393

94-
Create a new resource
95-
^^^^^^^^^^^^^^^^^^^^^
94+
Create new resources
95+
^^^^^^^^^^^^^^^^^^^^
9696

9797
To create a new resource inside a collection you can invoke the
9898
:meth:`~connect.client.models.Collection.create` method on the corresponding
@@ -111,6 +111,31 @@ To create a new resource inside a collection you can invoke the
111111
112112
This returns the newly created object json-decoded.
113113

114+
If the collection supports bulk creation of resources, you can use
115+
:meth:`~connect.client.models.Collection.bulk_create` and pass a list of
116+
resources to be created:
117+
118+
.. code-block:: python
119+
120+
payload = [
121+
{
122+
'name': 'An Awesome Product',
123+
'category': {
124+
'id': 'CAT-00000',
125+
},
126+
},
127+
{
128+
'name': 'Another Awesome Product',
129+
'category': {
130+
'id': 'CAT-00000',
131+
},
132+
},
133+
]
134+
135+
new_products = c.products.bulk_create(payload=payload)
136+
137+
In this case, this returns a list of the newly created objects json-decoded.
138+
114139
Access to a resource
115140
^^^^^^^^^^^^^^^^^^^^
116141

@@ -156,8 +181,8 @@ This call returns the json-decoded object or raise an exception
156181
if it does not exist.
157182

158183

159-
Update a resource
160-
^^^^^^^^^^^^^^^^^
184+
Update resources
185+
^^^^^^^^^^^^^^^^
161186

162187
To update a resource of the collection using its primary identifier,
163188
you can invoke the :meth:`~connect.client.models.Resource.update` method as shown below:
@@ -169,18 +194,54 @@ you can invoke the :meth:`~connect.client.models.Resource.update` method as show
169194
'detailed_description': 'This is the detailed description',
170195
}
171196
172-
product = client.products['PRD-000-000-000'].update(payload=payload)
197+
updated_product = client.products['PRD-000-000-000'].update(payload=payload)
173198
199+
If the collection supports bulk update of resources you can use the
200+
:meth:`~connect.client.models.Collection.bulk_update` method, passing the list of resources
201+
to be updated:
174202

175-
Delete a resource
176-
^^^^^^^^^^^^^^^^^
203+
.. code-block:: python
204+
205+
payload = [
206+
{
207+
'id': 'PRD-000-000-000',
208+
'short_description': 'This is the short description for 0',
209+
},
210+
{
211+
'id': 'PRD-000-000-001',
212+
'short_description': 'This is the short description for 1',
213+
},
214+
]
215+
216+
updated_products = c.products.bulk_update(payload=payload)
217+
218+
219+
Delete resources
220+
^^^^^^^^^^^^^^^^
177221

178222
To delete a resource the :meth:`~connect.client.models.Resource.delete` method is exposed:
179223

180224
.. code-block:: python
181225
182226
client.products['PRD-000-000-000'].delete()
183227
228+
Similarly to the update operation, if the collection supports bulk deletion of resources
229+
you can use the :meth:`~connect.client.models.Collection.bulk_delete` method, passing the list
230+
of resource identifiers to be deleted:
231+
232+
.. code-block:: python
233+
234+
payload = [
235+
{
236+
'id': 'PRD-000-000-000',
237+
},
238+
{
239+
'id': 'PRD-000-000-001',
240+
},
241+
]
242+
243+
c.products.bulk_delete(payload=payload)
244+
184245
Access to an action
185246
^^^^^^^^^^^^^^^^^^^
186247

tests/async_client/test_fluent.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,40 @@ async def test_update(async_mocker, attr):
6767

6868

6969
@pytest.mark.asyncio
70-
async def test_delete(async_mocker):
70+
async def test_delete_no_args(async_mocker):
71+
mocked = async_mocker.AsyncMock()
72+
url = 'https://localhost'
73+
74+
c = AsyncConnectClient('API_KEY', use_specs=False)
75+
c.execute = mocked
76+
77+
await c.delete(url)
78+
79+
mocked.assert_awaited_once_with('delete', url)
80+
81+
82+
@pytest.mark.asyncio
83+
@pytest.mark.parametrize('attr', ('payload', 'json'))
84+
async def test_delete(async_mocker, attr):
7185
mocked = async_mocker.AsyncMock()
7286
url = 'https://localhost'
7387

7488
kwargs = {
7589
'arg1': 'val1',
7690
}
91+
kwargs[attr] = {'k1': 'v1'}
7792

7893
c = AsyncConnectClient('API_KEY', use_specs=False)
7994
c.execute = mocked
8095

8196
await c.delete(url, **kwargs)
8297

83-
mocked.assert_awaited_once_with('delete', url, **kwargs)
98+
mocked.assert_awaited_once_with('delete', url, **{
99+
'arg1': 'val1',
100+
'json': {
101+
'k1': 'v1',
102+
},
103+
})
84104

85105

86106
@pytest.mark.asyncio

0 commit comments

Comments
 (0)