omnata-plugin-runtime 0.3.20a63__py3-none-any.whl → 0.3.21a64__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.
- omnata_plugin_runtime/configuration.py +12 -5
- omnata_plugin_runtime/omnata_plugin.py +22 -8
- {omnata_plugin_runtime-0.3.20a63.dist-info → omnata_plugin_runtime-0.3.21a64.dist-info}/METADATA +1 -1
- {omnata_plugin_runtime-0.3.20a63.dist-info → omnata_plugin_runtime-0.3.21a64.dist-info}/RECORD +6 -6
- {omnata_plugin_runtime-0.3.20a63.dist-info → omnata_plugin_runtime-0.3.21a64.dist-info}/LICENSE +0 -0
- {omnata_plugin_runtime-0.3.20a63.dist-info → omnata_plugin_runtime-0.3.21a64.dist-info}/WHEEL +0 -0
@@ -443,10 +443,9 @@ class StoredStreamConfiguration(SubscriptableBaseModel):
|
|
443
443
|
None,
|
444
444
|
description="The field to use as a cursor",
|
445
445
|
)
|
446
|
-
|
447
|
-
primary_key_field: Optional[str] = Field(
|
446
|
+
primary_key_field: Optional[Union[str,List[str]]] = Field(
|
448
447
|
None,
|
449
|
-
description="The field that will be used as primary key.",
|
448
|
+
description="The field(s) that will be used as primary key.",
|
450
449
|
)
|
451
450
|
storage_behaviour: InboundStorageBehaviour
|
452
451
|
stream: StreamConfiguration
|
@@ -465,9 +464,17 @@ class StreamConfiguration(SubscriptableBaseModel):
|
|
465
464
|
None,
|
466
465
|
description="If true, the plugin controls the cursor field",
|
467
466
|
)
|
468
|
-
source_defined_primary_key: Optional[str] = Field(
|
467
|
+
source_defined_primary_key: Optional[Union[str,List[str]]] = Field(
|
469
468
|
None,
|
470
|
-
description="If
|
469
|
+
description="If defined, the plugin controls the primary key field",
|
470
|
+
)
|
471
|
+
# For SaaS applications, typically the primary key is a single field, known to the plugin.
|
472
|
+
# For databases, the primary key can be composite, and the plugin may not know what it is if there is no key defined explicitly on the table.
|
473
|
+
# this flag was added to support this scenario. If it is to True, then source_defined_primary_key will be a list of fields.
|
474
|
+
# When this occurs, the plugin runtime will build the APP_IDENTIFIER from the fields in the order they are listed, by concatenating them.
|
475
|
+
primary_key_can_be_composite: Optional[bool] = Field(
|
476
|
+
False,
|
477
|
+
description="If true, the primary key can be composite",
|
471
478
|
)
|
472
479
|
default_cursor_field: Optional[str] = Field(
|
473
480
|
None,
|
@@ -19,6 +19,7 @@ import http
|
|
19
19
|
import json
|
20
20
|
import queue
|
21
21
|
import threading
|
22
|
+
import time
|
22
23
|
from abc import ABC, abstractmethod
|
23
24
|
from decimal import Decimal
|
24
25
|
from functools import partial, wraps, reduce
|
@@ -1200,7 +1201,8 @@ class InboundSyncRequest(SyncRequest):
|
|
1200
1201
|
raise ValueError(
|
1201
1202
|
f"Primary key field '{primary_key_field}' was not present in all records for stream {stream_name}"
|
1202
1203
|
)
|
1203
|
-
|
1204
|
+
# TODO: handle the scenario where the primary key field is a list, and concatenate the values to form the APP_IDENTIFIER
|
1205
|
+
# Currently this is only implemented in the java runtime, since it's typically databases that use composite keys
|
1204
1206
|
results_df["APP_IDENTIFIER"] = results_df["RECORD_DATA"].apply(lambda x: get_nested_value(dict(x),primary_key_field))
|
1205
1207
|
# ensure APP_IDENTIFIER is a string
|
1206
1208
|
results_df["APP_IDENTIFIER"] = results_df["APP_IDENTIFIER"].apply(str)
|
@@ -1836,9 +1838,13 @@ def managed_outbound_processing(concurrency: int, batch_size: int):
|
|
1836
1838
|
task.start()
|
1837
1839
|
|
1838
1840
|
# wait for workers to finish
|
1839
|
-
|
1840
|
-
task
|
1841
|
-
|
1841
|
+
while tasks:
|
1842
|
+
for task in tasks[:]: # shallow copy so we can remove items from the list while iterating
|
1843
|
+
if not task.is_alive():
|
1844
|
+
task.join() # Ensure the thread is fully finished
|
1845
|
+
tasks.remove(task)
|
1846
|
+
logger.info(f"Thread {task.name} has completed processing")
|
1847
|
+
time.sleep(1) # Avoid busy waiting
|
1842
1848
|
logger.info("All workers completed processing")
|
1843
1849
|
|
1844
1850
|
# it's possible that some records weren't applied, since they are processed asynchronously on a timer
|
@@ -1892,11 +1898,14 @@ def __managed_inbound_processing_worker(
|
|
1892
1898
|
logger.info(f"stream returned from queue: {stream}")
|
1893
1899
|
# restore the first argument, was originally the dataframe/generator but now it's the appropriately sized dataframe
|
1894
1900
|
try:
|
1901
|
+
logger.info(f"worker {worker_index} processing stream {stream.stream_name}, invoking plugin class method {method.__name__}")
|
1895
1902
|
method(plugin_class_obj, *(stream, *method_args), **method_kwargs)
|
1903
|
+
logger.info(f"worker {worker_index} completed processing stream {stream.stream_name}, marking as complete")
|
1896
1904
|
plugin_class_obj._sync_request.mark_stream_complete(stream.stream_name)
|
1897
1905
|
except InterruptedWhileWaitingException:
|
1898
1906
|
# If an inbound run is cancelled while waiting for rate limiting, this should mean that
|
1899
1907
|
# the cancellation is handled elsewhere, so we don't need to do anything special here other than stop waiting
|
1908
|
+
logger.info(f"worker {worker_index} interrupted while waiting, exiting")
|
1900
1909
|
return
|
1901
1910
|
except Exception as e:
|
1902
1911
|
# logging this to the omnata_plugin logger in this way,
|
@@ -1967,13 +1976,14 @@ def managed_inbound_processing(concurrency: int):
|
|
1967
1976
|
for stream in streams_list:
|
1968
1977
|
streams_queue.put(stream)
|
1969
1978
|
|
1970
|
-
tasks = []
|
1979
|
+
tasks:List[threading.Thread] = []
|
1971
1980
|
logger.info(f"Creating {concurrency_to_use} worker(s) for retrieving records")
|
1972
1981
|
|
1973
1982
|
for i in range(concurrency_to_use):
|
1974
1983
|
# the dataframe/generator was put on the queue, so we remove it from the method args
|
1975
1984
|
task = threading.Thread(
|
1976
1985
|
target=__managed_inbound_processing_worker,
|
1986
|
+
name=f"managed_inbound_processing_worker_{i}",
|
1977
1987
|
args=(
|
1978
1988
|
self,
|
1979
1989
|
method,
|
@@ -1988,9 +1998,13 @@ def managed_inbound_processing(concurrency: int):
|
|
1988
1998
|
task.start()
|
1989
1999
|
|
1990
2000
|
# wait for workers to finish
|
1991
|
-
|
1992
|
-
task
|
1993
|
-
|
2001
|
+
while tasks:
|
2002
|
+
for task in tasks[:]: # shallow copy so we can remove items from the list while iterating
|
2003
|
+
if not task.is_alive():
|
2004
|
+
task.join() # Ensure the thread is fully finished
|
2005
|
+
tasks.remove(task)
|
2006
|
+
logger.info(f"Thread {task.name} has completed processing")
|
2007
|
+
time.sleep(1) # Avoid busy waiting
|
1994
2008
|
logger.info("All workers completed processing")
|
1995
2009
|
|
1996
2010
|
# it's possible that some records weren't applied, since they are processed asynchronously on a timer
|
{omnata_plugin_runtime-0.3.20a63.dist-info → omnata_plugin_runtime-0.3.21a64.dist-info}/RECORD
RENAMED
@@ -1,12 +1,12 @@
|
|
1
1
|
omnata_plugin_runtime/__init__.py,sha256=w63LVME5nY-hQ4BBzfacy9kvTunwqHGs8iiSPGAX2ns,1214
|
2
2
|
omnata_plugin_runtime/api.py,sha256=_N5ok5LN7GDO4J9n3yduXp3tpjmhpySY__U2baiygrs,6217
|
3
|
-
omnata_plugin_runtime/configuration.py,sha256=
|
3
|
+
omnata_plugin_runtime/configuration.py,sha256=7cMekoY8CeZAJHpASU6tCMidF55Hzfr7CD74jtebqIY,35742
|
4
4
|
omnata_plugin_runtime/forms.py,sha256=pw_aKVsXSz47EP8PFBI3VDwdSN5IjvZxp8JTjO1V130,18421
|
5
5
|
omnata_plugin_runtime/logging.py,sha256=bn7eKoNWvtuyTk7RTwBS9UARMtqkiICtgMtzq3KA2V0,3272
|
6
|
-
omnata_plugin_runtime/omnata_plugin.py,sha256=
|
6
|
+
omnata_plugin_runtime/omnata_plugin.py,sha256=ArEtB7LvbLBq9u39Y_2ivg24Hi1Xe-kcnzj5UG4mLP0,100971
|
7
7
|
omnata_plugin_runtime/plugin_entrypoints.py,sha256=s-SrUnXaS6FaBq1igiJhcCB3Md_a6op-dp_g1H_55QU,27736
|
8
8
|
omnata_plugin_runtime/rate_limiting.py,sha256=se6MftQI5NrVHaLb1hByPCgAESPQhkAgIG7KIU1clDU,16562
|
9
|
-
omnata_plugin_runtime-0.3.
|
10
|
-
omnata_plugin_runtime-0.3.
|
11
|
-
omnata_plugin_runtime-0.3.
|
12
|
-
omnata_plugin_runtime-0.3.
|
9
|
+
omnata_plugin_runtime-0.3.21a64.dist-info/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
|
10
|
+
omnata_plugin_runtime-0.3.21a64.dist-info/METADATA,sha256=AN68hzVHcChygxFH76s7iZtvROwge5NqHDNttfkRfko,1604
|
11
|
+
omnata_plugin_runtime-0.3.21a64.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
12
|
+
omnata_plugin_runtime-0.3.21a64.dist-info/RECORD,,
|
{omnata_plugin_runtime-0.3.20a63.dist-info → omnata_plugin_runtime-0.3.21a64.dist-info}/LICENSE
RENAMED
File without changes
|
{omnata_plugin_runtime-0.3.20a63.dist-info → omnata_plugin_runtime-0.3.21a64.dist-info}/WHEEL
RENAMED
File without changes
|