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.
Files changed (53) hide show
  1. mpt_extension_sdk/__init__.py +0 -0
  2. mpt_extension_sdk/airtable/__init__.py +0 -0
  3. mpt_extension_sdk/airtable/wrap_http_error.py +45 -0
  4. mpt_extension_sdk/constants.py +11 -0
  5. mpt_extension_sdk/core/__init__.py +0 -0
  6. mpt_extension_sdk/core/events/__init__.py +0 -0
  7. mpt_extension_sdk/core/events/dataclasses.py +16 -0
  8. mpt_extension_sdk/core/events/registry.py +56 -0
  9. mpt_extension_sdk/core/extension.py +12 -0
  10. mpt_extension_sdk/core/security.py +50 -0
  11. mpt_extension_sdk/core/utils.py +17 -0
  12. mpt_extension_sdk/flows/__init__.py +0 -0
  13. mpt_extension_sdk/flows/context.py +39 -0
  14. mpt_extension_sdk/flows/pipeline.py +51 -0
  15. mpt_extension_sdk/key_vault/__init__.py +0 -0
  16. mpt_extension_sdk/key_vault/base.py +110 -0
  17. mpt_extension_sdk/mpt_http/__init__.py +0 -0
  18. mpt_extension_sdk/mpt_http/base.py +43 -0
  19. mpt_extension_sdk/mpt_http/mpt.py +530 -0
  20. mpt_extension_sdk/mpt_http/utils.py +2 -0
  21. mpt_extension_sdk/mpt_http/wrap_http_error.py +68 -0
  22. mpt_extension_sdk/runtime/__init__.py +10 -0
  23. mpt_extension_sdk/runtime/commands/__init__.py +0 -0
  24. mpt_extension_sdk/runtime/commands/django.py +42 -0
  25. mpt_extension_sdk/runtime/commands/run.py +44 -0
  26. mpt_extension_sdk/runtime/djapp/__init__.py +0 -0
  27. mpt_extension_sdk/runtime/djapp/apps.py +46 -0
  28. mpt_extension_sdk/runtime/djapp/conf/__init__.py +12 -0
  29. mpt_extension_sdk/runtime/djapp/conf/default.py +225 -0
  30. mpt_extension_sdk/runtime/djapp/conf/urls.py +9 -0
  31. mpt_extension_sdk/runtime/djapp/management/__init__.py +0 -0
  32. mpt_extension_sdk/runtime/djapp/management/commands/__init__.py +0 -0
  33. mpt_extension_sdk/runtime/djapp/management/commands/consume_events.py +38 -0
  34. mpt_extension_sdk/runtime/djapp/middleware.py +21 -0
  35. mpt_extension_sdk/runtime/events/__init__.py +0 -0
  36. mpt_extension_sdk/runtime/events/dispatcher.py +83 -0
  37. mpt_extension_sdk/runtime/events/producers.py +108 -0
  38. mpt_extension_sdk/runtime/events/utils.py +85 -0
  39. mpt_extension_sdk/runtime/initializer.py +62 -0
  40. mpt_extension_sdk/runtime/logging.py +36 -0
  41. mpt_extension_sdk/runtime/master.py +136 -0
  42. mpt_extension_sdk/runtime/swoext.py +69 -0
  43. mpt_extension_sdk/runtime/tracer.py +18 -0
  44. mpt_extension_sdk/runtime/utils.py +148 -0
  45. mpt_extension_sdk/runtime/workers.py +90 -0
  46. mpt_extension_sdk/swo_rql/__init__.py +5 -0
  47. mpt_extension_sdk/swo_rql/constants.py +7 -0
  48. mpt_extension_sdk/swo_rql/query_builder.py +392 -0
  49. mpt_extension_sdk-4.5.0.dist-info/METADATA +45 -0
  50. mpt_extension_sdk-4.5.0.dist-info/RECORD +53 -0
  51. mpt_extension_sdk-4.5.0.dist-info/WHEEL +4 -0
  52. mpt_extension_sdk-4.5.0.dist-info/entry_points.txt +6 -0
  53. 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,2 @@
1
+ def find_first(func, iterable, default=None):
2
+ return next(filter(func, iterable), default)
@@ -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
+ )
@@ -0,0 +1,10 @@
1
+ from importlib.metadata import version
2
+
3
+ try:
4
+ __version__ = version("swo-runtime")
5
+ except Exception: # pragma: no cover
6
+ __version__ = "0.0.0"
7
+
8
+
9
+ def get_version():
10
+ return __version__
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