ergon-framework-python 0.2.2__py3-none-any.whl → 0.2.4__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.
@@ -103,7 +103,7 @@ class ConsumerMixin(ABC):
103
103
  # -----------------------
104
104
  # 1) PROCESS STEP
105
105
  # -----------------------
106
- logger.info(f"Transaction {transaction.id} processing started")
106
+ logger.debug("Transaction %s processing started", transaction.id)
107
107
  process_ok, process_result = self._handle_process(transaction, policy.process.retry)
108
108
 
109
109
  # -----------------------
@@ -195,7 +195,7 @@ class ConsumerMixin(ABC):
195
195
  # PROCESS HANDLER
196
196
  # =====================================================================
197
197
  def _handle_process(self, transaction, retry: policies.RetryPolicy):
198
- logger.info(f"Transaction {transaction.id} process handler started")
198
+ logger.debug("Transaction %s process handler started", transaction.id)
199
199
  stage_start = time.perf_counter()
200
200
  success, result = helpers.run_fn(
201
201
  fn=lambda: self.process_transaction(transaction),
@@ -210,8 +210,10 @@ class ConsumerMixin(ABC):
210
210
  duration=time.perf_counter() - stage_start,
211
211
  outcome="ok" if success else "error",
212
212
  )
213
- logger.info(
214
- f"Transaction {transaction.id} process handler completed with status: {'success' if success else 'error'}"
213
+ logger.debug(
214
+ "Transaction %s process handler completed with status: %s",
215
+ transaction.id,
216
+ "success" if success else "error",
215
217
  )
216
218
  return success, result
217
219
 
@@ -219,7 +221,7 @@ class ConsumerMixin(ABC):
219
221
  # SUCCESS HANDLER
220
222
  # =====================================================================
221
223
  def _handle_success(self, transaction, result, retry: policies.RetryPolicy):
222
- logger.info(f"Transaction {transaction.id} success handler started")
224
+ logger.debug("Transaction %s success handler started", transaction.id)
223
225
  stage_start = time.perf_counter()
224
226
  success, handler_result = helpers.run_fn(
225
227
  fn=lambda: self.handle_process_success(transaction, result),
@@ -234,8 +236,10 @@ class ConsumerMixin(ABC):
234
236
  duration=time.perf_counter() - stage_start,
235
237
  outcome="ok" if success else "error",
236
238
  )
237
- logger.info(
238
- f"Transaction {transaction.id} success handler completed with status: {'success' if success else 'error'}"
239
+ logger.debug(
240
+ "Transaction %s success handler completed with status: %s",
241
+ transaction.id,
242
+ "success" if success else "error",
239
243
  )
240
244
  return success, handler_result
241
245
 
@@ -243,7 +247,7 @@ class ConsumerMixin(ABC):
243
247
  # EXCEPTION HANDLER
244
248
  # =====================================================================
245
249
  def _handle_exception(self, transaction, exc, retry: policies.RetryPolicy):
246
- logger.error(f"Transaction {transaction.id} exception handler started")
250
+ logger.debug("Transaction %s exception handler started", transaction.id)
247
251
  stage_start = time.perf_counter()
248
252
  success, result = helpers.run_fn(
249
253
  fn=lambda: self.handle_process_exception(transaction, exc),
@@ -258,8 +262,10 @@ class ConsumerMixin(ABC):
258
262
  duration=time.perf_counter() - stage_start,
259
263
  outcome="ok" if success else "error",
260
264
  )
261
- logger.info(
262
- f"Transaction {transaction.id} exception handler completed with status: {'success' if success else 'error'}"
265
+ logger.debug(
266
+ "Transaction %s exception handler completed with status: %s",
267
+ transaction.id,
268
+ "success" if success else "error",
263
269
  )
264
270
  return success, result
265
271
 
@@ -337,7 +343,7 @@ class ConsumerMixin(ABC):
337
343
  # -------------------------
338
344
  # FETCH
339
345
  # -------------------------
340
- logger.info(f"Fetching transactions batch with fetch policy: {policy.fetch.model_dump_json(indent=2)}")
346
+ logger.debug(f"Fetching transactions batch with fetch policy: {policy.fetch.model_dump_json(indent=2)}")
341
347
  success, result = self._handle_fetch(conn, policy.fetch)
342
348
  if not success:
343
349
  logger.error(f"Fetch failed → {result}")
@@ -645,7 +651,7 @@ class AsyncConsumerMixin(ABC):
645
651
  # -----------------------
646
652
  # 1) PROCESS STEP
647
653
  # -----------------------
648
- logger.info(f"Transaction {transaction.id} processing started")
654
+ logger.debug("Transaction %s processing started", transaction.id)
649
655
  process_ok, process_result = await self._handle_process(transaction, policy.process.retry)
650
656
 
651
657
  # -----------------------
@@ -735,7 +741,7 @@ class AsyncConsumerMixin(ABC):
735
741
  # PROCESS HANDLER WITH RETRIES
736
742
  # =====================================================================
737
743
  async def _handle_process(self, transaction, retry: policies.RetryPolicy):
738
- logger.info(f"Transaction {transaction.id} process handler started")
744
+ logger.debug("Transaction %s process handler started", transaction.id)
739
745
  stage_start = time.perf_counter()
740
746
  success, result = await helpers.run_fn_async(
741
747
  fn=lambda: self.process_transaction(transaction),
@@ -750,8 +756,10 @@ class AsyncConsumerMixin(ABC):
750
756
  duration=time.perf_counter() - stage_start,
751
757
  outcome="ok" if success else "error",
752
758
  )
753
- logger.info(
754
- f"Transaction {transaction.id} process handler completed with status: {'success' if success else 'error'}"
759
+ logger.debug(
760
+ "Transaction %s process handler completed with status: %s",
761
+ transaction.id,
762
+ "success" if success else "error",
755
763
  )
756
764
  return success, result
757
765
 
@@ -759,7 +767,7 @@ class AsyncConsumerMixin(ABC):
759
767
  # SUCCESS HANDLER
760
768
  # =====================================================================
761
769
  async def _handle_success(self, transaction, result, retry: policies.RetryPolicy):
762
- logger.info(f"Transaction {transaction.id} success handler started")
770
+ logger.debug("Transaction %s success handler started", transaction.id)
763
771
  stage_start = time.perf_counter()
764
772
  success, handler_result = await helpers.run_fn_async(
765
773
  fn=lambda: self.handle_process_success(transaction, result),
@@ -774,8 +782,10 @@ class AsyncConsumerMixin(ABC):
774
782
  duration=time.perf_counter() - stage_start,
775
783
  outcome="ok" if success else "error",
776
784
  )
777
- logger.info(
778
- f"Transaction {transaction.id} success handler completed with status: {'success' if success else 'error'}"
785
+ logger.debug(
786
+ "Transaction %s success handler completed with status: %s",
787
+ transaction.id,
788
+ "success" if success else "error",
779
789
  )
780
790
  return success, handler_result
781
791
 
@@ -783,7 +793,7 @@ class AsyncConsumerMixin(ABC):
783
793
  # EXCEPTION HANDLER
784
794
  # =====================================================================
785
795
  async def _handle_exception(self, transaction, exc, retry: policies.RetryPolicy):
786
- logger.error(f"Transaction {transaction.id} exception handler started")
796
+ logger.debug("Transaction %s exception handler started", transaction.id)
787
797
  stage_start = time.perf_counter()
788
798
  success, result = await helpers.run_fn_async(
789
799
  fn=lambda: self.handle_process_exception(transaction, exc),
@@ -798,8 +808,10 @@ class AsyncConsumerMixin(ABC):
798
808
  duration=time.perf_counter() - stage_start,
799
809
  outcome="ok" if success else "error",
800
810
  )
801
- logger.info(
802
- f"Transaction {transaction.id} exception handler completed with status: {'success' if success else 'error'}"
811
+ logger.debug(
812
+ "Transaction %s exception handler completed with status: %s",
813
+ transaction.id,
814
+ "success" if success else "error",
803
815
  )
804
816
  return success, result
805
817
 
@@ -869,7 +881,7 @@ class AsyncConsumerMixin(ABC):
869
881
  trace_name=f"{self.__class__.__name__}.start_processing",
870
882
  trace_attrs={"transaction_id": tr.id},
871
883
  )
872
- except TimeoutError as exc:
884
+ except asyncio.TimeoutError as exc:
873
885
  timeout_error = exceptions.TransactionTimeoutException(
874
886
  transaction_id=tr.id,
875
887
  cause=exc,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ergon-framework-python
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Ergon internal task-oriented project bootstrapper
5
5
  Author-email: Ergondata Technologies <anza.vossos@protonmail.com>
6
6
  License-Expression: MIT
@@ -31,6 +31,7 @@ Requires-Dist: asyncpg>=0.29.0; extra == "all"
31
31
  Requires-Dist: boto3>=1.35.0; extra == "all"
32
32
  Requires-Dist: ergon-platform-sdk>=0.2.0; extra == "all"
33
33
  Requires-Dist: httpx>=0.28.0; extra == "all"
34
+ Requires-Dist: msal>=1.37.0; extra == "all"
34
35
  Requires-Dist: nylas>=6.0.0; extra == "all"
35
36
  Requires-Dist: openpyxl>=3.1.5; extra == "all"
36
37
  Requires-Dist: pika>=1.3.0; extra == "all"
@@ -42,6 +43,9 @@ Provides-Extra: excel
42
43
  Requires-Dist: openpyxl>=3.1.5; extra == "excel"
43
44
  Provides-Extra: nylas
44
45
  Requires-Dist: nylas>=6.0.0; extra == "nylas"
46
+ Provides-Extra: outlook
47
+ Requires-Dist: msal>=1.37.0; extra == "outlook"
48
+ Requires-Dist: requests>=2.32.5; extra == "outlook"
45
49
  Provides-Extra: pipefy
46
50
  Requires-Dist: httpx>=0.28.0; extra == "pipefy"
47
51
  Requires-Dist: requests>=2.32.5; extra == "pipefy"
@@ -77,13 +81,53 @@ pull in client libraries that your application does not use:
77
81
  ```bash
78
82
  pip install 'ergon-framework-python[rabbitmq]'
79
83
  pip install 'ergon-framework-python[sqs,postgres]'
84
+ pip install 'ergon-framework-python[outlook]'
80
85
  pip install 'ergon-framework-python[all]'
81
86
  ```
82
87
 
83
88
  Available extras are `rabbitmq`, `sqs`, `postgres`, `pipefy`, `excel`,
84
- `ergon-platform`, and `nylas`.
89
+ `ergon-platform`, `nylas`, and `outlook`.
85
90
 
86
- For local development, install in editable mode:
91
+ ### Outlook / Microsoft Graph
92
+
93
+ ```bash
94
+ pip install 'ergon-framework-python[outlook]'
95
+ ```
96
+
97
+ The Outlook connector talks to Microsoft Graph with Entra **application**
98
+ credentials. Consuming messages and attachments needs `Mail.Read`; marking,
99
+ categorizing, moving, and deleting need `Mail.ReadWrite`; send, reply, and
100
+ forward need `Mail.Send`. Graph `$filter` and `$search` cannot be combined.
101
+
102
+ ```python
103
+ from ergon.connector.outlook import (
104
+ AsyncOutlookGraphConnector,
105
+ OutlookAckActionConfig,
106
+ OutlookConsumerConfig,
107
+ OutlookGraphClient,
108
+ OutlookMessageFilter,
109
+ )
110
+
111
+ client = OutlookGraphClient(
112
+ tenant_id="...",
113
+ client_id="...",
114
+ client_secret="...",
115
+ user_email="mailbox@example.com",
116
+ )
117
+ consumer = OutlookConsumerConfig(
118
+ folder_id="Inbox",
119
+ filter=OutlookMessageFilter(unread_only=True, has_attachments=True),
120
+ download_attachments=True,
121
+ ack_config=OutlookAckActionConfig(mark_as_read=True),
122
+ )
123
+ ```
124
+
125
+ Runnable example: [examples/outlook](https://github.com/danzanellovossos/ergon-framework/tree/main/sdks/python/examples/outlook).
126
+ Module reference: [Outlook connector README](https://github.com/danzanellovossos/ergon-framework/blob/main/sdks/python/src/ergon/connector/outlook/README.md).
127
+
128
+ ### Local development
129
+
130
+ Install in editable mode:
87
131
 
88
132
  ```bash
89
133
  pip install -e /path/to/ergon-framework/sdks/python
@@ -1,4 +1,4 @@
1
- ergon/__init__.py,sha256=o88UpQYh3RcjogyA8PsDjekDAK6Xfth93UCFdDWixyo,218
1
+ ergon/__init__.py,sha256=YxjNiBz68BV9DIN53Yy5zCKMVcca7VJk8A_8_Z184M4,218
2
2
  ergon/cli.py,sha256=CF8PvOjIguR18a657aEBEyhFXUoSVtSRVXv5PfnuWtA,6021
3
3
  ergon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  ergon/bootstrap/src/__project__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -22,7 +22,7 @@ ergon/bootstrap/src/__project__/tasks/example_task/exceptions.py,sha256=LTWwquhM
22
22
  ergon/bootstrap/src/__project__/tasks/example_task/helpers.py,sha256=uR_XZEtxR6JzbJloi7OhJb77f7Wc6pnOe1W3CHvl8vE,122
23
23
  ergon/bootstrap/src/__project__/tasks/example_task/schemas.py,sha256=3_qANKlJIQ90gaRNrDuBAM42UNzfDbLI4-F0kkjjWHc,155
24
24
  ergon/bootstrap/src/__project__/tasks/example_task/task.py,sha256=XnmVg-B0N8Ld48FMLJgGfTTJLjmpy5Nog_Gaj1ruVEQ,36
25
- ergon/connector/__init__.py,sha256=qBgpM6yArVb-ns7l09RN6hYU84Rxx1_3aD4ZZiCFTRE,6648
25
+ ergon/connector/__init__.py,sha256=zqKuXsx1nO7Dh2-Dei-GjRo2pre__PwmSUq9JpW2s-w,8396
26
26
  ergon/connector/_lazy.py,sha256=ANsKY3JTtmVo8Z8p3ey3LwkQGVVYITKO1eEK66IvA3U,1690
27
27
  ergon/connector/connector.py,sha256=jaF1iXKALE1IHHfcP54mTD5mvj-wzk5iNaLo96IOcDI,3944
28
28
  ergon/connector/transaction.py,sha256=LMGEFfisdjGGCPrWps3nYJ-SRBKa-P2pOFmwbSgDN-E,291
@@ -63,6 +63,13 @@ ergon/connector/nylas/connector.py,sha256=4mIRdaQ6YH6j1gt4htAVeXVoKvZMYses6M3d2G
63
63
  ergon/connector/nylas/models.py,sha256=dqPG5xhM5ZBFKN1hPW0US7lDztSaV-CZwwIherrdDkA,10230
64
64
  ergon/connector/nylas/service.py,sha256=kj0zY0cvBWkqlne_gZyw4fFDDiIJ5ACB4PX7vYksE3Q,11525
65
65
  ergon/connector/nylas/utils.py,sha256=jP-_hslfSWQgS4Sh5FPOKKLnX-n68m38mkuHOcRVaFk,4715
66
+ ergon/connector/outlook/__init__.py,sha256=lYLgvXtKTv-jxMyJaeGhd6MO_FWnd0N-1nxRGOmXLXs,1737
67
+ ergon/connector/outlook/async_connector.py,sha256=aV60SXsFM9rDmbNItdnvpTNYqFy0iqqbvaxXfO9sOGM,7545
68
+ ergon/connector/outlook/async_service.py,sha256=uC3ldsS8d-yN5inY6qA5-NOyWRaeAc3mxcX-hFslz6M,5618
69
+ ergon/connector/outlook/connector.py,sha256=jX8kGV6glQ1z5cF9Spg5V1fXlYHaslcT7Q9xpuWEJlE,7190
70
+ ergon/connector/outlook/models.py,sha256=zIww_QBqSSnkWv3c13pdtMr97-LwQZn1S4eD5OWXRS0,11219
71
+ ergon/connector/outlook/service.py,sha256=TdkTroNeQxZ-Sh5mcETRFkqb3OGEZuAKJtQfXCcgTNk,15695
72
+ ergon/connector/outlook/utils.py,sha256=--IXPNJAG-Bxe4ATFaT0aAKMznMazAOlMpjRMke3KMQ,4102
66
73
  ergon/connector/pipefy/__init__.py,sha256=FI9OfHDa_914NC2s-SMjNYwZj6tpgBve7TER9SE9zo4,1119
67
74
  ergon/connector/pipefy/async_connector.py,sha256=Ls4fRcTD21gWbeSj32WpSID6b5ZYTNBjWN7kNw-9Ey0,1739
68
75
  ergon/connector/pipefy/async_service.py,sha256=Kpd-LVTet3Ve6z85pUWZ2uUWFpgTaF74znbKFzmXAMA,26298
@@ -76,7 +83,7 @@ ergon/connector/postgres/async_service.py,sha256=PdPnNElMKzLoov1as3Kcb_viuSyBaVE
76
83
  ergon/connector/postgres/models.py,sha256=DL9wBdZ6QZPH1iVft71cTabnFUCH1Hg1RmG5f5E8578,1749
77
84
  ergon/connector/rabbitmq/__init__.py,sha256=R0VuHRuKRbshm7drw7p3vi2Az6e8PjIsvT-JTtRwxxo,1478
78
85
  ergon/connector/rabbitmq/async_connector.py,sha256=1ltA8wGcB91Sxq6xJcX0OeOTRd6co4cpq5drG-6gHAc,5175
79
- ergon/connector/rabbitmq/async_service.py,sha256=g9WQXYMszkJsQwwp7AVHWUf7CDKwQjOCbqoVaKOQYaI,33048
86
+ ergon/connector/rabbitmq/async_service.py,sha256=1nY-Bt9qEno1H4ycB31nG3USUFBoKjOcu6BRt8VEVLM,35157
80
87
  ergon/connector/rabbitmq/connector.py,sha256=vsSzbVuuE8rKkwBfPgD97qTsqADxJGALnUvrEwZIL5Q,1633
81
88
  ergon/connector/rabbitmq/helper.py,sha256=xlQGx2SGR2DC1fi-5BMlCOvWydWEFkcN7fPJjlNvGLA,372
82
89
  ergon/connector/rabbitmq/models.py,sha256=Q8tFPdRuvSxVe70HlTBRY66DdrC7kXuSCbRjtPSSq6k,9910
@@ -99,7 +106,7 @@ ergon/task/policies.py,sha256=3x1SV-yt3FXNgmnATdQdpRtsDtlskjx582SpbC6UH6A,7358
99
106
  ergon/task/runner.py,sha256=xUaMSO6V_iOq6QJdiyW1IA5kfWuwQjh1IJl6HUh06hY,20344
100
107
  ergon/task/utils.py,sha256=OmlP2AhLM32V8O9OMky6DdSvxwmMNn9x2LTguf0y-M0,2501
101
108
  ergon/task/mixins/__init__.py,sha256=6A8vNgdjaQYNJvymWNF-kBN2oTaQBYRLctS3Sq1w_-Y,320
102
- ergon/task/mixins/consumer.py,sha256=DhN2qBtNtB6xdKEdCokw1Z5ic2W8QfoUCm1QH_YPIFA,52270
109
+ ergon/task/mixins/consumer.py,sha256=LrCSbekQos5GU0xIY3F5_jUd69L09JV6FY_hMy2eol4,52467
103
110
  ergon/task/mixins/metrics.py,sha256=4bavwo8koWiN5ADD8t5qd2BVeVcgxo6ddzq-xOybeTg,13159
104
111
  ergon/task/mixins/producer.py,sha256=xENTZdZ7t0dpw5YsreQT20iTycSaj1EA4ny-60u-SWM,20427
105
112
  ergon/telemetry/__init__.py,sha256=8waeIAHEx6tMXWfw-zOEKgjrg7V8S1poG070MdAmi_8,100
@@ -109,9 +116,9 @@ ergon/telemetry/metrics.py,sha256=0T_CuNw9P5AG1U9k1-bNp1g7McyiNLUIt_zKyLOtrnA,28
109
116
  ergon/telemetry/tracing.py,sha256=_ktiZWAgB1DU-uN91Mzr0iYNXxbCN21LnKiRo5SOJxY,4454
110
117
  ergon/utils/__init__.py,sha256=M0_VCjMN7jvSPlRZLZHeFKTHAl1zu3BnAhjrzJjDfiI,57
111
118
  ergon/utils/env.py,sha256=lji4xENvfHqg2GTGnzG95VKpxxM4f3DHIKKP61xQG08,621
112
- ergon_framework_python-0.2.2.dist-info/licenses/LICENSE,sha256=bWCIhHzaadz_ecLdCdtwyMafeQ17s2d7_FSSuSuEcDs,1079
113
- ergon_framework_python-0.2.2.dist-info/METADATA,sha256=VNSh11YTgbAxLcsXPSj44s5pvfEniE9QST6-JEmsK1w,16914
114
- ergon_framework_python-0.2.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
115
- ergon_framework_python-0.2.2.dist-info/entry_points.txt,sha256=reZ934txb5OrtcNBgTGqCsXa1biUDQWubD2a9fGZynE,42
116
- ergon_framework_python-0.2.2.dist-info/top_level.txt,sha256=KSPt6N7RzsjU7PRAalWQ9Cul7YTUxbo84yAJ-y2vLm8,6
117
- ergon_framework_python-0.2.2.dist-info/RECORD,,
119
+ ergon_framework_python-0.2.4.dist-info/licenses/LICENSE,sha256=bWCIhHzaadz_ecLdCdtwyMafeQ17s2d7_FSSuSuEcDs,1079
120
+ ergon_framework_python-0.2.4.dist-info/METADATA,sha256=Mt-c5h17mfvb-JJDgqNfQKXg2zeIYBBxUh9XrsKrt2A,18371
121
+ ergon_framework_python-0.2.4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
122
+ ergon_framework_python-0.2.4.dist-info/entry_points.txt,sha256=reZ934txb5OrtcNBgTGqCsXa1biUDQWubD2a9fGZynE,42
123
+ ergon_framework_python-0.2.4.dist-info/top_level.txt,sha256=KSPt6N7RzsjU7PRAalWQ9Cul7YTUxbo84yAJ-y2vLm8,6
124
+ ergon_framework_python-0.2.4.dist-info/RECORD,,