mpt-extension-sdk 4.0.1__tar.gz → 4.0.3__tar.gz

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 (44) hide show
  1. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/PKG-INFO +1 -1
  2. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/constants.py +2 -1
  3. mpt_extension_sdk-4.0.3/mpt_extension_sdk/flows/context.py +39 -0
  4. mpt_extension_sdk-4.0.3/mpt_extension_sdk/flows/pipeline.py +51 -0
  5. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/mpt_http/mpt.py +7 -0
  6. mpt_extension_sdk-4.0.3/mpt_extension_sdk/mpt_http/utils.py +2 -0
  7. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/mpt_http/wrap_http_error.py +6 -0
  8. mpt_extension_sdk-4.0.3/mpt_extension_sdk/runtime/events/__init__.py +0 -0
  9. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/events/producers.py +17 -2
  10. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/events/utils.py +15 -0
  11. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/initializer.py +11 -4
  12. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/utils.py +12 -10
  13. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/pyproject.toml +2 -2
  14. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/LICENSE +0 -0
  15. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/README.md +0 -0
  16. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/__init__.py +0 -0
  17. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/airtable/wrap_http_error.py +0 -0
  18. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/core/__init__.py +0 -0
  19. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/core/events/__init__.py +0 -0
  20. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/core/events/dataclasses.py +0 -0
  21. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/core/events/registry.py +0 -0
  22. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/core/extension.py +0 -0
  23. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/core/security.py +0 -0
  24. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/core/utils.py +0 -0
  25. {mpt_extension_sdk-4.0.1/mpt_extension_sdk/mpt_http → mpt_extension_sdk-4.0.3/mpt_extension_sdk/flows}/__init__.py +0 -0
  26. {mpt_extension_sdk-4.0.1/mpt_extension_sdk/runtime/commands → mpt_extension_sdk-4.0.3/mpt_extension_sdk/mpt_http}/__init__.py +0 -0
  27. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/mpt_http/base.py +0 -0
  28. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/__init__.py +0 -0
  29. {mpt_extension_sdk-4.0.1/mpt_extension_sdk/runtime/djapp → mpt_extension_sdk-4.0.3/mpt_extension_sdk/runtime/commands}/__init__.py +0 -0
  30. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/commands/django.py +0 -0
  31. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/commands/run.py +0 -0
  32. {mpt_extension_sdk-4.0.1/mpt_extension_sdk/runtime/djapp/management → mpt_extension_sdk-4.0.3/mpt_extension_sdk/runtime/djapp}/__init__.py +0 -0
  33. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/djapp/apps.py +0 -0
  34. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/djapp/conf/__init__.py +0 -0
  35. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/djapp/conf/default.py +0 -0
  36. {mpt_extension_sdk-4.0.1/mpt_extension_sdk/runtime/djapp/management/commands → mpt_extension_sdk-4.0.3/mpt_extension_sdk/runtime/djapp/management}/__init__.py +0 -0
  37. {mpt_extension_sdk-4.0.1/mpt_extension_sdk/runtime/events → mpt_extension_sdk-4.0.3/mpt_extension_sdk/runtime/djapp/management/commands}/__init__.py +0 -0
  38. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/djapp/management/commands/consume_events.py +0 -0
  39. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/djapp/middleware.py +0 -0
  40. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/events/dispatcher.py +0 -0
  41. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/logging.py +0 -0
  42. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/master.py +0 -0
  43. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/swoext.py +0 -0
  44. {mpt_extension_sdk-4.0.1 → mpt_extension_sdk-4.0.3}/mpt_extension_sdk/runtime/workers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mpt-extension-sdk
3
- Version: 4.0.1
3
+ Version: 4.0.3
4
4
  Summary: Extensions SDK for SoftwareONE Marketplace Platform
5
5
  License: Apache-2.0
6
6
  Author: SoftwareOne AG
@@ -2,5 +2,6 @@ EVENT_TYPES = "orders"
2
2
  SECURITY_ALGORITHM = "HS256"
