ergon-framework-python 0.1.7__tar.gz → 0.1.8__tar.gz

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.
Files changed (105) hide show
  1. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/PKG-INFO +1 -1
  2. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/pyproject.toml +1 -1
  3. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/__init__.py +1 -1
  4. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/rabbitmq/async_service.py +29 -7
  5. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/runner.py +39 -16
  6. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon_framework_python.egg-info/PKG-INFO +1 -1
  7. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/tests/test_smoke.py +1 -1
  8. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/LICENSE +0 -0
  9. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/README.md +0 -0
  10. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/setup.cfg +0 -0
  11. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/__init__.py +0 -0
  12. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/_observability/docker-compose.telemetry.yml +0 -0
  13. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/_observability/grafana.yaml +0 -0
  14. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/_observability/loki.yaml +0 -0
  15. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/_observability/otel-collector-config.yaml +0 -0
  16. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/_observability/prometheus.yaml +0 -0
  17. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/_observability/tempo.yaml +0 -0
  18. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/connectors/__init__.py +0 -0
  19. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/main.py +0 -0
  20. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/tasks/__init__.py +0 -0
  21. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/tasks/constants.py +0 -0
  22. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/tasks/example_task/__init__.py +0 -0
  23. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/tasks/example_task/config.py +0 -0
  24. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/tasks/example_task/exceptions.py +0 -0
  25. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/tasks/example_task/helpers.py +0 -0
  26. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/tasks/example_task/schemas.py +0 -0
  27. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/tasks/example_task/task.py +0 -0
  28. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/tasks/exceptions.py +0 -0
  29. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/tasks/helpers.py +0 -0
  30. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/tasks/schemas.py +0 -0
  31. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/bootstrap/src/__project__/tasks/settings.py +0 -0
  32. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/cli.py +0 -0
  33. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/__init__.py +0 -0
  34. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/connector.py +0 -0
  35. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/ergon_platform/__init__.py +0 -0
  36. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/ergon_platform/_client.py +0 -0
  37. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/ergon_platform/_operations.py +0 -0
  38. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/ergon_platform/async_connector.py +0 -0
  39. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/ergon_platform/connector.py +0 -0
  40. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/ergon_platform/models.py +0 -0
  41. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/ergon_platform/utils.py +0 -0
  42. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/excel/__init__.py +0 -0
  43. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/excel/connector.py +0 -0
  44. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/excel/models.py +0 -0
  45. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/excel/service.py +0 -0
  46. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/nylas/__init__.py +0 -0
  47. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/nylas/async_auth_service.py +0 -0
  48. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/nylas/async_connector.py +0 -0
  49. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/nylas/async_service.py +0 -0
  50. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/nylas/auth_service.py +0 -0
  51. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/nylas/connector.py +0 -0
  52. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/nylas/models.py +0 -0
  53. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/nylas/service.py +0 -0
  54. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/nylas/utils.py +0 -0
  55. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/pipefy/__init__.py +0 -0
  56. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/pipefy/async_connector.py +0 -0
  57. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/pipefy/async_service.py +0 -0
  58. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/pipefy/connector.py +0 -0
  59. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/pipefy/models.py +0 -0
  60. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/pipefy/service.py +0 -0
  61. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/pipefy/version.py +0 -0
  62. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/postgres/__init__.py +0 -0
  63. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/postgres/async_connector.py +0 -0
  64. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/postgres/async_service.py +0 -0
  65. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/postgres/models.py +0 -0
  66. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/rabbitmq/__init__.py +0 -0
  67. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/rabbitmq/async_connector.py +0 -0
  68. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/rabbitmq/connector.py +0 -0
  69. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/rabbitmq/helper.py +0 -0
  70. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/rabbitmq/models.py +0 -0
  71. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/rabbitmq/service.py +0 -0
  72. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/sqs/__init__.py +0 -0
  73. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/sqs/async_connector.py +0 -0
  74. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/sqs/async_service.py +0 -0
  75. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/sqs/connector.py +0 -0
  76. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/sqs/models.py +0 -0
  77. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/sqs/service.py +0 -0
  78. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/connector/transaction.py +0 -0
  79. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/py.typed +0 -0
  80. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/service/__init__.py +0 -0
  81. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/service/service.py +0 -0
  82. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/__init__.py +0 -0
  83. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/base.py +0 -0
  84. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/exceptions.py +0 -0
  85. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/helpers.py +0 -0
  86. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/liveness.py +0 -0
  87. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/manager.py +0 -0
  88. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/mixins/__init__.py +0 -0
  89. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/mixins/consumer.py +0 -0
  90. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/mixins/metrics.py +0 -0
  91. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/mixins/producer.py +0 -0
  92. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/policies.py +0 -0
  93. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/task/utils.py +0 -0
  94. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/telemetry/__init__.py +0 -0
  95. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/telemetry/_resource.py +0 -0
  96. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/telemetry/logging.py +0 -0
  97. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/telemetry/metrics.py +0 -0
  98. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/telemetry/tracing.py +0 -0
  99. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/utils/__init__.py +0 -0
  100. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon/utils/env.py +0 -0
  101. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon_framework_python.egg-info/SOURCES.txt +0 -0
  102. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon_framework_python.egg-info/dependency_links.txt +0 -0
  103. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon_framework_python.egg-info/entry_points.txt +0 -0
  104. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon_framework_python.egg-info/requires.txt +0 -0
  105. {ergon_framework_python-0.1.7 → ergon_framework_python-0.1.8}/src/ergon_framework_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ergon-framework-python
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Ergon internal task-oriented project bootstrapper
5
5
  Author-email: Ergondata Technologies <anza.vossos@protonmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ergon-framework-python"
