ergon-framework-python 0.2.1__py3-none-any.whl → 0.2.3__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.
- ergon/__init__.py +1 -1
- ergon/connector/__init__.py +20 -0
- ergon/connector/ergon_platform/__init__.py +78 -11
- ergon/connector/ergon_platform/async_connector.py +3 -221
- ergon/connector/ergon_platform/channels/__init__.py +54 -0
- ergon/connector/ergon_platform/channels/adapters.py +230 -0
- ergon/connector/ergon_platform/channels/async_connector.py +327 -0
- ergon/connector/ergon_platform/channels/connector.py +294 -0
- ergon/connector/ergon_platform/channels/models.py +460 -0
- ergon/connector/ergon_platform/channels/services/__init__.py +39 -0
- ergon/connector/ergon_platform/channels/services/activity.py +300 -0
- ergon/connector/ergon_platform/channels/services/addresses.py +325 -0
- ergon/connector/ergon_platform/channels/services/attachments.py +283 -0
- ergon/connector/ergon_platform/channels/services/messages.py +127 -0
- ergon/connector/ergon_platform/channels/services/records.py +75 -0
- ergon/connector/ergon_platform/channels/services/service.py +34 -0
- ergon/connector/ergon_platform/connector.py +3 -198
- ergon/connector/ergon_platform/models.py +1 -73
- ergon/connector/ergon_platform/utils.py +39 -248
- ergon/connector/ergon_platform/workflows/__init__.py +52 -0
- ergon/connector/ergon_platform/{_operations.py → workflows/_operations.py} +8 -3
- ergon/connector/ergon_platform/workflows/async_connector.py +224 -0
- ergon/connector/ergon_platform/workflows/connector.py +201 -0
- ergon/connector/ergon_platform/workflows/models.py +90 -0
- ergon/connector/ergon_platform/workflows/utils.py +250 -0
- ergon/connector/rabbitmq/async_service.py +61 -18
- ergon/task/mixins/consumer.py +1 -3
- {ergon_framework_python-0.2.1.dist-info → ergon_framework_python-0.2.3.dist-info}/METADATA +3 -3
- {ergon_framework_python-0.2.1.dist-info → ergon_framework_python-0.2.3.dist-info}/RECORD +33 -16
- {ergon_framework_python-0.2.1.dist-info → ergon_framework_python-0.2.3.dist-info}/WHEEL +0 -0
- {ergon_framework_python-0.2.1.dist-info → ergon_framework_python-0.2.3.dist-info}/entry_points.txt +0 -0
- {ergon_framework_python-0.2.1.dist-info → ergon_framework_python-0.2.3.dist-info}/licenses/LICENSE +0 -0
- {ergon_framework_python-0.2.1.dist-info → ergon_framework_python-0.2.3.dist-info}/top_level.txt +0 -0
ergon/__init__.py
CHANGED
ergon/connector/__init__.py
CHANGED
|
@@ -7,13 +7,23 @@ from .transaction import Transaction
|
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from .ergon_platform import (
|
|
10
|
+
AsyncErgonPlatformChannelsConnector,
|
|
10
11
|
AsyncErgonPlatformConnector,
|
|
11
12
|
CreateItemInput,
|
|
13
|
+
ErgonPlatformChannelsConfig,
|
|
14
|
+
ErgonPlatformChannelsConnector,
|
|
15
|
+
ErgonPlatformChannelsConsumerConfig,
|
|
16
|
+
ErgonPlatformChannelsProducerConfig,
|
|
12
17
|
ErgonPlatformClient,
|
|
13
18
|
ErgonPlatformConnector,
|
|
14
19
|
ErgonPlatformConsumerConfig,
|
|
15
20
|
ErgonPlatformProducerConfig,
|
|
16
21
|
)
|
|
22
|
+
|
|
23
|
+
# NOTE: ``SendMessageInput`` is defined by both Nylas and Channels with
|
|
24
|
+
# different shapes. The top-level alias here points to the Nylas type
|
|
25
|
+
# (matches the pre-split lazy-export map); channels users must import it
|
|
26
|
+
# from ``ergon.connector.ergon_platform.channels``.
|
|
17
27
|
from .excel import ExcelConnector, ExcelFetchConfig, ExcelRow, ExcelService
|
|
18
28
|
from .nylas import (
|
|
19
29
|
AckActionConfig,
|
|
@@ -76,6 +86,7 @@ _CONNECTOR_SUBPACKAGES = (
|
|
|
76
86
|
|
|
77
87
|
_LAZY_EXPORTS = {
|
|
78
88
|
"AckActionConfig": "nylas",
|
|
89
|
+
"AsyncErgonPlatformChannelsConnector": "ergon_platform",
|
|
79
90
|
"AsyncErgonPlatformConnector": "ergon_platform",
|
|
80
91
|
"AsyncNylasAuthService": "nylas",
|
|
81
92
|
"AsyncNylasConnector": "nylas",
|
|
@@ -94,6 +105,10 @@ _LAZY_EXPORTS = {
|
|
|
94
105
|
"AuthUrlConfig": "nylas",
|
|
95
106
|
"CodeExchangeInput": "nylas",
|
|
96
107
|
"CreateItemInput": "ergon_platform",
|
|
108
|
+
"ErgonPlatformChannelsConfig": "ergon_platform",
|
|
109
|
+
"ErgonPlatformChannelsConnector": "ergon_platform",
|
|
110
|
+
"ErgonPlatformChannelsConsumerConfig": "ergon_platform",
|
|
111
|
+
"ErgonPlatformChannelsProducerConfig": "ergon_platform",
|
|
97
112
|
"ErgonPlatformClient": "ergon_platform",
|
|
98
113
|
"ErgonPlatformConnector": "ergon_platform",
|
|
99
114
|
"ErgonPlatformConsumerConfig": "ergon_platform",
|
|
@@ -148,6 +163,7 @@ def __dir__() -> list[str]:
|
|
|
148
163
|
__all__ = [
|
|
149
164
|
"AckActionConfig",
|
|
150
165
|
"AsyncConnector",
|
|
166
|
+
"AsyncErgonPlatformChannelsConnector",
|
|
151
167
|
"AsyncErgonPlatformConnector",
|
|
152
168
|
"AsyncNylasAuthService",
|
|
153
169
|
"AsyncNylasConnector",
|
|
@@ -169,6 +185,10 @@ __all__ = [
|
|
|
169
185
|
"Connector",
|
|
170
186
|
"ConnectorConfig",
|
|
171
187
|
"CreateItemInput",
|
|
188
|
+
"ErgonPlatformChannelsConfig",
|
|
189
|
+
"ErgonPlatformChannelsConnector",
|
|
190
|
+
"ErgonPlatformChannelsConsumerConfig",
|
|
191
|
+
"ErgonPlatformChannelsProducerConfig",
|
|
172
192
|
"ErgonPlatformClient",
|
|
173
193
|
"ErgonPlatformConnector",
|
|
174
194
|
"ErgonPlatformConsumerConfig",
|
|
@@ -1,24 +1,81 @@
|
|
|
1
|
+
"""Ergon Platform connectors — Workflows and Channels.
|
|
2
|
+
|
|
3
|
+
The Ergon Platform SDK exposes several services (workflows, channels, agents,
|
|
4
|
+
buckets, ...). This package groups every framework connector against that
|
|
5
|
+
platform. Each service lives in its own sub-package (``workflows``,
|
|
6
|
+
``channels``, ...) and reuses the shared credentials model
|
|
7
|
+
(:class:`ErgonPlatformClient`) so a single API key drives every connector.
|
|
8
|
+
|
|
9
|
+
Public workflow names are re-exported here for backward compatibility with the
|
|
10
|
+
pre-split layout::
|
|
11
|
+
|
|
12
|
+
from ergon.connector.ergon_platform import ErgonPlatformConnector # workflows
|
|
13
|
+
|
|
14
|
+
New code is encouraged to import directly from the sub-package::
|
|
15
|
+
|
|
16
|
+
from ergon.connector.ergon_platform.workflows import ErgonPlatformConnector
|
|
17
|
+
from ergon.connector.ergon_platform.channels import ErgonPlatformChannelsConnector
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from importlib import import_module
|
|
1
21
|
from typing import TYPE_CHECKING, Any
|
|
2
22
|
|
|
3
23
|
from .._lazy import exported_names, load_optional_export
|
|
4
|
-
from .models import
|
|
5
|
-
CreateItemInput,
|
|
6
|
-
ErgonPlatformClient,
|
|
7
|
-
ErgonPlatformConsumerConfig,
|
|
8
|
-
ErgonPlatformProducerConfig,
|
|
9
|
-
)
|
|
24
|
+
from .models import ErgonPlatformClient
|
|
10
25
|
|
|
11
26
|
if TYPE_CHECKING:
|
|
12
|
-
from .
|
|
13
|
-
|
|
27
|
+
from .channels import (
|
|
28
|
+
AsyncErgonPlatformChannelsConnector,
|
|
29
|
+
ErgonPlatformChannelsConfig,
|
|
30
|
+
ErgonPlatformChannelsConnector,
|
|
31
|
+
ErgonPlatformChannelsConsumerConfig,
|
|
32
|
+
ErgonPlatformChannelsProducerConfig,
|
|
33
|
+
ErgonPlatformChannelsService,
|
|
34
|
+
SendMessageInput,
|
|
35
|
+
SendMessagePayload,
|
|
36
|
+
)
|
|
37
|
+
from .workflows import (
|
|
38
|
+
AsyncErgonPlatformConnector,
|
|
39
|
+
CreateItemInput,
|
|
40
|
+
CreateItemPayload,
|
|
41
|
+
ErgonPlatformConnector,
|
|
42
|
+
ErgonPlatformConsumerConfig,
|
|
43
|
+
ErgonPlatformProducerConfig,
|
|
44
|
+
ErgonPlatformWorkflowsService,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
_SUBPACKAGES = ("channels", "workflows")
|
|
14
48
|
|
|
49
|
+
# Backward-compat aliases: names that used to live under
|
|
50
|
+
# ``ergon.connector.ergon_platform`` before the workflows/ split, plus the new
|
|
51
|
+
# channels API surface. Every entry is loaded lazily from its sub-package to
|
|
52
|
+
# keep the platform SDK out of the core import path.
|
|
15
53
|
_LAZY_EXPORTS = {
|
|
16
|
-
|
|
17
|
-
"
|
|
54
|
+
# Workflows (pre-split names — kept for backward compatibility)
|
|
55
|
+
"AsyncErgonPlatformConnector": "workflows",
|
|
56
|
+
"CreateItemInput": "workflows",
|
|
57
|
+
"CreateItemPayload": "workflows",
|
|
58
|
+
"ErgonPlatformConnector": "workflows",
|
|
59
|
+
"ErgonPlatformConsumerConfig": "workflows",
|
|
60
|
+
"ErgonPlatformProducerConfig": "workflows",
|
|
61
|
+
"ErgonPlatformWorkflowsService": "workflows",
|
|
62
|
+
# Channels
|
|
63
|
+
"AsyncErgonPlatformChannelsConnector": "channels",
|
|
64
|
+
"ErgonPlatformChannelsConfig": "channels",
|
|
65
|
+
"ErgonPlatformChannelsConnector": "channels",
|
|
66
|
+
"ErgonPlatformChannelsConsumerConfig": "channels",
|
|
67
|
+
"ErgonPlatformChannelsProducerConfig": "channels",
|
|
68
|
+
"ErgonPlatformChannelsService": "channels",
|
|
69
|
+
"SendMessageInput": "channels",
|
|
70
|
+
"SendMessagePayload": "channels",
|
|
18
71
|
}
|
|
19
72
|
|
|
20
73
|
|
|
21
74
|
def __getattr__(name: str) -> Any:
|
|
75
|
+
if name in _SUBPACKAGES:
|
|
76
|
+
module = import_module(f".{name}", __name__)
|
|
77
|
+
globals()[name] = module
|
|
78
|
+
return module
|
|
22
79
|
return load_optional_export(
|
|
23
80
|
name=name,
|
|
24
81
|
package=__name__,
|
|
@@ -30,14 +87,24 @@ def __getattr__(name: str) -> Any:
|
|
|
30
87
|
|
|
31
88
|
|
|
32
89
|
def __dir__() -> list[str]:
|
|
33
|
-
return exported_names(globals(), _LAZY_EXPORTS)
|
|
90
|
+
return sorted(set(exported_names(globals(), _LAZY_EXPORTS)) | set(_SUBPACKAGES))
|
|
34
91
|
|
|
35
92
|
|
|
36
93
|
__all__ = [
|
|
94
|
+
"AsyncErgonPlatformChannelsConnector",
|
|
37
95
|
"AsyncErgonPlatformConnector",
|
|
38
96
|
"CreateItemInput",
|
|
97
|
+
"CreateItemPayload",
|
|
98
|
+
"ErgonPlatformChannelsConfig",
|
|
99
|
+
"ErgonPlatformChannelsConnector",
|
|
100
|
+
"ErgonPlatformChannelsConsumerConfig",
|
|
101
|
+
"ErgonPlatformChannelsProducerConfig",
|
|
102
|
+
"ErgonPlatformChannelsService",
|
|
39
103
|
"ErgonPlatformClient",
|
|
40
104
|
"ErgonPlatformConnector",
|
|
41
105
|
"ErgonPlatformConsumerConfig",
|
|
42
106
|
"ErgonPlatformProducerConfig",
|
|
107
|
+
"ErgonPlatformWorkflowsService",
|
|
108
|
+
"SendMessageInput",
|
|
109
|
+
"SendMessagePayload",
|
|
43
110
|
]
|
|
@@ -1,223 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import logging
|
|
3
|
-
from typing import Any, Dict, List, Optional
|
|
1
|
+
"""Deprecated compatibility import for the async Workflows connector."""
|
|
4
2
|
|
|
5
|
-
from
|
|
6
|
-
from ..transaction import Transaction
|
|
7
|
-
from ._client import create_ergon_client
|
|
8
|
-
from ._operations import _ErgonPlatformOperations
|
|
9
|
-
from .models import (
|
|
10
|
-
CreateItemPayload,
|
|
11
|
-
ErgonPlatformClient,
|
|
12
|
-
ErgonPlatformConsumerConfig,
|
|
13
|
-
ErgonPlatformProducerConfig,
|
|
14
|
-
)
|
|
15
|
-
from .utils import get_value, normalize_create_payload
|
|
3
|
+
from .workflows.async_connector import AsyncErgonPlatformConnector
|
|
16
4
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class AsyncErgonPlatformConnector(AsyncConnector):
|
|
21
|
-
def __init__(
|
|
22
|
-
self,
|
|
23
|
-
client: ErgonPlatformClient,
|
|
24
|
-
consumer_config: Optional[ErgonPlatformConsumerConfig] = None,
|
|
25
|
-
producer_config: Optional[ErgonPlatformProducerConfig] = None,
|
|
26
|
-
) -> None:
|
|
27
|
-
self.client = create_ergon_client(client)
|
|
28
|
-
self._operations = _ErgonPlatformOperations(client, self.client)
|
|
29
|
-
self._consumer_config = consumer_config
|
|
30
|
-
self._producer_config = producer_config or ErgonPlatformProducerConfig()
|
|
31
|
-
|
|
32
|
-
async def fetch_transactions_async(
|
|
33
|
-
self,
|
|
34
|
-
batch_size: Optional[int] = None,
|
|
35
|
-
*args,
|
|
36
|
-
**kwargs,
|
|
37
|
-
) -> List[Transaction]:
|
|
38
|
-
if self._consumer_config is None:
|
|
39
|
-
raise ValueError("AsyncErgonPlatformConnector requires a consumer_config to fetch transactions")
|
|
40
|
-
|
|
41
|
-
config = self._consumer_config
|
|
42
|
-
limit = batch_size or config.batch_size
|
|
43
|
-
params: Dict[str, Any] = {**config.list_params, **kwargs}
|
|
44
|
-
if config.unassigned:
|
|
45
|
-
params.pop("assigned_to", None)
|
|
46
|
-
params["assigned"] = "no"
|
|
47
|
-
|
|
48
|
-
transactions = await asyncio.to_thread(
|
|
49
|
-
lambda: self._operations.fetch_items(
|
|
50
|
-
config.workflow_id,
|
|
51
|
-
config.phase_id,
|
|
52
|
-
limit=limit,
|
|
53
|
-
offset=config.offset,
|
|
54
|
-
**params,
|
|
55
|
-
)
|
|
56
|
-
)
|
|
57
|
-
if not config.unassigned:
|
|
58
|
-
return transactions
|
|
59
|
-
|
|
60
|
-
claimed_transactions: List[Transaction] = []
|
|
61
|
-
for transaction in transactions:
|
|
62
|
-
try:
|
|
63
|
-
await asyncio.to_thread(lambda: self.client.workflows.items.claim(transaction.id, {}))
|
|
64
|
-
claimed_transactions.append(await self.fetch_transaction_by_id_async(transaction.id))
|
|
65
|
-
except Exception:
|
|
66
|
-
logger.warning(
|
|
67
|
-
"Failed to claim item %s during unassigned fetch; skipping it",
|
|
68
|
-
transaction.id,
|
|
69
|
-
exc_info=True,
|
|
70
|
-
)
|
|
71
|
-
return claimed_transactions
|
|
72
|
-
|
|
73
|
-
async def dispatch_transactions_async(
|
|
74
|
-
self,
|
|
75
|
-
transactions: List[Transaction],
|
|
76
|
-
*args,
|
|
77
|
-
**kwargs,
|
|
78
|
-
) -> List[str]:
|
|
79
|
-
created_ids: List[str] = []
|
|
80
|
-
for transaction in transactions:
|
|
81
|
-
result = await self._create_from_payload(transaction.payload)
|
|
82
|
-
created_ids.append(self._created_item_id(result))
|
|
83
|
-
return created_ids
|
|
84
|
-
|
|
85
|
-
async def fetch_child_transactions_async(
|
|
86
|
-
self,
|
|
87
|
-
parent_item_id: str,
|
|
88
|
-
*args,
|
|
89
|
-
**kwargs,
|
|
90
|
-
) -> List[Transaction]:
|
|
91
|
-
return await asyncio.to_thread(lambda: self._operations.fetch_child_items(parent_item_id, **kwargs))
|
|
92
|
-
|
|
93
|
-
async def fetch_transaction_by_id_async(self, transaction_id: str, *args, **kwargs) -> Transaction:
|
|
94
|
-
workflow_id = self._consumer_config.workflow_id if self._consumer_config else ""
|
|
95
|
-
return await asyncio.to_thread(
|
|
96
|
-
lambda: self._operations.get_item_transaction(transaction_id, workflow_id, **kwargs)
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
async def fetch_items_by_query(
|
|
100
|
-
self, workflow_id: str, query: Optional[Dict[str, Any]] = None, **fields: Any
|
|
101
|
-
) -> List[Transaction]:
|
|
102
|
-
return await asyncio.to_thread(lambda: self._operations.fetch_items_by_query(workflow_id, query, **fields))
|
|
103
|
-
|
|
104
|
-
async def get_transactions_count_async(self, *args, **kwargs) -> int:
|
|
105
|
-
if self._consumer_config is None:
|
|
106
|
-
raise ValueError("AsyncErgonPlatformConnector requires a consumer_config to count transactions")
|
|
107
|
-
|
|
108
|
-
config = self._consumer_config
|
|
109
|
-
params: Dict[str, Any] = {**config.list_params, **kwargs}
|
|
110
|
-
return await asyncio.to_thread(
|
|
111
|
-
lambda: self._operations.get_phase_items_count(
|
|
112
|
-
config.workflow_id,
|
|
113
|
-
config.phase_id,
|
|
114
|
-
**params,
|
|
115
|
-
)
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
async def ack_transaction(self, transaction: Transaction, phase_id: Optional[str] = None) -> None:
|
|
119
|
-
target_phase = phase_id
|
|
120
|
-
if target_phase is None and self._consumer_config is not None:
|
|
121
|
-
target_phase = self._consumer_config.ack_phase_id
|
|
122
|
-
if not target_phase:
|
|
123
|
-
return
|
|
124
|
-
await asyncio.to_thread(lambda: self.client.workflows.items.route(transaction.id, to_phase_id=target_phase))
|
|
125
|
-
|
|
126
|
-
async def release_item(
|
|
127
|
-
self,
|
|
128
|
-
item_id: str,
|
|
129
|
-
data: Optional[Dict[str, Any]] = None,
|
|
130
|
-
*,
|
|
131
|
-
delay_seconds: Optional[int] = None,
|
|
132
|
-
**fields: Any,
|
|
133
|
-
) -> Any:
|
|
134
|
-
return await asyncio.to_thread(
|
|
135
|
-
lambda: self._operations.release_item(
|
|
136
|
-
item_id,
|
|
137
|
-
data,
|
|
138
|
-
delay_seconds=delay_seconds,
|
|
139
|
-
**fields,
|
|
140
|
-
)
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
async def nack_transaction(self, transaction: Transaction, requeue: bool = True, delay_seconds: int = 10) -> None:
|
|
144
|
-
if requeue:
|
|
145
|
-
await asyncio.to_thread(lambda: self._operations.release_item(transaction.id, delay_seconds=delay_seconds))
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
target_phase = self._consumer_config.nack_phase_id if self._consumer_config is not None else None
|
|
149
|
-
if not target_phase:
|
|
150
|
-
raise ValueError("nack_phase_id is required when nack_transaction is called with requeue=False")
|
|
151
|
-
|
|
152
|
-
await asyncio.to_thread(lambda: self.client.workflows.items.route(transaction.id, to_phase_id=target_phase))
|
|
153
|
-
|
|
154
|
-
async def close(self) -> None:
|
|
155
|
-
await asyncio.to_thread(self.client.close)
|
|
156
|
-
|
|
157
|
-
async def list_phase_fields(
|
|
158
|
-
self,
|
|
159
|
-
phase_id: str,
|
|
160
|
-
*,
|
|
161
|
-
workflow_id: Optional[str] = None,
|
|
162
|
-
include_workflow_fields: bool = True,
|
|
163
|
-
**params: Any,
|
|
164
|
-
) -> Any:
|
|
165
|
-
return await asyncio.to_thread(
|
|
166
|
-
lambda: self._operations.list_phase_fields(
|
|
167
|
-
phase_id,
|
|
168
|
-
workflow_id=workflow_id,
|
|
169
|
-
include_workflow_fields=include_workflow_fields,
|
|
170
|
-
**params,
|
|
171
|
-
)
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
async def get_pipeline_result(
|
|
175
|
-
self,
|
|
176
|
-
workflow_id: str,
|
|
177
|
-
item_id: str,
|
|
178
|
-
field_id: str,
|
|
179
|
-
buckets_file_id: Optional[str] = None,
|
|
180
|
-
) -> Dict[str, Any]:
|
|
181
|
-
return await asyncio.to_thread(
|
|
182
|
-
self._operations.get_pipeline_result,
|
|
183
|
-
workflow_id,
|
|
184
|
-
item_id,
|
|
185
|
-
field_id,
|
|
186
|
-
buckets_file_id,
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
async def _create_from_payload(self, payload: CreateItemPayload) -> Any:
|
|
190
|
-
data = normalize_create_payload(payload)
|
|
191
|
-
producer = self._producer_config
|
|
192
|
-
|
|
193
|
-
workflow_id = data.get("workflow_id") or producer.workflow_id
|
|
194
|
-
phase_id = data.get("phase_id") or producer.phase_id
|
|
195
|
-
if not workflow_id:
|
|
196
|
-
raise ValueError("workflow_id is required to create an item (set it in payload or producer_config)")
|
|
197
|
-
if not phase_id:
|
|
198
|
-
raise ValueError("phase_id is required to create an item (set it in payload or producer_config)")
|
|
199
|
-
|
|
200
|
-
attachment = data.get("attachment")
|
|
201
|
-
attachment_field_id = data.get("attachment_field_id") or producer.attachment_field_id
|
|
202
|
-
content_type = data.get("content_type") or producer.default_content_type
|
|
203
|
-
parent_item_id = data.get("parent_item_id") or producer.parent_item_id
|
|
204
|
-
|
|
205
|
-
return await asyncio.to_thread(
|
|
206
|
-
lambda: self._operations.create_item(
|
|
207
|
-
workflow_id,
|
|
208
|
-
phase_id,
|
|
209
|
-
data["title"],
|
|
210
|
-
parent_item_id=parent_item_id,
|
|
211
|
-
field_values=data.get("field_values"),
|
|
212
|
-
attachment=attachment,
|
|
213
|
-
attachment_field_id=attachment_field_id,
|
|
214
|
-
content_type=content_type,
|
|
215
|
-
**data.get("fields", {}),
|
|
216
|
-
)
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
@staticmethod
|
|
220
|
-
def _created_item_id(result: Any) -> str:
|
|
221
|
-
if isinstance(result, dict) and "item" in result:
|
|
222
|
-
return str(get_value(result["item"], "id", ""))
|
|
223
|
-
return str(get_value(result, "id", ""))
|
|
5
|
+
__all__ = ["AsyncErgonPlatformConnector"]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any
|
|
2
|
+
|
|
3
|
+
from ..._lazy import exported_names, load_optional_export
|
|
4
|
+
from .models import (
|
|
5
|
+
ChannelsActivityFilter,
|
|
6
|
+
ErgonPlatformChannelsConfig,
|
|
7
|
+
ErgonPlatformChannelsConsumerConfig,
|
|
8
|
+
ErgonPlatformChannelsProducerConfig,
|
|
9
|
+
InboxAttachmentFile,
|
|
10
|
+
SendMessageAttachment,
|
|
11
|
+
SendMessageInput,
|
|
12
|
+
SendMessagePayload,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from .async_connector import AsyncErgonPlatformChannelsConnector
|
|
17
|
+
from .connector import ErgonPlatformChannelsConnector
|
|
18
|
+
from .services import ErgonPlatformChannelsService
|
|
19
|
+
|
|
20
|
+
_LAZY_EXPORTS = {
|
|
21
|
+
"AsyncErgonPlatformChannelsConnector": "async_connector",
|
|
22
|
+
"ErgonPlatformChannelsConnector": "connector",
|
|
23
|
+
"ErgonPlatformChannelsService": "services",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def __getattr__(name: str) -> Any:
|
|
28
|
+
return load_optional_export(
|
|
29
|
+
name=name,
|
|
30
|
+
package=__name__,
|
|
31
|
+
exports=_LAZY_EXPORTS,
|
|
32
|
+
namespace=globals(),
|
|
33
|
+
extra="ergon-platform",
|
|
34
|
+
dependencies=("ergon_platform", "httpx"),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def __dir__() -> list[str]:
|
|
39
|
+
return exported_names(globals(), _LAZY_EXPORTS)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"AsyncErgonPlatformChannelsConnector",
|
|
44
|
+
"ChannelsActivityFilter",
|
|
45
|
+
"ErgonPlatformChannelsConnector",
|
|
46
|
+
"ErgonPlatformChannelsConfig",
|
|
47
|
+
"ErgonPlatformChannelsConsumerConfig",
|
|
48
|
+
"ErgonPlatformChannelsProducerConfig",
|
|
49
|
+
"ErgonPlatformChannelsService",
|
|
50
|
+
"InboxAttachmentFile",
|
|
51
|
+
"SendMessageAttachment",
|
|
52
|
+
"SendMessageInput",
|
|
53
|
+
"SendMessagePayload",
|
|
54
|
+
]
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
from ...transaction import Transaction
|
|
4
|
+
from .models import ErgonPlatformChannelsConsumerConfig
|
|
5
|
+
from .services.records import SdkRecord
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ActivityAdapter:
|
|
9
|
+
"""Map Channels activity events into the connector Transaction shape."""
|
|
10
|
+
|
|
11
|
+
_META_KEYS = (
|
|
12
|
+
"event_type",
|
|
13
|
+
"channel",
|
|
14
|
+
"direction",
|
|
15
|
+
"status",
|
|
16
|
+
"thread_id",
|
|
17
|
+
"correlation_id",
|
|
18
|
+
"provider_message_id",
|
|
19
|
+
"subject",
|
|
20
|
+
"from_address",
|
|
21
|
+
"to_addresses",
|
|
22
|
+
"address_id",
|
|
23
|
+
"consumption_status",
|
|
24
|
+
"acked_at",
|
|
25
|
+
"available_at",
|
|
26
|
+
"consumer_id",
|
|
27
|
+
"attempt_count",
|
|
28
|
+
)
|
|
29
|
+
_DELIVERY_KEYS = (
|
|
30
|
+
"subscription_id",
|
|
31
|
+
"lease_token",
|
|
32
|
+
"lease_expires_at",
|
|
33
|
+
"consumer_id",
|
|
34
|
+
"attempt_count",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def to_transaction(cls, event: Any, *, source: str) -> Transaction:
|
|
39
|
+
"""To transaction."""
|
|
40
|
+
payload = SdkRecord.serialize(event)
|
|
41
|
+
payload = payload if isinstance(payload, dict) else {"value": payload}
|
|
42
|
+
metadata: Dict[str, Any] = {"source": source}
|
|
43
|
+
for key in cls._META_KEYS:
|
|
44
|
+
value = payload.get(key)
|
|
45
|
+
if value is not None:
|
|
46
|
+
metadata[key] = value
|
|
47
|
+
nested = payload.get("payload")
|
|
48
|
+
if isinstance(nested, dict):
|
|
49
|
+
metadata["message_payload"] = nested
|
|
50
|
+
attachments = cls.public_attachments(nested.get("attachments"))
|
|
51
|
+
if attachments:
|
|
52
|
+
metadata["attachments"] = attachments
|
|
53
|
+
metadata["has_attachment"] = True
|
|
54
|
+
return Transaction(
|
|
55
|
+
id=SdkRecord.first_id(
|
|
56
|
+
payload,
|
|
57
|
+
"id",
|
|
58
|
+
"log_id",
|
|
59
|
+
"message_id",
|
|
60
|
+
"provider_message_id",
|
|
61
|
+
),
|
|
62
|
+
payload=payload,
|
|
63
|
+
metadata=metadata,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def claimed_transaction(cls, item: Any) -> Transaction:
|
|
68
|
+
"""Claimed transaction."""
|
|
69
|
+
claimed = SdkRecord.serialize(item)
|
|
70
|
+
claimed = claimed if isinstance(claimed, dict) else {}
|
|
71
|
+
transaction = cls.to_transaction(
|
|
72
|
+
claimed.get("event"),
|
|
73
|
+
source="config_activity_claim",
|
|
74
|
+
)
|
|
75
|
+
metadata = dict(transaction.metadata or {})
|
|
76
|
+
delivery = claimed.get("delivery")
|
|
77
|
+
if isinstance(delivery, dict):
|
|
78
|
+
metadata["delivery"] = {key: delivery[key] for key in cls._DELIVERY_KEYS if delivery.get(key) is not None}
|
|
79
|
+
return transaction.model_copy(update={"metadata": metadata})
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def delivery(transaction: Transaction) -> Dict[str, Any]:
|
|
83
|
+
"""Delivery."""
|
|
84
|
+
metadata = transaction.metadata or {}
|
|
85
|
+
delivery = metadata.get("delivery")
|
|
86
|
+
if not isinstance(delivery, dict):
|
|
87
|
+
raise ValueError("Transaction was not claimed and cannot be acknowledged or nacked")
|
|
88
|
+
if not delivery.get("subscription_id") or not delivery.get("lease_token"):
|
|
89
|
+
raise ValueError("Transaction claim is missing subscription_id or lease_token")
|
|
90
|
+
return delivery
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def belongs_to_address(
|
|
94
|
+
transaction: Transaction,
|
|
95
|
+
address_id: str,
|
|
96
|
+
) -> bool:
|
|
97
|
+
"""Belongs to address."""
|
|
98
|
+
metadata = transaction.metadata or {}
|
|
99
|
+
nested = metadata.get("message_payload")
|
|
100
|
+
if isinstance(nested, dict):
|
|
101
|
+
candidate = nested.get("address_id") or nested.get("channel_address_id")
|
|
102
|
+
if candidate is not None:
|
|
103
|
+
return str(candidate) == address_id
|
|
104
|
+
event = transaction.payload if isinstance(transaction.payload, dict) else {}
|
|
105
|
+
candidate = event.get("address_id") or event.get("channel_address_id")
|
|
106
|
+
if candidate is not None:
|
|
107
|
+
return str(candidate) == address_id
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def attachment_id(attachment: Any) -> Optional[str]:
|
|
112
|
+
"""Attachment id."""
|
|
113
|
+
if not isinstance(attachment, dict):
|
|
114
|
+
return None
|
|
115
|
+
for key in ("id", "resend_attachment_id", "attachment_id"):
|
|
116
|
+
value = attachment.get(key)
|
|
117
|
+
if value:
|
|
118
|
+
return str(value)
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def public_attachment(
|
|
123
|
+
cls,
|
|
124
|
+
item: Any,
|
|
125
|
+
) -> Optional[Dict[str, Any]]:
|
|
126
|
+
"""Public attachment."""
|
|
127
|
+
if not isinstance(item, dict):
|
|
128
|
+
return None
|
|
129
|
+
filename = str(item.get("filename") or item.get("name") or "").strip()
|
|
130
|
+
if not filename:
|
|
131
|
+
return None
|
|
132
|
+
attachment: Dict[str, Any] = {"filename": filename}
|
|
133
|
+
attachment_id = cls.attachment_id(item)
|
|
134
|
+
if attachment_id:
|
|
135
|
+
attachment["id"] = attachment_id
|
|
136
|
+
content_type = item.get("content_type")
|
|
137
|
+
if content_type:
|
|
138
|
+
attachment["content_type"] = content_type
|
|
139
|
+
size = item.get("size")
|
|
140
|
+
if size is not None:
|
|
141
|
+
attachment["size"] = size
|
|
142
|
+
content = item.get("content")
|
|
143
|
+
if isinstance(content, bytes):
|
|
144
|
+
attachment["content"] = content
|
|
145
|
+
return attachment
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def public_attachments(cls, items: Any) -> List[Dict[str, Any]]:
|
|
149
|
+
"""Public attachments."""
|
|
150
|
+
if not isinstance(items, list):
|
|
151
|
+
return []
|
|
152
|
+
attachments: List[Dict[str, Any]] = []
|
|
153
|
+
for item in items:
|
|
154
|
+
attachment = cls.public_attachment(item)
|
|
155
|
+
if attachment is not None:
|
|
156
|
+
attachments.append(attachment)
|
|
157
|
+
return attachments
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def attachments(
|
|
161
|
+
cls,
|
|
162
|
+
transaction: Transaction,
|
|
163
|
+
) -> List[Dict[str, Any]]:
|
|
164
|
+
"""Attachments."""
|
|
165
|
+
metadata = transaction.metadata or {}
|
|
166
|
+
items = metadata.get("attachments")
|
|
167
|
+
if isinstance(items, list):
|
|
168
|
+
return [item for item in items if isinstance(item, dict)]
|
|
169
|
+
nested = metadata.get("message_payload")
|
|
170
|
+
if isinstance(nested, dict):
|
|
171
|
+
return cls.public_attachments(nested.get("attachments"))
|
|
172
|
+
payload = transaction.payload
|
|
173
|
+
if isinstance(payload, dict):
|
|
174
|
+
nested = payload.get("payload")
|
|
175
|
+
if isinstance(nested, dict):
|
|
176
|
+
return cls.public_attachments(nested.get("attachments"))
|
|
177
|
+
return []
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def with_attachments(
|
|
181
|
+
transaction: Transaction,
|
|
182
|
+
attachments: List[Dict[str, Any]],
|
|
183
|
+
) -> Transaction:
|
|
184
|
+
"""With the attachments."""
|
|
185
|
+
metadata = dict(transaction.metadata or {})
|
|
186
|
+
metadata["attachments"] = attachments
|
|
187
|
+
metadata["has_attachment"] = bool(attachments)
|
|
188
|
+
nested = metadata.get("message_payload")
|
|
189
|
+
if isinstance(nested, dict):
|
|
190
|
+
metadata["message_payload"] = {
|
|
191
|
+
**nested,
|
|
192
|
+
"attachments": attachments,
|
|
193
|
+
}
|
|
194
|
+
payload = transaction.payload
|
|
195
|
+
if isinstance(payload, dict):
|
|
196
|
+
payload = dict(payload)
|
|
197
|
+
inner = payload.get("payload")
|
|
198
|
+
if isinstance(inner, dict):
|
|
199
|
+
payload["payload"] = {
|
|
200
|
+
**inner,
|
|
201
|
+
"attachments": attachments,
|
|
202
|
+
}
|
|
203
|
+
return transaction.model_copy(update={"payload": payload, "metadata": metadata})
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def unseen(
|
|
207
|
+
transactions: List[Transaction],
|
|
208
|
+
seen_ids: set[str],
|
|
209
|
+
) -> List[Transaction]:
|
|
210
|
+
fresh: List[Transaction] = []
|
|
211
|
+
"""Unseen transactions."""
|
|
212
|
+
for transaction in transactions:
|
|
213
|
+
if not transaction.id or transaction.id in seen_ids:
|
|
214
|
+
continue
|
|
215
|
+
seen_ids.add(transaction.id)
|
|
216
|
+
fresh.append(transaction)
|
|
217
|
+
return fresh
|
|
218
|
+
|
|
219
|
+
@classmethod
|
|
220
|
+
def finalize_fetch(
|
|
221
|
+
cls,
|
|
222
|
+
transactions: List[Transaction],
|
|
223
|
+
config: ErgonPlatformChannelsConsumerConfig,
|
|
224
|
+
seen_ids: Optional[set[str]] = None,
|
|
225
|
+
) -> List[Transaction]:
|
|
226
|
+
"""Finalize the fetched transactions."""
|
|
227
|
+
transactions = config.effective_activity_filter().select(transactions)
|
|
228
|
+
if seen_ids is not None:
|
|
229
|
+
transactions = cls.unseen(transactions, seen_ids)
|
|
230
|
+
return transactions
|