ergon-framework-python 0.2.0__py3-none-any.whl → 0.2.2__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/nylas/async_connector.py +6 -0
- ergon/connector/nylas/async_service.py +3 -0
- ergon/connector/nylas/connector.py +6 -0
- ergon/connector/nylas/models.py +8 -0
- ergon/connector/nylas/service.py +9 -0
- ergon/connector/nylas/utils.py +2 -4
- ergon/task/mixins/consumer.py +0 -2
- {ergon_framework_python-0.2.0.dist-info → ergon_framework_python-0.2.2.dist-info}/METADATA +3 -3
- {ergon_framework_python-0.2.0.dist-info → ergon_framework_python-0.2.2.dist-info}/RECORD +38 -21
- {ergon_framework_python-0.2.0.dist-info → ergon_framework_python-0.2.2.dist-info}/WHEEL +0 -0
- {ergon_framework_python-0.2.0.dist-info → ergon_framework_python-0.2.2.dist-info}/entry_points.txt +0 -0
- {ergon_framework_python-0.2.0.dist-info → ergon_framework_python-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {ergon_framework_python-0.2.0.dist-info → ergon_framework_python-0.2.2.dist-info}/top_level.txt +0 -0
|
@@ -1,200 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
from typing import Any, Dict, List, Optional
|
|
1
|
+
"""Deprecated compatibility import for the Workflows connector."""
|
|
3
2
|
|
|
4
|
-
from
|
|
5
|
-
from ..transaction import Transaction
|
|
6
|
-
from ._client import create_ergon_client
|
|
7
|
-
from ._operations import _ErgonPlatformOperations
|
|
8
|
-
from .models import (
|
|
9
|
-
CreateItemPayload,
|
|
10
|
-
ErgonPlatformClient,
|
|
11
|
-
ErgonPlatformConsumerConfig,
|
|
12
|
-
ErgonPlatformProducerConfig,
|
|
13
|
-
)
|
|
14
|
-
from .utils import get_value, normalize_create_payload
|
|
3
|
+
from .workflows.connector import ErgonPlatformConnector
|
|
15
4
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ErgonPlatformConnector(Connector):
|
|
20
|
-
def __init__(
|
|
21
|
-
self,
|
|
22
|
-
client: ErgonPlatformClient,
|
|
23
|
-
consumer_config: Optional[ErgonPlatformConsumerConfig] = None,
|
|
24
|
-
producer_config: Optional[ErgonPlatformProducerConfig] = None,
|
|
25
|
-
) -> None:
|
|
26
|
-
self.client = create_ergon_client(client)
|
|
27
|
-
self._operations = _ErgonPlatformOperations(client, self.client)
|
|
28
|
-
self._consumer_config = consumer_config
|
|
29
|
-
self._producer_config = producer_config or ErgonPlatformProducerConfig()
|
|
30
|
-
|
|
31
|
-
def fetch_transactions(
|
|
32
|
-
self,
|
|
33
|
-
batch_size: Optional[int] = None,
|
|
34
|
-
*args,
|
|
35
|
-
**kwargs,
|
|
36
|
-
) -> List[Transaction]:
|
|
37
|
-
if self._consumer_config is None:
|
|
38
|
-
raise ValueError("ErgonPlatformConnector requires a consumer_config to fetch transactions")
|
|
39
|
-
|
|
40
|
-
config = self._consumer_config
|
|
41
|
-
limit = batch_size or config.batch_size
|
|
42
|
-
params: Dict[str, Any] = {**config.list_params, **kwargs}
|
|
43
|
-
if config.unassigned:
|
|
44
|
-
params.pop("assigned_to", None)
|
|
45
|
-
params["assigned"] = "no"
|
|
46
|
-
|
|
47
|
-
transactions = self._operations.fetch_items(
|
|
48
|
-
config.workflow_id,
|
|
49
|
-
config.phase_id,
|
|
50
|
-
limit=limit,
|
|
51
|
-
offset=config.offset,
|
|
52
|
-
**params,
|
|
53
|
-
)
|
|
54
|
-
if not config.unassigned:
|
|
55
|
-
return transactions
|
|
56
|
-
|
|
57
|
-
claimed_transactions: List[Transaction] = []
|
|
58
|
-
for transaction in transactions:
|
|
59
|
-
try:
|
|
60
|
-
self.client.workflows.items.claim(transaction.id, {})
|
|
61
|
-
claimed_transactions.append(self.fetch_transaction_by_id(transaction.id))
|
|
62
|
-
except Exception:
|
|
63
|
-
logger.warning(
|
|
64
|
-
"Failed to claim item %s during unassigned fetch; skipping it",
|
|
65
|
-
transaction.id,
|
|
66
|
-
exc_info=True,
|
|
67
|
-
)
|
|
68
|
-
return claimed_transactions
|
|
69
|
-
|
|
70
|
-
def dispatch_transactions(self, transactions: List[Transaction], *args, **kwargs) -> List[str]:
|
|
71
|
-
created_ids: List[str] = []
|
|
72
|
-
for transaction in transactions:
|
|
73
|
-
result = self._create_from_payload(transaction.payload)
|
|
74
|
-
created_ids.append(self._created_item_id(result))
|
|
75
|
-
return created_ids
|
|
76
|
-
|
|
77
|
-
def fetch_child_transactions(
|
|
78
|
-
self,
|
|
79
|
-
parent_item_id: str,
|
|
80
|
-
*args,
|
|
81
|
-
**kwargs,
|
|
82
|
-
) -> List[Transaction]:
|
|
83
|
-
return self._operations.fetch_child_items(parent_item_id, **kwargs)
|
|
84
|
-
|
|
85
|
-
def fetch_transaction_by_id(self, transaction_id: str, *args, **kwargs) -> Transaction:
|
|
86
|
-
workflow_id = self._consumer_config.workflow_id if self._consumer_config else ""
|
|
87
|
-
return self._operations.get_item_transaction(transaction_id, workflow_id, **kwargs)
|
|
88
|
-
|
|
89
|
-
def fetch_items_by_query(
|
|
90
|
-
self, workflow_id: str, query: Optional[Dict[str, Any]] = None, **fields: Any
|
|
91
|
-
) -> List[Transaction]:
|
|
92
|
-
return self._operations.fetch_items_by_query(workflow_id, query, **fields)
|
|
93
|
-
|
|
94
|
-
def get_transactions_count(self, *args, **kwargs) -> int:
|
|
95
|
-
if self._consumer_config is None:
|
|
96
|
-
raise ValueError("ErgonPlatformConnector requires a consumer_config to count transactions")
|
|
97
|
-
|
|
98
|
-
config = self._consumer_config
|
|
99
|
-
params: Dict[str, Any] = {**config.list_params, **kwargs}
|
|
100
|
-
return self._operations.get_phase_items_count(
|
|
101
|
-
config.workflow_id,
|
|
102
|
-
config.phase_id,
|
|
103
|
-
**params,
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
def ack_transaction(self, transaction: Transaction, phase_id: Optional[str] = None) -> None:
|
|
107
|
-
target_phase = phase_id
|
|
108
|
-
if target_phase is None and self._consumer_config is not None:
|
|
109
|
-
target_phase = self._consumer_config.ack_phase_id
|
|
110
|
-
if not target_phase:
|
|
111
|
-
return
|
|
112
|
-
self.client.workflows.items.route(transaction.id, to_phase_id=target_phase)
|
|
113
|
-
|
|
114
|
-
def release_item(
|
|
115
|
-
self,
|
|
116
|
-
item_id: str,
|
|
117
|
-
data: Optional[Dict[str, Any]] = None,
|
|
118
|
-
*,
|
|
119
|
-
delay_seconds: Optional[int] = None,
|
|
120
|
-
**fields: Any,
|
|
121
|
-
) -> Any:
|
|
122
|
-
return self._operations.release_item(
|
|
123
|
-
item_id,
|
|
124
|
-
data,
|
|
125
|
-
delay_seconds=delay_seconds,
|
|
126
|
-
**fields,
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
def nack_transaction(self, transaction: Transaction, requeue: bool = True, delay_seconds: int = 10) -> None:
|
|
130
|
-
if requeue:
|
|
131
|
-
self._operations.release_item(transaction.id, delay_seconds=delay_seconds)
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
target_phase = self._consumer_config.nack_phase_id if self._consumer_config is not None else None
|
|
135
|
-
if not target_phase:
|
|
136
|
-
raise ValueError("nack_phase_id is required when nack_transaction is called with requeue=False")
|
|
137
|
-
|
|
138
|
-
self.client.workflows.items.route(transaction.id, to_phase_id=target_phase)
|
|
139
|
-
logger.debug("Item %s moved to nack phase %s", transaction.id, target_phase)
|
|
140
|
-
|
|
141
|
-
def close(self) -> None:
|
|
142
|
-
self.client.close()
|
|
143
|
-
|
|
144
|
-
def list_phase_fields(
|
|
145
|
-
self,
|
|
146
|
-
phase_id: str,
|
|
147
|
-
*,
|
|
148
|
-
workflow_id: Optional[str] = None,
|
|
149
|
-
include_workflow_fields: bool = True,
|
|
150
|
-
**params: Any,
|
|
151
|
-
) -> Any:
|
|
152
|
-
return self._operations.list_phase_fields(
|
|
153
|
-
phase_id,
|
|
154
|
-
workflow_id=workflow_id,
|
|
155
|
-
include_workflow_fields=include_workflow_fields,
|
|
156
|
-
**params,
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
def get_pipeline_result(
|
|
160
|
-
self,
|
|
161
|
-
workflow_id: str,
|
|
162
|
-
item_id: str,
|
|
163
|
-
field_id: str,
|
|
164
|
-
buckets_file_id: Optional[str] = None,
|
|
165
|
-
) -> Dict[str, Any]:
|
|
166
|
-
return self._operations.get_pipeline_result(workflow_id, item_id, field_id, buckets_file_id)
|
|
167
|
-
|
|
168
|
-
def _create_from_payload(self, payload: CreateItemPayload) -> Any:
|
|
169
|
-
data = normalize_create_payload(payload)
|
|
170
|
-
producer = self._producer_config
|
|
171
|
-
|
|
172
|
-
workflow_id = data.get("workflow_id") or producer.workflow_id
|
|
173
|
-
phase_id = data.get("phase_id") or producer.phase_id
|
|
174
|
-
if not workflow_id:
|
|
175
|
-
raise ValueError("workflow_id is required to create an item (set it in payload or producer_config)")
|
|
176
|
-
if not phase_id:
|
|
177
|
-
raise ValueError("phase_id is required to create an item (set it in payload or producer_config)")
|
|
178
|
-
|
|
179
|
-
attachment = data.get("attachment")
|
|
180
|
-
attachment_field_id = data.get("attachment_field_id") or producer.attachment_field_id
|
|
181
|
-
content_type = data.get("content_type") or producer.default_content_type
|
|
182
|
-
parent_item_id = data.get("parent_item_id") or producer.parent_item_id
|
|
183
|
-
|
|
184
|
-
return self._operations.create_item(
|
|
185
|
-
workflow_id,
|
|
186
|
-
phase_id,
|
|
187
|
-
data["title"],
|
|
188
|
-
parent_item_id=parent_item_id,
|
|
189
|
-
field_values=data.get("field_values"),
|
|
190
|
-
attachment=attachment,
|
|
191
|
-
attachment_field_id=attachment_field_id,
|
|
192
|
-
content_type=content_type,
|
|
193
|
-
**data.get("fields", {}),
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
@staticmethod
|
|
197
|
-
def _created_item_id(result: Any) -> str:
|
|
198
|
-
if isinstance(result, dict) and "item" in result:
|
|
199
|
-
return str(get_value(result["item"], "id", ""))
|
|
200
|
-
return str(get_value(result, "id", ""))
|
|
5
|
+
__all__ = ["ErgonPlatformConnector"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Optional
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
@@ -33,75 +33,3 @@ class ErgonPlatformClient(BaseModel):
|
|
|
33
33
|
)
|
|
34
34
|
timeout: float = Field(default=30.0, description="HTTP timeout in seconds")
|
|
35
35
|
max_retries: int = Field(default=2, ge=0, description="Max automatic HTTP retries")
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class ErgonPlatformConsumerConfig(BaseModel):
|
|
39
|
-
"""Parameters used to list workflow items as transactions."""
|
|
40
|
-
|
|
41
|
-
workflow_id: str = Field(description="Workflow ID to consume items from")
|
|
42
|
-
phase_id: str = Field(description="Phase ID to list items from")
|
|
43
|
-
batch_size: int = Field(default=50, ge=1, description="Max items per fetch")
|
|
44
|
-
offset: int = Field(default=0, ge=0, description="Pagination offset")
|
|
45
|
-
ack_phase_id: Optional[str] = Field(
|
|
46
|
-
default=None,
|
|
47
|
-
description="Phase to route an item to on ack; ack is a no-op when omitted",
|
|
48
|
-
)
|
|
49
|
-
nack_phase_id: Optional[str] = Field(
|
|
50
|
-
default=None,
|
|
51
|
-
description="Phase to route an item to on nack when requeue=False",
|
|
52
|
-
)
|
|
53
|
-
unassigned: bool = Field(
|
|
54
|
-
default=False,
|
|
55
|
-
description="When true, fetch only unassigned cards and claim them during fetch",
|
|
56
|
-
)
|
|
57
|
-
list_params: Dict[str, Any] = Field(
|
|
58
|
-
default_factory=dict,
|
|
59
|
-
description="Extra query params forwarded to the items listing endpoint",
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class ErgonPlatformProducerConfig(BaseModel):
|
|
64
|
-
"""Defaults applied when creating items via dispatch."""
|
|
65
|
-
|
|
66
|
-
workflow_id: Optional[str] = Field(default=None, description="Default workflow for created items")
|
|
67
|
-
phase_id: Optional[str] = Field(default=None, description="Default phase for created items")
|
|
68
|
-
attachment_field_id: Optional[str] = Field(
|
|
69
|
-
default=None,
|
|
70
|
-
description="Default field ID used when an attachment is provided",
|
|
71
|
-
)
|
|
72
|
-
default_content_type: Optional[str] = Field(
|
|
73
|
-
default=None,
|
|
74
|
-
description="Fallback MIME type when it cannot be guessed from the filename",
|
|
75
|
-
)
|
|
76
|
-
parent_item_id: Optional[str] = Field(
|
|
77
|
-
default=None,
|
|
78
|
-
description="Default parent item ID used to create child items via dispatch",
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class CreateItemInput(BaseModel):
|
|
83
|
-
"""Payload describing an item to create, with optional attachment upload."""
|
|
84
|
-
|
|
85
|
-
title: str = Field(description="Item title")
|
|
86
|
-
workflow_id: Optional[str] = Field(default=None, description="Workflow ID; falls back to producer config")
|
|
87
|
-
phase_id: Optional[str] = Field(default=None, description="Target phase ID; falls back to producer config")
|
|
88
|
-
parent_item_id: Optional[str] = Field(
|
|
89
|
-
default=None,
|
|
90
|
-
description="Optional parent item ID to create this item as a child",
|
|
91
|
-
)
|
|
92
|
-
field_values: Optional[Dict[str, Any]] = Field(default=None, description="Field values keyed by field ID")
|
|
93
|
-
attachment: Optional[str] = Field(default=None, description="Local file path to upload after creation")
|
|
94
|
-
attachment_field_id: Optional[str] = Field(
|
|
95
|
-
default=None,
|
|
96
|
-
description="Field ID that receives the attachment; falls back to producer config",
|
|
97
|
-
)
|
|
98
|
-
content_type: Optional[str] = Field(default=None, description="Override MIME type for the attachment")
|
|
99
|
-
extra_fields: Dict[str, Any] = Field(
|
|
100
|
-
default_factory=dict,
|
|
101
|
-
description="Additional keyword fields forwarded to create_item",
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
model_config = {"populate_by_name": True}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
CreateItemPayload = Union[CreateItemInput, Dict[str, Any]]
|
|
@@ -1,250 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
"""Deprecated compatibility exports for Workflows connector helpers."""
|
|
2
|
+
|
|
3
|
+
from .workflows.utils import (
|
|
4
|
+
as_payload,
|
|
5
|
+
build_redispatch_transaction,
|
|
6
|
+
classify_status,
|
|
7
|
+
decode_jwt_claims,
|
|
8
|
+
extract_buckets_file_id,
|
|
9
|
+
extract_buckets_file_id_from_attachments,
|
|
10
|
+
extract_items,
|
|
11
|
+
extract_status_entries,
|
|
12
|
+
extract_status_file_id,
|
|
13
|
+
extract_total,
|
|
14
|
+
find_status_entry,
|
|
15
|
+
get_value,
|
|
16
|
+
item_to_transaction,
|
|
17
|
+
latest_status_entry,
|
|
18
|
+
normalize_create_payload,
|
|
19
|
+
requeue,
|
|
20
|
+
serialize_object,
|
|
12
21
|
)
|
|
13
22
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
""
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return obj
|
|
34
|
-
if isinstance(obj, dict):
|
|
35
|
-
return {k: serialize_object(v) for k, v in obj.items()}
|
|
36
|
-
if isinstance(obj, (list, tuple)):
|
|
37
|
-
return [serialize_object(item) for item in obj]
|
|
38
|
-
if hasattr(obj, "model_dump"):
|
|
39
|
-
return obj.model_dump(mode="json")
|
|
40
|
-
if hasattr(obj, "to_dict"):
|
|
41
|
-
return obj.to_dict()
|
|
42
|
-
if hasattr(obj, "__dict__"):
|
|
43
|
-
return {k: serialize_object(v) for k, v in obj.__dict__.items() if not k.startswith("_")}
|
|
44
|
-
return obj
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def classify_status(raw_status: str) -> str:
|
|
48
|
-
"""Map a raw pipeline status string to a coarse state."""
|
|
49
|
-
status = (raw_status or "").lower()
|
|
50
|
-
if status in SUCCESS_STATUSES:
|
|
51
|
-
return "success"
|
|
52
|
-
if status in FAILURE_STATUSES:
|
|
53
|
-
return "failed"
|
|
54
|
-
if status in PROCESSING_STATUSES:
|
|
55
|
-
return "processing"
|
|
56
|
-
return "unknown"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def find_status_entry(statuses: Any, buckets_file_id: str) -> Any:
|
|
60
|
-
for status in extract_status_entries(statuses):
|
|
61
|
-
if extract_status_file_id(status) == str(buckets_file_id):
|
|
62
|
-
return status
|
|
63
|
-
return {}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def extract_status_entries(statuses: Any) -> List[Any]:
|
|
67
|
-
"""Normalize SDK attachment status responses to a list of status entries."""
|
|
68
|
-
if statuses is None:
|
|
69
|
-
return []
|
|
70
|
-
if isinstance(statuses, list):
|
|
71
|
-
return statuses
|
|
72
|
-
if isinstance(statuses, tuple):
|
|
73
|
-
return list(statuses)
|
|
74
|
-
if isinstance(statuses, dict):
|
|
75
|
-
for key in ("items", "data", "results", "statuses", "attachments", "files"):
|
|
76
|
-
value = statuses.get(key)
|
|
77
|
-
if isinstance(value, list):
|
|
78
|
-
return value
|
|
79
|
-
if get_value(statuses, "status") is not None or extract_status_file_id(statuses):
|
|
80
|
-
return [statuses]
|
|
81
|
-
return []
|
|
82
|
-
for key in ("items", "data", "results", "statuses", "attachments", "files"):
|
|
83
|
-
value = getattr(statuses, key, None)
|
|
84
|
-
if isinstance(value, (list, tuple)):
|
|
85
|
-
return list(value)
|
|
86
|
-
if get_value(statuses, "status") is not None or extract_status_file_id(statuses):
|
|
87
|
-
return [statuses]
|
|
88
|
-
return []
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def extract_status_file_id(status: Any) -> Optional[str]:
|
|
92
|
-
for key in ("buckets_file_id", "bucketsFileId", "file_id", "fileId"):
|
|
93
|
-
value = get_value(status, key)
|
|
94
|
-
if value:
|
|
95
|
-
return str(value)
|
|
96
|
-
return None
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def latest_status_entry(statuses: Any) -> Any:
|
|
100
|
-
entries = extract_status_entries(statuses)
|
|
101
|
-
return entries[-1] if entries else {}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def extract_buckets_file_id(item: Any, field_id: str) -> Optional[str]:
|
|
105
|
-
field_values = get_value(item, "field_values", []) or []
|
|
106
|
-
if isinstance(field_values, dict):
|
|
107
|
-
return extract_buckets_file_id_from_attachments(field_values.get(field_id))
|
|
108
|
-
|
|
109
|
-
for field_value in field_values:
|
|
110
|
-
if str(get_value(field_value, "field_id")) != str(field_id):
|
|
111
|
-
continue
|
|
112
|
-
buckets_file_id = extract_buckets_file_id_from_attachments(get_value(field_value, "value", []))
|
|
113
|
-
if buckets_file_id:
|
|
114
|
-
return buckets_file_id
|
|
115
|
-
return None
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def extract_buckets_file_id_from_attachments(value: Any) -> Optional[str]:
|
|
119
|
-
attachments = value or []
|
|
120
|
-
if isinstance(attachments, dict):
|
|
121
|
-
for key in ("attachments", "files", "items", "data", "results", "value"):
|
|
122
|
-
nested = attachments.get(key)
|
|
123
|
-
if isinstance(nested, list):
|
|
124
|
-
attachments = nested
|
|
125
|
-
break
|
|
126
|
-
else:
|
|
127
|
-
attachments = [attachments]
|
|
128
|
-
if not isinstance(attachments, list):
|
|
129
|
-
return None
|
|
130
|
-
for attachment in reversed(attachments):
|
|
131
|
-
buckets_file_id = get_value(attachment, "buckets_file_id")
|
|
132
|
-
if buckets_file_id:
|
|
133
|
-
return str(buckets_file_id)
|
|
134
|
-
return None
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def item_to_transaction(item: Any, workflow_id: str) -> Transaction:
|
|
138
|
-
payload = serialize_object(item)
|
|
139
|
-
payload = payload if isinstance(payload, dict) else {"value": payload}
|
|
140
|
-
return Transaction(
|
|
141
|
-
id=str(payload.get("id", "")),
|
|
142
|
-
payload=payload,
|
|
143
|
-
metadata={
|
|
144
|
-
"workflow_id": workflow_id,
|
|
145
|
-
"phase_id": payload.get("phase_id"),
|
|
146
|
-
"company_id": payload.get("company_id"),
|
|
147
|
-
"title": payload.get("title"),
|
|
148
|
-
},
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def normalize_create_payload(payload: CreateItemPayload) -> Dict[str, Any]:
|
|
153
|
-
"""Flatten a CreateItemInput/dict into kwargs accepted by the service create_item."""
|
|
154
|
-
if isinstance(payload, CreateItemInput):
|
|
155
|
-
data: Dict[str, Any] = {
|
|
156
|
-
"title": payload.title,
|
|
157
|
-
"workflow_id": payload.workflow_id,
|
|
158
|
-
"phase_id": payload.phase_id,
|
|
159
|
-
"parent_item_id": payload.parent_item_id,
|
|
160
|
-
"field_values": payload.field_values,
|
|
161
|
-
"attachment": payload.attachment,
|
|
162
|
-
"attachment_field_id": payload.attachment_field_id,
|
|
163
|
-
"content_type": payload.content_type,
|
|
164
|
-
"fields": dict(payload.extra_fields),
|
|
165
|
-
}
|
|
166
|
-
return data
|
|
167
|
-
if isinstance(payload, dict):
|
|
168
|
-
data = dict(payload)
|
|
169
|
-
data.setdefault("fields", {})
|
|
170
|
-
return data
|
|
171
|
-
raise TypeError(f"Unsupported create payload type: {type(payload)}")
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def requeue(transaction: Transaction) -> CreateItemInput:
|
|
175
|
-
"""Build a CreateItemInput from a transaction payload for re-dispatch."""
|
|
176
|
-
data = normalize_create_payload(transaction.payload)
|
|
177
|
-
workflow_id = data.get("workflow_id") or transaction.metadata.get("workflow_id")
|
|
178
|
-
phase_id = data.get("phase_id") or transaction.metadata.get("phase_id")
|
|
179
|
-
title = data.get("title")
|
|
180
|
-
if not isinstance(title, str) or not title.strip():
|
|
181
|
-
raise ValueError("title is required to requeue a transaction")
|
|
182
|
-
|
|
183
|
-
return CreateItemInput(
|
|
184
|
-
title=title,
|
|
185
|
-
workflow_id=workflow_id,
|
|
186
|
-
phase_id=phase_id,
|
|
187
|
-
parent_item_id=data.get("parent_item_id"),
|
|
188
|
-
field_values=data.get("field_values"),
|
|
189
|
-
attachment=data.get("attachment"),
|
|
190
|
-
attachment_field_id=data.get("attachment_field_id"),
|
|
191
|
-
content_type=data.get("content_type"),
|
|
192
|
-
extra_fields=dict(data.get("fields") or {}),
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def build_redispatch_transaction(transaction: Transaction) -> Transaction:
|
|
197
|
-
"""Prepare a transaction payload suitable for re-dispatch."""
|
|
198
|
-
return Transaction(
|
|
199
|
-
id=transaction.id,
|
|
200
|
-
payload=requeue(transaction),
|
|
201
|
-
metadata=dict(transaction.metadata or {}),
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def decode_jwt_claims(jwt_token: str) -> Dict[str, Any]:
|
|
206
|
-
"""Decode JWT payload claims without signature validation.
|
|
207
|
-
|
|
208
|
-
Raises:
|
|
209
|
-
ValueError: When the token shape or payload encoding is invalid.
|
|
210
|
-
"""
|
|
211
|
-
parts = jwt_token.split(".")
|
|
212
|
-
if len(parts) < 2 or not parts[1]:
|
|
213
|
-
raise ValueError("Invalid JWT format: payload segment is missing")
|
|
214
|
-
|
|
215
|
-
payload = parts[1]
|
|
216
|
-
payload += "=" * (-len(payload) % 4)
|
|
217
|
-
try:
|
|
218
|
-
decoded = base64.urlsafe_b64decode(payload.encode("ascii"))
|
|
219
|
-
claims = json.loads(decoded.decode("utf-8"))
|
|
220
|
-
except Exception as exc: # noqa: BLE001
|
|
221
|
-
raise ValueError("Invalid JWT payload encoding") from exc
|
|
222
|
-
|
|
223
|
-
if not isinstance(claims, dict):
|
|
224
|
-
raise ValueError("Invalid JWT payload: expected a JSON object")
|
|
225
|
-
return claims
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def extract_items(response: Any) -> List[Any]:
|
|
229
|
-
"""Pull the item list out of a Page, dict, or raw list response."""
|
|
230
|
-
if isinstance(response, list):
|
|
231
|
-
return response
|
|
232
|
-
if isinstance(response, dict):
|
|
233
|
-
for key in ("items", "data", "results"):
|
|
234
|
-
value = response.get(key)
|
|
235
|
-
if isinstance(value, list):
|
|
236
|
-
return value
|
|
237
|
-
return []
|
|
238
|
-
items = getattr(response, "items", None)
|
|
239
|
-
if isinstance(items, (list, tuple)):
|
|
240
|
-
return list(items)
|
|
241
|
-
return []
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def extract_total(response: Any) -> int:
|
|
245
|
-
"""Read total count from a paginated Page, dict, or SDK response."""
|
|
246
|
-
for key in ("total", "total_count", "count"):
|
|
247
|
-
value = get_value(response, key)
|
|
248
|
-
if value is not None:
|
|
249
|
-
return int(value)
|
|
250
|
-
return 0
|
|
23
|
+
__all__ = [
|
|
24
|
+
"as_payload",
|
|
25
|
+
"build_redispatch_transaction",
|
|
26
|
+
"classify_status",
|
|
27
|
+
"decode_jwt_claims",
|
|
28
|
+
"extract_buckets_file_id",
|
|
29
|
+
"extract_buckets_file_id_from_attachments",
|
|
30
|
+
"extract_items",
|
|
31
|
+
"extract_status_entries",
|
|
32
|
+
"extract_status_file_id",
|
|
33
|
+
"extract_total",
|
|
34
|
+
"find_status_entry",
|
|
35
|
+
"get_value",
|
|
36
|
+
"item_to_transaction",
|
|
37
|
+
"latest_status_entry",
|
|
38
|
+
"normalize_create_payload",
|
|
39
|
+
"requeue",
|
|
40
|
+
"serialize_object",
|
|
41
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any
|
|
2
|
+
|
|
3
|
+
from ..._lazy import exported_names, load_optional_export
|
|
4
|
+
from .models import (
|
|
5
|
+
FAILURE_STATUSES,
|
|
6
|
+
PROCESSING_STATUSES,
|
|
7
|
+
SUCCESS_STATUSES,
|
|
8
|
+
CreateItemInput,
|
|
9
|
+
CreateItemPayload,
|
|
10
|
+
ErgonPlatformConsumerConfig,
|
|
11
|
+
ErgonPlatformProducerConfig,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ._operations import ErgonPlatformWorkflowsService
|
|
16
|
+
from .async_connector import AsyncErgonPlatformConnector
|
|
17
|
+
from .connector import ErgonPlatformConnector
|
|
18
|
+
|
|
19
|
+
_LAZY_EXPORTS = {
|
|
20
|
+
"AsyncErgonPlatformConnector": "async_connector",
|
|
21
|
+
"ErgonPlatformConnector": "connector",
|
|
22
|
+
"ErgonPlatformWorkflowsService": "_operations",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def __getattr__(name: str) -> Any:
|
|
27
|
+
return load_optional_export(
|
|
28
|
+
name=name,
|
|
29
|
+
package=__name__,
|
|
30
|
+
exports=_LAZY_EXPORTS,
|
|
31
|
+
namespace=globals(),
|
|
32
|
+
extra="ergon-platform",
|
|
33
|
+
dependencies=("ergon_platform", "httpx"),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def __dir__() -> list[str]:
|
|
38
|
+
return exported_names(globals(), _LAZY_EXPORTS)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"AsyncErgonPlatformConnector",
|
|
43
|
+
"CreateItemInput",
|
|
44
|
+
"CreateItemPayload",
|
|
45
|
+
"ErgonPlatformConnector",
|
|
46
|
+
"ErgonPlatformConsumerConfig",
|
|
47
|
+
"ErgonPlatformProducerConfig",
|
|
48
|
+
"ErgonPlatformWorkflowsService",
|
|
49
|
+
"FAILURE_STATUSES",
|
|
50
|
+
"PROCESSING_STATUSES",
|
|
51
|
+
"SUCCESS_STATUSES",
|
|
52
|
+
]
|
|
@@ -5,8 +5,8 @@ from typing import Any, Dict, List, Optional
|
|
|
5
5
|
|
|
6
6
|
import httpx
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from
|
|
8
|
+
from ...transaction import Transaction
|
|
9
|
+
from ..models import ErgonPlatformClient
|
|
10
10
|
from .utils import (
|
|
11
11
|
as_payload,
|
|
12
12
|
classify_status,
|
|
@@ -22,7 +22,9 @@ from .utils import (
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class
|
|
25
|
+
class ErgonPlatformWorkflowsService:
|
|
26
|
+
"""Workflows HTTP, pagination, claim, and attachment service."""
|
|
27
|
+
|
|
26
28
|
def __init__(self, config: ErgonPlatformClient, client: Any) -> None:
|
|
27
29
|
self.config = config
|
|
28
30
|
self.client = client
|
|
@@ -262,3 +264,6 @@ class _ErgonPlatformOperations:
|
|
|
262
264
|
seen_ids.add(field_id)
|
|
263
265
|
merged.append(field)
|
|
264
266
|
return merged
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
_ErgonPlatformOperations = ErgonPlatformWorkflowsService
|