3
- version = "0.1.7"
3
+ version = "0.1.8"
4
4
  description = "Ergon internal task-oriented project bootstrapper"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -10,4 +10,4 @@ __all__ = [
10
10
  "manager",
11
11
  ]
12
12
 
13
- __version__ = "0.1.7"
13
+ __version__ = "0.1.8"
@@ -155,11 +155,33 @@ class AsyncRabbitMQService:
155
155
 
156
156
  connection = self._connection
157
157
  self._connection = None
158
- if connection is not None and not connection.is_closed:
159
- try:
158
+ await self._close_connection_safely(connection, reason)
159
+
160
+ async def _close_connection_safely(
161
+ self,
162
+ connection: Optional[AbstractRobustConnection],
163
+ reason: str,
164
+ ) -> None:
165
+ """Stop transport and robust-reconnect ownership within a fixed deadline.
166
+
167
+ ``RobustConnection.is_closed`` only describes its current transport. Its
168
+ background reconnection task can still be alive after the broker closes
169
+ that transport, so ``close()`` must always run for an owned connection.
170
+ """
171
+ if connection is None:
172
+ return
173
+ close_timeout = min(5.0, self.client.connect_timeout)
174
+ try:
175
+ async with timeout_after(close_timeout):
160
176
  await connection.close()
161
- except Exception as exc: # noqa: BLE001 - best-effort reset
162
- logger.warning("Error closing RabbitMQ connection during reset (%s): %r", reason, exc)
177
+ except TimeoutError:
178
+ logger.error(
179
+ "Timed out closing RabbitMQ connection after %.1fs (%s)",
180
+ close_timeout,
181
+ reason,
182
+ )
183
+ except Exception as exc: # noqa: BLE001 - best-effort reset
184
+ logger.warning("Error closing RabbitMQ connection (%s): %r", reason, exc)
163
185
 
164
186
  def _invalidate_consume_channel(self, reason: str = "explicit invalidation") -> None:
