Skip to content

Commit ae3ba53

Browse files
author
Вадим Козыревский
committed
Add Python3.9-3.15 supporting
1 parent b3c7a0f commit ae3ba53

82 files changed

Lines changed: 528 additions & 449 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/python-publish.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ jobs:
1313
runs-on: ubuntu-latest
1414
strategy:
1515
matrix:
16-
python-version: [ "3.10", "3.11", "3.12" ]
16+
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13", "3.14" ]
1717

1818
steps:
1919
- uses: actions/checkout@v4
2020
- name: Set up Python
21-
uses: actions/setup-python@v3
21+
uses: actions/setup-python@v5
2222
with:
2323
python-version: ${{ matrix.python-version }}
2424
- name: Install dependencies
@@ -28,6 +28,6 @@ jobs:
2828
- name: Build package
2929
run: python -m build
3030
- name: Publish package
31-
if: success() && github.event_name == 'release' && matrix.python-version == '3.12'
31+
if: success() && github.event_name == 'release' && matrix.python-version == '3.14'
3232
run: |
3333
twine upload dist/* --username __token__ --password ${{ secrets.PYPI_API_TOKEN }}

.github/workflows/tests.yml

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,26 @@ on:
99
jobs:
1010
lint:
1111
runs-on: ubuntu-latest
12+
continue-on-error: ${{ matrix.experimental || false }}
1213
strategy:
1314
fail-fast: false
1415
matrix:
15-
python-version: ["3.10", "3.11", "3.12"]
16+
include:
17+
- python-version: "3.9"
18+
pyright-version: "3.9"
19+
- python-version: "3.10"
20+
pyright-version: "3.10"
21+
- python-version: "3.11"
22+
pyright-version: "3.11"
23+
- python-version: "3.12"
24+
pyright-version: "3.12"
25+
- python-version: "3.13"
26+
pyright-version: "3.13"
27+
- python-version: "3.14"
28+
pyright-version: "3.14"
29+
- python-version: "3.15-dev"
30+
pyright-version: "3.14"
31+
experimental: true
1632

1733
steps:
1834
- uses: actions/checkout@v4
@@ -23,6 +39,7 @@ jobs:
2339
uses: actions/setup-python@v5
2440
with:
2541
python-version: ${{ matrix.python-version }}
42+
allow-prereleases: true
2643

2744
- name: Install dependencies
2845
run: |
@@ -61,20 +78,30 @@ jobs:
6178
while IFS= read -r f; do [ -f "$f" ] && echo "$f"; done < changed.txt | xargs -r ruff format --check --config ruff.toml
6279
6380
- name: Run pyright
81+
if: ${{ matrix.python-version != '3.15-dev' }}
6482
run: |
65-
pyright --pythonversion ${{ matrix.python-version }} src tests examples
83+
pyright --pythonversion ${{ matrix.pyright-version }} src
6684
6785
- name: Check minimum Python version (vermin)
6886
run: |
69-
vermin --target=3.10- --violations --eval-annotations --backport typing_extensions --exclude=venv --exclude=build --exclude=.git --exclude=.venv src examples tests
87+
vermin --target=3.9- --violations --eval-annotations --backport typing_extensions --exclude=venv --exclude=build --exclude=.git --exclude=.venv src examples tests
7088
7189
test:
7290
name: test (py ${{ matrix.python-version }})
7391
runs-on: ubuntu-latest
92+
continue-on-error: ${{ matrix.experimental || false }}
7493
strategy:
7594
fail-fast: false
7695
matrix:
77-
python-version: ["3.10", "3.11", "3.12"]
96+
include:
97+
- python-version: "3.9"
98+
- python-version: "3.10"
99+
- python-version: "3.11"
100+
- python-version: "3.12"
101+
- python-version: "3.13"
102+
- python-version: "3.14"
103+
- python-version: "3.15-dev"
104+
experimental: true
78105

79106
steps:
80107
- uses: actions/checkout@v4
@@ -83,6 +110,7 @@ jobs:
83110
uses: actions/setup-python@v5
84111
with:
85112
python-version: ${{ matrix.python-version }}
113+
allow-prereleases: true
86114

87115
- name: Install dependencies
88116
run: |

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ repos:
5353
hooks:
5454
- id: pytest-unit
5555
name: unit tests
56-
entry: pytest -c ./tests/pytest-config.ini ./tests/unit
56+
entry: env PYTHONPATH=src ./venv/bin/python -m pytest -c ./tests/pytest-config.ini ./tests/unit
5757
language: system
5858
types: [python]
5959
pass_filenames: false
6060
always_run: true
6161
- id: pytest-integration
6262
name: integration tests
63-
entry: pytest -c ./tests/pytest-config.ini ./tests/integration
63+
entry: env PYTHONPATH=src ./venv/bin/python -m pytest -c ./tests/pytest-config.ini ./tests/integration
6464
language: system
6565
types: [python]
6666
pass_filenames: false

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<h1>Python CQRS</h1>
33
<p><strong>Event-Driven Architecture Framework for Distributed Systems</strong></p>
44
<p>
5-
<strong>Python 3.10+</strong> · Full documentation: <a href="https://mkdocs.python-cqrs.dev/">mkdocs.python-cqrs.dev</a>
5+
<strong>Python 3.9+</strong> · Full documentation: <a href="https://mkdocs.python-cqrs.dev/">mkdocs.python-cqrs.dev</a>
66
</p>
77
<p>
88
<a href="https://pypi.org/project/python-cqrs/">
@@ -88,7 +88,7 @@ project ([documentation](https://akhundmurad.github.io/diator/)) with several en
8888

8989
## Installation
9090

91-
**Python 3.10+** is required.
91+
**Python 3.9+** is required. CI runs on Python **3.9-3.14** and also tracks **3.15-dev** compatibility.
9292

9393
```bash
9494
pip install python-cqrs

docker-compose-dev.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
version: '3'
21
services:
32
mysql_dev:
43
image: mysql:8.3.0

examples/cor_mermaid.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class PaymentResult(cqrs.Response):
8181
"""Payment processing result."""
8282

8383
success: bool
84-
transaction_id: str | None = None
84+
transaction_id: typing.Optional[str] = None
8585
message: str = ""
8686

8787

@@ -109,7 +109,7 @@ def __init__(self) -> None:
109109
def events(self) -> typing.List[Event]:
110110
return self._events.copy()
111111

112-
async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
112+
async def handle(self, request: ProcessPaymentCommand) -> typing.Optional[PaymentResult]:
113113
"""Process credit card payment."""
114114
if request.payment_method == "credit_card":
115115
transaction_id = f"cc_{request.user_id}_{int(request.amount * 100)}"
@@ -143,7 +143,7 @@ def __init__(self) -> None:
143143
def events(self) -> typing.List[Event]:
144144
return self._events.copy()
145145

146-
async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
146+
async def handle(self, request: ProcessPaymentCommand) -> typing.Optional[PaymentResult]:
147147
"""Process PayPal payment."""
148148
if request.payment_method == "paypal":
149149
transaction_id = f"pp_{request.user_id}_{int(request.amount * 100)}"
@@ -177,7 +177,7 @@ def __init__(self) -> None:
177177
def events(self) -> typing.List[Event]:
178178
return self._events.copy()
179179

180-
async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
180+
async def handle(self, request: ProcessPaymentCommand) -> typing.Optional[PaymentResult]:
181181
"""Process bank transfer payment."""
182182
if request.payment_method == "bank_transfer":
183183
transaction_id = f"bt_{request.user_id}_{int(request.amount * 100)}"
@@ -207,7 +207,7 @@ class DefaultPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentResu
207207
def events(self) -> typing.List[Event]:
208208
return []
209209

210-
async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
210+
async def handle(self, request: ProcessPaymentCommand) -> typing.Optional[PaymentResult]:
211211
"""Handle unsupported payment methods."""
212212
return PaymentResult(
213213
success=False,

examples/cor_request_fallback.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555

5656
import asyncio
5757
import logging
58+
import typing
5859

5960
import di
6061
from di import dependent
@@ -96,7 +97,7 @@ class SourceAHandler(CORRequestHandler[FetchDataCommand, FetchDataResult]):
9697
def events(self) -> list[cqrs.Event]:
9798
return []
9899

99-
async def handle(self, request: FetchDataCommand) -> FetchDataResult | None:
100+
async def handle(self, request: FetchDataCommand) -> typing.Optional[FetchDataResult]:
100101
if request.source == "a":
101102
logger.info("COR chain: SourceAHandler handled source=a")
102103
HANDLER_SOURCE.append("chain")
@@ -109,7 +110,7 @@ class SourceBHandler(CORRequestHandler[FetchDataCommand, FetchDataResult]):
109110
def events(self) -> list[cqrs.Event]:
110111
return []
111112

112-
async def handle(self, request: FetchDataCommand) -> FetchDataResult | None:
113+
async def handle(self, request: FetchDataCommand) -> typing.Optional[FetchDataResult]:
113114
if request.source == "b":
114115
logger.info("COR chain: SourceBHandler handled source=b")
115116
HANDLER_SOURCE.append("chain")
@@ -124,7 +125,7 @@ class DefaultChainHandler(CORRequestHandler[FetchDataCommand, FetchDataResult]):
124125
def events(self) -> list[cqrs.Event]:
125126
return []
126127

127-
async def handle(self, request: FetchDataCommand) -> FetchDataResult | None:
128+
async def handle(self, request: FetchDataCommand) -> typing.Optional[FetchDataResult]:
128129
if request.source == "error":
129130
logger.info("COR chain: DefaultChainHandler raising ConnectionError for source=error")
130131
raise ConnectionError("Downstream service unavailable")

examples/cor_request_handler.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class ProcessPaymentCommand(cqrs.Request):
8282

8383
class PaymentResult(cqrs.Response):
8484
success: bool
85-
transaction_id: str | None = None
85+
transaction_id: typing.Optional[str] = None
8686
message: str = ""
8787

8888

@@ -101,7 +101,7 @@ def __init__(self) -> None:
101101
super().__init__()
102102
self._events: typing.List[cqrs.Event] = []
103103

104-
async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
104+
async def handle(self, request: ProcessPaymentCommand) -> typing.Optional[PaymentResult]:
105105
if request.payment_method == "credit_card":
106106
transaction_id = f"cc_{request.user_id}_{int(request.amount * 100)}"
107107
TRANSACTIONS["credit_card"].append(transaction_id)
@@ -134,7 +134,7 @@ def __init__(self) -> None:
134134
super().__init__()
135135
self._events: typing.List[cqrs.Event] = []
136136

137-
async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
137+
async def handle(self, request: ProcessPaymentCommand) -> typing.Optional[PaymentResult]:
138138
if request.payment_method == "paypal":
139139
transaction_id = f"pp_{request.user_id}_{int(request.amount * 100)}"
140140
TRANSACTIONS["paypal"].append(transaction_id)
@@ -167,7 +167,7 @@ def __init__(self) -> None:
167167
super().__init__()
168168
self._events: typing.List[cqrs.Event] = []
169169

170-
async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
170+
async def handle(self, request: ProcessPaymentCommand) -> typing.Optional[PaymentResult]:
171171
if request.payment_method == "bank_transfer":
172172
transaction_id = f"bt_{request.user_id}_{int(request.amount * 100)}"
173173
TRANSACTIONS["bank_transfer"].append(transaction_id)
@@ -198,7 +198,7 @@ class DefaultPaymentHandler(CORRequestHandler[ProcessPaymentCommand, PaymentResu
198198
def events(self) -> typing.List[cqrs.Event]:
199199
return []
200200

201-
async def handle(self, request: ProcessPaymentCommand) -> PaymentResult | None:
201+
async def handle(self, request: ProcessPaymentCommand) -> typing.Optional[PaymentResult]:
202202
# Default handler always handles the request (end of chain)
203203
print(
204204
f"Default: Unsupported payment method '{request.payment_method}' for user {request.user_id}",

examples/kafka_event_consuming.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ async def empty_message_decoder(
114114
[kafka.KafkaMessage],
115115
typing.Awaitable[types.DecodedMessage],
116116
],
117-
) -> types.DecodedMessage | None:
117+
) -> typing.Optional[types.DecodedMessage]:
118118
"""
119119
Decode a kafka message and return it if it is not empty.
120120
"""
@@ -158,7 +158,7 @@ def mediator_factory() -> cqrs.EventMediator:
158158
decoder=empty_message_decoder,
159159
)
160160
async def hello_world_event_handler(
161-
body: cqrs.NotificationEvent[HelloWorldPayload] | deserializers.DeserializeJsonError | None,
161+
body: typing.Union[cqrs.NotificationEvent[HelloWorldPayload], typing.Optional[deserializers.DeserializeJsonError]],
162162
msg: kafka.KafkaMessage,
163163
mediator: cqrs.EventMediator = faststream.Depends(mediator_factory),
164164
):

examples/saga.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
import asyncio
8787
import dataclasses
8888
import logging
89+
import typing
8990
import uuid
9091

9192
import di
@@ -119,9 +120,9 @@ class OrderContext(SagaContext):
119120
shipping_address: str
120121

121122
# These fields are populated by steps during execution
122-
inventory_reservation_id: str | None = None
123-
payment_id: str | None = None
124-
shipment_id: str | None = None
123+
inventory_reservation_id: typing.Optional[str] = None
124+
payment_id: typing.Optional[str] = None
125+
shipment_id: typing.Optional[str] = None
125126

126127

127128
# ============================================================================

0 commit comments

Comments
 (0)