python-cqrs 5.3.0__tar.gz → 5.3.1__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 (118) hide show
  1. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/PKG-INFO +1 -1
  2. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/pyproject.toml +1 -1
  3. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/outbox/repository.py +5 -0
  4. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/outbox/sqlalchemy.py +55 -2
  5. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/outbox/sqlmodel.py +3 -1
  6. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/python_cqrs.egg-info/PKG-INFO +1 -1
  7. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/LICENSE +0 -0
  8. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/README.md +0 -0
  9. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/setup.cfg +0 -0
  10. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/__init__.py +0 -0
  11. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/_dataclass_utils.py +0 -0
  12. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/adapters/__init__.py +0 -0
  13. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/adapters/amqp.py +0 -0
  14. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/adapters/circuit_breaker.py +0 -0
  15. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/adapters/kafka.py +0 -0
  16. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/adapters/protocol.py +0 -0
  17. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/bootstrap/__init__.py +0 -0
  18. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/bootstrap/events.py +0 -0
  19. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/bootstrap/requests.py +0 -0
  20. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/bootstrap/saga.py +0 -0
  21. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/circuit_breaker.py +0 -0
  22. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/compressors/__init__.py +0 -0
  23. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/compressors/protocol.py +0 -0
  24. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/compressors/zlib.py +0 -0
  25. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/container/__init__.py +0 -0
  26. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/container/dependency_injector.py +0 -0
  27. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/container/di.py +0 -0
  28. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/container/dishka.py +0 -0
  29. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/container/protocol.py +0 -0
  30. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/container/scope.py +0 -0
  31. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/deserializers/__init__.py +0 -0
  32. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/deserializers/exceptions.py +0 -0
  33. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/deserializers/json.py +0 -0
  34. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/deserializers/protobuf.py +0 -0
  35. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/dispatcher/__init__.py +0 -0
  36. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/dispatcher/event.py +0 -0
  37. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/dispatcher/exceptions.py +0 -0
  38. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/dispatcher/models.py +0 -0
  39. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/dispatcher/request.py +0 -0
  40. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/dispatcher/saga.py +0 -0
  41. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/dispatcher/streaming.py +0 -0
  42. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/events/__init__.py +0 -0
  43. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/events/event_emitter.py +0 -0
  44. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/events/event_processor.py +0 -0
  45. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/events/fallback.py +0 -0
  46. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/generic_utils.py +0 -0
  47. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/handlers/__init__.py +0 -0
  48. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/handlers/cor.py +0 -0
  49. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/handlers/event.py +0 -0
  50. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/handlers/request.py +0 -0
  51. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/handlers/saga.py +0 -0
  52. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/inbox/__init__.py +0 -0
  53. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/inbox/map.py +0 -0
  54. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/inbox/memory.py +0 -0
  55. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/inbox/processor.py +0 -0
  56. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/inbox/repository.py +0 -0
  57. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/inbox/sqlalchemy.py +0 -0
  58. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/inbox/sqlmodel.py +0 -0
  59. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mapping/__init__.py +0 -0
  60. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mapping/events.py +0 -0
  61. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mapping/outbox.py +0 -0
  62. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mapping/requests.py +0 -0
  63. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mediators/__init__.py +0 -0
  64. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mediators/_common.py +0 -0
  65. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mediators/event.py +0 -0
  66. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mediators/request.py +0 -0
  67. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mediators/saga.py +0 -0
  68. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mediators/streaming.py +0 -0
  69. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mermaid/__init__.py +0 -0
  70. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mermaid/cor.py +0 -0
  71. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/mermaid/saga.py +0 -0
  72. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/message_brokers/__init__.py +0 -0
  73. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/message_brokers/amqp.py +0 -0
  74. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/message_brokers/devnull.py +0 -0
  75. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/message_brokers/kafka.py +0 -0
  76. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/message_brokers/producer.py +0 -0
  77. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/message_brokers/protocol.py +0 -0
  78. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/middlewares/__init__.py +0 -0
  79. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/middlewares/base.py +0 -0
  80. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/middlewares/logging.py +0 -0
  81. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/models/__init__.py +0 -0
  82. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/models/event.py +0 -0
  83. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/models/pydantic.py +0 -0
  84. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/models/request.py +0 -0
  85. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/models/response.py +0 -0
  86. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/outbox/__init__.py +0 -0
  87. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/outbox/mock.py +0 -0
  88. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/py.typed +0 -0
  89. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/requests/__init__.py +0 -0
  90. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/requests/fallback.py +0 -0
  91. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/__init__.py +0 -0
  92. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/circuit_breaker.py +0 -0
  93. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/compensation.py +0 -0
  94. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/execution.py +0 -0
  95. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/fallback.py +0 -0
  96. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/models.py +0 -0
  97. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/recovery.py +0 -0
  98. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/saga.py +0 -0
  99. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/storage/__init__.py +0 -0
  100. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/storage/enums.py +0 -0
  101. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/storage/memory.py +0 -0
  102. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/storage/models.py +0 -0
  103. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/storage/protocol.py +0 -0
  104. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/storage/sqlalchemy.py +0 -0
  105. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/storage/sqlmodel.py +0 -0
  106. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/saga/validation.py +0 -0
  107. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/serializers/__init__.py +0 -0
  108. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/serializers/default.py +0 -0
  109. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/serializers/json.py +0 -0
  110. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/serializers/protobuf.py +0 -0
  111. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/serializers/protocol.py +0 -0
  112. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/sqlalchemy_types.py +0 -0
  113. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/sqlmodel_base.py +0 -0
  114. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/cqrs/types.py +0 -0
  115. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/python_cqrs.egg-info/SOURCES.txt +0 -0
  116. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/python_cqrs.egg-info/dependency_links.txt +0 -0
  117. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/python_cqrs.egg-info/requires.txt +0 -0
  118. {python_cqrs-5.3.0 → python_cqrs-5.3.1}/src/python_cqrs.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-cqrs
