mpt-extension-sdk 4.5.0__py3-none-any.whl → 5.17.2__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/airtable/wrap_http_error.py +10 -6
- mpt_extension_sdk/constants.py +2 -0
- mpt_extension_sdk/core/events/dataclasses.py +2 -0
- mpt_extension_sdk/core/events/registry.py +8 -3
- mpt_extension_sdk/core/extension.py +2 -0
- mpt_extension_sdk/core/security.py +35 -27
- mpt_extension_sdk/core/utils.py +2 -0
- mpt_extension_sdk/flows/context.py +12 -1
- mpt_extension_sdk/flows/pipeline.py +11 -2
- mpt_extension_sdk/key_vault/base.py +36 -25
- mpt_extension_sdk/mpt_http/base.py +15 -3
- mpt_extension_sdk/mpt_http/mpt.py +73 -12
- mpt_extension_sdk/mpt_http/utils.py +1 -0
- mpt_extension_sdk/mpt_http/wrap_http_error.py +30 -8
- mpt_extension_sdk/runtime/__init__.py +1 -0
- mpt_extension_sdk/runtime/commands/django.py +5 -5
- mpt_extension_sdk/runtime/commands/run.py +2 -4
- mpt_extension_sdk/runtime/djapp/apps.py +7 -1
- mpt_extension_sdk/runtime/djapp/conf/__init__.py +2 -3
- mpt_extension_sdk/runtime/djapp/conf/default.py +9 -15
- mpt_extension_sdk/runtime/djapp/management/commands/consume_events.py +12 -7
- mpt_extension_sdk/runtime/djapp/middleware.py +5 -3
- mpt_extension_sdk/runtime/errors.py +5 -0
- mpt_extension_sdk/runtime/events/dispatcher.py +21 -19
- mpt_extension_sdk/runtime/events/producers.py +29 -25
- mpt_extension_sdk/runtime/events/utils.py +6 -5
- mpt_extension_sdk/runtime/initializer.py +4 -5
- mpt_extension_sdk/runtime/logging.py +11 -2
- mpt_extension_sdk/runtime/master.py +25 -17
- mpt_extension_sdk/runtime/swoext.py +10 -8
- mpt_extension_sdk/runtime/tracer.py +2 -0
- mpt_extension_sdk/runtime/utils.py +37 -38
- mpt_extension_sdk/runtime/workers.py +14 -8
- mpt_extension_sdk/swo_rql/query_builder.py +17 -14
- mpt_extension_sdk-5.17.2.dist-info/METADATA +39 -0
- mpt_extension_sdk-5.17.2.dist-info/RECORD +54 -0
- {mpt_extension_sdk-4.5.0.dist-info → mpt_extension_sdk-5.17.2.dist-info}/WHEEL +1 -1
- mpt_extension_sdk-5.17.2.dist-info/entry_points.txt +5 -0
- mpt_extension_sdk-4.5.0.dist-info/METADATA +0 -45
- mpt_extension_sdk-4.5.0.dist-info/RECORD +0 -53
- mpt_extension_sdk-4.5.0.dist-info/entry_points.txt +0 -6
- {mpt_extension_sdk-4.5.0.dist-info → mpt_extension_sdk-5.17.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -19,30 +19,31 @@ def _has_more_pages(page):
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def _paginated(mpt_client, url, limit=10):
|
|
22
|
-
|
|
22
|
+
pages_items = []
|
|
23
23
|
page = None
|
|
24
24
|
offset = 0
|
|
25
25
|
while _has_more_pages(page):
|
|
26
26
|
response = mpt_client.get(f"{url}&limit={limit}&offset={offset}")
|
|
27
27
|
response.raise_for_status()
|
|
28
28
|
page = response.json()
|
|
29
|
-
|
|
29
|
+
pages_items.extend(page["data"])
|
|
30
30
|
offset += limit
|
|
31
31
|
|
|
32
|
-
return
|
|
32
|
+
return pages_items
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
@wrap_mpt_http_error
|
|
36
36
|
def get_agreement(mpt_client, agreement_id):
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
)
|
|
37
|
+
"""Retrieve an agreement by ID."""
|
|
38
|
+
query = "select=seller,buyer,listing,product,subscriptions,assets,lines,parameters"
|
|
39
|
+
response = mpt_client.get(f"/commerce/agreements/{agreement_id}?{query}")
|
|
40
40
|
response.raise_for_status()
|
|
41
41
|
return response.json()
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
@wrap_mpt_http_error
|
|
45
45
|
def get_licensee(mpt_client, licensee_id):
|
|
46
|
+
"""Retrieve a licensee by ID."""
|
|
46
47
|
response = mpt_client.get(f"/accounts/licensees/{licensee_id}")
|
|
47
48
|
response.raise_for_status()
|
|
48
49
|
return response.json()
|
|
@@ -50,6 +51,7 @@ def get_licensee(mpt_client, licensee_id):
|
|
|
50
51
|
|
|
51
52
|
@wrap_mpt_http_error
|
|
52
53
|
def update_order(mpt_client, order_id, **kwargs):
|
|
54
|
+
"""Update an order."""
|
|
53
55
|
response = mpt_client.put(
|
|
54
56
|
f"/commerce/orders/{order_id}",
|
|
55
57
|
json=kwargs,
|
|
@@ -60,6 +62,7 @@ def update_order(mpt_client, order_id, **kwargs):
|
|
|
60
62
|
|
|
61
63
|
@wrap_mpt_http_error
|
|
62
64
|
def query_order(mpt_client, order_id, **kwargs):
|
|
65
|
+
"""Query an order."""
|
|
63
66
|
response = mpt_client.post(
|
|
64
67
|
f"/commerce/orders/{order_id}/query",
|
|
65
68
|
json=kwargs,
|
|
@@ -70,6 +73,7 @@ def query_order(mpt_client, order_id, **kwargs):
|
|
|
70
73
|
|
|
71
74
|
@wrap_mpt_http_error
|
|
72
75
|
def fail_order(mpt_client, order_id, status_notes, **kwargs):
|
|
76
|
+
"""Fail an order."""
|
|
73
77
|
response = mpt_client.post(
|
|
74
78
|
f"/commerce/orders/{order_id}/fail",
|
|
75
79
|
json={
|
|
@@ -83,6 +87,7 @@ def fail_order(mpt_client, order_id, status_notes, **kwargs):
|
|
|
83
87
|
|
|
84
88
|
@wrap_mpt_http_error
|
|
85
89
|
def complete_order(mpt_client, order_id, template, **kwargs):
|
|
90
|
+
"""Complete an order."""
|
|
86
91
|
response = mpt_client.post(
|
|
87
92
|
f"/commerce/orders/{order_id}/complete",
|
|
88
93
|
json={"template": template, **kwargs},
|
|
@@ -93,6 +98,7 @@ def complete_order(mpt_client, order_id, template, **kwargs):
|
|
|
93
98
|
|
|
94
99
|
@wrap_mpt_http_error
|
|
95
100
|
def set_processing_template(mpt_client, order_id, template):
|
|
101
|
+
"""Set the processing template for an order."""
|
|
96
102
|
response = mpt_client.put(
|
|
97
103
|
f"/commerce/orders/{order_id}",
|
|
98
104
|
json={"template": template},
|
|
@@ -171,6 +177,7 @@ def get_order_asset_by_external_id(mpt_client, order_id, asset_external_id):
|
|
|
171
177
|
|
|
172
178
|
@wrap_mpt_http_error
|
|
173
179
|
def create_subscription(mpt_client, order_id, subscription):
|
|
180
|
+
"""Create a new subscription for an order."""
|
|
174
181
|
response = mpt_client.post(
|
|
175
182
|
f"/commerce/orders/{order_id}/subscriptions",
|
|
176
183
|
json=subscription,
|
|
@@ -181,6 +188,7 @@ def create_subscription(mpt_client, order_id, subscription):
|
|
|
181
188
|
|
|
182
189
|
@wrap_mpt_http_error
|
|
183
190
|
def update_subscription(mpt_client, order_id, subscription_id, **kwargs):
|
|
191
|
+
"""Update an order subscription."""
|
|
184
192
|
response = mpt_client.put(
|
|
185
193
|
f"/commerce/orders/{order_id}/subscriptions/{subscription_id}",
|
|
186
194
|
json=kwargs,
|
|
@@ -199,6 +207,7 @@ def get_order_subscription_by_external_id(mpt_client, order_id, subscription_ext
|
|
|
199
207
|
subscriptions = response.json()
|
|
200
208
|
if subscriptions["$meta"]["pagination"]["total"] == 1:
|
|
201
209
|
return subscriptions["data"][0]
|
|
210
|
+
return None
|
|
202
211
|
|
|
203
212
|
|
|
204
213
|
@wrap_mpt_http_error
|
|
@@ -213,6 +222,7 @@ def get_product_items_by_skus(mpt_client, product_id, skus):
|
|
|
213
222
|
@cache
|
|
214
223
|
@wrap_mpt_http_error
|
|
215
224
|
def get_webhook(mpt_client, webhook_id):
|
|
225
|
+
"""Retrieve a webhook by ID."""
|
|
216
226
|
response = mpt_client.get(f"/notifications/webhooks/{webhook_id}?select=criteria")
|
|
217
227
|
response.raise_for_status()
|
|
218
228
|
|
|
@@ -221,6 +231,7 @@ def get_webhook(mpt_client, webhook_id):
|
|
|
221
231
|
|
|
222
232
|
@wrap_mpt_http_error
|
|
223
233
|
def get_product_template_or_default(mpt_client, product_id, status, name=None):
|
|
234
|
+
"""Retrieve a product template by ID or return the default template."""
|
|
224
235
|
name_or_default_filter = "eq(default,true)"
|
|
225
236
|
if name:
|
|
226
237
|
name_or_default_filter = f"or({name_or_default_filter},eq(name,{name}))"
|
|
@@ -229,19 +240,34 @@ def get_product_template_or_default(mpt_client, product_id, status, name=None):
|
|
|
229
240
|
response = mpt_client.get(url)
|
|
230
241
|
response.raise_for_status()
|
|
231
242
|
templates = response.json()
|
|
232
|
-
return templates["data"][0]
|
|
243
|
+
return templates["data"][0] if templates["data"] else None
|
|
233
244
|
|
|
234
245
|
|
|
246
|
+
@wrap_mpt_http_error
|
|
235
247
|
def get_template_by_name(mpt_client, product_id, template_name):
|
|
248
|
+
"""Retrieve a product template by name."""
|
|
236
249
|
url = f"/catalog/products/{product_id}/templates?eq(name,{template_name})"
|
|
237
250
|
response = mpt_client.get(url)
|
|
238
251
|
response.raise_for_status()
|
|
239
252
|
templates = response.json()
|
|
240
|
-
return templates["data"][0]
|
|
253
|
+
return templates["data"][0] if templates["data"] else None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@wrap_mpt_http_error
|
|
257
|
+
def get_asset_template_by_name(mpt_client, product_id, template_name):
|
|
258
|
+
"""Retrieve an asset template by name."""
|
|
259
|
+
rql_filter = f"and(eq(type,Asset),eq(name,{template_name}))"
|
|
260
|
+
url = f"/catalog/products/{product_id}/templates?{rql_filter}&limit=1"
|
|
261
|
+
response = mpt_client.get(url)
|
|
262
|
+
response.raise_for_status()
|
|
263
|
+
|
|
264
|
+
templates = response.json()
|
|
265
|
+
return templates["data"][0] if templates["data"] else None
|
|
241
266
|
|
|
242
267
|
|
|
243
268
|
@wrap_mpt_http_error
|
|
244
269
|
def update_agreement(mpt_client, agreement_id, **kwargs):
|
|
270
|
+
"""Update an agreement."""
|
|
245
271
|
response = mpt_client.put(
|
|
246
272
|
f"/commerce/agreements/{agreement_id}",
|
|
247
273
|
json=kwargs,
|
|
@@ -251,13 +277,26 @@ def update_agreement(mpt_client, agreement_id, **kwargs):
|
|
|
251
277
|
|
|
252
278
|
|
|
253
279
|
@wrap_mpt_http_error
|
|
254
|
-
def get_agreements_by_query(mpt_client, query):
|
|
280
|
+
def get_agreements_by_query(mpt_client: MPTClient, query: str, limit: int = 10):
|
|
281
|
+
"""Retrieve agreements by query."""
|
|
255
282
|
url = f"/commerce/agreements?{query}"
|
|
256
|
-
return _paginated(mpt_client, url)
|
|
283
|
+
return _paginated(mpt_client, url, limit=limit)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@wrap_mpt_http_error
|
|
287
|
+
def get_subscriptions_by_query(
|
|
288
|
+
mpt_client: MPTClient,
|
|
289
|
+
query: str,
|
|
290
|
+
limit: int = 10,
|
|
291
|
+
) -> list[dict]:
|
|
292
|
+
"""Retrieve subscriptions by query."""
|
|
293
|
+
url = f"/commerce/subscriptions?{query}"
|
|
294
|
+
return _paginated(mpt_client, url, limit=limit)
|
|
257
295
|
|
|
258
296
|
|
|
259
297
|
@wrap_mpt_http_error
|
|
260
298
|
def update_agreement_subscription(mpt_client, subscription_id, **kwargs):
|
|
299
|
+
"""Update an agreement subscription."""
|
|
261
300
|
response = mpt_client.put(
|
|
262
301
|
f"/commerce/subscriptions/{subscription_id}",
|
|
263
302
|
json=kwargs,
|
|
@@ -268,6 +307,7 @@ def update_agreement_subscription(mpt_client, subscription_id, **kwargs):
|
|
|
268
307
|
|
|
269
308
|
@wrap_mpt_http_error
|
|
270
309
|
def get_agreement_subscription(mpt_client, subscription_id):
|
|
310
|
+
"""Retrieve an agreement subscription by ID."""
|
|
271
311
|
response = mpt_client.get(
|
|
272
312
|
f"/commerce/subscriptions/{subscription_id}",
|
|
273
313
|
)
|
|
@@ -277,6 +317,7 @@ def get_agreement_subscription(mpt_client, subscription_id):
|
|
|
277
317
|
|
|
278
318
|
@wrap_mpt_http_error
|
|
279
319
|
def get_rendered_template(mpt_client, order_id):
|
|
320
|
+
"""Retrieve the rendered template for an order."""
|
|
280
321
|
response = mpt_client.get(
|
|
281
322
|
f"/commerce/orders/{order_id}/template",
|
|
282
323
|
)
|
|
@@ -286,8 +327,10 @@ def get_rendered_template(mpt_client, order_id):
|
|
|
286
327
|
|
|
287
328
|
@wrap_mpt_http_error
|
|
288
329
|
def get_product_onetime_items_by_ids(mpt_client, product_id, item_ids):
|
|
330
|
+
"""Retrieve one-time product items by their IDs."""
|
|
331
|
+
item_ids_str = ",".join(item_ids)
|
|
289
332
|
product_cond = f"eq(product.id,{product_id})"
|
|
290
|
-
items_cond = f"in(id,({
|
|
333
|
+
items_cond = f"in(id,({item_ids_str}))"
|
|
291
334
|
rql_query = f"and({product_cond},{items_cond},eq(terms.period,one-time))"
|
|
292
335
|
url = f"/catalog/items?{rql_query}"
|
|
293
336
|
|
|
@@ -362,6 +405,7 @@ def get_authorizations_by_currency_and_seller_id(mpt_client, product_id, currenc
|
|
|
362
405
|
|
|
363
406
|
@wrap_mpt_http_error
|
|
364
407
|
def get_gc_price_list_by_currency(mpt_client, product_id, currency):
|
|
408
|
+
"""Retrieve a GC price list by product ID and currency."""
|
|
365
409
|
response = mpt_client.get(
|
|
366
410
|
f"/catalog/price-lists?eq(product.id,{product_id})&eq(currency,{currency})"
|
|
367
411
|
)
|
|
@@ -373,6 +417,7 @@ def get_gc_price_list_by_currency(mpt_client, product_id, currency):
|
|
|
373
417
|
def get_listings_by_price_list_and_seller_and_authorization(
|
|
374
418
|
mpt_client, product_id, price_list_id, seller_id, authorization_id
|
|
375
419
|
):
|
|
420
|
+
"""Retrieve listings by price list, seller, and authorization."""
|
|
376
421
|
response = mpt_client.get(
|
|
377
422
|
f"/catalog/listings?eq(product.id,{product_id})&eq(priceList.id,{price_list_id})"
|
|
378
423
|
f"&eq(seller.id,{seller_id})"
|
|
@@ -382,8 +427,17 @@ def get_listings_by_price_list_and_seller_and_authorization(
|
|
|
382
427
|
return response.json()["data"]
|
|
383
428
|
|
|
384
429
|
|
|
430
|
+
@wrap_mpt_http_error
|
|
431
|
+
def get_item_prices_by_pricelist_id(mpt_client, price_list_id, item_id):
|
|
432
|
+
"""Retrieve item prices by price list ID."""
|
|
433
|
+
response = mpt_client.get(f"/catalog/price-lists/{price_list_id}/items?eq(item.id,{item_id})")
|
|
434
|
+
response.raise_for_status()
|
|
435
|
+
return response.json()["data"]
|
|
436
|
+
|
|
437
|
+
|
|
385
438
|
@wrap_mpt_http_error
|
|
386
439
|
def create_listing(mpt_client, listing):
|
|
440
|
+
"""Create a new listing."""
|
|
387
441
|
response = mpt_client.post(
|
|
388
442
|
"/catalog/listings",
|
|
389
443
|
json=listing,
|
|
@@ -394,6 +448,7 @@ def create_listing(mpt_client, listing):
|
|
|
394
448
|
|
|
395
449
|
@wrap_mpt_http_error
|
|
396
450
|
def create_agreement(mpt_client, agreement):
|
|
451
|
+
"""Create a new agreement."""
|
|
397
452
|
response = mpt_client.post(
|
|
398
453
|
"/commerce/agreements",
|
|
399
454
|
json=agreement,
|
|
@@ -404,6 +459,7 @@ def create_agreement(mpt_client, agreement):
|
|
|
404
459
|
|
|
405
460
|
@wrap_mpt_http_error
|
|
406
461
|
def create_agreement_subscription(mpt_client, subscription):
|
|
462
|
+
"""Create a new agreement subscription."""
|
|
407
463
|
response = mpt_client.post(
|
|
408
464
|
"/commerce/subscriptions",
|
|
409
465
|
json=subscription,
|
|
@@ -414,6 +470,7 @@ def create_agreement_subscription(mpt_client, subscription):
|
|
|
414
470
|
|
|
415
471
|
@wrap_mpt_http_error
|
|
416
472
|
def get_listing_by_id(mpt_client, listing_id):
|
|
473
|
+
"""Retrieve a listing by ID."""
|
|
417
474
|
response = mpt_client.get(f"/catalog/listings/{listing_id}")
|
|
418
475
|
response.raise_for_status()
|
|
419
476
|
return response.json()
|
|
@@ -436,6 +493,7 @@ def get_agreement_subscription_by_external_id(mpt_client, agreement_id, subscrip
|
|
|
436
493
|
|
|
437
494
|
@wrap_mpt_http_error
|
|
438
495
|
def get_agreements_by_external_id_values(mpt_client, external_id, display_values):
|
|
496
|
+
"""Retrieve agreements by external ID and display values."""
|
|
439
497
|
display_values_list = ",".join(display_values)
|
|
440
498
|
rql_query = (
|
|
441
499
|
f"any(parameters.fulfillment,and("
|
|
@@ -457,7 +515,7 @@ def get_agreements_by_customer_deployments(mpt_client, deployment_id_parameter,
|
|
|
457
515
|
f"any(parameters.fulfillment,and("
|
|
458
516
|
f"eq(externalId,{deployment_id_parameter}),"
|
|
459
517
|
f"in(displayValue,({deployments_list}))))"
|
|
460
|
-
f"&select=lines,parameters,subscriptions,subscriptions.parameters,product,listing"
|
|
518
|
+
f"&select=lines,parameters,subscriptions,subscriptions.parameters,assets,product,listing"
|
|
461
519
|
)
|
|
462
520
|
|
|
463
521
|
url = f"/commerce/agreements?{rql_query}"
|
|
@@ -467,6 +525,7 @@ def get_agreements_by_customer_deployments(mpt_client, deployment_id_parameter,
|
|
|
467
525
|
|
|
468
526
|
@wrap_mpt_http_error
|
|
469
527
|
def get_buyer(mpt_client, buyer_id):
|
|
528
|
+
"""Retrieve a buyer by ID."""
|
|
470
529
|
response = mpt_client.get(f"/accounts/buyers/{buyer_id}")
|
|
471
530
|
response.raise_for_status()
|
|
472
531
|
return response.json()
|
|
@@ -483,6 +542,8 @@ def notify(
|
|
|
483
542
|
limit: int = 1000,
|
|
484
543
|
) -> None:
|
|
485
544
|
"""
|
|
545
|
+
Sends notification to multiple recipients for a specific buyer.
|
|
546
|
+
|
|
486
547
|
Sends notifications to multiple recipients in batches for a specific buyer and
|
|
487
548
|
category through the MPTClient service. The function retrieves recipients,
|
|
488
549
|
groups them into manageable batches, and sends notifications using the provided
|
|
@@ -2,13 +2,20 @@ import json
|
|
|
2
2
|
from functools import wraps
|
|
3
3
|
|
|
4
4
|
from requests import HTTPError, JSONDecodeError
|
|
5
|
+
from requests.exceptions import RetryError
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class MPTError(Exception):
|
|
8
|
-
|
|
9
|
+
"""Represents a generic MPT error."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MPTMaxRetryError(MPTError):
|
|
13
|
+
"""Represents a maximum request retry error."""
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
class MPTHttpError(MPTError):
|
|
17
|
+
"""Represents an HTTP error."""
|
|
18
|
+
|
|
12
19
|
def __init__(self, status_code: int, content: str):
|
|
13
20
|
self.status_code = status_code
|
|
14
21
|
self.content = content
|
|
@@ -16,6 +23,8 @@ class MPTHttpError(MPTError):
|
|
|
16
23
|
|
|
17
24
|
|
|
18
25
|
class MPTAPIError(MPTHttpError):
|
|
26
|
+
"""Represents an API error."""
|
|
27
|
+
|
|
19
28
|
def __init__(self, status_code, payload):
|
|
20
29
|
super().__init__(status_code, json.dumps(payload))
|
|
21
30
|
self.payload = payload
|
|
@@ -36,26 +45,39 @@ class MPTAPIError(MPTHttpError):
|
|
|
36
45
|
return str(self.payload)
|
|
37
46
|
|
|
38
47
|
|
|
48
|
+
def transform_http_error(err):
|
|
49
|
+
"""Transform an HTTP error to an MPT error."""
|
|
50
|
+
try:
|
|
51
|
+
payload = err.response.json()
|
|
52
|
+
except JSONDecodeError:
|
|
53
|
+
return MPTHttpError(err.response.status_code, err.response.content.decode())
|
|
54
|
+
return MPTAPIError(err.response.status_code, payload)
|
|
55
|
+
|
|
56
|
+
|
|
39
57
|
def wrap_mpt_http_error(func):
|
|
58
|
+
"""Wrap a function to catch MPT HTTP errors."""
|
|
59
|
+
|
|
40
60
|
@wraps(func)
|
|
41
61
|
def _wrapper(*args, **kwargs):
|
|
42
62
|
try:
|
|
43
63
|
return func(*args, **kwargs)
|
|
44
|
-
except HTTPError as
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
raise MPTHttpError(e.response.status_code, e.response.content.decode())
|
|
64
|
+
except HTTPError as err:
|
|
65
|
+
raise transform_http_error(err) from err
|
|
66
|
+
except RetryError as retry_error:
|
|
67
|
+
raise MPTMaxRetryError(str(retry_error))
|
|
49
68
|
|
|
50
69
|
return _wrapper
|
|
51
70
|
|
|
52
71
|
|
|
53
72
|
class ValidationError:
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
"""Represents a validation error."""
|
|
74
|
+
|
|
75
|
+
def __init__(self, err_id, message):
|
|
76
|
+
self.id = err_id
|
|
56
77
|
self.message = message
|
|
57
78
|
|
|
58
79
|
def to_dict(self, **kwargs):
|
|
80
|
+
"""Convert the validation error to a dictionary."""
|
|
59
81
|
return {
|
|
60
82
|
"id": self.id,
|
|
61
83
|
"message": self.message.format(**kwargs),
|
|
@@ -13,14 +13,14 @@ from mpt_extension_sdk.runtime.utils import initialize_extension
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@click.command(
|
|
16
|
-
add_help_option=False,
|
|
16
|
+
add_help_option=False,
|
|
17
|
+
context_settings={"ignore_unknown_options": True},
|
|
17
18
|
)
|
|
18
19
|
@click.argument("management_args", nargs=-1, type=click.UNPROCESSED)
|
|
19
20
|
@click.pass_context
|
|
20
21
|
def django(ctx, management_args): # pragma: no cover
|
|
21
|
-
"Execute Django subcommands."
|
|
22
|
-
|
|
23
|
-
from django.conf import settings
|
|
22
|
+
"""Execute Django subcommands."""
|
|
23
|
+
from django.conf import settings # noqa: PLC0415
|
|
24
24
|
|
|
25
25
|
options = {
|
|
26
26
|
"group": DEFAULT_APP_CONFIG_GROUP,
|
|
@@ -39,4 +39,4 @@ def django(ctx, management_args): # pragma: no cover
|
|
|
39
39
|
tracer_context = nullcontext()
|
|
40
40
|
|
|
41
41
|
with tracer_context:
|
|
42
|
-
execute_from_command_line(argv=[ctx.command_path
|
|
42
|
+
execute_from_command_line(argv=[ctx.command_path, *management_args])
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import click
|
|
2
|
-
import debugpy
|
|
2
|
+
import debugpy # noqa
|
|
3
3
|
from django.conf import settings
|
|
4
4
|
|
|
5
5
|
from mpt_extension_sdk.runtime.master import Master
|
|
@@ -19,16 +19,14 @@ from mpt_extension_sdk.runtime.master import Master
|
|
|
19
19
|
def run(component, color, debug, reload, debug_py):
|
|
20
20
|
"""Run the extension.
|
|
21
21
|
|
|
22
|
-
\b
|
|
23
22
|
COMPONENT is the the name of the component to run. Possible values:
|
|
24
23
|
* all - run both API and Event Consumer threads (default)
|
|
25
24
|
* api - run only API thread
|
|
26
25
|
* consumer - run only Event Consumer thread
|
|
27
26
|
"""
|
|
28
|
-
|
|
29
27
|
if debug_py:
|
|
30
28
|
host, port = debug_py.split(":")
|
|
31
|
-
debugpy.listen((host, int(port))) #
|
|
29
|
+
debugpy.listen((host, int(port))) # noqa
|
|
32
30
|
|
|
33
31
|
options = {
|
|
34
32
|
"color": color,
|
|
@@ -8,9 +8,12 @@ ext = Extension()
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class DjAppConfig(AppConfig):
|
|
11
|
+
"""Django AppConfig for the extension."""
|
|
12
|
+
|
|
11
13
|
name = "mpt_extension_sdk.runtime.djapp"
|
|
12
14
|
|
|
13
15
|
def ready(self):
|
|
16
|
+
"""Check if the extension is properly configured."""
|
|
14
17
|
if not hasattr(settings, "MPT_PRODUCTS_IDS") or not settings.MPT_PRODUCTS_IDS:
|
|
15
18
|
raise ImproperlyConfigured(
|
|
16
19
|
f"Extension {self.verbose_name} is not properly configured."
|
|
@@ -20,15 +23,18 @@ class DjAppConfig(AppConfig):
|
|
|
20
23
|
self.extension_ready()
|
|
21
24
|
|
|
22
25
|
def extension_ready(self):
|
|
23
|
-
|
|
26
|
+
"""Perform actions when the extension is ready."""
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
class ExtensionConfig(DjAppConfig):
|
|
30
|
+
"""Configuration for the extension."""
|
|
31
|
+
|
|
27
32
|
name = "mpt_extension_sdk"
|
|
28
33
|
verbose_name = "SWO Extension SDK"
|
|
29
34
|
extension = ext
|
|
30
35
|
|
|
31
36
|
def extension_ready(self):
|
|
37
|
+
"""Perform actions when the extension is ready."""
|
|
32
38
|
error_msgs = []
|
|
33
39
|
|
|
34
40
|
for product_id in settings.MPT_PRODUCTS_IDS:
|
|
@@ -2,11 +2,10 @@ from typing import Any
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def extract_product_ids(product_ids: str) -> list[str]:
|
|
5
|
+
"""Extract product IDs from a comma-separated string."""
|
|
5
6
|
return product_ids.split(",")
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def get_for_product(settings, variable_name: str, product_id: str) -> Any:
|
|
9
|
-
"""
|
|
10
|
-
A shortcut to return product scoped variable from the extension settings.
|
|
11
|
-
"""
|
|
10
|
+
"""A shortcut to return product scoped variable from the extension settings."""
|
|
12
11
|
return settings.EXTENSION_CONFIG[variable_name][product_id]
|
|
@@ -14,9 +14,9 @@ import os
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
16
|
from azure.monitor.opentelemetry.exporter import AzureMonitorLogExporter
|
|
17
|
-
from opentelemetry._logs import set_logger_provider
|
|
18
|
-
from opentelemetry.sdk._logs import LoggerProvider
|
|
19
|
-
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
|
17
|
+
from opentelemetry._logs import set_logger_provider # noqa: PLC2701
|
|
18
|
+
from opentelemetry.sdk._logs import LoggerProvider # noqa: PLC2701
|
|
19
|
+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor # noqa: PLC2701
|
|
20
20
|
|
|
21
21
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
22
22
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
@@ -34,7 +34,7 @@ SECRET_KEY = os.getenv(
|
|
|
34
34
|
# SECURITY WARNING: don't run with debug turned on in production!
|
|
35
35
|
DEBUG = True
|
|
36
36
|
|
|
37
|
-
ALLOWED_HOSTS =
|
|
37
|
+
ALLOWED_HOSTS = ("*",)
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
# Application definition
|
|
@@ -127,18 +127,14 @@ STATIC_URL = "static/"
|
|
|
127
127
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
128
128
|
|
|
129
129
|
# OpenTelemetry configuration
|
|
130
|
-
APPLICATIONINSIGHTS_CONNECTION_STRING = os.getenv(
|
|
131
|
-
|
|
132
|
-
)
|
|
133
|
-
USE_APPLICATIONINSIGHTS = APPLICATIONINSIGHTS_CONNECTION_STRING != ""
|
|
130
|
+
APPLICATIONINSIGHTS_CONNECTION_STRING = os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING", "")
|
|
131
|
+
USE_APPLICATIONINSIGHTS = bool(APPLICATIONINSIGHTS_CONNECTION_STRING)
|
|
134
132
|
|
|
135
133
|
|
|
136
134
|
if USE_APPLICATIONINSIGHTS: # pragma: no cover
|
|
137
135
|
logger_provider = LoggerProvider()
|
|
138
136
|
set_logger_provider(logger_provider)
|
|
139
|
-
exporter = AzureMonitorLogExporter(
|
|
140
|
-
connection_string=APPLICATIONINSIGHTS_CONNECTION_STRING
|
|
141
|
-
)
|
|
137
|
+
exporter = AzureMonitorLogExporter(connection_string=APPLICATIONINSIGHTS_CONNECTION_STRING)
|
|
142
138
|
logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter))
|
|
143
139
|
|
|
144
140
|
LOGGING = {
|
|
@@ -166,7 +162,7 @@ LOGGING = {
|
|
|
166
162
|
"rich": {
|
|
167
163
|
"class": "mpt_extension_sdk.runtime.logging.RichHandler",
|
|
168
164
|
"formatter": "rich",
|
|
169
|
-
"log_time_format": lambda
|
|
165
|
+
"log_time_format": lambda log_time: log_time.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3],
|
|
170
166
|
"rich_tracebacks": True,
|
|
171
167
|
},
|
|
172
168
|
"opentelemetry": {
|
|
@@ -210,9 +206,7 @@ MPT_PRODUCTS_IDS = os.getenv("MPT_PRODUCTS_IDS", "PRD-1111-1111")
|
|
|
210
206
|
MPT_PORTAL_BASE_URL = os.getenv("MPT_PORTAL_BASE_URL", "https://portal.s1.show")
|
|
211
207
|
MPT_KEY_VAULT_NAME = os.getenv("MPT_KEY_VAULT_NAME", "mpt-key-vault")
|
|
212
208
|
|
|
213
|
-
MPT_ORDERS_API_POLLING_INTERVAL_SECS = int(
|
|
214
|
-
os.getenv("MPT_ORDERS_API_POLLING_INTERVAL_SECS", "120")
|
|
215
|
-
)
|
|
209
|
+
MPT_ORDERS_API_POLLING_INTERVAL_SECS = int(os.getenv("MPT_ORDERS_API_POLLING_INTERVAL_SECS", "120"))
|
|
216
210
|
|
|
217
211
|
EXTENSION_CONFIG = {
|
|
218
212
|
"DUE_DATE_DAYS": "30",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import signal # pragma: no cover
|
|
2
2
|
from threading import Event # pragma: no cover
|
|
3
|
+
from typing import ClassVar # pragma: no cover
|
|
3
4
|
|
|
4
5
|
from django.core.management.base import BaseCommand # pragma: no cover
|
|
5
6
|
|
|
@@ -11,22 +12,22 @@ from mpt_extension_sdk.runtime.events.producers import (
|
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class Command(BaseCommand): # pragma: no cover
|
|
15
|
+
"""Command to consume events."""
|
|
16
|
+
|
|
14
17
|
help = CONSUME_EVENTS_HELP_TEXT
|
|
15
|
-
producer_classes = [
|
|
18
|
+
producer_classes: ClassVar[list] = [
|
|
16
19
|
OrderEventProducer,
|
|
17
20
|
]
|
|
18
|
-
producers = []
|
|
21
|
+
producers: ClassVar[list] = []
|
|
19
22
|
|
|
20
23
|
def handle(self, *args, **options):
|
|
24
|
+
"""Handle the command."""
|
|
21
25
|
self.shutdown_event = Event()
|
|
22
26
|
self.dispatcher = Dispatcher()
|
|
23
27
|
self.dispatcher.start()
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
signal.signal(signal.SIGTERM, shutdown)
|
|
29
|
-
signal.signal(signal.SIGINT, shutdown)
|
|
29
|
+
signal.signal(signal.SIGTERM, self.shutdown)
|
|
30
|
+
signal.signal(signal.SIGINT, self.shutdown)
|
|
30
31
|
for producer_cls in self.producer_classes:
|
|
31
32
|
producer = producer_cls(self.dispatcher)
|
|
32
33
|
self.producers.append(producer)
|
|
@@ -36,3 +37,7 @@ class Command(BaseCommand): # pragma: no cover
|
|
|
36
37
|
for producer in self.producers:
|
|
37
38
|
producer.stop()
|
|
38
39
|
self.dispatcher.stop()
|
|
40
|
+
|
|
41
|
+
def shutdown(self, signum, frame):
|
|
42
|
+
"""Shutdown the command."""
|
|
43
|
+
self.shutdown_event.set()
|
|
@@ -6,16 +6,18 @@ _CLIENT = None
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class MPTClientMiddleware: # pragma: no cover
|
|
9
|
+
"""Middleware to set up MPTClient for each request."""
|
|
10
|
+
|
|
9
11
|
def __init__(self, get_response):
|
|
10
12
|
self.get_response = get_response
|
|
11
13
|
|
|
12
14
|
def __call__(self, request):
|
|
13
|
-
|
|
15
|
+
"""Set up MPTClient for the request."""
|
|
16
|
+
global _CLIENT # noqa: PLW0603
|
|
14
17
|
if not _CLIENT:
|
|
15
18
|
_CLIENT = MPTClient(
|
|
16
19
|
f"{settings.MPT_API_BASE_URL}/v1/",
|
|
17
20
|
settings.MPT_API_TOKEN,
|
|
18
21
|
)
|
|
19
22
|
request.client = _CLIENT
|
|
20
|
-
|
|
21
|
-
return response
|
|
23
|
+
return self.get_response(request)
|