165
187
  """Drop cached consume channel + queue/exchange handles.
@@ -752,9 +774,9 @@ class AsyncRabbitMQService:
752
774
  logger.warning("Error closing %s: %r", attr_name, exc)
753
775
  setattr(self, attr_name, None)
754
776
 
755
- if self._connection is not None and not self._connection.is_closed:
756
- await self._connection.close()
757
- self._connection = None
777
+ connection = self._connection
778
+ self._connection = None
779
+ await self._close_connection_safely(connection, "service close")
758
780
 
759
781
  self._active_consumer_tag = None
760
782
  self._active_consumer_tags.clear()
@@ -106,15 +106,18 @@ class _TaskLivenessSupervisor:
106
106
  self,
107
107
  provider: LivenessProvider,
108
108
  policy: TaskSupervisionPolicy,
109
- request_shutdown,
110
- hard_exit=os._exit,
109
+ request_shutdown: Callable[[str], None],
110
+ hard_exit: Callable[[int], None] | None = None,
111
111
  ) -> None:
112
112
  self.provider = provider
113
113
  self.policy = policy
114
114
  self.request_shutdown = request_shutdown
115
- self.hard_exit = hard_exit
115
+ self.hard_exit = hard_exit or os._exit
116
116
  self._stop = threading.Event()
117
117
  self._thread: threading.Thread | None = None
118
+ self._shutdown_lock = threading.Lock()
119
+ self._shutdown_requested_at: float | None = None
120
+ self._shutdown_reason: str | None = None
118
121
 
119
122
  def start(self) -> None:
120
123
  self._thread = threading.Thread(
@@ -129,13 +132,39 @@ class _TaskLivenessSupervisor:
129
132
  if self._thread is not None and self._thread is not threading.current_thread():
130
133
  self._thread.join(timeout=max(1.0, self.policy.check_interval * 2))
131
134
 
135
+ def arm_shutdown_deadline(self, reason: str) -> None:
136
+ """Force process exit if any cooperative shutdown or cleanup wedges."""
137
+ with self._shutdown_lock:
138
+ if self._shutdown_requested_at is not None:
139
+ return
140
+ self._shutdown_requested_at = time.monotonic()
141
+ self._shutdown_reason = reason
142
+ _supervisor_logger.warning(
143
+ "Task shutdown deadline armed for %.1fs (%s)",
144
+ self.policy.shutdown_grace,
145
+ reason,
146
+ )
147
+
132
148
  def _run(self) -> None:
133
149
  started_at = time.monotonic()
134
150
  unhealthy_since: float | None = None
135
- shutdown_requested_at: float | None = None
136
151
 
137
152
  while not self._stop.wait(self.policy.check_interval):
138
153
  now = time.monotonic()
154
+ with self._shutdown_lock:
155
+ shutdown_requested_at = self._shutdown_requested_at
156
+ shutdown_reason = self._shutdown_reason
157
+ if shutdown_requested_at is not None:
158
+ if now - shutdown_requested_at >= self.policy.shutdown_grace:
159
+ _supervisor_logger.critical(
160
+ "Task did not stop within %.1fs after shutdown began (%s); forcing exit",
161
+ self.policy.shutdown_grace,
162
+ shutdown_reason,
163
+ )
164
+ self._stop.set()
165
+ self.hard_exit(int(ExitCode.ERROR))
166
+ continue
167
+
139
168
  if now - started_at < self.policy.startup_grace:
140
169
  continue
141
170
 
@@ -157,22 +186,14 @@ class _TaskLivenessSupervisor:
157
186
  _supervisor_logger.error("Task liveness check failed: %s", reason)
158
187
  continue
159
188
 
160
- if shutdown_requested_at is None and now - unhealthy_since >= self.policy.unhealthy_grace:
161
- shutdown_requested_at = now
189
+ if now - unhealthy_since >= self.policy.unhealthy_grace:
162
190
  _supervisor_logger.critical(
163
191
  "Task remained unhealthy for %.1fs (%s); requesting process shutdown",
164
192
  now - unhealthy_since,
165
193
  reason,
166
194
  )
195
+ self.arm_shutdown_deadline(reason)
167
196
  self.request_shutdown(reason)
168
- continue
169
-
170
- if shutdown_requested_at is not None and now - shutdown_requested_at >= self.policy.shutdown_grace:
171
- _supervisor_logger.critical(
172
- "Task did not stop within %.1fs after liveness shutdown request; forcing exit",
173
- self.policy.shutdown_grace,
174
- )
175
- self.hard_exit(int(ExitCode.ERROR))
176
197
 
177
198
 
178
199
  # =============================================================
@@ -313,8 +334,10 @@ async def __run_task_async(
313
334
  raise
314
335
  finally:
315
336
  _unregister_shutdown_callback(request_signal_shutdown)
316
- if supervisor is not None and not liveness_shutdown_requested.is_set():
317
- supervisor.stop()
337
+ if supervisor is not None:
338
+ supervisor.arm_shutdown_deadline(
339
+ "liveness shutdown cleanup" if liveness_shutdown_requested.is_set() else "task cleanup"
340
+ )
318
341
  try:
319
342
  if instance is not None:
320
343
  await instance.exit()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ergon-framework-python
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Ergon internal task-oriented project bootstrapper
5
5
  Author-email: Ergondata Technologies <anza.vossos@protonmail.com>
6
6
  License-Expression: MIT
@@ -4,4 +4,4 @@ import ergon
4
4
 
5
5
 
6
6
  def test_ergon_package_is_importable() -> None:
7
- assert ergon.__version__ == "0.1.7"
7
+ assert ergon.__version__ == "0.1.8"