mpt-extension-sdk 4.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mpt_extension_sdk/__init__.py +0 -0
- mpt_extension_sdk/airtable/__init__.py +0 -0
- mpt_extension_sdk/airtable/wrap_http_error.py +45 -0
- mpt_extension_sdk/constants.py +11 -0
- mpt_extension_sdk/core/__init__.py +0 -0
- mpt_extension_sdk/core/events/__init__.py +0 -0
- mpt_extension_sdk/core/events/dataclasses.py +16 -0
- mpt_extension_sdk/core/events/registry.py +56 -0
- mpt_extension_sdk/core/extension.py +12 -0
- mpt_extension_sdk/core/security.py +50 -0
- mpt_extension_sdk/core/utils.py +17 -0
- mpt_extension_sdk/flows/__init__.py +0 -0
- mpt_extension_sdk/flows/context.py +39 -0
- mpt_extension_sdk/flows/pipeline.py +51 -0
- mpt_extension_sdk/key_vault/__init__.py +0 -0
- mpt_extension_sdk/key_vault/base.py +110 -0
- mpt_extension_sdk/mpt_http/__init__.py +0 -0
- mpt_extension_sdk/mpt_http/base.py +43 -0
- mpt_extension_sdk/mpt_http/mpt.py +530 -0
- mpt_extension_sdk/mpt_http/utils.py +2 -0
- mpt_extension_sdk/mpt_http/wrap_http_error.py +68 -0
- mpt_extension_sdk/runtime/__init__.py +10 -0
- mpt_extension_sdk/runtime/commands/__init__.py +0 -0
- mpt_extension_sdk/runtime/commands/django.py +42 -0
- mpt_extension_sdk/runtime/commands/run.py +44 -0
- mpt_extension_sdk/runtime/djapp/__init__.py +0 -0
- mpt_extension_sdk/runtime/djapp/apps.py +46 -0
- mpt_extension_sdk/runtime/djapp/conf/__init__.py +12 -0
- mpt_extension_sdk/runtime/djapp/conf/default.py +225 -0
- mpt_extension_sdk/runtime/djapp/conf/urls.py +9 -0
- mpt_extension_sdk/runtime/djapp/management/__init__.py +0 -0
- mpt_extension_sdk/runtime/djapp/management/commands/__init__.py +0 -0
- mpt_extension_sdk/runtime/djapp/management/commands/consume_events.py +38 -0
- mpt_extension_sdk/runtime/djapp/middleware.py +21 -0
- mpt_extension_sdk/runtime/events/__init__.py +0 -0
- mpt_extension_sdk/runtime/events/dispatcher.py +83 -0
- mpt_extension_sdk/runtime/events/producers.py +108 -0
- mpt_extension_sdk/runtime/events/utils.py +85 -0
- mpt_extension_sdk/runtime/initializer.py +62 -0
- mpt_extension_sdk/runtime/logging.py +36 -0
- mpt_extension_sdk/runtime/master.py +136 -0
- mpt_extension_sdk/runtime/swoext.py +69 -0
- mpt_extension_sdk/runtime/tracer.py +18 -0
- mpt_extension_sdk/runtime/utils.py +148 -0
- mpt_extension_sdk/runtime/workers.py +90 -0
- mpt_extension_sdk/swo_rql/__init__.py +5 -0
- mpt_extension_sdk/swo_rql/constants.py +7 -0
- mpt_extension_sdk/swo_rql/query_builder.py +392 -0
- mpt_extension_sdk-4.5.0.dist-info/METADATA +45 -0
- mpt_extension_sdk-4.5.0.dist-info/RECORD +53 -0
- mpt_extension_sdk-4.5.0.dist-info/WHEEL +4 -0
- mpt_extension_sdk-4.5.0.dist-info/entry_points.txt +6 -0
- mpt_extension_sdk-4.5.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections.abc import Iterable
|
|
3
|
+
from functools import cache
|
|
4
|
+
from itertools import batched
|
|
5
|
+
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
|
|
8
|
+
from mpt_extension_sdk.mpt_http.base import MPTClient
|
|
9
|
+
from mpt_extension_sdk.mpt_http.wrap_http_error import wrap_mpt_http_error
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _has_more_pages(page):
|
|
15
|
+
if not page:
|
|
16
|
+
return True
|
|
17
|
+
pagination = page["$meta"]["pagination"]
|
|
18
|
+
return pagination["total"] > pagination["limit"] + pagination["offset"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _paginated(mpt_client, url, limit=10):
|
|
22
|
+
items = []
|
|
23
|
+
page = None
|
|
24
|
+
offset = 0
|
|
25
|
+
while _has_more_pages(page):
|
|
26
|
+
response = mpt_client.get(f"{url}&limit={limit}&offset={offset}")
|
|
27
|
+
response.raise_for_status()
|
|
28
|
+
page = response.json()
|
|
29
|
+
items.extend(page["data"])
|
|
30
|
+
offset += limit
|
|
31
|
+
|
|
32
|
+
return items
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@wrap_mpt_http_error
|
|
36
|
+
def get_agreement(mpt_client, agreement_id):
|
|
37
|
+
response = mpt_client.get(
|
|
38
|
+
f"/commerce/agreements/{agreement_id}?select=seller,buyer,listing,product,subscriptions"
|
|
39
|
+
)
|
|
40
|
+
response.raise_for_status()
|
|
41
|
+
return response.json()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@wrap_mpt_http_error
|
|
45
|
+
def get_licensee(mpt_client, licensee_id):
|
|
46
|
+
response = mpt_client.get(f"/accounts/licensees/{licensee_id}")
|
|
47
|
+
response.raise_for_status()
|
|
48
|
+
return response.json()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@wrap_mpt_http_error
|
|
52
|
+
def update_order(mpt_client, order_id, **kwargs):
|
|
53
|
+
response = mpt_client.put(
|
|
54
|
+
f"/commerce/orders/{order_id}",
|
|
55
|
+
json=kwargs,
|
|
56
|
+
)
|
|
57
|
+
response.raise_for_status()
|
|
58
|
+
return response.json()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@wrap_mpt_http_error
|
|
62
|
+
def query_order(mpt_client, order_id, **kwargs):
|
|
63
|
+
response = mpt_client.post(
|
|
64
|
+
f"/commerce/orders/{order_id}/query",
|
|
65
|
+
json=kwargs,
|
|
66
|
+
)
|
|
67
|
+
response.raise_for_status()
|
|
68
|
+
return response.json()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@wrap_mpt_http_error
|
|
72
|
+
def fail_order(mpt_client, order_id, status_notes, **kwargs):
|
|
73
|
+
response = mpt_client.post(
|
|
74
|
+
f"/commerce/orders/{order_id}/fail",
|
|
75
|
+
json={
|
|
76
|
+
"statusNotes": status_notes,
|
|
77
|
+
**kwargs,
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
response.raise_for_status()
|
|
81
|
+
return response.json()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@wrap_mpt_http_error
|
|
85
|
+
def complete_order(mpt_client, order_id, template, **kwargs):
|
|
86
|
+
response = mpt_client.post(
|
|
87
|
+
f"/commerce/orders/{order_id}/complete",
|
|
88
|
+
json={"template": template, **kwargs},
|
|
89
|
+
)
|
|
90
|
+
response.raise_for_status()
|
|
91
|
+
return response.json()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@wrap_mpt_http_error
|
|
95
|
+
def set_processing_template(mpt_client, order_id, template):
|
|
96
|
+
response = mpt_client.put(
|
|
97
|
+
f"/commerce/orders/{order_id}",
|
|
98
|
+
json={"template": template},
|
|
99
|
+
)
|
|
100
|
+
response.raise_for_status()
|
|
101
|
+
return response.json()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@wrap_mpt_http_error
|
|
105
|
+
def create_asset(mpt_client, asset):
|
|
106
|
+
"""Create a new asset."""
|
|
107
|
+
response = mpt_client.post("/commerce/assets", json=asset)
|
|
108
|
+
response.raise_for_status()
|
|
109
|
+
return response.json()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@wrap_mpt_http_error
|
|
113
|
+
def create_order_asset(mpt_client, order_id, asset):
|
|
114
|
+
"""Create a new asset for an order."""
|
|
115
|
+
response = mpt_client.post(f"/commerce/orders/{order_id}/assets", json=asset)
|
|
116
|
+
response.raise_for_status()
|
|
117
|
+
return response.json()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@wrap_mpt_http_error
|
|
121
|
+
def update_asset(mpt_client, asset_id, **kwargs):
|
|
122
|
+
"""Update an asset."""
|
|
123
|
+
response = mpt_client.put(f"/commerce/assets/{asset_id}", json=kwargs)
|
|
124
|
+
response.raise_for_status()
|
|
125
|
+
return response.json()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@wrap_mpt_http_error
|
|
129
|
+
def update_order_asset(mpt_client, order_id, asset_id, **kwargs):
|
|
130
|
+
"""Update an order asset."""
|
|
131
|
+
response = mpt_client.put(f"/commerce/orders/{order_id}/assets/{asset_id}", json=kwargs)
|
|
132
|
+
response.raise_for_status()
|
|
133
|
+
return response.json()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@wrap_mpt_http_error
|
|
137
|
+
def get_agreement_asset_by_external_id(mpt_client, agreement_id, asset_external_id):
|
|
138
|
+
"""Retrieve an agreement asset by external ID."""
|
|
139
|
+
response = mpt_client.get(
|
|
140
|
+
f"/commerce/assets?eq(externalIds.vendor,{asset_external_id})"
|
|
141
|
+
f"&eq(agreement.id,{agreement_id})"
|
|
142
|
+
f"&eq(status,Active)"
|
|
143
|
+
f"&select=agreement.id&limit=1"
|
|
144
|
+
)
|
|
145
|
+
response.raise_for_status()
|
|
146
|
+
assets = response.json()
|
|
147
|
+
return assets["data"][0] if assets["data"] else None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@wrap_mpt_http_error
|
|
151
|
+
def get_asset_by_id(mpt_client, asset_id):
|
|
152
|
+
"""Get an asset by ID."""
|
|
153
|
+
response = mpt_client.get(f"/commerce/assets/{asset_id}")
|
|
154
|
+
response.raise_for_status()
|
|
155
|
+
return response.json()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@wrap_mpt_http_error
|
|
159
|
+
def get_order_asset_by_external_id(mpt_client, order_id, asset_external_id):
|
|
160
|
+
"""Retrieve an order asset by its external ID."""
|
|
161
|
+
response = mpt_client.get(
|
|
162
|
+
f"/commerce/orders/{order_id}/assets?eq(externalIds.vendor,{asset_external_id})&limit=1",
|
|
163
|
+
)
|
|
164
|
+
response.raise_for_status()
|
|
165
|
+
assets = response.json()
|
|
166
|
+
if assets["$meta"]["pagination"]["total"] == 1:
|
|
167
|
+
return assets["data"][0]
|
|
168
|
+
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@wrap_mpt_http_error
|
|
173
|
+
def create_subscription(mpt_client, order_id, subscription):
|
|
174
|
+
response = mpt_client.post(
|
|
175
|
+
f"/commerce/orders/{order_id}/subscriptions",
|
|
176
|
+
json=subscription,
|
|
177
|
+
)
|
|
178
|
+
response.raise_for_status()
|
|
179
|
+
return response.json()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@wrap_mpt_http_error
|
|
183
|
+
def update_subscription(mpt_client, order_id, subscription_id, **kwargs):
|
|
184
|
+
response = mpt_client.put(
|
|
185
|
+
f"/commerce/orders/{order_id}/subscriptions/{subscription_id}",
|
|
186
|
+
json=kwargs,
|
|
187
|
+
)
|
|
188
|
+
response.raise_for_status()
|
|
189
|
+
return response.json()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@wrap_mpt_http_error
|
|
193
|
+
def get_order_subscription_by_external_id(mpt_client, order_id, subscription_external_id):
|
|
194
|
+
"""Retrieve an order subscription by its external ID."""
|
|
195
|
+
response = mpt_client.get(
|
|
196
|
+
f"/commerce/orders/{order_id}/subscriptions?eq(externalIds.vendor,{subscription_external_id})&limit=1",
|
|
197
|
+
)
|
|
198
|
+
response.raise_for_status()
|
|
199
|
+
subscriptions = response.json()
|
|
200
|
+
if subscriptions["$meta"]["pagination"]["total"] == 1:
|
|
201
|
+
return subscriptions["data"][0]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@wrap_mpt_http_error
|
|
205
|
+
def get_product_items_by_skus(mpt_client, product_id, skus):
|
|
206
|
+
"""Retrieve product items by their SKUs."""
|
|
207
|
+
skus_str = ",".join(skus)
|
|
208
|
+
rql_query = f"and(eq(product.id,{product_id}),in(externalIds.vendor,({skus_str})))"
|
|
209
|
+
url = f"/catalog/items?{rql_query}"
|
|
210
|
+
return _paginated(mpt_client, url)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@cache
|
|
214
|
+
@wrap_mpt_http_error
|
|
215
|
+
def get_webhook(mpt_client, webhook_id):
|
|
216
|
+
response = mpt_client.get(f"/notifications/webhooks/{webhook_id}?select=criteria")
|
|
217
|
+
response.raise_for_status()
|
|
218
|
+
|
|
219
|
+
return response.json()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@wrap_mpt_http_error
|
|
223
|
+
def get_product_template_or_default(mpt_client, product_id, status, name=None):
|
|
224
|
+
name_or_default_filter = "eq(default,true)"
|
|
225
|
+
if name:
|
|
226
|
+
name_or_default_filter = f"or({name_or_default_filter},eq(name,{name}))"
|
|
227
|
+
rql_filter = f"and(eq(type,Order{status}),{name_or_default_filter})"
|
|
228
|
+
url = f"/catalog/products/{product_id}/templates?{rql_filter}&order=default&limit=1"
|
|
229
|
+
response = mpt_client.get(url)
|
|
230
|
+
response.raise_for_status()
|
|
231
|
+
templates = response.json()
|
|
232
|
+
return templates["data"][0]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def get_template_by_name(mpt_client, product_id, template_name):
|
|
236
|
+
url = f"/catalog/products/{product_id}/templates?eq(name,{template_name})"
|
|
237
|
+
response = mpt_client.get(url)
|
|
238
|
+
response.raise_for_status()
|
|
239
|
+
templates = response.json()
|
|
240
|
+
return templates["data"][0]
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@wrap_mpt_http_error
|
|
244
|
+
def update_agreement(mpt_client, agreement_id, **kwargs):
|
|
245
|
+
response = mpt_client.put(
|
|
246
|
+
f"/commerce/agreements/{agreement_id}",
|
|
247
|
+
json=kwargs,
|
|
248
|
+
)
|
|
249
|
+
response.raise_for_status()
|
|
250
|
+
return response.json()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@wrap_mpt_http_error
|
|
254
|
+
def get_agreements_by_query(mpt_client, query):
|
|
255
|
+
url = f"/commerce/agreements?{query}"
|
|
256
|
+
return _paginated(mpt_client, url)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@wrap_mpt_http_error
|
|
260
|
+
def update_agreement_subscription(mpt_client, subscription_id, **kwargs):
|
|
261
|
+
response = mpt_client.put(
|
|
262
|
+
f"/commerce/subscriptions/{subscription_id}",
|
|
263
|
+
json=kwargs,
|
|
264
|
+
)
|
|
265
|
+
response.raise_for_status()
|
|
266
|
+
return response.json()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@wrap_mpt_http_error
|
|
270
|
+
def get_agreement_subscription(mpt_client, subscription_id):
|
|
271
|
+
response = mpt_client.get(
|
|
272
|
+
f"/commerce/subscriptions/{subscription_id}",
|
|
273
|
+
)
|
|
274
|
+
response.raise_for_status()
|
|
275
|
+
return response.json()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@wrap_mpt_http_error
|
|
279
|
+
def get_rendered_template(mpt_client, order_id):
|
|
280
|
+
response = mpt_client.get(
|
|
281
|
+
f"/commerce/orders/{order_id}/template",
|
|
282
|
+
)
|
|
283
|
+
response.raise_for_status()
|
|
284
|
+
return response.json()
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@wrap_mpt_http_error
|
|
288
|
+
def get_product_onetime_items_by_ids(mpt_client, product_id, item_ids):
|
|
289
|
+
product_cond = f"eq(product.id,{product_id})"
|
|
290
|
+
items_cond = f"in(id,({','.join(item_ids)}))"
|
|
291
|
+
rql_query = f"and({product_cond},{items_cond},eq(terms.period,one-time))"
|
|
292
|
+
url = f"/catalog/items?{rql_query}"
|
|
293
|
+
|
|
294
|
+
return _paginated(mpt_client, url)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@wrap_mpt_http_error
|
|
298
|
+
def get_product_items_by_period(
|
|
299
|
+
mpt_client,
|
|
300
|
+
product_id: str,
|
|
301
|
+
period: str,
|
|
302
|
+
vendor_external_ids: Iterable[str] | None = None,
|
|
303
|
+
):
|
|
304
|
+
"""
|
|
305
|
+
Fetches product items based on a specified period and filters.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
mpt_client: Client in tance to interact with the required API.
|
|
309
|
+
product_id (str): The unique identifier of the product to fetch items for.
|
|
310
|
+
period (str): The period for which to fetch the product items.
|
|
311
|
+
vendor_external_ids (Iterable[str] | None):
|
|
312
|
+
Optional. A list of vendor external IDs to filter out the product items by. Defaults
|
|
313
|
+
to None.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
list:
|
|
317
|
+
A paginated list of product items matching the specified criteria.
|
|
318
|
+
|
|
319
|
+
"""
|
|
320
|
+
product_cond = f"eq(product.id,{product_id})"
|
|
321
|
+
vendors_cond = ""
|
|
322
|
+
if vendor_external_ids:
|
|
323
|
+
vendor_ids = ",".join(vendor_external_ids)
|
|
324
|
+
vendors_cond = f",in(externalIds.vendor,({vendor_ids})))"
|
|
325
|
+
rql_query = f"and({product_cond},eq(terms.period,{period}){vendors_cond})"
|
|
326
|
+
url = f"/catalog/items?{rql_query}"
|
|
327
|
+
|
|
328
|
+
return _paginated(mpt_client, url)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def get_agreements_by_ids(mpt_client, ids):
|
|
332
|
+
"""Retrieve agreements by their IDs."""
|
|
333
|
+
ids_str = ",".join(ids)
|
|
334
|
+
rql_query = (
|
|
335
|
+
f"and(in(id,({ids_str})),eq(status,Active))"
|
|
336
|
+
"&select=assets,lines,parameters,subscriptions,product,listing"
|
|
337
|
+
)
|
|
338
|
+
return get_agreements_by_query(mpt_client, rql_query)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def get_all_agreements(mpt_client):
|
|
342
|
+
"""Retrieve all active agreements for specific products."""
|
|
343
|
+
product_ids_str = ",".join(settings.MPT_PRODUCTS_IDS)
|
|
344
|
+
product_condition = f"in(product.id,({product_ids_str}))"
|
|
345
|
+
|
|
346
|
+
return get_agreements_by_query(
|
|
347
|
+
mpt_client,
|
|
348
|
+
f"and(eq(status,Active),{product_condition})&select=assets,lines,parameters,subscriptions,product,listing",
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@wrap_mpt_http_error
|
|
353
|
+
def get_authorizations_by_currency_and_seller_id(mpt_client, product_id, currency, owner_id):
|
|
354
|
+
"""Retrieve authorizations by product ID, currency, and owner ID."""
|
|
355
|
+
authorization_filter = (
|
|
356
|
+
f"eq(product.id,{product_id})&eq(currency,{currency})&eq(owner.id,{owner_id})"
|
|
357
|
+
)
|
|
358
|
+
response = mpt_client.get(f"/catalog/authorizations?{authorization_filter}")
|
|
359
|
+
response.raise_for_status()
|
|
360
|
+
return response.json()["data"]
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@wrap_mpt_http_error
|
|
364
|
+
def get_gc_price_list_by_currency(mpt_client, product_id, currency):
|
|
365
|
+
response = mpt_client.get(
|
|
366
|
+
f"/catalog/price-lists?eq(product.id,{product_id})&eq(currency,{currency})"
|
|
367
|
+
)
|
|
368
|
+
response.raise_for_status()
|
|
369
|
+
return response.json()["data"]
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@wrap_mpt_http_error
|
|
373
|
+
def get_listings_by_price_list_and_seller_and_authorization(
|
|
374
|
+
mpt_client, product_id, price_list_id, seller_id, authorization_id
|
|
375
|
+
):
|
|
376
|
+
response = mpt_client.get(
|
|
377
|
+
f"/catalog/listings?eq(product.id,{product_id})&eq(priceList.id,{price_list_id})"
|
|
378
|
+
f"&eq(seller.id,{seller_id})"
|
|
379
|
+
f"&eq(authorization.id,{authorization_id})"
|
|
380
|
+
)
|
|
381
|
+
response.raise_for_status()
|
|
382
|
+
return response.json()["data"]
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
@wrap_mpt_http_error
|
|
386
|
+
def create_listing(mpt_client, listing):
|
|
387
|
+
response = mpt_client.post(
|
|
388
|
+
"/catalog/listings",
|
|
389
|
+
json=listing,
|
|
390
|
+
)
|
|
391
|
+
response.raise_for_status()
|
|
392
|
+
return response.json()
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@wrap_mpt_http_error
|
|
396
|
+
def create_agreement(mpt_client, agreement):
|
|
397
|
+
response = mpt_client.post(
|
|
398
|
+
"/commerce/agreements",
|
|
399
|
+
json=agreement,
|
|
400
|
+
)
|
|
401
|
+
response.raise_for_status()
|
|
402
|
+
return response.json()
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@wrap_mpt_http_error
|
|
406
|
+
def create_agreement_subscription(mpt_client, subscription):
|
|
407
|
+
response = mpt_client.post(
|
|
408
|
+
"/commerce/subscriptions",
|
|
409
|
+
json=subscription,
|
|
410
|
+
)
|
|
411
|
+
response.raise_for_status()
|
|
412
|
+
return response.json()
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@wrap_mpt_http_error
|
|
416
|
+
def get_listing_by_id(mpt_client, listing_id):
|
|
417
|
+
response = mpt_client.get(f"/catalog/listings/{listing_id}")
|
|
418
|
+
response.raise_for_status()
|
|
419
|
+
return response.json()
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@wrap_mpt_http_error
|
|
423
|
+
def get_agreement_subscription_by_external_id(mpt_client, agreement_id, subscription_external_id):
|
|
424
|
+
"""Retrieve an agreement subscription by external ID."""
|
|
425
|
+
response = mpt_client.get(
|
|
426
|
+
f"/commerce/subscriptions?eq(externalIds.vendor,{subscription_external_id})"
|
|
427
|
+
f"&eq(agreement.id,{agreement_id})"
|
|
428
|
+
f"&in(status,(Active,Updating))"
|
|
429
|
+
f"&select=agreement.id&limit=1"
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
response.raise_for_status()
|
|
433
|
+
subscriptions = response.json()
|
|
434
|
+
return subscriptions["data"][0] if subscriptions["data"] else None
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@wrap_mpt_http_error
|
|
438
|
+
def get_agreements_by_external_id_values(mpt_client, external_id, display_values):
|
|
439
|
+
display_values_list = ",".join(display_values)
|
|
440
|
+
rql_query = (
|
|
441
|
+
f"any(parameters.fulfillment,and("
|
|
442
|
+
f"eq(externalId,{external_id}),"
|
|
443
|
+
f"in(displayValue,({display_values_list}))))"
|
|
444
|
+
f"&select=lines,parameters,subscriptions,product,listing"
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
url = f"/commerce/agreements?{rql_query}"
|
|
448
|
+
|
|
449
|
+
return _paginated(mpt_client, url)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@wrap_mpt_http_error
|
|
453
|
+
def get_agreements_by_customer_deployments(mpt_client, deployment_id_parameter, deployment_ids):
|
|
454
|
+
"""Retrieve agreements by customer deployments."""
|
|
455
|
+
deployments_list = ",".join(deployment_ids)
|
|
456
|
+
rql_query = (
|
|
457
|
+
f"any(parameters.fulfillment,and("
|
|
458
|
+
f"eq(externalId,{deployment_id_parameter}),"
|
|
459
|
+
f"in(displayValue,({deployments_list}))))"
|
|
460
|
+
f"&select=lines,parameters,subscriptions,subscriptions.parameters,product,listing"
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
url = f"/commerce/agreements?{rql_query}"
|
|
464
|
+
|
|
465
|
+
return _paginated(mpt_client, url)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@wrap_mpt_http_error
|
|
469
|
+
def get_buyer(mpt_client, buyer_id):
|
|
470
|
+
response = mpt_client.get(f"/accounts/buyers/{buyer_id}")
|
|
471
|
+
response.raise_for_status()
|
|
472
|
+
return response.json()
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
@wrap_mpt_http_error
|
|
476
|
+
def notify(
|
|
477
|
+
mpt_client: MPTClient,
|
|
478
|
+
category_id: str,
|
|
479
|
+
account_id: str,
|
|
480
|
+
buyer_id: str,
|
|
481
|
+
subject: str,
|
|
482
|
+
message_body: str,
|
|
483
|
+
limit: int = 1000,
|
|
484
|
+
) -> None:
|
|
485
|
+
"""
|
|
486
|
+
Sends notifications to multiple recipients in batches for a specific buyer and
|
|
487
|
+
category through the MPTClient service. The function retrieves recipients,
|
|
488
|
+
groups them into manageable batches, and sends notifications using the provided
|
|
489
|
+
message details.
|
|
490
|
+
"""
|
|
491
|
+
recipients = _paginated(
|
|
492
|
+
mpt_client,
|
|
493
|
+
url=(
|
|
494
|
+
f"notifications/accounts/{account_id}/categories/{category_id}/contacts?"
|
|
495
|
+
f"select=id,-email,-name,-status,-user&"
|
|
496
|
+
f"filter(group.buyers.id,{buyer_id})"
|
|
497
|
+
),
|
|
498
|
+
limit=limit,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
for contacts in batched(recipients, limit):
|
|
502
|
+
response = mpt_client.post(
|
|
503
|
+
"notifications/batches",
|
|
504
|
+
json={
|
|
505
|
+
"category": {"id": category_id},
|
|
506
|
+
"subject": subject,
|
|
507
|
+
"body": message_body,
|
|
508
|
+
"contacts": contacts,
|
|
509
|
+
"buyer": {"id": buyer_id},
|
|
510
|
+
},
|
|
511
|
+
)
|
|
512
|
+
response.raise_for_status()
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
@wrap_mpt_http_error
|
|
516
|
+
def terminate_subscription(mpt_client: MPTClient, subscription_id: str, reason: str) -> dict:
|
|
517
|
+
"""
|
|
518
|
+
Terminates a subscription by calling the MPT API.
|
|
519
|
+
|
|
520
|
+
Raises:
|
|
521
|
+
HTTPError: If the HTTP request fails, an HTTPError is raised with
|
|
522
|
+
information about the issue.
|
|
523
|
+
"""
|
|
524
|
+
response = mpt_client.post(
|
|
525
|
+
f"/commerce/subscriptions/{subscription_id}/terminate",
|
|
526
|
+
json={"description": reason},
|
|
527
|
+
)
|
|
528
|
+
response.raise_for_status()
|
|
529
|
+
|
|
530
|
+
return response.json()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from functools import wraps
|
|
3
|
+
|
|
4
|
+
from requests import HTTPError, JSONDecodeError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MPTError(Exception):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MPTHttpError(MPTError):
|
|
12
|
+
def __init__(self, status_code: int, content: str):
|
|
13
|
+
self.status_code = status_code
|
|
14
|
+
self.content = content
|
|
15
|
+
super().__init__(f"{self.status_code} - {self.content}")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MPTAPIError(MPTHttpError):
|
|
19
|
+
def __init__(self, status_code, payload):
|
|
20
|
+
super().__init__(status_code, json.dumps(payload))
|
|
21
|
+
self.payload = payload
|
|
22
|
+
self.status = payload.get("status")
|
|
23
|
+
self.title = payload.get("title")
|
|
24
|
+
self.detail = payload.get("detail")
|
|
25
|
+
self.trace_id = payload.get("traceId")
|
|
26
|
+
self.errors = payload.get("errors")
|
|
27
|
+
|
|
28
|
+
def __str__(self):
|
|
29
|
+
base = f"{self.status} {self.title} - {self.detail} ({self.trace_id})"
|
|
30
|
+
|
|
31
|
+
if self.errors:
|
|
32
|
+
return f"{base}\n{json.dumps(self.errors, indent=2)}"
|
|
33
|
+
return base
|
|
34
|
+
|
|
35
|
+
def __repr__(self):
|
|
36
|
+
return str(self.payload)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def wrap_mpt_http_error(func):
|
|
40
|
+
@wraps(func)
|
|
41
|
+
def _wrapper(*args, **kwargs):
|
|
42
|
+
try:
|
|
43
|
+
return func(*args, **kwargs)
|
|
44
|
+
except HTTPError as e:
|
|
45
|
+
try:
|
|
46
|
+
raise MPTAPIError(e.response.status_code, e.response.json())
|
|
47
|
+
except JSONDecodeError:
|
|
48
|
+
raise MPTHttpError(e.response.status_code, e.response.content.decode())
|
|
49
|
+
|
|
50
|
+
return _wrapper
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ValidationError:
|
|
54
|
+
def __init__(self, id, message):
|
|
55
|
+
self.id = id
|
|
56
|
+
self.message = message
|
|
57
|
+
|
|
58
|
+
def to_dict(self, **kwargs):
|
|
59
|
+
return {
|
|
60
|
+
"id": self.id,
|
|
61
|
+
"message": self.message.format(**kwargs),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
ERR_EXT_UNHANDLED_EXCEPTION = ValidationError(
|
|
66
|
+
"EXT001",
|
|
67
|
+
"Order can't be processed. Failure reason: {error}",
|
|
68
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from contextlib import nullcontext
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from django.core.management import execute_from_command_line
|
|
5
|
+
from opentelemetry import trace
|
|
6
|
+
|
|
7
|
+
from mpt_extension_sdk.constants import (
|
|
8
|
+
DEFAULT_APP_CONFIG_GROUP,
|
|
9
|
+
DEFAULT_APP_CONFIG_NAME,
|
|
10
|
+
DJANGO_SETTINGS_MODULE,
|
|
11
|
+
)
|
|
12
|
+
from mpt_extension_sdk.runtime.utils import initialize_extension
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command(
|
|
16
|
+
add_help_option=False, context_settings=dict(ignore_unknown_options=True)
|
|
17
|
+
)
|
|
18
|
+
@click.argument("management_args", nargs=-1, type=click.UNPROCESSED)
|
|
19
|
+
@click.pass_context
|
|
20
|
+
def django(ctx, management_args): # pragma: no cover
|
|
21
|
+
"Execute Django subcommands."
|
|
22
|
+
|
|
23
|
+
from django.conf import settings
|
|
24
|
+
|
|
25
|
+
options = {
|
|
26
|
+
"group": DEFAULT_APP_CONFIG_GROUP,
|
|
27
|
+
"name": DEFAULT_APP_CONFIG_NAME,
|
|
28
|
+
"django_settings_module": DJANGO_SETTINGS_MODULE,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
initialize_extension(options=options)
|
|
32
|
+
|
|
33
|
+
if settings.USE_APPLICATIONINSIGHTS:
|
|
34
|
+
tracer = trace.get_tracer(__name__)
|
|
35
|
+
tracer_context = tracer.start_as_current_span(
|
|
36
|
+
f"Running Django command {management_args[0]}",
|
|
37
|
+
)
|
|
38
|
+
else:
|
|
39
|
+
tracer_context = nullcontext()
|
|
40
|
+
|
|
41
|
+
with tracer_context:
|
|
42
|
+
execute_from_command_line(argv=[ctx.command_path] + list(management_args))
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import debugpy
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
|
|
5
|
+
from mpt_extension_sdk.runtime.master import Master
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command()
|
|
9
|
+
@click.argument(
|
|
10
|
+
"component",
|
|
11
|
+
default="all",
|
|
12
|
+
type=click.Choice(["all", "api", "consumer"]),
|
|
13
|
+
metavar="[COMPONENT]",
|
|
14
|
+
)
|
|
15
|
+
@click.option("--color/--no-color", default=True)
|
|
16
|
+
@click.option("--debug", is_flag=True, default=False)
|
|
17
|
+
@click.option("--reload", is_flag=True, default=False)
|
|
18
|
+
@click.option("--debug-py", default=None)
|
|
19
|
+
def run(component, color, debug, reload, debug_py):
|
|
20
|
+
"""Run the extension.
|
|
21
|
+
|
|
22
|
+
\b
|
|
23
|
+
COMPONENT is the the name of the component to run. Possible values:
|
|
24
|
+
* all - run both API and Event Consumer threads (default)
|
|
25
|
+
* api - run only API thread
|
|
26
|
+
* consumer - run only Event Consumer thread
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
if debug_py:
|
|
30
|
+
host, port = debug_py.split(":")
|
|
31
|
+
debugpy.listen((host, int(port))) # pragma: no cover
|
|
32
|
+
|
|
33
|
+
options = {
|
|
34
|
+
"color": color,
|
|
35
|
+
"debug": debug,
|
|
36
|
+
"reload": reload,
|
|
37
|
+
"component": component,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
master = Master(
|
|
41
|
+
options,
|
|
42
|
+
settings=settings,
|
|
43
|
+
)
|
|
44
|
+
master.run() # pragma: no cover
|
|
File without changes
|