3
3
  USER_AGENT = "swo-extensions/1.0"
4
4
  CONSUME_EVENTS_HELP_TEXT = "Consume events from the MPT platform"
5
- DEFAULT_APP_CONFIG_GROUP = "swo.mpt.ext"
5
+ DEFAULT_APP_CONFIG_GROUP = "swo.mpt.sdk"
6
6
  DEFAULT_APP_CONFIG_NAME = "app_config"
7
+ DJANGO_SETTINGS_MODULE = "mpt_extension_sdk.runtime.djapp.conf.default"
@@ -0,0 +1,39 @@
1
+ from dataclasses import asdict, dataclass
2
+
3
+ ORDER_TYPE_PURCHASE = "Purchase"
4
+ ORDER_TYPE_CHANGE = "Change"
5
+ ORDER_TYPE_TERMINATION = "Termination"
6
+
7
+
8
+ @dataclass
9
+ class Context:
10
+ order: dict
11
+
12
+ @property
13
+ def order_id(self):
14
+ return self.order.get("id", None)
15
+
16
+ @property
17
+ def order_type(self):
18
+ return self.order.get("type", None)
19
+
20
+ @property
21
+ def product_id(self):
22
+ return self.order.get("product", {}).get("id", None)
23
+
24
+ def is_purchase_order(self):
25
+ return self.order["type"] == ORDER_TYPE_PURCHASE
26
+
27
+ def is_change_order(self):
28
+ return self.order["type"] == ORDER_TYPE_CHANGE
29
+
30
+ def is_termination_order(self):
31
+ return self.order["type"] == ORDER_TYPE_TERMINATION
32
+
33
+ @classmethod
34
+ def from_context(cls, context):
35
+ base_data = asdict(context)
36
+ return cls(**base_data)
37
+
38
+ def __str__(self):
39
+ return f"Context: {self.order.get("id", None)} {self.order.get("type", None)}"
@@ -0,0 +1,51 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Callable
3
+
4
+ from swo.mpt.client import MPTClient
5
+ from swo.mpt.extensions.flows.context import Context
6
+
7
+ NextStep = Callable[[MPTClient, Context], None]
8
+
9
+
10
+ class Step(ABC):
11
+ @abstractmethod
12
+ def __call__(
13
+ self,
14
+ client: MPTClient,
15
+ context: Context,
16
+ next_step: NextStep,
17
+ ) -> None:
18
+ raise NotImplementedError() # pragma: no cover
19
+
20
+
21
+ def _default_error_handler(error: Exception, context: Context, next_step: NextStep):
22
+ raise error
23
+
24
+
25
+ class Cursor:
26
+ def __init__(self, steps, error_handler):
27
+ self.queue = steps
28
+ self.error_handler = error_handler
29
+
30
+ def __call__(self, client: MPTClient, context: Context):
31
+ if not self.queue:
32
+ return
33
+ current_step = self.queue[0]
34
+ next_step = Cursor(self.queue[1:], self.error_handler)
35
+
36
+ try:
37
+ current_step(client, context, next_step)
38
+ except Exception as error:
39
+ self.error_handler(error, context, next_step)
40
+
41
+
42
+ class Pipeline:
43
+ def __init__(self, *steps):
44
+ self.queue = steps
45
+
46
+ def run(self, client: MPTClient, context: Context, error_handler=None):
47
+ execute = Cursor(self.queue, error_handler or _default_error_handler)
48
+ return execute(client, context)
49
+
50
+ def __len__(self):
51
+ return len(self.queue)
@@ -367,3 +367,10 @@ def get_agreements_by_customer_deployments(
367
367
  url = f"/commerce/agreements?{rql_query}"
368
368
 
369
369
  return _paginated(mpt_client, url)
370
+
371
+
372
+ @wrap_mpt_http_error
373
+ def get_buyer(mpt_client, buyer_id):
374
+ response = mpt_client.get(f"/accounts/buyers/{buyer_id}")
375
+ response.raise_for_status()
376
+ return response.json()
@@ -0,0 +1,2 @@
1
+ def find_first(func, iterable, default=None):
2
+ return next(filter(func, iterable), default)
@@ -60,3 +60,9 @@ class ValidationError:
60
60
  "id": self.id,
61
61
  "message": self.message.format(**kwargs),
62
62
  }