3
- Version: 5.3.0
3
+ Version: 5.3.1
4
4
  Summary: Event-Driven Architecture Framework for Distributed Systems
5
5
  Author-email: Vadim Kozyrevskiy <vadikko2@mail.ru>, Dmitry Kutlubaev <kutlubaev00@mail.ru>
6
6
  Maintainer-email: Vadim Kozyrevskiy <vadikko2@mail.ru>
@@ -30,7 +30,7 @@ maintainers = [{ name = "Vadim Kozyrevskiy", email = "vadikko2@mail.ru" }]
30
30
  name = "python-cqrs"
31
31
  readme = "README.md"
32
32
  requires-python = ">=3.10"
33
- version = "5.3.0"
33
+ version = "5.3.1"
34
34
 
35
35
  [project.optional-dependencies]
36
36
  aiobreaker = ["aiobreaker>=0.3.0"]
@@ -110,6 +110,11 @@ class OutboxedEventRepository(abc.ABC):
110
110
  """
111
111
  Get many events from the repository.
112
112
 
113
+ SQLAlchemy and SQLModel take rows with ``FOR UPDATE SKIP LOCKED`` on
114
+ PostgreSQL, MySQL 8.0 or later, and MariaDB 10.6 or later, until the
115
+ caller commits or rolls back. Older MySQL and MariaDB, SQLite, and
116
+ other dialects do not.
117
+
113
118
  [AI Instruction]
114
119
  Read a batch of unpublished outbox rows. ``EventProducer`` calls this.
115
120
  A command handler does not.
@@ -47,6 +47,23 @@ DEFAULT_OUTBOX_TABLE_NAME = "outbox"
47
47
 
48
48
  MAX_FLUSH_COUNTER_VALUE = 5
49
49
 
50
+ # SKIP LOCKED arrived in MySQL 8.0 and MariaDB 10.6. PostgreSQL has it
51
+ # without a version gate. Older servers and every other dialect stay unlocked.
52
+ _MYSQL_SKIP_LOCKED_SINCE = (8, 0)
53
+ _MARIADB_SKIP_LOCKED_SINCE = (10, 6)
54
+
55
+
56
+ def _dialect_is_mariadb(dialect: typing.Any) -> bool:
57
+ """MariaDB via ``mariadb://`` or the MySQL dialect's ``is_mariadb`` flag.
58
+
59
+ A MariaDB reached through the MySQL dialect keeps ``dialect.name == "mysql"``
60
+ and a version such as ``(10, 5)``, which would pass a MySQL 8.0 check.
61
+ """
62
+
63
+ if getattr(dialect, "name", None) == "mariadb":
64
+ return True
65
+ return bool(getattr(dialect, "is_mariadb", False))
66
+
50
67
 
51
68
  # Deprecated name of cqrs.sqlalchemy_types.UUIDBinary — kept importable for
52
69
  # backward compatibility (e.g. already generated Alembic migrations).
@@ -125,8 +142,10 @@ class OutboxModel(Base):
125
142
  cls,
126
143
  size: int,
127
144
  topic: typing.Text | None = None,
145
+ *,
146
+ lock: bool = False,
128
147
  ) -> sqlalchemy.Select:
129
- return (
148
+ query = (
130
149
  sqlalchemy.select(cls)
131
150
  .select_from(cls)
132
151
  .where(
@@ -145,6 +164,9 @@ class OutboxModel(Base):
145
164
  .order_by(cls.id.asc())
146
165
  .limit(size)
147
166
  )
167
+ if lock:
168
+ query = query.with_for_update(skip_locked=True)
169
+ return query
148
170
 
149
171
  @classmethod
150
172
  def update_status_query(
@@ -277,13 +299,44 @@ class SqlAlchemyOutboxedEventRepository(repository.OutboxedEventRepository):
277
299
  content_type=codec.content_type_for(event),
278
300
  )
279
301
 
302
+ async def _skip_locked(self) -> bool:
303
+ dialect = self.session.get_bind().dialect
304
+ name = dialect.name
305
+ if name == "postgresql":
306
+ return True
307
+ if name != "mysql" and name != "mariadb":
308
+ return False
309
+
310
+ # The dialect leaves server_version_info unset until the first
311
+ # connection runs initialize(). Reading it earlier looks like an old
312
+ # server and drops SKIP LOCKED on MySQL 8 and MariaDB 10.6.
313
+ version = getattr(dialect, "server_version_info", None)
314
+ if version is None:
315
+ await self.session.connection()
316
+ version = getattr(dialect, "server_version_info", None)
317
+ if version is None:
318
+ return False
319
+
320
+ minimum = _MARIADB_SKIP_LOCKED_SINCE if _dialect_is_mariadb(dialect) else _MYSQL_SKIP_LOCKED_SINCE
321
+ return tuple(version) >= minimum
322
+
280
323
  async def get_many(
281
324
  self,
282
325
  batch_size: int = 100,
283
326
  topic: typing.Text | None = None,
284
327
  ) -> typing.List[repository.OutboxedEvent]:
285
328
  events: typing.Sequence[typing.Any] = (
286
- (await self.session.execute(self._model.get_batch_query(batch_size, topic))).scalars().all()
329
+ (
330
+ await self.session.execute(
331
+ self._model.get_batch_query(
332
+ batch_size,
333
+ topic,
334
+ lock=await self._skip_locked(),
335
+ ),
336
+ )
337
+ )
338
+ .scalars()
339
+ .all()
287
340
  )
288
341
 
289
342
  result = []
@@ -140,9 +140,11 @@ class OutboxModel(Base, table=True):
140
140
  cls,
141
141
  size: int,
142
142
  topic: typing.Text | None = None,
143
+ *,
144
+ lock: bool = False,
143
145
  ) -> sqlalchemy.Select:
144
146
  query = SqlAlchemyOutboxModel.get_batch_query
145
- return query.__func__(cls, size, topic) # type: ignore[attr-defined]
147
+ return query.__func__(cls, size, topic, lock=lock) # type: ignore[attr-defined]
146
148
 
147
149
  @classmethod
148
150
  def update_status_query(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-cqrs
3
- Version: 5.3.0
3
+ Version: 5.3.1
4
4
  Summary: Event-Driven Architecture Framework for Distributed Systems
5
5
  Author-email: Vadim Kozyrevskiy <vadikko2@mail.ru>, Dmitry Kutlubaev <kutlubaev00@mail.ru>
6
6
  Maintainer-email: Vadim Kozyrevskiy <vadikko2@mail.ru>
File without changes
File without changes
File without changes