omnata-plugin-runtime 0.5.6a128__tar.gz → 0.5.7a130__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/PKG-INFO +1 -1
- {omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/pyproject.toml +1 -1
- {omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/src/omnata_plugin_runtime/configuration.py +31 -1
- {omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/src/omnata_plugin_runtime/omnata_plugin.py +74 -1
- {omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/src/omnata_plugin_runtime/plugin_entrypoints.py +7 -31
- {omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/LICENSE +0 -0
- {omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/README.md +0 -0
- {omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/src/omnata_plugin_runtime/__init__.py +0 -0
- {omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/src/omnata_plugin_runtime/api.py +0 -0
- {omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/src/omnata_plugin_runtime/forms.py +0 -0
- {omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/src/omnata_plugin_runtime/logging.py +0 -0
- {omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/src/omnata_plugin_runtime/rate_limiting.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "omnata-plugin-runtime"
|
3
|
-
version = "0.5.
|
3
|
+
version = "0.5.7-a130"
|
4
4
|
description = "Classes and common runtime components for building and running Omnata Plugins"
|
5
5
|
authors = ["James Weakley <james.weakley@omnata.com>"]
|
6
6
|
readme = "README.md"
|
@@ -3,12 +3,14 @@ Omnata Plugin Runtime configuration objects.
|
|
3
3
|
Includes data container classes related to plugin configuration.
|
4
4
|
"""
|
5
5
|
from __future__ import annotations
|
6
|
+
import json
|
6
7
|
import sys
|
8
|
+
import logging
|
7
9
|
from typing import Any, List, Dict, Literal, Union, Optional
|
8
10
|
from enum import Enum
|
9
11
|
|
10
12
|
from abc import ABC
|
11
|
-
from pydantic import BaseModel, Field, PrivateAttr, SerializationInfo, field_validator, model_serializer, validator # pylint: disable=no-name-in-module
|
13
|
+
from pydantic import BaseModel, Field, PrivateAttr, SerializationInfo, TypeAdapter, field_validator, model_serializer, validator # pylint: disable=no-name-in-module
|
12
14
|
|
13
15
|
if tuple(sys.version_info[:2]) >= (3, 9):
|
14
16
|
# Python 3.9 and above
|
@@ -17,6 +19,8 @@ else:
|
|
17
19
|
# Python 3.8 and below
|
18
20
|
from typing_extensions import Annotated
|
19
21
|
|
22
|
+
logger = logging.getLogger(__name__)
|
23
|
+
|
20
24
|
|
21
25
|
class MapperType(str, Enum):
|
22
26
|
FIELD_MAPPING_SELECTOR = "field_mapping_selector"
|
@@ -860,3 +864,29 @@ StoredStreamConfiguration.model_rebuild()
|
|
860
864
|
InboundSyncStreamsConfiguration.model_rebuild()
|
861
865
|
StoredFieldMappings.model_rebuild()
|
862
866
|
OutboundSyncConfigurationParameters.model_rebuild()
|
867
|
+
|
868
|
+
|
869
|
+
def get_secrets(oauth_secret_name: Optional[str], other_secrets_name: Optional[str]
|
870
|
+
) -> Dict[str, StoredConfigurationValue]:
|
871
|
+
connection_secrets = {}
|
872
|
+
import _snowflake # pylint: disable=import-error, import-outside-toplevel # type: ignore
|
873
|
+
if oauth_secret_name is not None:
|
874
|
+
connection_secrets["access_token"] = StoredConfigurationValue(
|
875
|
+
value=_snowflake.get_oauth_access_token(oauth_secret_name)
|
876
|
+
)
|
877
|
+
if other_secrets_name is not None:
|
878
|
+
try:
|
879
|
+
secret_string_content = _snowflake.get_generic_secret_string(
|
880
|
+
other_secrets_name
|
881
|
+
)
|
882
|
+
if len(secret_string_content) > 2:
|
883
|
+
other_secrets = json.loads(secret_string_content)
|
884
|
+
connection_secrets = {
|
885
|
+
**connection_secrets,
|
886
|
+
**TypeAdapter(Dict[str, StoredConfigurationValue]).validate_python(other_secrets),
|
887
|
+
}
|
888
|
+
except Exception as exception:
|
889
|
+
logger.error(f"Error parsing secrets content for secret {other_secrets_name}: {str(exception)}")
|
890
|
+
raise ValueError(f"Error parsing secrets content: {str(exception)}") from exception
|
891
|
+
return connection_secrets
|
892
|
+
|
@@ -4,6 +4,7 @@ Omnata Plugin Runtime.
|
|
4
4
|
Includes data container classes and defines the contract for a plugin.
|
5
5
|
"""
|
6
6
|
from __future__ import annotations
|
7
|
+
from inspect import signature
|
7
8
|
import sys
|
8
9
|
from typing import Union
|
9
10
|
if tuple(sys.version_info[:2]) >= (3, 9):
|
@@ -31,7 +32,7 @@ from typing import Any, Callable, Dict, Iterable, List, Literal, Optional, Type,
|
|
31
32
|
import jinja2
|
32
33
|
import pandas
|
33
34
|
from pydantic_core import to_jsonable_python
|
34
|
-
from pydantic import Field, TypeAdapter, root_validator, BaseModel
|
35
|
+
from pydantic import Field, TypeAdapter, ValidationError, create_model, root_validator, BaseModel
|
35
36
|
from dateutil.parser import parse
|
36
37
|
from jinja2 import Environment
|
37
38
|
from snowflake.connector.pandas_tools import write_pandas
|
@@ -65,6 +66,7 @@ from .configuration import (
|
|
65
66
|
StreamConfiguration,
|
66
67
|
SubscriptableBaseModel,
|
67
68
|
SyncConfigurationParameters,
|
69
|
+
get_secrets
|
68
70
|
)
|
69
71
|
from .forms import (
|
70
72
|
ConnectionMethod,
|
@@ -2220,3 +2222,74 @@ def get_nested_value(nested_dict:Dict, keys:List[str]):
|
|
2220
2222
|
if isinstance(keys, str):
|
2221
2223
|
keys = [keys]
|
2222
2224
|
return reduce(lambda d, key: d.get(key) if isinstance(d, dict) else None, keys, nested_dict)
|
2225
|
+
|
2226
|
+
def consumer_udtf(param_docs: Dict[str, Dict[str, Any]]):
|
2227
|
+
def class_decorator(cls):
|
2228
|
+
# Get the original 'process' method from the class
|
2229
|
+
cls._is_omnata_consumer_udtf = True
|
2230
|
+
original_process = getattr(cls, 'process')
|
2231
|
+
sig = signature(original_process)
|
2232
|
+
params = sig.parameters
|
2233
|
+
|
2234
|
+
# Ensure the first argument is mandatory and positional
|
2235
|
+
if list(params.keys())[0] != 'self':
|
2236
|
+
raise ValueError("The first argument should be 'self' for class methods.")
|
2237
|
+
|
2238
|
+
if list(params.keys())[1] != 'connection_parameters':
|
2239
|
+
raise ValueError("The second argument should be 'connection_parameters'.")
|
2240
|
+
|
2241
|
+
# Create a Pydantic model based on the function signature and the provided documentation
|
2242
|
+
fields = {
|
2243
|
+
name: (param.annotation, param_docs.get(name, {}).get("default", param.default))
|
2244
|
+
for name, param in params.items()
|
2245
|
+
if name != 'self' and name != 'mandatory_arg'
|
2246
|
+
}
|
2247
|
+
|
2248
|
+
DynamicModel = create_model('DynamicModel', **fields)
|
2249
|
+
|
2250
|
+
# Attach the documentation to the function
|
2251
|
+
original_process.__doc__ = original_process.__doc__ or ""
|
2252
|
+
original_process.__doc__ += "\n\nArgs:\n"
|
2253
|
+
for name, param in params.items():
|
2254
|
+
if name != 'self':
|
2255
|
+
doc = param_docs.get(name, {})
|
2256
|
+
original_process.__doc__ += f" {name} ({param.annotation.__name__}): {doc.get('description', 'No description provided.')}\n"
|
2257
|
+
|
2258
|
+
@wraps(original_process)
|
2259
|
+
def wrapped_process(self, connection_parameters, *args, **kwargs):
|
2260
|
+
if connection_parameters is None:
|
2261
|
+
raise ValueError("Connection not found")
|
2262
|
+
|
2263
|
+
if not isinstance(connection_parameters, Dict):
|
2264
|
+
raise ValueError("The first argument must be an object, the result of calling PLUGIN_CONNECTION.")
|
2265
|
+
|
2266
|
+
try:
|
2267
|
+
# Validate the arguments using the dynamic Pydantic model
|
2268
|
+
validated_args = DynamicModel(**kwargs)
|
2269
|
+
except ValidationError as e:
|
2270
|
+
raise ValueError(f"Argument validation error: {e}")
|
2271
|
+
|
2272
|
+
# convert the connection parameters dictionary to a ConnectionConfigurationParameters object which includes the real secrets
|
2273
|
+
if 'other_secrets_name' in connection_parameters:
|
2274
|
+
# this is the new way, where the sync engine only passes the name of the secret
|
2275
|
+
oauth_secrets_name = None
|
2276
|
+
if 'oauth_secret_name' in connection_parameters:
|
2277
|
+
oauth_secrets_name = connection_parameters['oauth_secret_name']
|
2278
|
+
del connection_parameters['oauth_secret_name']
|
2279
|
+
result = get_secrets(oauth_secrets_name,connection_parameters['other_secrets_name'])
|
2280
|
+
connection_parameters['connection_secrets'] = result
|
2281
|
+
del connection_parameters['other_secrets_name']
|
2282
|
+
parameters = ConnectionConfigurationParameters.model_validate(connection_parameters)
|
2283
|
+
else:
|
2284
|
+
# deprecated way, where the sync engine passes the secrets directly
|
2285
|
+
parameters = ConnectionConfigurationParameters.model_validate(connection_parameters)
|
2286
|
+
|
2287
|
+
# Pass the validated arguments to the function
|
2288
|
+
return original_process(self, parameters, *args, **validated_args.dict())
|
2289
|
+
# Replace the original 'process' method with the wrapped version
|
2290
|
+
setattr(cls, 'process', wrapped_process)
|
2291
|
+
|
2292
|
+
return cls
|
2293
|
+
|
2294
|
+
return class_decorator
|
2295
|
+
|
@@ -20,6 +20,7 @@ from .configuration import (
|
|
20
20
|
OutboundSyncStrategy,
|
21
21
|
StoredConfigurationValue,
|
22
22
|
StoredMappingValue,
|
23
|
+
get_secrets
|
23
24
|
)
|
24
25
|
from .forms import ConnectionMethod, FormInputField, FormOption
|
25
26
|
from .logging import OmnataPluginLogHandler
|
@@ -71,7 +72,7 @@ class PluginEntrypoint:
|
|
71
72
|
def sync(self, sync_request: Dict):
|
72
73
|
logger.info("Entered sync method")
|
73
74
|
request = TypeAdapter(SyncRequestPayload).validate_python(sync_request)
|
74
|
-
connection_secrets =
|
75
|
+
connection_secrets = get_secrets(
|
75
76
|
request.oauth_secret_name, request.other_secrets_name
|
76
77
|
)
|
77
78
|
omnata_log_handler = OmnataPluginLogHandler(
|
@@ -270,7 +271,7 @@ class PluginEntrypoint:
|
|
270
271
|
sync_strategy = normalise_nulls(sync_strategy)
|
271
272
|
oauth_secret_name = normalise_nulls(oauth_secret_name)
|
272
273
|
other_secrets_name = normalise_nulls(other_secrets_name)
|
273
|
-
connection_secrets =
|
274
|
+
connection_secrets = get_secrets(oauth_secret_name, other_secrets_name)
|
274
275
|
connection_parameters = TypeAdapter(
|
275
276
|
Dict[str, StoredConfigurationValue]).validate_python(connection_parameters)
|
276
277
|
sync_parameters = TypeAdapter(
|
@@ -324,7 +325,7 @@ class PluginEntrypoint:
|
|
324
325
|
logger.info("Entered list_streams method")
|
325
326
|
oauth_secret_name = normalise_nulls(oauth_secret_name)
|
326
327
|
other_secrets_name = normalise_nulls(other_secrets_name)
|
327
|
-
connection_secrets =
|
328
|
+
connection_secrets = get_secrets(oauth_secret_name, other_secrets_name)
|
328
329
|
connection_parameters = TypeAdapter(
|
329
330
|
Dict[str, StoredConfigurationValue]).validate_python(connection_parameters)
|
330
331
|
sync_parameters = TypeAdapter(
|
@@ -411,31 +412,6 @@ class PluginEntrypoint:
|
|
411
412
|
raise e
|
412
413
|
return [e.model_dump() for e in events]
|
413
414
|
|
414
|
-
def get_secrets(
|
415
|
-
self, oauth_secret_name: Optional[str], other_secrets_name: Optional[str]
|
416
|
-
) -> Dict[str, StoredConfigurationValue]:
|
417
|
-
connection_secrets = {}
|
418
|
-
import _snowflake # pylint: disable=import-error, import-outside-toplevel # type: ignore
|
419
|
-
if oauth_secret_name is not None:
|
420
|
-
connection_secrets["access_token"] = StoredConfigurationValue(
|
421
|
-
value=_snowflake.get_oauth_access_token(oauth_secret_name)
|
422
|
-
)
|
423
|
-
if other_secrets_name is not None:
|
424
|
-
try:
|
425
|
-
secret_string_content = _snowflake.get_generic_secret_string(
|
426
|
-
other_secrets_name
|
427
|
-
)
|
428
|
-
if len(secret_string_content) > 2:
|
429
|
-
other_secrets = json.loads(secret_string_content)
|
430
|
-
connection_secrets = {
|
431
|
-
**connection_secrets,
|
432
|
-
**TypeAdapter(Dict[str, StoredConfigurationValue]).validate_python(other_secrets),
|
433
|
-
}
|
434
|
-
except Exception as exception:
|
435
|
-
logger.error(f"Error parsing secrets content for secret {other_secrets_name}: {str(exception)}")
|
436
|
-
raise ValueError(f"Error parsing secrets content: {str(exception)}") from exception
|
437
|
-
return connection_secrets
|
438
|
-
|
439
415
|
def ngrok_post_tunnel_fields(
|
440
416
|
self,
|
441
417
|
connection_method: str,
|
@@ -447,7 +423,7 @@ class PluginEntrypoint:
|
|
447
423
|
logger.info("Entered ngrok_post_tunnel_fields method")
|
448
424
|
oauth_secret_name = normalise_nulls(oauth_secret_name)
|
449
425
|
other_secrets_name = normalise_nulls(other_secrets_name)
|
450
|
-
connection_secrets =
|
426
|
+
connection_secrets = get_secrets(oauth_secret_name, other_secrets_name)
|
451
427
|
connection_parameters = TypeAdapter(
|
452
428
|
Dict[str, StoredConfigurationValue]).validate_python(connection_parameters)
|
453
429
|
parameters = ConnectionConfigurationParameters(
|
@@ -493,7 +469,7 @@ class PluginEntrypoint:
|
|
493
469
|
):
|
494
470
|
logger.info("Entered connect method")
|
495
471
|
logger.info(f"Connection parameters: {connection_parameters}")
|
496
|
-
connection_secrets =
|
472
|
+
connection_secrets = get_secrets(oauth_secret_name, other_secrets_name)
|
497
473
|
|
498
474
|
from omnata_plugin_runtime.omnata_plugin import (
|
499
475
|
ConnectionConfigurationParameters,
|
@@ -540,7 +516,7 @@ class PluginEntrypoint:
|
|
540
516
|
oauth_secret_name: Optional[str],
|
541
517
|
other_secrets_name: Optional[str]):
|
542
518
|
logger.info("Entered api_limits method")
|
543
|
-
connection_secrets =
|
519
|
+
connection_secrets = get_secrets(oauth_secret_name, other_secrets_name)
|
544
520
|
from omnata_plugin_runtime.omnata_plugin import (
|
545
521
|
ConnectionConfigurationParameters,
|
546
522
|
)
|
File without changes
|
File without changes
|
File without changes
|
{omnata_plugin_runtime-0.5.6a128 → omnata_plugin_runtime-0.5.7a130}/src/omnata_plugin_runtime/api.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|