63
+
64
+
65
+ ERR_EXT_UNHANDLED_EXCEPTION = ValidationError(
66
+ "EXT001",
67
+ "Order can't be processed. Failure reason: {error}",
68
+ )
@@ -43,6 +43,10 @@ class EventProducer(ABC):
43
43
  def produce_events(self):
44
44
  pass
45
45
 
46
+ @abstractmethod
47
+ def produce_events_with_context(self):
48
+ pass
49
+
46
50
 
47
51
  class OrderEventProducer(EventProducer):
48
52
  def __init__(self, dispatcher):
@@ -57,13 +61,24 @@ class OrderEventProducer(EventProducer):
57
61
  for order in orders:
58
62
  self.dispatcher.dispatch_event(Event(order["id"], "orders", order))
59
63
 
64
+ def produce_events_with_context(self):
65
+ while self.running:
66
+ with self.sleep(settings.MPT_ORDERS_API_POLLING_INTERVAL_SECS):
67
+ orders = self.get_processing_orders()
68
+ orders, contexts = self.filter_and_enrich(self.client, orders)
69
+ logger.info(f"{len(orders)} orders found for processing...")
70
+ for order, context in zip(orders, contexts):
71
+ self.dispatcher.dispatch_event(
72
+ Event(order["id"], "orders", order, context)
73
+ )
74
+
60
75
  def get_processing_orders(self):
61
76
  products = ",".join(settings.MPT_PRODUCTS_IDS)
62
77
  orders = []
63
78
  rql_query = f"and(in(agreement.product.id,({products})),eq(status,processing))"
64
79
  url = (
65
- f"/commerce/orders?{rql_query}"
66
- "&select=audit,parameters,lines,subscriptions,subscriptions.lines&order=audit.created.at"
80
+ f"/commerce/orders?{rql_query}&select=audit,parameters,lines,subscriptions,"
81
+ f"subscriptions.lines,agreement,buyer&order=audit.created.at"
67
82
  )
68
83
  page = None
69
84
  limit = 10
@@ -12,6 +12,8 @@ from opentelemetry.instrumentation.requests import RequestsInstrumentor
12
12
  from opentelemetry.sdk.trace import TracerProvider
13
13
  from opentelemetry.sdk.trace.export import BatchSpanProcessor
14
14
 
15
+ from mpt_extension_sdk.flows.context import Context
16
+
15
17
  logger = logging.getLogger(__name__)
16
18
 
17
19
 
@@ -68,3 +70,16 @@ def wrap_for_trace(func, event_type): # pragma: no cover
68
70
  logger.exception("Unhandled exception!")
69
71
 
70
72
  return opentelemetry_wrapper if settings.USE_APPLICATIONINSIGHTS else wrapper
73
+
74
+
75
+ def setup_contexts(mpt_client, orders):
76
+ """
77
+ List of contexts from orders
78
+ Args:
79
+ mpt_client (MPTClient): MPT client
80
+ orders (list): List of orders
81
+
82
+ Returns: List of contexts
83
+
84
+ """
85
+ return [Context(order=order) for order in orders]
@@ -3,6 +3,11 @@ import os
3
3
  import rich
4
4
  from rich.theme import Theme
5
5
 
6
+ from mpt_extension_sdk.constants import (
7
+ DEFAULT_APP_CONFIG_GROUP,
8
+ DEFAULT_APP_CONFIG_NAME,
9
+ DJANGO_SETTINGS_MODULE,
10
+ )
6
11
  from mpt_extension_sdk.runtime.djapp.conf import extract_product_ids
7
12
  from mpt_extension_sdk.runtime.events.utils import instrument_logging
