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.
Files changed (42) hide show
  1. mpt_extension_sdk/airtable/wrap_http_error.py +10 -6
  2. mpt_extension_sdk/constants.py +2 -0
  3. mpt_extension_sdk/core/events/dataclasses.py +2 -0
  4. mpt_extension_sdk/core/events/registry.py +8 -3
  5. mpt_extension_sdk/core/extension.py +2 -0
  6. mpt_extension_sdk/core/security.py +35 -27
  7. mpt_extension_sdk/core/utils.py +2 -0
  8. mpt_extension_sdk/flows/context.py +12 -1
  9. mpt_extension_sdk/flows/pipeline.py +11 -2
  10. mpt_extension_sdk/key_vault/base.py +36 -25
  11. mpt_extension_sdk/mpt_http/base.py +15 -3
  12. mpt_extension_sdk/mpt_http/mpt.py +73 -12
  13. mpt_extension_sdk/mpt_http/utils.py +1 -0
  14. mpt_extension_sdk/mpt_http/wrap_http_error.py +30 -8
  15. mpt_extension_sdk/runtime/__init__.py +1 -0
  16. mpt_extension_sdk/runtime/commands/django.py +5 -5
  17. mpt_extension_sdk/runtime/commands/run.py +2 -4
  18. mpt_extension_sdk/runtime/djapp/apps.py +7 -1
  19. mpt_extension_sdk/runtime/djapp/conf/__init__.py +2 -3
  20. mpt_extension_sdk/runtime/djapp/conf/default.py +9 -15
  21. mpt_extension_sdk/runtime/djapp/management/commands/consume_events.py +12 -7
  22. mpt_extension_sdk/runtime/djapp/middleware.py +5 -3
  23. mpt_extension_sdk/runtime/errors.py +5 -0
  24. mpt_extension_sdk/runtime/events/dispatcher.py +21 -19
  25. mpt_extension_sdk/runtime/events/producers.py +29 -25
  26. mpt_extension_sdk/runtime/events/utils.py +6 -5
  27. mpt_extension_sdk/runtime/initializer.py +4 -5
  28. mpt_extension_sdk/runtime/logging.py +11 -2
  29. mpt_extension_sdk/runtime/master.py +25 -17
  30. mpt_extension_sdk/runtime/swoext.py +10 -8
  31. mpt_extension_sdk/runtime/tracer.py +2 -0
  32. mpt_extension_sdk/runtime/utils.py +37 -38
  33. mpt_extension_sdk/runtime/workers.py +14 -8
  34. mpt_extension_sdk/swo_rql/query_builder.py +17 -14
  35. mpt_extension_sdk-5.17.2.dist-info/METADATA +39 -0
  36. mpt_extension_sdk-5.17.2.dist-info/RECORD +54 -0
  37. {mpt_extension_sdk-4.5.0.dist-info → mpt_extension_sdk-5.17.2.dist-info}/WHEEL +1 -1
  38. mpt_extension_sdk-5.17.2.dist-info/entry_points.txt +5 -0
  39. mpt_extension_sdk-4.5.0.dist-info/METADATA +0 -45
  40. mpt_extension_sdk-4.5.0.dist-info/RECORD +0 -53
  41. mpt_extension_sdk-4.5.0.dist-info/entry_points.txt +0 -6
  42. {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
- items = []
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
- items.extend(page["data"])
29
+ pages_items.extend(page["data"])
30
30
  offset += limit
31
31
 
32
- return items
32
+ return pages_items
33
33
 
34
34
 
35
35
  @wrap_mpt_http_error
36
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
- )
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,({','.join(item_ids)}))"
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
@@ -1,2 +1,3 @@
1
1
  def find_first(func, iterable, default=None):
2
+ """Find the first item in an iterable that matches a predicate."""
2
3
  return next(filter(func, iterable), default)
@@ -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
- pass
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 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())
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
- def __init__(self, id, message):
55
- self.id = id
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),
@@ -7,4 +7,5 @@ except Exception: # pragma: no cover
7
7
 
8
8
 
9
9
  def get_version():
10
+ """Get the current version of the package."""
10
11
  return __version__
@@ -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, context_settings=dict(ignore_unknown_options=True)
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] + list(management_args))
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))) # pragma: no cover
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
- pass
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
- "APPLICATIONINSIGHTS_CONNECTION_STRING", ""
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 x: x.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3],
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
- def shutdown(signum, frame):
26
- self.shutdown_event.set()
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
- global _CLIENT
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
- response = self.get_response(request)
21
- return response
23
+ return self.get_response(request)
@@ -0,0 +1,5 @@
1
+ # Custom exception for variable format errors
2
+
3
+
4
+ class VariableNotWellFormedError(Exception):
5
+ """Raised when a variable is not well-formed."""