omnata-plugin-runtime 0.8.3a204__tar.gz → 0.9.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/PKG-INFO +1 -1
- {omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/pyproject.toml +1 -1
- {omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/src/omnata_plugin_runtime/api.py +36 -0
- {omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/src/omnata_plugin_runtime/configuration.py +40 -0
- {omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/src/omnata_plugin_runtime/omnata_plugin.py +6 -34
- {omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/src/omnata_plugin_runtime/plugin_entrypoints.py +30 -52
- {omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/LICENSE +0 -0
- {omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/README.md +0 -0
- {omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/src/omnata_plugin_runtime/__init__.py +0 -0
- {omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/src/omnata_plugin_runtime/forms.py +0 -0
- {omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/src/omnata_plugin_runtime/logging.py +0 -0
- {omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/src/omnata_plugin_runtime/rate_limiting.py +0 -0
{omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/src/omnata_plugin_runtime/api.py
RENAMED
@@ -120,6 +120,7 @@ class OutboundSyncRequestPayload(BaseModel):
|
|
120
120
|
results_table_name: str # used to stage results back to the engine, resides in the main Omnata app database
|
121
121
|
logging_level: str
|
122
122
|
connection_method: str
|
123
|
+
target_type: Optional[str] = None
|
123
124
|
connectivity_option: ConnectivityOption = Field(default=ConnectivityOption.DIRECT)
|
124
125
|
connection_parameters: Dict[str, StoredConfigurationValue]
|
125
126
|
oauth_secret_name: Optional[str] = None
|
@@ -166,6 +167,41 @@ SyncRequestPayload = Annotated[
|
|
166
167
|
Field(discriminator="sync_direction"),
|
167
168
|
]
|
168
169
|
|
170
|
+
class OutboundConfigurationFormPayload(BaseModel):
|
171
|
+
"""
|
172
|
+
Encapsulates the payload that is sent to the plugin when it is invoked to provide a configuration form for an outbound sync.
|
173
|
+
"""
|
174
|
+
connectivity_option: ConnectivityOption = Field(default=ConnectivityOption.DIRECT)
|
175
|
+
connection_method: str
|
176
|
+
connection_parameters: Dict[str, StoredConfigurationValue]
|
177
|
+
oauth_secret_name: Optional[str] = None
|
178
|
+
other_secrets_name: Optional[str] = None
|
179
|
+
sync_direction: Literal["outbound"] = "outbound"
|
180
|
+
target_type: Optional[str] = None
|
181
|
+
sync_strategy: OutboundSyncStrategy
|
182
|
+
function_name: str = "outbound_configuration_form"
|
183
|
+
sync_parameters: Dict[str, StoredConfigurationValue]
|
184
|
+
current_form_parameters: Optional[Dict[str, StoredConfigurationValue]]
|
185
|
+
|
186
|
+
class InboundConfigurationFormPayload(BaseModel):
|
187
|
+
"""
|
188
|
+
Encapsulates the payload that is sent to the plugin when it is invoked to provide a configuration form for an inbound sync.
|
189
|
+
"""
|
190
|
+
connectivity_option: ConnectivityOption = Field(default=ConnectivityOption.DIRECT)
|
191
|
+
connection_method: str
|
192
|
+
connection_parameters: Dict[str, StoredConfigurationValue]
|
193
|
+
oauth_secret_name: Optional[str] = None
|
194
|
+
other_secrets_name: Optional[str] = None
|
195
|
+
sync_direction: Literal["inbound"] = "inbound"
|
196
|
+
function_name: str = "inbound_configuration_form"
|
197
|
+
sync_parameters: Dict[str, StoredConfigurationValue]
|
198
|
+
current_form_parameters: Optional[Dict[str, StoredConfigurationValue]]
|
199
|
+
|
200
|
+
ConfigurationFormPayload = Annotated[
|
201
|
+
Union[OutboundConfigurationFormPayload, InboundConfigurationFormPayload],
|
202
|
+
Field(discriminator="sync_direction"),
|
203
|
+
]
|
204
|
+
|
169
205
|
|
170
206
|
def handle_proc_result(query_result: List[Row]) -> Dict:
|
171
207
|
"""
|
@@ -220,6 +220,46 @@ STANDARD_OUTBOUND_SYNC_ACTIONS: Dict[str, OutboundSyncAction] = {
|
|
220
220
|
"Recreate": RecreateSyncAction,
|
221
221
|
}
|
222
222
|
|
223
|
+
class OutboundTargetParameter(BaseModel):
|
224
|
+
"""
|
225
|
+
Accomodates testing outbound syncs in production by nominating a form field who's value stays in the branch.
|
226
|
+
The reason this information is set statically here instead of as a flag on the FormField, is so that the sync engine
|
227
|
+
can have this information readily available without calling the plugin.
|
228
|
+
"""
|
229
|
+
field_name: str = Field(title="""The name of the form field that toggles the location, e.g. 'channel','customer_list'.
|
230
|
+
This must match a field which will be returned by the outbound_configuration_form for this target type.""")
|
231
|
+
is_branching_toggle: bool = Field(title="""Whether or not this field is a target toggle for branching.
|
232
|
+
If true, the value of this field will be used to determine the location of the sync in production.
|
233
|
+
For example, a messaging plugin could have a "channel" field to route messages to an alternate location.
|
234
|
+
Or, a marketing platform could have an alternate customer list name which is connected to test campaigns that don't actually send.
|
235
|
+
|
236
|
+
This should only be used in situations where all other sync parameters and field mappings can remain consistent between branches.""")
|
237
|
+
label: str = Field(title="""Used in the UI when describing the location., e.g. 'Channel','Customer List'.
|
238
|
+
It should completely describe the behaviour when used in a sentence like this:
|
239
|
+
'Changes will be tested against a different <label> when running in a branch.'""")
|
240
|
+
|
241
|
+
class OutboundTargetType(BaseModel):
|
242
|
+
"""
|
243
|
+
Some products have APIs that can be grouped together in ways that support different strategies and may or may not support toggling.
|
244
|
+
The label should answer the question: "What would you like to sync to?"
|
245
|
+
Examples:
|
246
|
+
- A CRM system may have "Standard objects", "Custom objects" or "Events"
|
247
|
+
- A messaging platform may have "Channels", "Users" or "Messages"
|
248
|
+
- A marketing platform may have "Customer lists", "Campaigns" or "Automations"
|
249
|
+
- An Ad platform may have "Campaigns", "Ad groups" or "Ads"
|
250
|
+
The target type cannot be changed after the sync is created.
|
251
|
+
"""
|
252
|
+
label: str
|
253
|
+
supported_strategies: List[str] = Field(
|
254
|
+
title="The names of the sync strategies supported by this target. Each one must match the name of a sync strategy declared in supported_outbound_strategies."
|
255
|
+
)
|
256
|
+
target_parameter: Optional[OutboundTargetParameter] = Field(
|
257
|
+
default=None,
|
258
|
+
title="""The sync configuration parameter that designates the target object, if applicable. For example, 'object_name' or 'channel_name'.
|
259
|
+
This will be used for two purposes:
|
260
|
+
1. To show a more readable indication of what this sync is doing in the UI, e.g. Standard object: Account
|
261
|
+
2. Designates this field as serving as a br toggle for testing in production.""")
|
262
|
+
|
223
263
|
|
224
264
|
class OutboundSyncStrategy(SubscriptableBaseModel, ABC):
|
225
265
|
"""OutboundSyncStrategy is a base class for all outbound sync strategies.
|
@@ -74,7 +74,8 @@ from .configuration import (
|
|
74
74
|
SubscriptableBaseModel,
|
75
75
|
SyncConfigurationParameters,
|
76
76
|
get_secrets,
|
77
|
-
ConnectivityOption
|
77
|
+
ConnectivityOption,
|
78
|
+
OutboundTargetType,
|
78
79
|
)
|
79
80
|
from .forms import (
|
80
81
|
ConnectionMethod,
|
@@ -112,45 +113,16 @@ class PluginManifest(SubscriptableBaseModel):
|
|
112
113
|
docs_url: str
|
113
114
|
supports_inbound: bool
|
114
115
|
supported_outbound_strategies: List[OutboundSyncStrategy] = Field(
|
115
|
-
|
116
|
-
deprecated=True,
|
117
|
-
title="A list of sync strategies that the plugin can support. This has been replaced by OutboundTargetTypes."
|
116
|
+
title="A list of sync strategies that the plugin can support. If outbound_target_types are specified, theses strategies are allocated there. Otherwise, they apply globally"
|
118
117
|
)
|
119
118
|
supported_connectivity_options: List[ConnectivityOption] = Field(
|
120
119
|
default_factory=lambda: [ConnectivityOption.DIRECT]
|
121
120
|
)
|
122
|
-
outbound_target_types: List[OutboundTargetType] = Field(
|
123
|
-
|
124
|
-
title="
|
121
|
+
outbound_target_types: Optional[List[OutboundTargetType]] = Field(
|
122
|
+
default=None,
|
123
|
+
title="An optional list of target types that the plugin can support."
|
125
124
|
)
|
126
125
|
|
127
|
-
class OutboundTargetType(BaseModel):
|
128
|
-
"""
|
129
|
-
Some products have APIs that can be grouped together in ways that support different strategies and may or may not support toggling.
|
130
|
-
The label should answer the question: "What would you like to sync to?"
|
131
|
-
Examples:
|
132
|
-
- A CRM system may have "Standard objects", "Custom objects" or "Events"
|
133
|
-
- A messaging platform may have "Channels", "Users" or "Messages"
|
134
|
-
- A marketing platform may have "Customer lists", "Campaigns" or "Automations"
|
135
|
-
- An Ad platform may have "Campaigns", "Ad groups" or "Ads"
|
136
|
-
The target type cannot be changed after the sync is created.
|
137
|
-
"""
|
138
|
-
label: str
|
139
|
-
supported_strategies: List[OutboundSyncStrategy]
|
140
|
-
target_toggle_field: Optional[OutboundTargetToggleField] = None
|
141
|
-
|
142
|
-
class OutboundTargetToggleField(BaseModel):
|
143
|
-
"""
|
144
|
-
Accomodates testing outbound syncs in production by nominating a form field who's value stays in the branch.
|
145
|
-
For example, a messaging plugin could have a "channel" field to route messages to an alternate location.
|
146
|
-
Or, a marketing platform could have an alternate customer list name which is connected to test campaigns that don't actually send.
|
147
|
-
The field_name must match a field which will be returned by the outbound_configuration_form for this target type.
|
148
|
-
The reason this is set separately here instead of as a flag on the FormField, is so that the sync engine can know
|
149
|
-
whether or not location toggling is supported for a target type without calling the plugin.
|
150
|
-
"""
|
151
|
-
field_name: str = Field(..., title="The name of the form field that toggles the location")
|
152
|
-
label: str = Field(..., title="Used in the UI when describing the location. It should completely describe the behaviour when used in a sentence like this: 'Changes will be tested against a different <label> when running in a branch.'")
|
153
|
-
|
154
126
|
class SnowflakeFunctionParameter(BaseModel):
|
155
127
|
"""
|
156
128
|
Represents a parameter for a Snowflake UDF or UDTF
|
@@ -6,13 +6,13 @@ import os
|
|
6
6
|
import sys
|
7
7
|
import time
|
8
8
|
import threading
|
9
|
-
from typing import Dict, List, Optional
|
9
|
+
from typing import Dict, List, Optional, cast
|
10
10
|
|
11
11
|
from pydantic import BaseModel,TypeAdapter # pylint: disable=no-name-in-module
|
12
12
|
from pydantic_core import to_jsonable_python
|
13
13
|
from snowflake.snowpark import Session
|
14
14
|
|
15
|
-
from .api import PluginMessageStreamProgressUpdate, SyncRequestPayload,
|
15
|
+
from .api import PluginMessageStreamProgressUpdate, SyncRequestPayload, ConfigurationFormPayload
|
16
16
|
from .configuration import (
|
17
17
|
ConnectionConfigurationParameters,
|
18
18
|
InboundSyncConfigurationParameters,
|
@@ -83,7 +83,7 @@ class PluginEntrypoint:
|
|
83
83
|
|
84
84
|
|
85
85
|
def sync(self, sync_request: Dict):
|
86
|
-
request = TypeAdapter(SyncRequestPayload).validate_python(sync_request)
|
86
|
+
request:SyncRequestPayload = TypeAdapter(SyncRequestPayload).validate_python(sync_request)
|
87
87
|
logger.add_extra('omnata.operation', 'sync')
|
88
88
|
logger.add_extra('omnata.sync.id', request.sync_id)
|
89
89
|
logger.add_extra('omnata.sync.direction', request.sync_direction)
|
@@ -160,6 +160,7 @@ class PluginEntrypoint:
|
|
160
160
|
connection_secrets=connection_secrets,
|
161
161
|
sync_parameters=request.sync_parameters,
|
162
162
|
current_form_parameters={},
|
163
|
+
target_type=request.target_type,
|
163
164
|
sync_strategy=request.sync_strategy,
|
164
165
|
field_mappings=request.field_mappings,
|
165
166
|
)
|
@@ -289,67 +290,43 @@ class PluginEntrypoint:
|
|
289
290
|
logger.info("Finished applying records")
|
290
291
|
return return_dict
|
291
292
|
|
292
|
-
def configuration_form(
|
293
|
-
|
294
|
-
connectivity_option:str,
|
295
|
-
connection_method: str,
|
296
|
-
connection_parameters: Dict,
|
297
|
-
oauth_secret_name: Optional[str],
|
298
|
-
other_secrets_name: Optional[str],
|
299
|
-
sync_direction: str,
|
300
|
-
sync_strategy: Dict,
|
301
|
-
function_name: str,
|
302
|
-
sync_parameters: Dict,
|
303
|
-
current_form_parameters: Optional[Dict],
|
304
|
-
):
|
305
|
-
if function_name is None:
|
306
|
-
function_name = f"{sync_direction}_configuration_form"
|
293
|
+
def configuration_form(self, configuration_form_request: Dict):
|
294
|
+
request:ConfigurationFormPayload = TypeAdapter(ConfigurationFormPayload).validate_python(configuration_form_request)
|
307
295
|
logger.add_extra('omnata.operation', 'configuration_form')
|
308
|
-
logger.add_extra('omnata.connection.connectivity_option', connectivity_option)
|
309
|
-
logger.add_extra('omnata.connection.connection_method', connection_method)
|
310
|
-
logger.add_extra('omnata.configuration_form.function_name', function_name)
|
311
|
-
logger.add_extra('omnata.sync.direction', sync_direction)
|
296
|
+
logger.add_extra('omnata.connection.connectivity_option', request.connectivity_option)
|
297
|
+
logger.add_extra('omnata.connection.connection_method', request.connection_method)
|
298
|
+
logger.add_extra('omnata.configuration_form.function_name', request.function_name)
|
299
|
+
logger.add_extra('omnata.sync.direction', request.sync_direction)
|
312
300
|
|
313
301
|
logger.info("Entered configuration_form method")
|
314
|
-
|
315
|
-
|
316
|
-
other_secrets_name = normalise_nulls(other_secrets_name)
|
317
|
-
connection_secrets = get_secrets(oauth_secret_name, other_secrets_name)
|
318
|
-
connectivity_option = TypeAdapter(ConnectivityOption).validate_python(connectivity_option)
|
319
|
-
connection_parameters = TypeAdapter(
|
320
|
-
Dict[str, StoredConfigurationValue]).validate_python(connection_parameters)
|
321
|
-
sync_parameters = TypeAdapter(
|
322
|
-
Dict[str, StoredConfigurationValue]).validate_python(sync_parameters)
|
323
|
-
form_parameters = None
|
324
|
-
if current_form_parameters is not None:
|
325
|
-
form_parameters = TypeAdapter(Dict[str, StoredConfigurationValue]).validate_python(current_form_parameters)
|
326
|
-
if sync_direction == "outbound":
|
327
|
-
sync_strat = OutboundSyncStrategy.model_validate(sync_strategy) if sync_strategy is not None else None
|
302
|
+
connection_secrets = get_secrets(request.oauth_secret_name, request.other_secrets_name)
|
303
|
+
if request.sync_direction == "outbound":
|
328
304
|
parameters = OutboundSyncConfigurationParameters(
|
329
|
-
connection_parameters=connection_parameters,
|
305
|
+
connection_parameters=request.connection_parameters,
|
330
306
|
connection_secrets=connection_secrets,
|
331
|
-
sync_strategy=
|
332
|
-
sync_parameters=sync_parameters,
|
333
|
-
connection_method=connection_method,
|
334
|
-
connectivity_option=connectivity_option,
|
335
|
-
current_form_parameters=
|
307
|
+
sync_strategy=request.sync_strategy,
|
308
|
+
sync_parameters=request.sync_parameters,
|
309
|
+
connection_method=request.connection_method,
|
310
|
+
connectivity_option=request.connectivity_option,
|
311
|
+
current_form_parameters=request.current_form_parameters,
|
312
|
+
target_type=request.target_type
|
336
313
|
)
|
337
|
-
elif sync_direction == "inbound":
|
314
|
+
elif request.sync_direction == "inbound":
|
338
315
|
parameters = InboundSyncConfigurationParameters(
|
339
|
-
connection_parameters=connection_parameters,
|
316
|
+
connection_parameters=request.connection_parameters,
|
340
317
|
connection_secrets=connection_secrets,
|
341
|
-
sync_parameters=sync_parameters,
|
342
|
-
connection_method=connection_method,
|
343
|
-
connectivity_option=connectivity_option,
|
344
|
-
current_form_parameters=
|
318
|
+
sync_parameters=request.sync_parameters,
|
319
|
+
connection_method=request.connection_method,
|
320
|
+
connectivity_option=request.connectivity_option,
|
321
|
+
current_form_parameters=request.current_form_parameters,
|
345
322
|
)
|
346
323
|
else:
|
347
|
-
raise ValueError(f"Unknown direction {sync_direction}")
|
348
|
-
if oauth_secret_name is not None:
|
349
|
-
parameters.access_token_secret_name = oauth_secret_name
|
324
|
+
raise ValueError(f"Unknown direction {request.sync_direction}")
|
325
|
+
if request.oauth_secret_name is not None:
|
326
|
+
parameters.access_token_secret_name = request.oauth_secret_name
|
350
327
|
the_function = getattr(
|
351
328
|
self._plugin_instance,
|
352
|
-
function_name
|
329
|
+
request.function_name
|
353
330
|
)
|
354
331
|
with tracer.start_as_current_span("invoke_plugin"):
|
355
332
|
script_result = the_function(parameters)
|
@@ -357,6 +334,7 @@ class PluginEntrypoint:
|
|
357
334
|
script_result = script_result.model_dump()
|
358
335
|
elif isinstance(script_result, List):
|
359
336
|
if len(script_result) > 0 and isinstance(script_result[0], BaseModel):
|
337
|
+
script_result = cast(List[BaseModel], script_result)
|
360
338
|
script_result = [r.model_dump() for r in script_result]
|
361
339
|
return script_result
|
362
340
|
|
File without changes
|
File without changes
|
File without changes
|
{omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/src/omnata_plugin_runtime/forms.py
RENAMED
File without changes
|
{omnata_plugin_runtime-0.8.3a204 → omnata_plugin_runtime-0.9.0}/src/omnata_plugin_runtime/logging.py
RENAMED
File without changes
|
File without changes
|