8
13
  from mpt_extension_sdk.runtime.utils import (
@@ -21,10 +26,12 @@ JSON_EXT_VARIABLES = {
21
26
 
22
27
  def initialize(options):
23
28
  rich.reconfigure(theme=Theme({"repr.mpt_id": "bold light_salmon3"}))
24
-
25
- os.environ.setdefault(
26
- "DJANGO_SETTINGS_MODULE", "mpt_extension_sdk.runtime.djapp.conf.default"
29
+ group = options.get("app_config_group", DEFAULT_APP_CONFIG_GROUP)
30
+ name = options.get("app_config_name", DEFAULT_APP_CONFIG_NAME)
31
+ django_settings_module = options.get(
32
+ "django_settings_module", DJANGO_SETTINGS_MODULE
27
33
  )
34
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", django_settings_module)
28
35
  import django
29
36
  from django.conf import settings
30
37
 
@@ -36,7 +43,7 @@ def initialize(options):
36
43
 
37
44
  logging_level = "DEBUG" if options.get("debug") else "INFO"
38
45
 
39
- app_config_name = get_extension_app_config_name()
46
+ app_config_name = get_extension_app_config_name(group=group, name=name)
40
47
  app_root_module, _ = app_config_name.split(".", 1)
41
48
  settings.DEBUG = options.get("debug", False)
42
49
  settings.INSTALLED_APPS.append(app_config_name)
@@ -14,17 +14,19 @@ from mpt_extension_sdk.constants import (
14
14
  )
15
15
 
16
16
 
17
- def get_extension_app_config_name():
17
+ def get_extension_app_config_name(
18
+ group=DEFAULT_APP_CONFIG_GROUP, name=DEFAULT_APP_CONFIG_NAME
19
+ ):
18
20
  eps = entry_points()
19
- (app_config_ep,) = eps.select(
20
- group=DEFAULT_APP_CONFIG_GROUP, name=DEFAULT_APP_CONFIG_NAME
21
- )
21
+ (app_config_ep,) = eps.select(group=group, name=name)
22
22
  app_config = app_config_ep.load()
23
23
  return f"{app_config.__module__}.{app_config.__name__}"
24
24
 
25
25
 
26
- def get_extension_app_config():
27
- app_config_name = get_extension_app_config_name()
26
+ def get_extension_app_config(
27
+ group=DEFAULT_APP_CONFIG_GROUP, name=DEFAULT_APP_CONFIG_NAME
28
+ ):
29
+ app_config_name = get_extension_app_config_name(group=group, name=name)
28
30
  return next(
29
31
  filter(
30
32
  lambda app: app_config_name
@@ -35,12 +37,12 @@ def get_extension_app_config():
35
37
  )
36
38
 
37
39
 
38
- def get_extension():
39
- return get_extension_app_config().extension
40
+ def get_extension(group=DEFAULT_APP_CONFIG_GROUP, name=DEFAULT_APP_CONFIG_NAME):
41
+ return get_extension_app_config(group=group, name=name).extension
40
42
 
41
43
 
42
- def get_events_registry():
43
- return get_extension().events
44
+ def get_events_registry(group=DEFAULT_APP_CONFIG_GROUP, name=DEFAULT_APP_CONFIG_NAME):
45
+ return get_extension(group=group, name=name).events
44
46
 
45
47
 
46
48
  def gradient(start_hex, end_hex, num_samples=10): # pragma: no cover
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "mpt-extension-sdk"
3
- version = "4.0.1"
3
+ version = "4.0.3"
4
4
  description = "Extensions SDK for SoftwareONE Marketplace Platform"
5
5
  authors = ["SoftwareOne AG"]
6
6
  readme = "README.md"
@@ -58,7 +58,7 @@ types-requests = "2.31.*"
58
58
  swoext = 'mpt_extension_sdk.runtime.swoext:main'
59
59
 
60
60
 
61
- [tool.poetry.plugins."swo.mpt.ext"]
61
+ [tool.poetry.plugins."swo.mpt.sdk"]
62
62
  "app_config" = "mpt_extension_sdk.runtime.djapp.apps:ExtensionConfig"
63
63
 
64
64