mpt-extension-sdk 4.3.3__py3-none-any.whl → 4.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mpt_extension_sdk/airtable/__init__.py +0 -0
- mpt_extension_sdk/constants.py +5 -1
- mpt_extension_sdk/mpt_http/mpt.py +90 -49
- mpt_extension_sdk/runtime/commands/django.py +17 -4
- mpt_extension_sdk/runtime/commands/run.py +13 -10
- mpt_extension_sdk/runtime/djapp/conf/default.py +16 -9
- mpt_extension_sdk/runtime/djapp/conf/urls.py +9 -0
- mpt_extension_sdk/runtime/djapp/management/commands/consume_events.py +8 -6
- mpt_extension_sdk/runtime/events/dispatcher.py +7 -3
- mpt_extension_sdk/runtime/events/producers.py +1 -1
- mpt_extension_sdk/runtime/initializer.py +1 -3
- mpt_extension_sdk/runtime/master.py +7 -7
- mpt_extension_sdk/runtime/tracer.py +18 -0
- mpt_extension_sdk/runtime/utils.py +45 -4
- mpt_extension_sdk/runtime/workers.py +14 -5
- mpt_extension_sdk/swo_rql/__init__.py +5 -0
- mpt_extension_sdk/swo_rql/constants.py +7 -0
- mpt_extension_sdk/swo_rql/query_builder.py +392 -0
- {mpt_extension_sdk-4.3.3.dist-info → mpt_extension_sdk-4.4.0.dist-info}/METADATA +1 -1
- {mpt_extension_sdk-4.3.3.dist-info → mpt_extension_sdk-4.4.0.dist-info}/RECORD +23 -17
- {mpt_extension_sdk-4.3.3.dist-info → mpt_extension_sdk-4.4.0.dist-info}/entry_points.txt +1 -1
- {mpt_extension_sdk-4.3.3.dist-info → mpt_extension_sdk-4.4.0.dist-info}/LICENSE +0 -0
- {mpt_extension_sdk-4.3.3.dist-info → mpt_extension_sdk-4.4.0.dist-info}/WHEEL +0 -0
|
File without changes
|
mpt_extension_sdk/constants.py
CHANGED
|
@@ -2,6 +2,10 @@ 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.
|
|
5
|
+
DEFAULT_APP_CONFIG_GROUP = "swo.mpt.ext"
|
|
6
6
|
DEFAULT_APP_CONFIG_NAME = "app_config"
|
|
7
7
|
DJANGO_SETTINGS_MODULE = "mpt_extension_sdk.runtime.djapp.conf.default"
|
|
8
|
+
ERR_DJANGO_SETTINGS_MODULE_TEXT = (
|
|
9
|
+
"DJANGO_SETTINGS_MODULE environment variable is not set. "
|
|
10
|
+
"Please set it to your Django settings module before running this command."
|
|
11
|
+
)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from
|
|
3
|
-
from enum import Enum
|
|
2
|
+
from collections.abc import Iterable
|
|
4
3
|
from functools import cache
|
|
5
4
|
from itertools import batched
|
|
6
5
|
|
|
@@ -11,8 +10,6 @@ from mpt_extension_sdk.mpt_http.wrap_http_error import wrap_mpt_http_error
|
|
|
11
10
|
|
|
12
11
|
logger = logging.getLogger(__name__)
|
|
13
12
|
|
|
14
|
-
NotifyCategories = Enum("NotifyCategories", settings.MPT_NOTIFY_CATEGORIES)
|
|
15
|
-
|
|
16
13
|
|
|
17
14
|
def _has_more_pages(page):
|
|
18
15
|
if not page:
|
|
@@ -104,6 +101,22 @@ def set_processing_template(mpt_client, order_id, template):
|
|
|
104
101
|
return response.json()
|
|
105
102
|
|
|
106
103
|
|
|
104
|
+
@wrap_mpt_http_error
|
|
105
|
+
def create_asset(mpt_client, order_id, asset):
|
|
106
|
+
"""Create a new asset for an order."""
|
|
107
|
+
response = mpt_client.post(f"/commerce/orders/{order_id}/assets", json=asset)
|
|
108
|
+
response.raise_for_status()
|
|
109
|
+
return response.json()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@wrap_mpt_http_error
|
|
113
|
+
def update_asset(mpt_client, order_id, asset_id, **kwargs):
|
|
114
|
+
"""Update an order asset."""
|
|
115
|
+
response = mpt_client.put(f"/commerce/orders/{order_id}/assets/{asset_id}", json=kwargs)
|
|
116
|
+
response.raise_for_status()
|
|
117
|
+
return response.json()
|
|
118
|
+
|
|
119
|
+
|
|
107
120
|
@wrap_mpt_http_error
|
|
108
121
|
def create_subscription(mpt_client, order_id, subscription):
|
|
109
122
|
response = mpt_client.post(
|
|
@@ -125,9 +138,8 @@ def update_subscription(mpt_client, order_id, subscription_id, **kwargs):
|
|
|
125
138
|
|
|
126
139
|
|
|
127
140
|
@wrap_mpt_http_error
|
|
128
|
-
def get_order_subscription_by_external_id(
|
|
129
|
-
|
|
130
|
-
):
|
|
141
|
+
def get_order_subscription_by_external_id(mpt_client, order_id, subscription_external_id):
|
|
142
|
+
"""Retrieve an order subscription by its external ID."""
|
|
131
143
|
response = mpt_client.get(
|
|
132
144
|
f"/commerce/orders/{order_id}/subscriptions?eq(externalIds.vendor,{subscription_external_id})&limit=1",
|
|
133
145
|
)
|
|
@@ -139,9 +151,9 @@ def get_order_subscription_by_external_id(
|
|
|
139
151
|
|
|
140
152
|
@wrap_mpt_http_error
|
|
141
153
|
def get_product_items_by_skus(mpt_client, product_id, skus):
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
)
|
|
154
|
+
"""Retrieve product items by their SKUs."""
|
|
155
|
+
skus_str = ",".join(skus)
|
|
156
|
+
rql_query = f"and(eq(product.id,{product_id}),in(externalIds.vendor,({skus_str})))"
|
|
145
157
|
url = f"/catalog/items?{rql_query}"
|
|
146
158
|
return _paginated(mpt_client, url)
|
|
147
159
|
|
|
@@ -168,6 +180,14 @@ def get_product_template_or_default(mpt_client, product_id, status, name=None):
|
|
|
168
180
|
return templates["data"][0]
|
|
169
181
|
|
|
170
182
|
|
|
183
|
+
def get_template_by_name(mpt_client, product_id, template_name):
|
|
184
|
+
url = f"/catalog/products/{product_id}/templates?eq(name,{template_name})"
|
|
185
|
+
response = mpt_client.get(url)
|
|
186
|
+
response.raise_for_status()
|
|
187
|
+
templates = response.json()
|
|
188
|
+
return templates["data"][0]
|
|
189
|
+
|
|
190
|
+
|
|
171
191
|
@wrap_mpt_http_error
|
|
172
192
|
def update_agreement(mpt_client, agreement_id, **kwargs):
|
|
173
193
|
response = mpt_client.put(
|
|
@@ -184,21 +204,6 @@ def get_agreements_by_query(mpt_client, query):
|
|
|
184
204
|
return _paginated(mpt_client, url)
|
|
185
205
|
|
|
186
206
|
|
|
187
|
-
def get_agreements_by_next_sync(mpt_client, next_sync_parameter):
|
|
188
|
-
today = date.today().isoformat()
|
|
189
|
-
param_condition = (
|
|
190
|
-
f"any(parameters.fulfillment,and(eq(externalId,{next_sync_parameter})"
|
|
191
|
-
f",lt(displayValue,{today})))"
|
|
192
|
-
)
|
|
193
|
-
status_condition = "eq(status,Active)"
|
|
194
|
-
|
|
195
|
-
rql_query = (
|
|
196
|
-
f"and({status_condition},{param_condition})"
|
|
197
|
-
"&select=lines,parameters,subscriptions,product,listing"
|
|
198
|
-
)
|
|
199
|
-
return get_agreements_by_query(mpt_client, rql_query)
|
|
200
|
-
|
|
201
|
-
|
|
202
207
|
@wrap_mpt_http_error
|
|
203
208
|
def update_agreement_subscription(mpt_client, subscription_id, **kwargs):
|
|
204
209
|
response = mpt_client.put(
|
|
@@ -237,6 +242,40 @@ def get_product_onetime_items_by_ids(mpt_client, product_id, item_ids):
|
|
|
237
242
|
return _paginated(mpt_client, url)
|
|
238
243
|
|
|
239
244
|
|
|
245
|
+
@wrap_mpt_http_error
|
|
246
|
+
def get_product_items_by_period(
|
|
247
|
+
mpt_client,
|
|
248
|
+
product_id: str,
|
|
249
|
+
period: str,
|
|
250
|
+
vendor_external_ids: Iterable[str] | None = None,
|
|
251
|
+
):
|
|
252
|
+
"""
|
|
253
|
+
Fetches product items based on a specified period and filters.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
mpt_client: Client in tance to interact with the required API.
|
|
257
|
+
product_id (str): The unique identifier of the product to fetch items for.
|
|
258
|
+
period (str): The period for which to fetch the product items.
|
|
259
|
+
vendor_external_ids (Iterable[str] | None):
|
|
260
|
+
Optional. A list of vendor external IDs to filter out the product items by. Defaults
|
|
261
|
+
to None.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
list:
|
|
265
|
+
A paginated list of product items matching the specified criteria.
|
|
266
|
+
|
|
267
|
+
"""
|
|
268
|
+
product_cond = f"eq(product.id,{product_id})"
|
|
269
|
+
vendors_cond = ""
|
|
270
|
+
if vendor_external_ids:
|
|
271
|
+
vendor_ids = ",".join(vendor_external_ids)
|
|
272
|
+
vendors_cond = f",in(externalIds.vendor,({vendor_ids})))"
|
|
273
|
+
rql_query = f"and({product_cond},eq(terms.period,{period}){vendors_cond})"
|
|
274
|
+
url = f"/catalog/items?{rql_query}"
|
|
275
|
+
|
|
276
|
+
return _paginated(mpt_client, url)
|
|
277
|
+
|
|
278
|
+
|
|
240
279
|
def get_agreements_by_ids(mpt_client, ids):
|
|
241
280
|
rql_query = (
|
|
242
281
|
f"and(in(id,({','.join(ids)})),eq(status,Active))"
|
|
@@ -257,9 +296,8 @@ def get_all_agreements(
|
|
|
257
296
|
|
|
258
297
|
|
|
259
298
|
@wrap_mpt_http_error
|
|
260
|
-
def get_authorizations_by_currency_and_seller_id(
|
|
261
|
-
|
|
262
|
-
):
|
|
299
|
+
def get_authorizations_by_currency_and_seller_id(mpt_client, product_id, currency, owner_id):
|
|
300
|
+
"""Retrieve authorizations by product ID, currency, and owner ID."""
|
|
263
301
|
authorization_filter = (
|
|
264
302
|
f"eq(product.id,{product_id})&eq(currency,{currency})&eq(owner.id,{owner_id})"
|
|
265
303
|
)
|
|
@@ -328,9 +366,8 @@ def get_listing_by_id(mpt_client, listing_id):
|
|
|
328
366
|
|
|
329
367
|
|
|
330
368
|
@wrap_mpt_http_error
|
|
331
|
-
def get_agreement_subscription_by_external_id(
|
|
332
|
-
|
|
333
|
-
):
|
|
369
|
+
def get_agreement_subscription_by_external_id(mpt_client, agreement_id, subscription_external_id):
|
|
370
|
+
"""Retrieve an agreement subscription by external ID."""
|
|
334
371
|
response = mpt_client.get(
|
|
335
372
|
f"/commerce/subscriptions?eq(externalIds.vendor,{subscription_external_id})"
|
|
336
373
|
f"&eq(agreement.id,{agreement_id})"
|
|
@@ -359,15 +396,14 @@ def get_agreements_by_external_id_values(mpt_client, external_id, display_values
|
|
|
359
396
|
|
|
360
397
|
|
|
361
398
|
@wrap_mpt_http_error
|
|
362
|
-
def get_agreements_by_customer_deployments(
|
|
363
|
-
|
|
364
|
-
):
|
|
399
|
+
def get_agreements_by_customer_deployments(mpt_client, deployment_id_parameter, deployment_ids):
|
|
400
|
+
"""Retrieve agreements by customer deployments."""
|
|
365
401
|
deployments_list = ",".join(deployment_ids)
|
|
366
402
|
rql_query = (
|
|
367
403
|
f"any(parameters.fulfillment,and("
|
|
368
404
|
f"eq(externalId,{deployment_id_parameter}),"
|
|
369
405
|
f"in(displayValue,({deployments_list}))))"
|
|
370
|
-
f"&select=lines,parameters,subscriptions,product,listing"
|
|
406
|
+
f"&select=lines,parameters,subscriptions,subscriptions.parameters,product,listing"
|
|
371
407
|
)
|
|
372
408
|
|
|
373
409
|
url = f"/commerce/agreements?{rql_query}"
|
|
@@ -391,25 +427,12 @@ def notify(
|
|
|
391
427
|
subject: str,
|
|
392
428
|
message_body: str,
|
|
393
429
|
limit: int = 1000,
|
|
394
|
-
):
|
|
430
|
+
) -> None:
|
|
395
431
|
"""
|
|
396
432
|
Sends notifications to multiple recipients in batches for a specific buyer and
|
|
397
433
|
category through the MPTClient service. The function retrieves recipients,
|
|
398
434
|
groups them into manageable batches, and sends notifications using the provided
|
|
399
435
|
message details.
|
|
400
|
-
|
|
401
|
-
Args:
|
|
402
|
-
mpt_client (MPTClient): Client object for interacting with MPT service.
|
|
403
|
-
category_id (str): Identifier for the category of recipients or messages.
|
|
404
|
-
account_id (str): Identifier for the associated account.
|
|
405
|
-
buyer_id (str): Identifier for the buyer related to the notification.
|
|
406
|
-
subject (str): Subject/title of the notification to be sent.
|
|
407
|
-
message_body (str): Content/body of the notification message.
|
|
408
|
-
limit (int): Maximum number of recipients to process per batch. Defaults
|
|
409
|
-
to 1000.
|
|
410
|
-
|
|
411
|
-
Returns:
|
|
412
|
-
None
|
|
413
436
|
"""
|
|
414
437
|
recipients = _paginated(
|
|
415
438
|
mpt_client,
|
|
@@ -433,3 +456,21 @@ def notify(
|
|
|
433
456
|
},
|
|
434
457
|
)
|
|
435
458
|
response.raise_for_status()
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@wrap_mpt_http_error
|
|
462
|
+
def terminate_subscription(mpt_client: MPTClient, subscription_id: str, reason: str) -> dict:
|
|
463
|
+
"""
|
|
464
|
+
Terminates a subscription by calling the MPT API.
|
|
465
|
+
|
|
466
|
+
Raises:
|
|
467
|
+
HTTPError: If the HTTP request fails, an HTTPError is raised with
|
|
468
|
+
information about the issue.
|
|
469
|
+
"""
|
|
470
|
+
response = mpt_client.post(
|
|
471
|
+
f"/commerce/subscriptions/{subscription_id}/terminate",
|
|
472
|
+
json={"description": reason},
|
|
473
|
+
)
|
|
474
|
+
response.raise_for_status()
|
|
475
|
+
|
|
476
|
+
return response.json()
|
|
@@ -1,21 +1,34 @@
|
|
|
1
1
|
from contextlib import nullcontext
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
|
+
from django.core.management import execute_from_command_line
|
|
4
5
|
from opentelemetry import trace
|
|
5
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
|
+
|
|
6
14
|
|
|
7
15
|
@click.command(
|
|
8
16
|
add_help_option=False, context_settings=dict(ignore_unknown_options=True)
|
|
9
17
|
)
|
|
10
18
|
@click.argument("management_args", nargs=-1, type=click.UNPROCESSED)
|
|
11
19
|
@click.pass_context
|
|
12
|
-
def django(ctx, management_args):
|
|
20
|
+
def django(ctx, management_args): # pragma: no cover
|
|
13
21
|
"Execute Django subcommands."
|
|
14
|
-
from mpt_extension_sdk.runtime.initializer import initialize
|
|
15
22
|
|
|
16
|
-
initialize({})
|
|
17
23
|
from django.conf import settings
|
|
18
|
-
|
|
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)
|
|
19
32
|
|
|
20
33
|
if settings.USE_APPLICATIONINSIGHTS:
|
|
21
34
|
tracer = trace.get_tracer(__name__)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import click
|
|
2
|
+
import debugpy
|
|
3
|
+
from django.conf import settings
|
|
2
4
|
|
|
3
5
|
from mpt_extension_sdk.runtime.master import Master
|
|
4
6
|
|
|
@@ -25,17 +27,18 @@ def run(component, color, debug, reload, debug_py):
|
|
|
25
27
|
"""
|
|
26
28
|
|
|
27
29
|
if debug_py:
|
|
28
|
-
import debugpy
|
|
29
|
-
|
|
30
30
|
host, port = debug_py.split(":")
|
|
31
|
-
debugpy.listen((host, int(port)))
|
|
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
|
+
}
|
|
32
39
|
|
|
33
40
|
master = Master(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"debug": debug,
|
|
37
|
-
"reload": reload,
|
|
38
|
-
"component": component,
|
|
39
|
-
},
|
|
41
|
+
options,
|
|
42
|
+
settings=settings,
|
|
40
43
|
)
|
|
41
|
-
master.run()
|
|
44
|
+
master.run() # pragma: no cover
|
|
@@ -10,7 +10,6 @@ For the full list of settings and their values, see
|
|
|
10
10
|
https://docs.djangoproject.com/en/4.2/ref/settings/
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
import json
|
|
14
13
|
import os
|
|
15
14
|
from pathlib import Path
|
|
16
15
|
|
|
@@ -26,6 +25,12 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|
|
26
25
|
# Quick-start development settings - unsuitable for production
|
|
27
26
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
|
28
27
|
|
|
28
|
+
# SECURITY WARNING: keep the secret key used in production secret!
|
|
29
|
+
SECRET_KEY = os.getenv(
|
|
30
|
+
"MPT_DJANGO_SECRET_KEY",
|
|
31
|
+
"",
|
|
32
|
+
)
|
|
33
|
+
|
|
29
34
|
# SECURITY WARNING: don't run with debug turned on in production!
|
|
30
35
|
DEBUG = True
|
|
31
36
|
|
|
@@ -128,7 +133,7 @@ APPLICATIONINSIGHTS_CONNECTION_STRING = os.getenv(
|
|
|
128
133
|
USE_APPLICATIONINSIGHTS = APPLICATIONINSIGHTS_CONNECTION_STRING != ""
|
|
129
134
|
|
|
130
135
|
|
|
131
|
-
if USE_APPLICATIONINSIGHTS:
|
|
136
|
+
if USE_APPLICATIONINSIGHTS: # pragma: no cover
|
|
132
137
|
logger_provider = LoggerProvider()
|
|
133
138
|
set_logger_provider(logger_provider)
|
|
134
139
|
exporter = AzureMonitorLogExporter(
|
|
@@ -141,7 +146,7 @@ LOGGING = {
|
|
|
141
146
|
"disable_existing_loggers": False,
|
|
142
147
|
"formatters": {
|
|
143
148
|
"verbose": {
|
|
144
|
-
"format": "{asctime} {name} {levelname} (pid: {process}) {message}",
|
|
149
|
+
"format": "{asctime} {name} {levelname} (pid: {process}, thread: {thread}) {message}",
|
|
145
150
|
"style": "{",
|
|
146
151
|
},
|
|
147
152
|
"rich": {
|
|
@@ -149,7 +154,7 @@ LOGGING = {
|
|
|
149
154
|
"style": "{",
|
|
150
155
|
},
|
|
151
156
|
"opentelemetry": {
|
|
152
|
-
"format": "(pid: {process}) {message}",
|
|
157
|
+
"format": "(pid: {process}, thread: {thread}) {message}",
|
|
153
158
|
"style": "{",
|
|
154
159
|
},
|
|
155
160
|
},
|
|
@@ -203,16 +208,18 @@ MPT_API_TOKEN = os.getenv("MPT_API_TOKEN", "change-me!")
|
|
|
203
208
|
MPT_API_TOKEN_OPERATIONS = os.getenv("MPT_API_TOKEN_OPERATIONS", "change-me!")
|
|
204
209
|
MPT_PRODUCTS_IDS = os.getenv("MPT_PRODUCTS_IDS", "PRD-1111-1111")
|
|
205
210
|
MPT_PORTAL_BASE_URL = os.getenv("MPT_PORTAL_BASE_URL", "https://portal.s1.show")
|
|
211
|
+
MPT_KEY_VAULT_NAME = os.getenv("MPT_KEY_VAULT_NAME", "mpt-key-vault")
|
|
206
212
|
|
|
207
213
|
MPT_ORDERS_API_POLLING_INTERVAL_SECS = int(
|
|
208
214
|
os.getenv("MPT_ORDERS_API_POLLING_INTERVAL_SECS", "120")
|
|
209
215
|
)
|
|
210
216
|
|
|
211
|
-
# TODO: Should be synced with the initializer.py::initialize function
|
|
212
|
-
MPT_NOTIFY_CATEGORIES = json.loads(
|
|
213
|
-
os.getenv("MPT_NOTIFY_CATEGORIES", '{"ORDERS": "NTC-0000-0006"}')
|
|
214
|
-
)
|
|
215
|
-
|
|
216
217
|
EXTENSION_CONFIG = {
|
|
217
218
|
"DUE_DATE_DAYS": "30",
|
|
219
|
+
"ORDER_CREATION_WINDOW_HOURS": os.getenv("EXT_ORDER_CREATION_WINDOW_HOURS", "24"),
|
|
218
220
|
}
|
|
221
|
+
|
|
222
|
+
MPT_SETUP_CONTEXTS_FUNC = os.getenv(
|
|
223
|
+
"MPT_SETUP_CONTEXTS_FUNC",
|
|
224
|
+
"mpt_extension_sdk.runtime.events.utils.setup_contexts",
|
|
225
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from mpt_extension_sdk.constants import (
|
|
2
|
+
DEFAULT_APP_CONFIG_GROUP,
|
|
3
|
+
DEFAULT_APP_CONFIG_NAME,
|
|
4
|
+
)
|
|
5
|
+
from mpt_extension_sdk.runtime.utils import get_extension, get_urlpatterns
|
|
6
|
+
|
|
7
|
+
extension = get_extension(name=DEFAULT_APP_CONFIG_NAME, group=DEFAULT_APP_CONFIG_GROUP)
|
|
8
|
+
|
|
9
|
+
urlpatterns = get_urlpatterns(extension)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import signal
|
|
2
|
-
from threading import Event
|
|
1
|
+
import signal # pragma: no cover
|
|
2
|
+
from threading import Event # pragma: no cover
|
|
3
3
|
|
|
4
|
-
from django.core.management.base import BaseCommand
|
|
4
|
+
from django.core.management.base import BaseCommand # pragma: no cover
|
|
5
5
|
|
|
6
|
-
from mpt_extension_sdk.constants import CONSUME_EVENTS_HELP_TEXT
|
|
7
|
-
from mpt_extension_sdk.runtime.events.dispatcher import Dispatcher
|
|
8
|
-
from mpt_extension_sdk.runtime.events.producers import
|
|
6
|
+
from mpt_extension_sdk.constants import CONSUME_EVENTS_HELP_TEXT # pragma: no cover
|
|
7
|
+
from mpt_extension_sdk.runtime.events.dispatcher import Dispatcher # pragma: no cover
|
|
8
|
+
from mpt_extension_sdk.runtime.events.producers import (
|
|
9
|
+
OrderEventProducer, # pragma: no cover
|
|
10
|
+
)
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class Command(BaseCommand): # pragma: no cover
|
|
@@ -5,6 +5,10 @@ import time
|
|
|
5
5
|
from collections import deque
|
|
6
6
|
from concurrent.futures import ThreadPoolExecutor
|
|
7
7
|
|
|
8
|
+
from mpt_extension_sdk.constants import (
|
|
9
|
+
DEFAULT_APP_CONFIG_GROUP,
|
|
10
|
+
DEFAULT_APP_CONFIG_NAME,
|
|
11
|
+
)
|
|
8
12
|
from mpt_extension_sdk.core.events.dataclasses import Event
|
|
9
13
|
from mpt_extension_sdk.core.events.registry import EventsRegistry
|
|
10
14
|
from mpt_extension_sdk.core.utils import setup_client
|
|
@@ -24,8 +28,8 @@ def done_callback(futures, key, future): # pragma: no cover
|
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
class Dispatcher:
|
|
27
|
-
def __init__(self):
|
|
28
|
-
self.registry: EventsRegistry = get_events_registry()
|
|
31
|
+
def __init__(self, group=DEFAULT_APP_CONFIG_GROUP, name=DEFAULT_APP_CONFIG_NAME):
|
|
32
|
+
self.registry: EventsRegistry = get_events_registry(group=group, name=name)
|
|
29
33
|
self.queue = deque()
|
|
30
34
|
self.futures = {}
|
|
31
35
|
self.executor = ThreadPoolExecutor()
|
|
@@ -45,7 +49,7 @@ class Dispatcher:
|
|
|
45
49
|
def running(self):
|
|
46
50
|
return self.running_event.is_set()
|
|
47
51
|
|
|
48
|
-
def dispatch_event(self, event: Event):
|
|
52
|
+
def dispatch_event(self, event: Event): # pragma: no cover
|
|
49
53
|
if self.registry.is_event_supported(event.type):
|
|
50
54
|
logger.info(f"event of type {event.type} with id {event.id} accepted")
|
|
51
55
|
self.queue.appendleft((event.type, event))
|
|
@@ -78,7 +78,7 @@ class OrderEventProducer(EventProducer):
|
|
|
78
78
|
rql_query = f"and(in(agreement.product.id,({products})),eq(status,processing))"
|
|
79
79
|
url = (
|
|
80
80
|
f"/commerce/orders?{rql_query}&select=audit,parameters,lines,subscriptions,"
|
|
81
|
-
f"subscriptions.lines,agreement,buyer&order=audit.created.at"
|
|
81
|
+
f"subscriptions.lines,agreement,buyer,seller&order=audit.created.at"
|
|
82
82
|
)
|
|
83
83
|
page = None
|
|
84
84
|
limit = 10
|
|
@@ -24,10 +24,8 @@ JSON_EXT_VARIABLES = {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def initialize(options):
|
|
27
|
+
def initialize(options, group=DEFAULT_APP_CONFIG_GROUP, name=DEFAULT_APP_CONFIG_NAME):
|
|
28
28
|
rich.reconfigure(theme=Theme({"repr.mpt_id": "bold light_salmon3"}))
|
|
29
|
-
group = options.get("app_config_group", DEFAULT_APP_CONFIG_GROUP)
|
|
30
|
-
name = options.get("app_config_name", DEFAULT_APP_CONFIG_NAME)
|
|
31
29
|
django_settings_module = options.get(
|
|
32
30
|
"django_settings_module", DJANGO_SETTINGS_MODULE
|
|
33
31
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import os
|
|
2
3
|
import signal
|
|
3
4
|
import threading
|
|
4
5
|
import time
|
|
@@ -14,20 +15,21 @@ logger = logging.getLogger(__name__)
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
HANDLED_SIGNALS = (signal.SIGINT, signal.SIGTERM)
|
|
17
|
-
PROCESS_CHECK_INTERVAL_SECS = 5
|
|
18
|
+
PROCESS_CHECK_INTERVAL_SECS = int(os.environ.get("PROCESS_CHECK_INTERVAL_SECS", 5))
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
def _display_path(path):
|
|
21
|
+
def _display_path(path): # pragma: no cover
|
|
21
22
|
try:
|
|
22
23
|
return f'"{path.relative_to(Path.cwd())}"'
|
|
23
|
-
except ValueError:
|
|
24
|
+
except ValueError:
|
|
24
25
|
return f'"{path}"'
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
class Master:
|
|
28
|
-
def __init__(self, options):
|
|
29
|
+
def __init__(self, options, settings):
|
|
29
30
|
self.workers = {}
|
|
30
31
|
self.options = options
|
|
32
|
+
self.settings = settings
|
|
31
33
|
self.stop_event = threading.Event()
|
|
32
34
|
self.monitor_event = threading.Event()
|
|
33
35
|
self.watch_filter = PythonFilter(ignore_paths=None)
|
|
@@ -51,9 +53,7 @@ class Master:
|
|
|
51
53
|
"gunicorn": start_gunicorn,
|
|
52
54
|
}
|
|
53
55
|
case "consumer":
|
|
54
|
-
self.proc_targets = {
|
|
55
|
-
"event-consumer": start_event_consumer,
|
|
56
|
-
}
|
|
56
|
+
self.proc_targets = {"event-consumer": start_event_consumer}
|
|
57
57
|
case _:
|
|
58
58
|
self.proc_targets = {
|
|
59
59
|
"event-consumer": start_event_consumer,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
|
|
3
|
+
from opentelemetry import trace
|
|
4
|
+
|
|
5
|
+
tracer = trace.get_tracer(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def dynamic_trace_span(name_fn):
|
|
9
|
+
def decorator(func):
|
|
10
|
+
@wraps(func)
|
|
11
|
+
def wrapper(*args, **kwargs):
|
|
12
|
+
span_name = name_fn(*args, **kwargs)
|
|
13
|
+
with tracer.start_as_current_span(span_name):
|
|
14
|
+
return func(*args, **kwargs)
|
|
15
|
+
|
|
16
|
+
return wrapper
|
|
17
|
+
|
|
18
|
+
return decorator
|
|
@@ -4,6 +4,9 @@ import sys
|
|
|
4
4
|
from importlib.metadata import entry_points
|
|
5
5
|
|
|
6
6
|
from django.apps import apps
|
|
7
|
+
from django.contrib import admin
|
|
8
|
+
from django.urls import path
|
|
9
|
+
from django.utils.module_loading import import_string
|
|
7
10
|
from pyfiglet import Figlet
|
|
8
11
|
from rich.console import Console
|
|
9
12
|
from rich.text import Text
|
|
@@ -85,10 +88,9 @@ def show_banner(): # pragma: no cover
|
|
|
85
88
|
|
|
86
89
|
for line in banner_lines:
|
|
87
90
|
colored_line = Text()
|
|
88
|
-
for
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
colored_line = Text.assemble(colored_line, char)
|
|
91
|
+
for idx, line_char in enumerate(line):
|
|
92
|
+
line_char.stylize(colors[idx])
|
|
93
|
+
colored_line = Text.assemble(colored_line, line_char)
|
|
92
94
|
console.print(colored_line)
|
|
93
95
|
|
|
94
96
|
|
|
@@ -105,3 +107,42 @@ def get_extension_variables(json_ext_variables):
|
|
|
105
107
|
|
|
106
108
|
variables[var[0][4:]] = value
|
|
107
109
|
return variables
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_api_url(extension):
|
|
113
|
+
if extension:
|
|
114
|
+
api_url = extension.api.urls
|
|
115
|
+
return api_url
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_urlpatterns(extension):
|
|
120
|
+
urlpatterns = [
|
|
121
|
+
path("admin/", admin.site.urls),
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
api_url = get_api_url(extension)
|
|
125
|
+
|
|
126
|
+
if api_url:
|
|
127
|
+
urlpatterns.append(path("api/", api_url))
|
|
128
|
+
|
|
129
|
+
return urlpatterns
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_initializer_function():
|
|
133
|
+
"""
|
|
134
|
+
Dynamically import and return the initializer function from settings.INITIALIZER.
|
|
135
|
+
"""
|
|
136
|
+
# Read from environment variable instead of Django settings to avoid circular dependency
|
|
137
|
+
# (Django settings need to be configured before we can read settings.INITIALIZER)
|
|
138
|
+
return os.getenv(
|
|
139
|
+
"MPT_INITIALIZER", "mpt_extension_sdk.runtime.initializer.initialize"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def initialize_extension(
|
|
144
|
+
options, group=DEFAULT_APP_CONFIG_GROUP, name=DEFAULT_APP_CONFIG_NAME
|
|
145
|
+
):
|
|
146
|
+
initialize_path = get_initializer_function()
|
|
147
|
+
initialize_func = import_string(initialize_path)
|
|
148
|
+
initialize_func(options, group=group, name=name)
|
|
@@ -2,7 +2,11 @@ from django.core.management import call_command
|
|
|
2
2
|
from django.core.wsgi import get_wsgi_application
|
|
3
3
|
from gunicorn.app.base import BaseApplication
|
|
4
4
|
|
|
5
|
-
from mpt_extension_sdk.
|
|
5
|
+
from mpt_extension_sdk.constants import (
|
|
6
|
+
DEFAULT_APP_CONFIG_GROUP,
|
|
7
|
+
DEFAULT_APP_CONFIG_NAME,
|
|
8
|
+
)
|
|
9
|
+
from mpt_extension_sdk.runtime.utils import initialize_extension
|
|
6
10
|
|
|
7
11
|
|
|
8
12
|
class ExtensionWebApplication(BaseApplication):
|
|
@@ -25,19 +29,24 @@ class ExtensionWebApplication(BaseApplication):
|
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
def start_event_consumer(options):
|
|
28
|
-
|
|
32
|
+
initialize_extension(options)
|
|
29
33
|
call_command("consume_events")
|
|
30
34
|
|
|
31
35
|
|
|
32
|
-
def start_gunicorn(
|
|
33
|
-
|
|
36
|
+
def start_gunicorn(
|
|
37
|
+
options,
|
|
38
|
+
group=DEFAULT_APP_CONFIG_GROUP,
|
|
39
|
+
name=DEFAULT_APP_CONFIG_NAME,
|
|
40
|
+
):
|
|
41
|
+
initialize_extension(options, group=group, name=name)
|
|
34
42
|
|
|
35
43
|
logging_config = {
|
|
36
44
|
"version": 1,
|
|
37
45
|
"disable_existing_loggers": False,
|
|
38
46
|
"formatters": {
|
|
39
47
|
"verbose": {
|
|
40
|
-
"format":
|
|
48
|
+
"format":
|
|
49
|
+
"{asctime} {name} {levelname} (pid: {process}, thread: {thread}) {message}",
|
|
41
50
|
"style": "{",
|
|
42
51
|
},
|
|
43
52
|
"rich": {
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
from mpt_extension_sdk.swo_rql import constants
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def parse_kwargs(query_dict):
|
|
5
|
+
query = []
|
|
6
|
+
for lookup, value in query_dict.items():
|
|
7
|
+
tokens = lookup.split("__")
|
|
8
|
+
if len(tokens) == 1:
|
|
9
|
+
# field=value
|
|
10
|
+
field = tokens[0]
|
|
11
|
+
value = rql_encode("eq", value)
|
|
12
|
+
query.append(f"eq({field},{value})")
|
|
13
|
+
continue
|
|
14
|
+
op = tokens[-1]
|
|
15
|
+
if op not in constants.KEYWORDS:
|
|
16
|
+
# field__nested=value
|
|
17
|
+
field = ".".join(tokens)
|
|
18
|
+
value = rql_encode("eq", value)
|
|
19
|
+
query.append(f"eq({field},{value})")
|
|
20
|
+
continue
|
|
21
|
+
field = ".".join(tokens[:-1])
|
|
22
|
+
if op in constants.COMP or op in constants.SEARCH:
|
|
23
|
+
value = rql_encode(op, value)
|
|
24
|
+
query.append(f"{op}({field},{value})")
|
|
25
|
+
continue
|
|
26
|
+
if op in constants.LIST:
|
|
27
|
+
value = rql_encode(op, value)
|
|
28
|
+
query.append(f"{op}({field},({value}))")
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
cmpop = "eq" if value is True else "ne"
|
|
32
|
+
expr = "null()" if op == constants.NULL else "empty()"
|
|
33
|
+
query.append(f"{cmpop}({field},{expr})")
|
|
34
|
+
|
|
35
|
+
return query
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def rql_encode(op, value):
|
|
39
|
+
from datetime import date, datetime
|
|
40
|
+
from decimal import Decimal
|
|
41
|
+
|
|
42
|
+
if op not in constants.LIST:
|
|
43
|
+
if isinstance(value, str):
|
|
44
|
+
return value
|
|
45
|
+
if isinstance(value, bool):
|
|
46
|
+
return "true" if value else "false"
|
|
47
|
+
if isinstance(value, int | float | Decimal):
|
|
48
|
+
return str(value)
|
|
49
|
+
if isinstance(value, date | datetime):
|
|
50
|
+
return value.isoformat()
|
|
51
|
+
if op in constants.LIST and isinstance(value, list | tuple):
|
|
52
|
+
return ",".join(value)
|
|
53
|
+
raise TypeError(f"the `{op}` operator doesn't support the {type(value)} type.")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RQLQuery:
|
|
57
|
+
"""
|
|
58
|
+
Helper class to construct complex RQL queries.
|
|
59
|
+
|
|
60
|
+
Usage:
|
|
61
|
+
|
|
62
|
+
```py3
|
|
63
|
+
rql = R(field='value', field2__in=('v1', 'v2'), field3__empty=True)
|
|
64
|
+
```
|
|
65
|
+
!!! note
|
|
66
|
+
All the lookups expressed as keyword arguments are combined together with a logical `and`.
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
Using the ``n`` method:
|
|
70
|
+
|
|
71
|
+
```py3
|
|
72
|
+
rql = (
|
|
73
|
+
R().n('field').eq('value')
|
|
74
|
+
& R().n('field2').anyof(('v1', 'v2'))
|
|
75
|
+
& R().n('field3').empty(True)
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The previous query can be expressed in a more concise form like:
|
|
80
|
+
|
|
81
|
+
```py3
|
|
82
|
+
rql = R().field.eq('value') & R().field2.anyof(('v1', 'v2')) & r.field3.empty(True)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```py3
|
|
86
|
+
rql = R("field").eq("value")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The R object support the bitwise operators `&`, `|` and `~`.
|
|
90
|
+
|
|
91
|
+
Nested fields can be expressed using dot notation:
|
|
92
|
+
|
|
93
|
+
```py3
|
|
94
|
+
rql = R().n('nested.field').eq('value')
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
or
|
|
98
|
+
|
|
99
|
+
```py3
|
|
100
|
+
rql = R().nested.field.eq('value')
|
|
101
|
+
```
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
AND = "and"
|
|
105
|
+
OR = "or"
|
|
106
|
+
EXPRESSION = "expr"
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
_field=None,
|
|
111
|
+
*,
|
|
112
|
+
_op=EXPRESSION,
|
|
113
|
+
_children=None,
|
|
114
|
+
_negated=False,
|
|
115
|
+
_expr=None,
|
|
116
|
+
**kwargs,
|
|
117
|
+
):
|
|
118
|
+
self.op = _op
|
|
119
|
+
self.children = _children or []
|
|
120
|
+
self.negated = _negated
|
|
121
|
+
self.expr = _expr
|
|
122
|
+
self._path = []
|
|
123
|
+
self._field = None
|
|
124
|
+
if _field:
|
|
125
|
+
self.n(_field)
|
|
126
|
+
if len(kwargs) == 1:
|
|
127
|
+
self.op = self.EXPRESSION
|
|
128
|
+
self.expr = parse_kwargs(kwargs)[0]
|
|
129
|
+
if len(kwargs) > 1:
|
|
130
|
+
self.op = self.AND
|
|
131
|
+
for token in parse_kwargs(kwargs):
|
|
132
|
+
self.children.append(RQLQuery(_expr=token))
|
|
133
|
+
|
|
134
|
+
def __len__(self):
|
|
135
|
+
if self.op == self.EXPRESSION:
|
|
136
|
+
if self.expr:
|
|
137
|
+
return 1
|
|
138
|
+
return 0
|
|
139
|
+
return len(self.children)
|
|
140
|
+
|
|
141
|
+
def __bool__(self):
|
|
142
|
+
return bool(self.children) or bool(self.expr)
|
|
143
|
+
|
|
144
|
+
def __eq__(self, other):
|
|
145
|
+
return (
|
|
146
|
+
self.op == other.op
|
|
147
|
+
and self.children == other.children
|
|
148
|
+
and self.negated == other.negated
|
|
149
|
+
and self.expr == other.expr
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def __hash__(self):
|
|
153
|
+
return hash(
|
|
154
|
+
(
|
|
155
|
+
self.op,
|
|
156
|
+
self.expr,
|
|
157
|
+
self.negated,
|
|
158
|
+
*(hash(value) for value in self.children),
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def __repr__(self):
|
|
163
|
+
if self.op == self.EXPRESSION:
|
|
164
|
+
return f"<R({self.op}) {self.expr}>"
|
|
165
|
+
return f"<R({self.op})>"
|
|
166
|
+
|
|
167
|
+
def __and__(self, other):
|
|
168
|
+
return self._join(other, self.AND)
|
|
169
|
+
|
|
170
|
+
def __or__(self, other):
|
|
171
|
+
return self._join(other, self.OR)
|
|
172
|
+
|
|
173
|
+
def __invert__(self):
|
|
174
|
+
query = RQLQuery(_op=self.AND, _expr=self.expr, _negated=True)
|
|
175
|
+
query._append(self)
|
|
176
|
+
return query
|
|
177
|
+
|
|
178
|
+
def __getattr__(self, name):
|
|
179
|
+
return self.n(name)
|
|
180
|
+
|
|
181
|
+
def __str__(self):
|
|
182
|
+
return self._to_string(self)
|
|
183
|
+
|
|
184
|
+
def n(self, name):
|
|
185
|
+
"""
|
|
186
|
+
Set the current field for this `R` object.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
name (str): Name of the field.
|
|
190
|
+
"""
|
|
191
|
+
if self._field:
|
|
192
|
+
raise AttributeError("Already evaluated")
|
|
193
|
+
|
|
194
|
+
self._path.extend(name.split("."))
|
|
195
|
+
return self
|
|
196
|
+
|
|
197
|
+
def ne(self, value):
|
|
198
|
+
"""
|
|
199
|
+
Apply the `ne` operator to the field this `R` object refers to.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
value (str): The value to which compare the field.
|
|
203
|
+
"""
|
|
204
|
+
return self._bin("ne", value)
|
|
205
|
+
|
|
206
|
+
def eq(self, value):
|
|
207
|
+
"""
|
|
208
|
+
Apply the `eq` operator to the field this `R` object refers to.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
value (str): The value to which compare the field.
|
|
212
|
+
"""
|
|
213
|
+
return self._bin("eq", value)
|
|
214
|
+
|
|
215
|
+
def lt(self, value):
|
|
216
|
+
"""
|
|
217
|
+
Apply the `lt` operator to the field this `R` object refers to.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
value (str): The value to which compare the field.
|
|
221
|
+
"""
|
|
222
|
+
return self._bin("lt", value)
|
|
223
|
+
|
|
224
|
+
def le(self, value):
|
|
225
|
+
"""
|
|
226
|
+
Apply the `le` operator to the field this `R` object refers to.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
value (str): The value to which compare the field.
|
|
230
|
+
"""
|
|
231
|
+
return self._bin("le", value)
|
|
232
|
+
|
|
233
|
+
def gt(self, value):
|
|
234
|
+
"""
|
|
235
|
+
Apply the `gt` operator to the field this `R` object refers to.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
value (str): The value to which compare the field.
|
|
239
|
+
"""
|
|
240
|
+
return self._bin("gt", value)
|
|
241
|
+
|
|
242
|
+
def ge(self, value):
|
|
243
|
+
"""
|
|
244
|
+
Apply the `ge` operator to the field this `R` object refers to.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
value (str): The value to which compare the field.
|
|
248
|
+
"""
|
|
249
|
+
return self._bin("ge", value)
|
|
250
|
+
|
|
251
|
+
def out(self, value: list[str]):
|
|
252
|
+
"""
|
|
253
|
+
Apply the `out` operator to the field this `R` object refers to.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
value (list[str]): The list of values to which compare the field.
|
|
257
|
+
"""
|
|
258
|
+
return self._list("out", value)
|
|
259
|
+
|
|
260
|
+
def in_(self, value):
|
|
261
|
+
return self._list("in", value)
|
|
262
|
+
|
|
263
|
+
def oneof(self, value: list[str]):
|
|
264
|
+
"""
|
|
265
|
+
Apply the `in` operator to the field this `R` object refers to.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
value (list[str]): The list of values to which compare the field.
|
|
269
|
+
"""
|
|
270
|
+
return self._list("in", value)
|
|
271
|
+
|
|
272
|
+
def null(self, value: list[str]):
|
|
273
|
+
"""
|
|
274
|
+
Apply the `null` operator to the field this `R` object refers to.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
value (list[str]): The value to which compare the field.
|
|
278
|
+
"""
|
|
279
|
+
return self._bool("null", value)
|
|
280
|
+
|
|
281
|
+
def empty(self, value: bool = True):
|
|
282
|
+
"""
|
|
283
|
+
Apply the `empty` operator to the field this `R` object refers to.
|
|
284
|
+
|
|
285
|
+
Usage: `R().field.empty()
|
|
286
|
+
|
|
287
|
+
For not empty: `R().field.empty(False)` or `R().field.not_empty()`
|
|
288
|
+
"""
|
|
289
|
+
return self._bool("empty", value)
|
|
290
|
+
|
|
291
|
+
def not_empty(self):
|
|
292
|
+
"""
|
|
293
|
+
Apply the `not_empty` operator to the field this `R` object refers to.
|
|
294
|
+
"""
|
|
295
|
+
query = self._bool("empty", False)
|
|
296
|
+
return query
|
|
297
|
+
|
|
298
|
+
def like(self, value: list[str]):
|
|
299
|
+
"""
|
|
300
|
+
Apply the `like` operator to the field this `R` object refers to.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
value (list[str]): The value to which compare the field.
|
|
304
|
+
"""
|
|
305
|
+
return self._bin("like", value)
|
|
306
|
+
|
|
307
|
+
def ilike(self, value: list[str]):
|
|
308
|
+
"""
|
|
309
|
+
Apply the `ilike` operator to the field this `R` object refers to.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
value (list[str]): The value to which compare the field.
|
|
313
|
+
"""
|
|
314
|
+
return self._bin("ilike", value)
|
|
315
|
+
|
|
316
|
+
def _bin(self, op, value):
|
|
317
|
+
self._field = ".".join(self._path)
|
|
318
|
+
value = rql_encode(op, value)
|
|
319
|
+
self.expr = f"{op}({self._field},{value})"
|
|
320
|
+
return self
|
|
321
|
+
|
|
322
|
+
def _list(self, op, value):
|
|
323
|
+
self._field = ".".join(self._path)
|
|
324
|
+
value = rql_encode(op, value)
|
|
325
|
+
self.expr = f"{op}({self._field},({value}))"
|
|
326
|
+
return self
|
|
327
|
+
|
|
328
|
+
def _bool(self, expr, value):
|
|
329
|
+
self._field = ".".join(self._path)
|
|
330
|
+
if bool(value) is False:
|
|
331
|
+
self.expr = f"ne({self._field},{expr}())"
|
|
332
|
+
return self
|
|
333
|
+
self.expr = f"eq({self._field},{expr}())"
|
|
334
|
+
return self
|
|
335
|
+
|
|
336
|
+
def _to_string(self, query):
|
|
337
|
+
tokens = []
|
|
338
|
+
if query.expr:
|
|
339
|
+
if query.negated:
|
|
340
|
+
return f"not({query.expr})"
|
|
341
|
+
return query.expr
|
|
342
|
+
for c in query.children:
|
|
343
|
+
if c.expr:
|
|
344
|
+
if c.negated:
|
|
345
|
+
tokens.append(f"not({c.expr})")
|
|
346
|
+
else:
|
|
347
|
+
tokens.append(c.expr)
|
|
348
|
+
continue
|
|
349
|
+
tokens.append(self._to_string(c))
|
|
350
|
+
|
|
351
|
+
if not tokens:
|
|
352
|
+
return ""
|
|
353
|
+
|
|
354
|
+
if query.negated:
|
|
355
|
+
return f'not({query.op}({",".join(tokens)}))'
|
|
356
|
+
return f'{query.op}({",".join(tokens)})'
|
|
357
|
+
|
|
358
|
+
def _copy(self, other):
|
|
359
|
+
return RQLQuery(
|
|
360
|
+
_op=other.op,
|
|
361
|
+
_children=other.children[:],
|
|
362
|
+
_expr=other.expr,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
def _join(self, other, op):
|
|
366
|
+
if self == other:
|
|
367
|
+
return self._copy(self)
|
|
368
|
+
if not other:
|
|
369
|
+
return self._copy(self)
|
|
370
|
+
if not self:
|
|
371
|
+
return self._copy(other)
|
|
372
|
+
|
|
373
|
+
query = RQLQuery(_op=op)
|
|
374
|
+
query._append(self)
|
|
375
|
+
query._append(other)
|
|
376
|
+
return query
|
|
377
|
+
|
|
378
|
+
def _append(self, other):
|
|
379
|
+
if other in self.children:
|
|
380
|
+
return other
|
|
381
|
+
|
|
382
|
+
if (
|
|
383
|
+
other.op == self.op or (len(other) == 1 and other.op != self.EXPRESSION)
|
|
384
|
+
) and not other.negated:
|
|
385
|
+
self.children.extend(other.children)
|
|
386
|
+
return self
|
|
387
|
+
|
|
388
|
+
self.children.append(other)
|
|
389
|
+
return self
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
R = RQLQuery
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
mpt_extension_sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
mpt_extension_sdk/airtable/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
3
|
mpt_extension_sdk/airtable/wrap_http_error.py,sha256=Shiy4uwpdpgkevooLjhpe1grhgJRj7cV6lUMMCQKogc,1263
|
|
3
|
-
mpt_extension_sdk/constants.py,sha256=
|
|
4
|
+
mpt_extension_sdk/constants.py,sha256=rlO7WRImnzrb41zlkjka25CQQtEYid-0iGaupg_pk_k,485
|
|
4
5
|
mpt_extension_sdk/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
6
|
mpt_extension_sdk/core/events/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
7
|
mpt_extension_sdk/core/events/dataclasses.py,sha256=wBxjEmjzIwpSH4trpCQKYQPNzONBAgr8eQzTr3ulzkA,469
|
|
@@ -15,33 +16,38 @@ mpt_extension_sdk/key_vault/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
|
|
|
15
16
|
mpt_extension_sdk/key_vault/base.py,sha256=RVG_Wiq-MrPyngJ4gfUd8oxoh1LUop1SvtCM2xdlx8M,3361
|
|
16
17
|
mpt_extension_sdk/mpt_http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
18
|
mpt_extension_sdk/mpt_http/base.py,sha256=3qgo9BW-uTRAbex26urMvLZU0fwnYnBprDRIJCHvTOk,1284
|
|
18
|
-
mpt_extension_sdk/mpt_http/mpt.py,sha256=
|
|
19
|
+
mpt_extension_sdk/mpt_http/mpt.py,sha256=xmEu6zDJO03j4iC1NYfi0XHXLLZkz6ORydIJvi1copA,14236
|
|
19
20
|
mpt_extension_sdk/mpt_http/utils.py,sha256=3wJTT84CXYGjZw6FksNDX8tIHijkL3Wcmld9r6Iz0xc,95
|
|
20
21
|
mpt_extension_sdk/mpt_http/wrap_http_error.py,sha256=j8K6Ddx7NiM-FPRMQi25vuULAW8LXxzZ4EwuIJufxis,1801
|
|
21
22
|
mpt_extension_sdk/runtime/__init__.py,sha256=PQSAz9qvXHbxrn4KvuXSHB8y-A4Jpgbm6ZV-wPiNPyw,194
|
|
22
23
|
mpt_extension_sdk/runtime/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
mpt_extension_sdk/runtime/commands/django.py,sha256=
|
|
24
|
-
mpt_extension_sdk/runtime/commands/run.py,sha256=
|
|
24
|
+
mpt_extension_sdk/runtime/commands/django.py,sha256=j0UC9WKhaTi-sxwV4F-ZF3SE-657-Ucu_jEG0mIzJU4,1229
|
|
25
|
+
mpt_extension_sdk/runtime/commands/run.py,sha256=j-2eI4JJj4VNgVmES5tqKe1Pe2aKWgYchhpvT8Koq7Q,1146
|
|
25
26
|
mpt_extension_sdk/runtime/djapp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
27
|
mpt_extension_sdk/runtime/djapp/apps.py,sha256=YA1G5HaRqFBS-0DfYQuFpMgzUCYpu5T8x5LEC6MwI_M,1398
|
|
27
28
|
mpt_extension_sdk/runtime/djapp/conf/__init__.py,sha256=_sHo76rGeXSTH8bW3pacyGFfNYSln8oLRwOSstYOmco,350
|
|
28
|
-
mpt_extension_sdk/runtime/djapp/conf/default.py,sha256=
|
|
29
|
+
mpt_extension_sdk/runtime/djapp/conf/default.py,sha256=9eAB_hRnkok2I7E1NjYHs3x8ifrVc4_ihosm37Qq7Zo,6405
|
|
30
|
+
mpt_extension_sdk/runtime/djapp/conf/urls.py,sha256=g-h1vzwDgCGLliSE2BDjb1eRJevZxZ7ehJrqSis12So,309
|
|
29
31
|
mpt_extension_sdk/runtime/djapp/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
32
|
mpt_extension_sdk/runtime/djapp/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
-
mpt_extension_sdk/runtime/djapp/management/commands/consume_events.py,sha256=
|
|
33
|
+
mpt_extension_sdk/runtime/djapp/management/commands/consume_events.py,sha256=BVb4sgKI2Ep759bDcOH5Qm1xJEewc5-bNt_snspV2ck,1229
|
|
32
34
|
mpt_extension_sdk/runtime/djapp/middleware.py,sha256=oAA5khXW6xeUjnOV63pNCF4D7QFVD6PayIa_489KRmg,555
|
|
33
35
|
mpt_extension_sdk/runtime/events/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
-
mpt_extension_sdk/runtime/events/dispatcher.py,sha256=
|
|
35
|
-
mpt_extension_sdk/runtime/events/producers.py,sha256=
|
|
36
|
+
mpt_extension_sdk/runtime/events/dispatcher.py,sha256=ZMswk50vGGfUgKeQ2Cc3k9wtrm63z0OH7EtlKCkWQYM,3008
|
|
37
|
+
mpt_extension_sdk/runtime/events/producers.py,sha256=lsHFfBxL5czZEAqZEyaIDZ-l8Q1T5_OhH5BGiXqw4Bk,3597
|
|
36
38
|
mpt_extension_sdk/runtime/events/utils.py,sha256=bxFo-iQUqEJDqcIotC88Ty3Xt0K2FyYjA9UMWFEyMKI,2663
|
|
37
|
-
mpt_extension_sdk/runtime/initializer.py,sha256=
|
|
39
|
+
mpt_extension_sdk/runtime/initializer.py,sha256=V8fubCULUJlvCBkYhJM52GUNoHNWVI-siaecmZv3kzk,2154
|
|
38
40
|
mpt_extension_sdk/runtime/logging.py,sha256=wJ6KyvnuvMZELI1Hd0fYbuHr7YiP3GIXFBvvSdmd7nQ,885
|
|
39
|
-
mpt_extension_sdk/runtime/master.py,sha256=
|
|
41
|
+
mpt_extension_sdk/runtime/master.py,sha256=ALbtAsb6ubhWcEpAXpzHSZixYXZ3Qfdf2ttOLb04nlc,4536
|
|
40
42
|
mpt_extension_sdk/runtime/swoext.py,sha256=LS0YZXwQsHDaYjalDGfYQi5cAL3aKQhphf3chrNmNBk,1665
|
|
41
|
-
mpt_extension_sdk/runtime/
|
|
42
|
-
mpt_extension_sdk/runtime/
|
|
43
|
-
mpt_extension_sdk
|
|
44
|
-
mpt_extension_sdk
|
|
45
|
-
mpt_extension_sdk
|
|
46
|
-
mpt_extension_sdk
|
|
47
|
-
mpt_extension_sdk-4.
|
|
43
|
+
mpt_extension_sdk/runtime/tracer.py,sha256=Mvt1BKBdSvb4FlOcjUDhIKEXmiYETrFGgHdhOQwEr7g,415
|
|
44
|
+
mpt_extension_sdk/runtime/utils.py,sha256=FWMsGCTPxgKPX8lbCpfCu-ZWuJCqnXZHVbt9bUHVJX4,4505
|
|
45
|
+
mpt_extension_sdk/runtime/workers.py,sha256=8KdI6neBEiMcc0zha8toCsZutvK2BhEXxb8NdTMrNO4,2677
|
|
46
|
+
mpt_extension_sdk/swo_rql/__init__.py,sha256=QrvRDYhpK-Pd3OF-U2aMeazuKm_kbvXwLRIjd9Mm6ok,104
|
|
47
|
+
mpt_extension_sdk/swo_rql/constants.py,sha256=39BZ78OzdU_dIQtoy-Z_5utXqEXRweIFvM9Zfxg9j5M,171
|
|
48
|
+
mpt_extension_sdk/swo_rql/query_builder.py,sha256=XAoMYV1jaAouYjpqrh8nMaDNzD0as-BhMx8rsvdeZ0k,10500
|
|
49
|
+
mpt_extension_sdk-4.4.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
50
|
+
mpt_extension_sdk-4.4.0.dist-info/METADATA,sha256=fo481WyKgGnxl0zVaFc7gqZ0EDLyXOAmLPuo0CQUnTo,1635
|
|
51
|
+
mpt_extension_sdk-4.4.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
52
|
+
mpt_extension_sdk-4.4.0.dist-info/entry_points.txt,sha256=8uZQihFseK4Kp5XWHl9EDYneg-CLS8BFO-ygp0h34nc,143
|
|
53
|
+
mpt_extension_sdk-4.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|