omnata-plugin-runtime 0.1.73__py3-none-any.whl → 0.1.80__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/api.py +21 -8
- omnata_plugin_runtime/configuration.py +12 -12
- omnata_plugin_runtime/forms.py +19 -7
- omnata_plugin_runtime/logging.py +5 -3
- omnata_plugin_runtime/omnata_plugin.py +78 -84
- omnata_plugin_runtime/plugin_entrypoints.py +15 -17
- omnata_plugin_runtime/rate_limiting.py +1 -2
- {omnata_plugin_runtime-0.1.73.dist-info → omnata_plugin_runtime-0.1.80.dist-info}/METADATA +3 -3
- omnata_plugin_runtime-0.1.80.dist-info/RECORD +12 -0
- {omnata_plugin_runtime-0.1.73.dist-info → omnata_plugin_runtime-0.1.80.dist-info}/WHEEL +1 -1
- omnata_plugin_runtime-0.1.73.dist-info/RECORD +0 -12
- {omnata_plugin_runtime-0.1.73.dist-info → omnata_plugin_runtime-0.1.80.dist-info}/LICENSE +0 -0
omnata_plugin_runtime/api.py
CHANGED
@@ -5,14 +5,24 @@ Not used in plugins.
|
|
5
5
|
import sys
|
6
6
|
import json
|
7
7
|
|
8
|
-
from typing import
|
8
|
+
from typing import (
|
9
|
+
Dict,
|
10
|
+
List,
|
11
|
+
Literal,
|
12
|
+
Optional,
|
13
|
+
Union,
|
14
|
+
cast,
|
15
|
+
) # pylint: disable=ungrouped-imports
|
9
16
|
|
10
17
|
from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
|
11
18
|
from snowflake.snowpark import Row
|
12
19
|
|
13
|
-
from .configuration import (
|
14
|
-
|
15
|
-
|
20
|
+
from .configuration import (
|
21
|
+
InboundSyncStreamsConfiguration,
|
22
|
+
OutboundSyncStrategy,
|
23
|
+
StoredConfigurationValue,
|
24
|
+
StoredMappingValue,
|
25
|
+
)
|
16
26
|
from .rate_limiting import ApiLimits, RateLimitState
|
17
27
|
|
18
28
|
if tuple(sys.version_info[:2]) >= (3, 9):
|
@@ -75,7 +85,7 @@ PluginMessage = Annotated[
|
|
75
85
|
PluginMessageStreamState,
|
76
86
|
PluginMessageStreamProgressUpdate,
|
77
87
|
PluginMessageCancelledStreams,
|
78
|
-
PluginMessageAbandonedStreams
|
88
|
+
PluginMessageAbandonedStreams,
|
79
89
|
],
|
80
90
|
Field(discriminator="message_type"),
|
81
91
|
]
|
@@ -105,7 +115,7 @@ class OutboundSyncRequestPayload(BaseModel):
|
|
105
115
|
sync_parameters: Dict[str, StoredConfigurationValue]
|
106
116
|
api_limit_overrides: List[ApiLimits]
|
107
117
|
rate_limits_state: Dict[str, RateLimitState]
|
108
|
-
field_mappings: StoredMappingValue
|
118
|
+
field_mappings: Optional[StoredMappingValue]
|
109
119
|
|
110
120
|
|
111
121
|
class InboundSyncRequestPayload(BaseModel):
|
@@ -138,6 +148,7 @@ SyncRequestPayload = Annotated[
|
|
138
148
|
Field(discriminator="sync_direction"),
|
139
149
|
]
|
140
150
|
|
151
|
+
|
141
152
|
def handle_proc_result(query_result: List[Row]) -> Dict:
|
142
153
|
"""
|
143
154
|
Our standard proc response is a single row with a single column, which is a JSON string
|
@@ -145,9 +156,11 @@ def handle_proc_result(query_result: List[Row]) -> Dict:
|
|
145
156
|
Otherwise we return the data
|
146
157
|
"""
|
147
158
|
if len(query_result) != 1:
|
148
|
-
raise ValueError(
|
159
|
+
raise ValueError(
|
160
|
+
f"Expected a single row result from procedure (got {len(query_result)})"
|
161
|
+
)
|
149
162
|
first_row = cast(Row, query_result[0])
|
150
163
|
result = json.loads(str(first_row[0]))
|
151
164
|
if result["success"] is not True:
|
152
165
|
raise ValueError(result["error"])
|
153
|
-
return result["data"] if "data" in result else result
|
166
|
+
return result["data"] if "data" in result else result
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# it's not the 1980s anymore
|
2
|
-
# pylint: disable=line-too-long,multiple-imports,logging-fstring-interpolation
|
3
1
|
"""
|
4
2
|
Omnata Plugin Runtime configuration objects.
|
5
3
|
Includes data container classes related to plugin configuration.
|
@@ -14,7 +12,7 @@ from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
|
|
14
12
|
|
15
13
|
if tuple(sys.version_info[:2]) >= (3, 9):
|
16
14
|
# Python 3.9 and above
|
17
|
-
from typing import Annotated
|
15
|
+
from typing import Annotated # pylint: disable=ungrouped-imports
|
18
16
|
else:
|
19
17
|
# Python 3.8 and below
|
20
18
|
from typing_extensions import Annotated
|
@@ -207,10 +205,10 @@ class OutboundSyncStrategy(SubscriptableBaseModel, ABC):
|
|
207
205
|
name: str
|
208
206
|
description: str
|
209
207
|
icon_source: str = ICON_URL_CODE
|
210
|
-
action_on_record_create: OutboundSyncAction = None
|
211
|
-
action_on_record_update: OutboundSyncAction = None
|
212
|
-
action_on_record_delete: OutboundSyncAction = None
|
213
|
-
action_on_record_unchanged: OutboundSyncAction = None
|
208
|
+
action_on_record_create: Optional[OutboundSyncAction] = None
|
209
|
+
action_on_record_update: Optional[OutboundSyncAction] = None
|
210
|
+
action_on_record_delete: Optional[OutboundSyncAction] = None
|
211
|
+
action_on_record_unchanged: Optional[OutboundSyncAction] = None
|
214
212
|
custom_strategy: bool = True
|
215
213
|
|
216
214
|
def __eq__(self, other):
|
@@ -498,7 +496,7 @@ class ConnectionConfigurationParameters(SubscriptableBaseModel):
|
|
498
496
|
raise ValueError("Connection parameters were not provided")
|
499
497
|
if parameter_name not in self.connection_parameters.keys():
|
500
498
|
raise ValueError(f"Connection parameter '{parameter_name}' not available")
|
501
|
-
return self.connection_parameters[
|
499
|
+
return self.connection_parameters[ # pylint: disable=unsubscriptable-object
|
502
500
|
parameter_name
|
503
501
|
]
|
504
502
|
|
@@ -516,7 +514,7 @@ class ConnectionConfigurationParameters(SubscriptableBaseModel):
|
|
516
514
|
raise ValueError("Connection secrets were not provided")
|
517
515
|
if parameter_name not in self.connection_secrets.keys():
|
518
516
|
raise ValueError(f"Connection secret '{parameter_name}' not available")
|
519
|
-
return self.connection_secrets[
|
517
|
+
return self.connection_secrets[ # pylint: disable=unsubscriptable-object
|
520
518
|
parameter_name
|
521
519
|
]
|
522
520
|
|
@@ -559,7 +557,7 @@ class SyncConfigurationParameters(ConnectionConfigurationParameters):
|
|
559
557
|
if default_value is not None:
|
560
558
|
return {"value": default_value, "metadata": {}}
|
561
559
|
raise ValueError(f"Sync parameter '{parameter_name}' not available")
|
562
|
-
return self.sync_parameters[
|
560
|
+
return self.sync_parameters[ # pylint: disable=unsubscriptable-object
|
563
561
|
parameter_name
|
564
562
|
]
|
565
563
|
|
@@ -580,7 +578,7 @@ class SyncConfigurationParameters(ConnectionConfigurationParameters):
|
|
580
578
|
if default_value is not None:
|
581
579
|
return StoredConfigurationValue(value=default_value)
|
582
580
|
raise ValueError(f"Form parameter '{parameter_name}' not available")
|
583
|
-
return self.current_form_parameters[
|
581
|
+
return self.current_form_parameters[ # pylint: disable=unsubscriptable-object
|
584
582
|
parameter_name
|
585
583
|
]
|
586
584
|
|
@@ -591,7 +589,9 @@ class SyncConfigurationParameters(ConnectionConfigurationParameters):
|
|
591
589
|
return_dict = {}
|
592
590
|
for dict_key in self.current_form_parameters.keys():
|
593
591
|
if dict_key not in exclude_fields:
|
594
|
-
return_dict[
|
592
|
+
return_dict[
|
593
|
+
dict_key
|
594
|
+
] = self.current_form_parameters[ # pylint: disable=unsubscriptable-object
|
595
595
|
dict_key
|
596
596
|
].value
|
597
597
|
return return_dict
|
omnata_plugin_runtime/forms.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# it's not the 1980s anymore
|
2
|
-
# pylint: disable=line-too-long,multiple-imports,logging-fstring-interpolation,too-many-arguments,no-self-argument
|
3
1
|
"""
|
4
2
|
Contains form elements for Omnata plugin configuration
|
5
3
|
"""
|
@@ -207,7 +205,7 @@ class FormJinjaTemplate(SubscriptableBaseModel):
|
|
207
205
|
# Everything above here has no dependencies on other BaseModels in this module
|
208
206
|
# ----------------------------------------------------------------------------
|
209
207
|
|
210
|
-
NewOptionCreator = ForwardRef("NewOptionCreator")
|
208
|
+
NewOptionCreator = ForwardRef("NewOptionCreator") # type: ignore
|
211
209
|
|
212
210
|
|
213
211
|
class StaticFormOptionsDataSource(SubscriptableBaseModel):
|
@@ -390,20 +388,34 @@ class InboundSyncConfigurationForm(ConfigurationFormBase):
|
|
390
388
|
fields: List[FormFieldBase] = Field(default_factory=list)
|
391
389
|
stream_lister: StreamLister
|
392
390
|
|
391
|
+
class SecurityIntegrationTemplate(BaseModel):
|
392
|
+
"""
|
393
|
+
Provides values used to populate a security integration instructions template, which
|
394
|
+
in turn allows the customer to create an OAuth based secret object
|
395
|
+
"""
|
396
|
+
oauth_docs_url: Optional[str] = None
|
397
|
+
oauth_type: Literal["code_grant"] = "code_grant"
|
398
|
+
oauth_client_id: str = '<client id>'
|
399
|
+
oauth_client_secret: str = '<client secret>'
|
400
|
+
oauth_token_endpoint: str = '<token endpoint>'
|
401
|
+
oauth_authorization_endpoint: str = '<authorization endpoint>'
|
402
|
+
oauth_allowed_scopes: List[str] = []
|
403
|
+
|
393
404
|
|
394
405
|
class ConnectionMethod(SubscriptableBaseModel):
|
395
406
|
"""
|
396
407
|
Defines a method of connecting to an application.
|
397
408
|
:param str data_source: The name of the connection method, e.g. "OAuth", "API Key", "Credentials"
|
398
409
|
:param List[FormFieldBase] fields: A list of fields that are used to collect the connection information from the user.
|
399
|
-
:param
|
400
|
-
|
410
|
+
:param Optional[SecurityIntegrationTemplate] oauth_template: If provided, the user will be guided through the process
|
411
|
+
of creating a security integration, followed by a secret and performing the OAuth flow. Once this secret is completed,
|
412
|
+
the rest of the values from the form will be captured and then the connection will be tested.
|
401
413
|
:param str description: A markdown description of the connection method, which will be displayed to the user.
|
402
|
-
This should be concise as it will be displayed in a sidebar, but you can include a link to the connection section
|
414
|
+
This should be concise as it will be displayed in a sidebar, but you can include a link to the connection section
|
403
415
|
of the plugin's documentation.
|
404
416
|
"""
|
405
417
|
|
406
418
|
name: str
|
407
419
|
fields: List[FormFieldBase]
|
408
|
-
|
420
|
+
oauth_template: Optional[SecurityIntegrationTemplate] = None
|
409
421
|
description: str = ""
|
omnata_plugin_runtime/logging.py
CHANGED
@@ -37,7 +37,7 @@ class CustomLogger(logging.getLoggerClass()):
|
|
37
37
|
if isinstance(exc_value, ValidationError):
|
38
38
|
record.msg = exc_value.errors()
|
39
39
|
record.exc_info = (exc_type, None, tb)
|
40
|
-
super().handleError(record)
|
40
|
+
super().handleError(record) # type: ignore
|
41
41
|
|
42
42
|
|
43
43
|
class OmnataPluginLogHandler(logging.Handler):
|
@@ -66,11 +66,13 @@ class OmnataPluginLogHandler(logging.Handler):
|
|
66
66
|
|
67
67
|
def emit(self, record: logging.LogRecord):
|
68
68
|
if hasattr(record, "stream_name"):
|
69
|
-
stream_name: str = record.stream_name
|
69
|
+
stream_name: str = record.stream_name # type: ignore
|
70
70
|
if record.levelno >= logging.ERROR:
|
71
71
|
self.stream_has_errors[stream_name] = True
|
72
72
|
|
73
|
-
def register(
|
73
|
+
def register(
|
74
|
+
self, logging_level: str, additional_loggers: Optional[List[str]] = None
|
75
|
+
):
|
74
76
|
"""
|
75
77
|
Register the handler with the omnata_plugin namespace
|
76
78
|
"""
|
@@ -1,5 +1,4 @@
|
|
1
|
-
#
|
2
|
-
# pylint: disable=line-too-long,multiple-imports,logging-fstring-interpolation
|
1
|
+
# pylint: disable=protected-access
|
3
2
|
"""
|
4
3
|
Omnata Plugin Runtime.
|
5
4
|
Includes data container classes and defines the contract for a plugin.
|
@@ -28,23 +27,40 @@ from snowflake.connector.pandas_tools import write_pandas
|
|
28
27
|
from snowflake.snowpark import Session
|
29
28
|
from snowflake.snowpark.functions import col
|
30
29
|
|
31
|
-
from .api import (
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
30
|
+
from .api import (
|
31
|
+
PluginMessage,
|
32
|
+
PluginMessageAbandonedStreams,
|
33
|
+
PluginMessageCancelledStreams,
|
34
|
+
PluginMessageCurrentActivity,
|
35
|
+
PluginMessageStreamProgressUpdate,
|
36
|
+
PluginMessageStreamState,
|
37
|
+
handle_proc_result,
|
38
|
+
)
|
39
|
+
from .configuration import (
|
40
|
+
STANDARD_OUTBOUND_SYNC_ACTIONS,
|
41
|
+
ConnectionConfigurationParameters,
|
42
|
+
InboundSyncConfigurationParameters,
|
43
|
+
OutboundSyncAction,
|
44
|
+
OutboundSyncConfigurationParameters,
|
45
|
+
OutboundSyncStrategy,
|
46
|
+
StoredConfigurationValue,
|
47
|
+
StoredMappingValue,
|
48
|
+
StoredStreamConfiguration,
|
49
|
+
StreamConfiguration,
|
50
|
+
SubscriptableBaseModel,
|
51
|
+
SyncConfigurationParameters,
|
52
|
+
)
|
53
|
+
from .forms import (
|
54
|
+
ConnectionMethod,
|
55
|
+
InboundSyncConfigurationForm,
|
56
|
+
OutboundSyncConfigurationForm,
|
57
|
+
)
|
58
|
+
from .rate_limiting import (
|
59
|
+
ApiLimits,
|
60
|
+
HttpMethodType,
|
61
|
+
InterruptedWhileWaitingException,
|
62
|
+
RateLimitState,
|
63
|
+
)
|
48
64
|
|
49
65
|
logger = getLogger(__name__)
|
50
66
|
SortDirectionType = Literal["asc", "desc"]
|
@@ -142,7 +158,7 @@ class SyncRequest(ABC):
|
|
142
158
|
self._run_id = run_id
|
143
159
|
self.api_limits = api_limits
|
144
160
|
self.rate_limit_state = rate_limit_state
|
145
|
-
self._apply_results = None
|
161
|
+
self._apply_results = None # this will be re-initialised by subclasses
|
146
162
|
# these deal with applying the results, not sure they belong here
|
147
163
|
self._apply_results_lock = threading.Lock()
|
148
164
|
# Snowflake connector appears to not be thread safe
|
@@ -204,7 +220,7 @@ class SyncRequest(ABC):
|
|
204
220
|
Designed to be run in a thread, this method checks to see if the sync run has been cancelled.
|
205
221
|
"""
|
206
222
|
while not cancellation_token.is_set():
|
207
|
-
logger.info("cancel checking
|
223
|
+
logger.info("cancel checking worker checking for results")
|
208
224
|
|
209
225
|
with self._snowflake_query_lock:
|
210
226
|
try:
|
@@ -344,7 +360,7 @@ class HttpRateLimiting:
|
|
344
360
|
It does this by patching http.client.HTTPConnection.putrequest
|
345
361
|
"""
|
346
362
|
self_outer = self
|
347
|
-
self.original_putrequest = http.client.HTTPConnection.putrequest
|
363
|
+
self.original_putrequest = http.client.HTTPConnection.putrequest # type: ignore
|
348
364
|
|
349
365
|
def new_putrequest(
|
350
366
|
self,
|
@@ -372,10 +388,10 @@ class HttpRateLimiting:
|
|
372
388
|
self, method, url, skip_host, skip_accept_encoding
|
373
389
|
)
|
374
390
|
|
375
|
-
http.client.HTTPConnection.putrequest = new_putrequest
|
391
|
+
http.client.HTTPConnection.putrequest = new_putrequest # type: ignore
|
376
392
|
|
377
393
|
def __exit__(self, exc_type, exc_value, traceback):
|
378
|
-
http.client.HTTPConnection.putrequest = self.original_putrequest
|
394
|
+
http.client.HTTPConnection.putrequest = self.original_putrequest # type: ignore
|
379
395
|
|
380
396
|
|
381
397
|
class OutboundSyncRequest(SyncRequest):
|
@@ -554,23 +570,29 @@ class OutboundSyncRequest(SyncRequest):
|
|
554
570
|
# use a random table name with a random string to avoid collisions
|
555
571
|
with self._snowflake_query_lock:
|
556
572
|
success, nchunks, nrows, _ = write_pandas(
|
557
|
-
conn=self._session._conn._cursor.connection,
|
573
|
+
conn=self._session._conn._cursor.connection, # pylint: disable=protected-access
|
558
574
|
df=self._preprocess_results_dataframe(results_df),
|
559
575
|
quote_identifiers=False,
|
560
576
|
table_name=self._full_results_table_name,
|
561
577
|
auto_create_table=False,
|
562
578
|
)
|
563
579
|
if not success:
|
564
|
-
raise ValueError(
|
565
|
-
|
580
|
+
raise ValueError(
|
581
|
+
f"Failed to write results to table {self._full_results_table_name}"
|
582
|
+
)
|
583
|
+
logger.info(
|
584
|
+
f"Wrote {nrows} rows and {nchunks} chunks to table {self._full_results_table_name}"
|
585
|
+
)
|
566
586
|
|
567
|
-
def __dataframe_wrapper(
|
587
|
+
def __dataframe_wrapper(
|
588
|
+
self, data_frame: pandas.DataFrame, render_jinja: bool = True
|
589
|
+
) -> pandas.DataFrame:
|
568
590
|
"""
|
569
591
|
Takes care of some common stuff we need to do for each dataframe for outbound syncs.
|
570
592
|
Parses the JSON in the transformed record column (Snowflake passes it as a string).
|
571
593
|
Also when the mapper is a jinja template, renders it.
|
572
594
|
"""
|
573
|
-
#if data_frame is None:
|
595
|
+
# if data_frame is None:
|
574
596
|
# logger.info(
|
575
597
|
# "Dataframe wrapper skipping pre-processing as dataframe is None"
|
576
598
|
# )
|
@@ -672,9 +694,7 @@ class OutboundSyncRequest(SyncRequest):
|
|
672
694
|
with self._snowflake_query_lock:
|
673
695
|
dataframe = (
|
674
696
|
self._session.table(self._full_records_table_name)
|
675
|
-
.filter(
|
676
|
-
(col("SYNC_ACTION").in_(sync_action_names)) # type: ignore
|
677
|
-
)
|
697
|
+
.filter((col("SYNC_ACTION").in_(sync_action_names))) # type: ignore
|
678
698
|
.select(
|
679
699
|
col("IDENTIFIER"), col("SYNC_ACTION"), col("TRANSFORMED_RECORD")
|
680
700
|
)
|
@@ -828,9 +848,7 @@ class InboundSyncRequest(SyncRequest):
|
|
828
848
|
if stream.stream_name not in self._completed_streams
|
829
849
|
]
|
830
850
|
self._plugin_message(
|
831
|
-
message=PluginMessageAbandonedStreams(
|
832
|
-
abandoned_streams = abandoned_streams
|
833
|
-
)
|
851
|
+
message=PluginMessageAbandonedStreams(abandoned_streams=abandoned_streams)
|
834
852
|
)
|
835
853
|
|
836
854
|
def enqueue_results(self, stream_name: str, results: List[Dict], new_state: Any):
|
@@ -932,7 +950,7 @@ class InboundSyncRequest(SyncRequest):
|
|
932
950
|
[
|
933
951
|
{
|
934
952
|
"RECORD_DATA": self._convert_by_json_schema(
|
935
|
-
stream_name, data, stream_obj.json_schema
|
953
|
+
stream_name, data, stream_obj.json_schema # type: ignore
|
936
954
|
)
|
937
955
|
}
|
938
956
|
for data in results
|
@@ -987,7 +1005,7 @@ class InboundSyncRequest(SyncRequest):
|
|
987
1005
|
f"Applying {len(results_df)} results to {self._full_results_table_name}"
|
988
1006
|
)
|
989
1007
|
success, nchunks, nrows, _ = write_pandas(
|
990
|
-
conn=self._session._conn._cursor.connection,
|
1008
|
+
conn=self._session._conn._cursor.connection, # pylint: disable=protected-access
|
991
1009
|
df=results_df,
|
992
1010
|
table_name=self._full_results_table_name,
|
993
1011
|
quote_identifiers=False, # already done in get_temp_table_name
|
@@ -995,8 +1013,12 @@ class InboundSyncRequest(SyncRequest):
|
|
995
1013
|
table_type="transient",
|
996
1014
|
)
|
997
1015
|
if not success:
|
998
|
-
raise ValueError(
|
999
|
-
|
1016
|
+
raise ValueError(
|
1017
|
+
f"Failed to write results to table {self._full_results_table_name}"
|
1018
|
+
)
|
1019
|
+
logger.info(
|
1020
|
+
f"Wrote {nrows} rows and {nchunks} chunks to table {self._full_results_table_name}"
|
1021
|
+
)
|
1000
1022
|
# temp tables aren't allowed
|
1001
1023
|
# snowflake_df = self._session.create_dataframe(results_df)
|
1002
1024
|
# snowflake_df.write.save_as_table(table_name=temp_table,
|
@@ -1016,23 +1038,6 @@ class InboundSyncRequest(SyncRequest):
|
|
1016
1038
|
self._plugin_message(PluginMessageStreamState(stream_state=self._latest_states))
|
1017
1039
|
|
1018
1040
|
|
1019
|
-
class OAuthParameters(SubscriptableBaseModel):
|
1020
|
-
"""
|
1021
|
-
Encapsulates a set of OAuth Parameters
|
1022
|
-
"""
|
1023
|
-
|
1024
|
-
scope: str
|
1025
|
-
authorization_url: str
|
1026
|
-
access_token_url: str
|
1027
|
-
client_id: str
|
1028
|
-
state: Optional[str] = None
|
1029
|
-
response_type: str = "code"
|
1030
|
-
access_type: str = "offline"
|
1031
|
-
|
1032
|
-
class Config:
|
1033
|
-
extra = "allow" # OAuth can contain extra fields
|
1034
|
-
|
1035
|
-
|
1036
1041
|
class ConnectResponse(SubscriptableBaseModel):
|
1037
1042
|
"""
|
1038
1043
|
Encapsulates the response to a connection request. This is used to pass back any additional
|
@@ -1063,14 +1068,11 @@ class BillingEvent(BaseModel):
|
|
1063
1068
|
class BillingEventRequest(BaseModel):
|
1064
1069
|
"""
|
1065
1070
|
Represents a request to provide billing events for that day.
|
1066
|
-
The Omnata engine provides the number of runs for the most frequent inbound and outbound syncs, and the sync ids
|
1067
1071
|
"""
|
1068
1072
|
|
1069
1073
|
billing_schedule: Literal["DAILY"]
|
1070
|
-
|
1071
|
-
|
1072
|
-
outbound_most_frequent_run_count: int
|
1073
|
-
outbound_most_frequent_sync_id: int
|
1074
|
+
has_active_inbound: bool
|
1075
|
+
has_active_outbound: bool
|
1074
1076
|
|
1075
1077
|
|
1076
1078
|
# BillingEventRequest = Annotated[Union[DailyBillingEventRequest,...],Field(discriminator='billing_schedule')]
|
@@ -1171,21 +1173,6 @@ class OmnataPlugin(ABC):
|
|
1171
1173
|
"""
|
1172
1174
|
raise NotImplementedError("Your plugin class must implement the connect method")
|
1173
1175
|
|
1174
|
-
def oauth_parameters(
|
1175
|
-
self, parameters: ConnectionConfigurationParameters
|
1176
|
-
) -> OAuthParameters:
|
1177
|
-
"""
|
1178
|
-
This function is called for any connection method where the "oauth" flag is set to true.
|
1179
|
-
Connection Parameters are provided in case they are needed to construct the OAuth parameters.
|
1180
|
-
|
1181
|
-
:param PluginConfigurationParameters parameters the parameters of the sync, as configured by the user
|
1182
|
-
:return A OAuthParameters, which contains information to commence an OAuth flow
|
1183
|
-
:rtype OAuthParameters
|
1184
|
-
"""
|
1185
|
-
raise NotImplementedError(
|
1186
|
-
"Your plugin class must implement the oauth_parameters method"
|
1187
|
-
)
|
1188
|
-
|
1189
1176
|
def sync_outbound(
|
1190
1177
|
self,
|
1191
1178
|
parameters: OutboundSyncConfigurationParameters,
|
@@ -1222,7 +1209,6 @@ class OmnataPlugin(ABC):
|
|
1222
1209
|
source_types: a dictionary of field names to the original SQL type of the source column/literal/expression (before conversion to variant), as returned by SYSTEM$TYPEOF.
|
1223
1210
|
Leveraging this information may be simpler than trying to parse the transformed values to determine if the original type is compatible
|
1224
1211
|
"""
|
1225
|
-
pass
|
1226
1212
|
|
1227
1213
|
def sync_inbound(
|
1228
1214
|
self,
|
@@ -1366,10 +1352,14 @@ def __managed_outbound_processing_worker(
|
|
1366
1352
|
logger.info(
|
1367
1353
|
f"worker {worker_index} processing. Cancelled: {cancellation_token.is_set()}"
|
1368
1354
|
)
|
1369
|
-
assert
|
1370
|
-
|
1355
|
+
assert (
|
1356
|
+
plugin_class_obj._sync_request is not None
|
1357
|
+
) # pylint: disable=protected-access
|
1358
|
+
if (
|
1359
|
+
datetime.datetime.now() > plugin_class_obj._sync_request._run_deadline
|
1360
|
+
): # pylint: disable=protected-access
|
1371
1361
|
# if we've reached the deadline for the run, end it
|
1372
|
-
plugin_class_obj._sync_request.apply_deadline_reached()
|
1362
|
+
plugin_class_obj._sync_request.apply_deadline_reached() # pylint: disable=protected-access
|
1373
1363
|
return
|
1374
1364
|
records_df = next(dataframe_generator)
|
1375
1365
|
logger.info(f"records returned from dataframe generator: {records_df}")
|
@@ -1406,8 +1396,12 @@ def __managed_outbound_processing_worker(
|
|
1406
1396
|
|
1407
1397
|
# we want to write the results of the batch back to Snowflake, so we
|
1408
1398
|
# enqueue them and they'll be picked up by the apply_results worker
|
1409
|
-
outbound_sync_request = cast(
|
1410
|
-
|
1399
|
+
outbound_sync_request = cast(
|
1400
|
+
OutboundSyncRequest, plugin_class_obj._sync_request
|
1401
|
+
) # pylint: disable=protected-access
|
1402
|
+
outbound_sync_request.enqueue_results(
|
1403
|
+
results_df
|
1404
|
+
) # pylint: disable=protected-access
|
1411
1405
|
logger.info(
|
1412
1406
|
f"worker {worker_index} applied results, marking queue task as done"
|
1413
1407
|
)
|
@@ -1430,10 +1424,10 @@ def managed_outbound_processing(concurrency: int, batch_size: int):
|
|
1430
1424
|
|
1431
1425
|
def actual_decorator(method):
|
1432
1426
|
@wraps(method)
|
1433
|
-
def _impl(self:OmnataPlugin, *method_args, **method_kwargs):
|
1427
|
+
def _impl(self: OmnataPlugin, *method_args, **method_kwargs):
|
1434
1428
|
logger.info(f"method_args: {method_args}")
|
1435
1429
|
logger.info(f"method_kwargs: {method_kwargs}")
|
1436
|
-
if self._sync_request is None:
|
1430
|
+
if self._sync_request is None: # pylint: disable=protected-access
|
1437
1431
|
raise ValueError(
|
1438
1432
|
"To use the managed_outbound_processing decorator, you must attach a sync request to the plugin instance (via the _sync_request property)"
|
1439
1433
|
)
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# pylint: disable=logging-fstring-interpolation
|
2
1
|
import datetime
|
3
2
|
import importlib
|
4
3
|
import json
|
@@ -101,9 +100,9 @@ class PluginEntrypoint:
|
|
101
100
|
# if any endpoint categories have no state, give them an empty state
|
102
101
|
for api_limit in api_limits:
|
103
102
|
if api_limit.endpoint_category not in request.rate_limits_state:
|
104
|
-
request.rate_limits_state[
|
105
|
-
|
106
|
-
|
103
|
+
request.rate_limits_state[api_limit.endpoint_category] = RateLimitState(
|
104
|
+
wait_until=None, previous_request_timestamps=None
|
105
|
+
)
|
107
106
|
logger.info(
|
108
107
|
f"Rate limits state: {json.dumps(request.rate_limits_state, default=pydantic.json.pydantic_encoder)}"
|
109
108
|
)
|
@@ -318,11 +317,11 @@ class PluginEntrypoint:
|
|
318
317
|
events: List[BillingEvent] = self._plugin_instance.create_billing_events(
|
319
318
|
request
|
320
319
|
)
|
321
|
-
# create each billing event, waiting a
|
320
|
+
# create each billing event, waiting a second between each one
|
322
321
|
first_time = True
|
323
322
|
for billing_event in events:
|
324
323
|
if not first_time:
|
325
|
-
time.sleep(
|
324
|
+
time.sleep(1)
|
326
325
|
else:
|
327
326
|
first_time = False
|
328
327
|
current_time = int(time.time() * 1000)
|
@@ -342,8 +341,8 @@ class PluginEntrypoint:
|
|
342
341
|
$${json.dumps(billing_event.additional_info)}$$)
|
343
342
|
"""
|
344
343
|
logger.info(f"Executing billing event query: {event_query}")
|
345
|
-
|
346
|
-
|
344
|
+
result = session.sql(event_query).collect()
|
345
|
+
logger.info(f"Billing event result: {result}")
|
347
346
|
return [e.dict() for e in events]
|
348
347
|
|
349
348
|
def get_secrets(
|
@@ -362,14 +361,15 @@ class PluginEntrypoint:
|
|
362
361
|
secret_string_content = _snowflake.get_generic_secret_string(
|
363
362
|
other_secrets_name
|
364
363
|
)
|
365
|
-
|
364
|
+
if len(secret_string_content) > 2:
|
365
|
+
other_secrets = json.loads(secret_string_content)
|
366
|
+
connection_secrets = {
|
367
|
+
**connection_secrets,
|
368
|
+
**parse_obj_as(Dict[str, StoredConfigurationValue], other_secrets),
|
369
|
+
}
|
366
370
|
except Exception as exception:
|
367
|
-
logger.error(f"Error parsing secrets content: {str(exception)}")
|
371
|
+
logger.error(f"Error parsing secrets content for secret {other_secrets_name}: {str(exception)}")
|
368
372
|
raise ValueError("Error parsing secrets content:") from exception
|
369
|
-
connection_secrets = {
|
370
|
-
**connection_secrets,
|
371
|
-
**parse_obj_as(Dict[str, StoredConfigurationValue], other_secrets),
|
372
|
-
}
|
373
373
|
return connection_secrets
|
374
374
|
|
375
375
|
def network_addresses(self, method: str, connection_parameters: Dict) -> List[str]:
|
@@ -411,9 +411,7 @@ class PluginEntrypoint:
|
|
411
411
|
connection_parameters=parse_obj_as(
|
412
412
|
Dict[str, StoredConfigurationValue], connection_parameters
|
413
413
|
),
|
414
|
-
connection_secrets=
|
415
|
-
Dict[str, StoredConfigurationValue], connection_secrets
|
416
|
-
),
|
414
|
+
connection_secrets=connection_secrets
|
417
415
|
)
|
418
416
|
)
|
419
417
|
# the connect method can also return more network addresses. If so, we need to update the
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# it's not the 1980s anymore
|
2
|
-
# pylint: disable=line-too-long,multiple-imports,logging-fstring-interpolation
|
3
1
|
"""
|
4
2
|
Contains functionality for limiting http requests made by Omnata plugins
|
5
3
|
"""
|
@@ -25,6 +23,7 @@ HttpMethodType = Literal[
|
|
25
23
|
"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"
|
26
24
|
]
|
27
25
|
|
26
|
+
|
28
27
|
class HttpRequestMatcher(SubscriptableBaseModel):
|
29
28
|
"""
|
30
29
|
A class used to match an HTTP request
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: omnata-plugin-runtime
|
3
|
-
Version: 0.1.
|
4
|
-
Summary:
|
3
|
+
Version: 0.1.80
|
4
|
+
Summary: Classes and common runtime components for building and running Omnata Plugins
|
5
5
|
Author: James Weakley
|
6
6
|
Author-email: james.weakley@omnata.com
|
7
7
|
Requires-Python: >=3.10,<3.11
|
@@ -11,7 +11,7 @@ Requires-Dist: jinja2 (>=3,<4)
|
|
11
11
|
Requires-Dist: pandas
|
12
12
|
Requires-Dist: pydantic (>=1,<2)
|
13
13
|
Requires-Dist: requests (>=2,<3)
|
14
|
-
Requires-Dist: snowflake-snowpark-python (>=1
|
14
|
+
Requires-Dist: snowflake-snowpark-python (>=1,<2)
|
15
15
|
Description-Content-Type: text/markdown
|
16
16
|
|
17
17
|
# omnata-plugin-runtime
|
@@ -0,0 +1,12 @@
|
|
1
|
+
omnata_plugin_runtime/__init__.py,sha256=w63LVME5nY-hQ4BBzfacy9kvTunwqHGs8iiSPGAX2ns,1214
|
2
|
+
omnata_plugin_runtime/api.py,sha256=vCDTCxPZ5rIhi8aSM6Z0TXWHtGpbCoNvCnM3mKa-47Q,5591
|
3
|
+
omnata_plugin_runtime/configuration.py,sha256=7P1g8ryOqiXULhKnosAp_uS-h4bZP7i0VZOQ3Izmsac,29513
|
4
|
+
omnata_plugin_runtime/forms.py,sha256=AEj74Wko89zBSgucVfPRctU-MI-AuYsIOEBDPhDi6Hc,15050
|
5
|
+
omnata_plugin_runtime/logging.py,sha256=ne1sLh5cBkjdRS54B30PGc5frABgjy0sF1_2RMcJ_Tk,3012
|
6
|
+
omnata_plugin_runtime/omnata_plugin.py,sha256=LLn4o8xZHXFpdfhIgUiC-eIbvhZOGfhNeqZVsjRQxB8,74217
|
7
|
+
omnata_plugin_runtime/plugin_entrypoints.py,sha256=RyOtV2OZ1gRmlXJKpg7EwyWaUK_5oNAGDymXgInMnck,21267
|
8
|
+
omnata_plugin_runtime/rate_limiting.py,sha256=OnFnCdMenpMpAZYumpe6mypRnMmDl1Q02vzlgmQgiw0,10733
|
9
|
+
omnata_plugin_runtime-0.1.80.dist-info/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
|
10
|
+
omnata_plugin_runtime-0.1.80.dist-info/METADATA,sha256=eIByiBLYOznPEF_zymN5f7q7UQpNRQAqSRzld1zyIsI,987
|
11
|
+
omnata_plugin_runtime-0.1.80.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
12
|
+
omnata_plugin_runtime-0.1.80.dist-info/RECORD,,
|
@@ -1,12 +0,0 @@
|
|
1
|
-
omnata_plugin_runtime/__init__.py,sha256=w63LVME5nY-hQ4BBzfacy9kvTunwqHGs8iiSPGAX2ns,1214
|
2
|
-
omnata_plugin_runtime/api.py,sha256=hxEOcT_CWb0jbQ1ahcHCBPhXAa0N8fLSg4fcADopRfQ,5563
|
3
|
-
omnata_plugin_runtime/configuration.py,sha256=HjXeuJEKvYXPCXLmlSwntvRq67sOewxDmoMDQ3T0yB4,29537
|
4
|
-
omnata_plugin_runtime/forms.py,sha256=8n6z7dVm4YnVHGeHMyg5ccLW6ZAWxB9UUgVZDuaXrXo,14446
|
5
|
-
omnata_plugin_runtime/logging.py,sha256=QjM4x6Hpix7UN_9qVRx24HwtICCfOIS6w0cptgH-DFE,2996
|
6
|
-
omnata_plugin_runtime/omnata_plugin.py,sha256=sJhTegSehM-reszYzzWQ1zQNLP1yiEYDFMEod95SSKA,75590
|
7
|
-
omnata_plugin_runtime/plugin_entrypoints.py,sha256=jpohxu9vUhKzaukkHhat0xGDu5O-GZ2UfFgqm48iJco,21239
|
8
|
-
omnata_plugin_runtime/rate_limiting.py,sha256=8DBz-OxzBGUCoZqWe8fJvT4TGIs4b3WFZgO--9_UbF0,10840
|
9
|
-
omnata_plugin_runtime-0.1.73.dist-info/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
|
10
|
-
omnata_plugin_runtime-0.1.73.dist-info/METADATA,sha256=Wk66C8wHMAED06sEhH-5txz9_ZdR8igtxt2oymfVWZk,998
|
11
|
-
omnata_plugin_runtime-0.1.73.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
12
|
-
omnata_plugin_runtime-0.1.73.dist-info/RECORD,,
|
File without changes
|