Skip to content

Commit 89e9130

Browse files
Mantisusvdusek
andauthored
feat: Add support for Python 3.14 (#1553)
### Description - Add support for Python 3.14 At this stage, it is not possible to provide PostgreSQL support for `SqlStorageClient` on Python 3.14, as `asyncpg` does not yet support Python 3.14. ### Testing - Some tests for Redis are limited because `fakeredis` does not yet support Python 3.14, which causes problems for Windows. - Updated the `test_memory_estimation_does_not_overestimate_due_to_shared_memory` test to use `multiprocessing.get_context('fork')` due to the update of `multiprocessing.Process` in Python 3.14/ --------- Co-authored-by: Vlada Dusek <[email protected]>
1 parent a3d6958 commit 89e9130

File tree

9 files changed

+37
-20
lines changed

9 files changed

+37
-20
lines changed

.github/workflows/build_and_deploy_docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010

1111
env:
1212
NODE_VERSION: 20
13-
PYTHON_VERSION: 3.13
13+
PYTHON_VERSION: 3.14
1414

1515
jobs:
1616
build_and_deploy_docs:

.github/workflows/release.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,21 @@ jobs:
4747
name: Lint check
4848
uses: apify/workflows/.github/workflows/python_lint_check.yaml@main
4949
with:
50-
python-versions: '["3.10", "3.11", "3.12", "3.13"]'
50+
python-versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'
5151

5252
type_check:
5353
name: Type check
5454
uses: apify/workflows/.github/workflows/python_type_check.yaml@main
5555
with:
56-
python-versions: '["3.10", "3.11", "3.12", "3.13"]'
56+
python-versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'
5757

5858
unit_tests:
5959
name: Unit tests
6060
uses: apify/workflows/.github/workflows/python_unit_tests.yaml@main
6161
secrets:
6262
httpbin_url: ${{ secrets.APIFY_HTTPBIN_TOKEN && format('https://httpbin.apify.actor?token={0}', secrets.APIFY_HTTPBIN_TOKEN) || 'https://httpbin.org'}}
6363
with:
64-
python-versions: '["3.10", "3.11", "3.12", "3.13"]'
64+
python-versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'
6565

6666
update_changelog:
6767
name: Update changelog

.github/workflows/run_code_checks.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,21 @@ jobs:
2121
name: Lint check
2222
uses: apify/workflows/.github/workflows/python_lint_check.yaml@main
2323
with:
24-
python-versions: '["3.10", "3.11", "3.12", "3.13"]'
24+
python-versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'
2525

2626
type_check:
2727
name: Type check
2828
uses: apify/workflows/.github/workflows/python_type_check.yaml@main
2929
with:
30-
python-versions: '["3.10", "3.11", "3.12", "3.13"]'
30+
python-versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'
3131

3232
unit_tests:
3333
name: Unit tests
3434
uses: apify/workflows/.github/workflows/python_unit_tests.yaml@main
3535
secrets:
3636
httpbin_url: ${{ secrets.APIFY_HTTPBIN_TOKEN && format('https://httpbin.apify.actor?token={0}', secrets.APIFY_HTTPBIN_TOKEN) || 'https://httpbin.org'}}
3737
with:
38-
python-versions: '["3.10", "3.11", "3.12", "3.13"]'
38+
python-versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'
3939

4040
docs_check:
4141
name: Docs check

.github/workflows/templates_e2e_tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77

88
env:
99
NODE_VERSION: 22
10-
PYTHON_VERSION: 3.13
10+
PYTHON_VERSION: 3.14
1111

1212
jobs:
1313
end_to_end_tests:

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ classifiers = [
2020
"Programming Language :: Python :: 3.11",
2121
"Programming Language :: Python :: 3.12",
2222
"Programming Language :: Python :: 3.13",
23+
"Programming Language :: Python :: 3.14",
2324
"Topic :: Software Development :: Libraries",
2425
]
2526
keywords = [
@@ -35,7 +36,7 @@ keywords = [
3536
dependencies = [
3637
"cachetools>=5.5.0",
3738
"colorama>=0.4.0",
38-
"impit>=0.6.1",
39+
"impit>=0.8.0",
3940
"more-itertools>=10.2.0",
4041
"protego>=0.5.0",
4142
"psutil>=6.0.0",
@@ -73,7 +74,7 @@ otel = [
7374
]
7475
sql_postgres = [
7576
"sqlalchemy[asyncio]>=2.0.0,<3.0.0",
76-
"asyncpg>=0.24.0"
77+
"asyncpg>=0.24.0; python_version < '3.14'" # TODO: https://github.com/apify/crawlee-python/issues/1555
7778
]
7879
sql_sqlite = [
7980
"sqlalchemy[asyncio]>=2.0.0,<3.0.0",

src/crawlee/storage_clients/_sql/_storage_client.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import sys
34
import warnings
45
from datetime import timedelta
56
from pathlib import Path
@@ -268,6 +269,14 @@ def _get_or_create_engine(self, configuration: Configuration) -> AsyncEngine:
268269
'Unsupported database. Supported: sqlite, postgresql. Consider using a different database.'
269270
)
270271

272+
# TODO: https://github.com/apify/crawlee-python/issues/1555
273+
if 'postgresql' in connection_string and sys.version_info >= (3, 14):
274+
raise ValueError(
275+
'SqlStorageClient cannot use PostgreSQL with Python 3.14 '
276+
'due to asyncpg compatibility limitations. '
277+
'Please use Python 3.13 or earlier, or switch to SQLite.'
278+
)
279+
271280
self._engine = create_async_engine(
272281
connection_string,
273282
future=True,

tests/unit/_utils/test_system.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import sys
4-
from multiprocessing import Barrier, Process, Value, synchronize
4+
from multiprocessing import get_context, synchronize
55
from multiprocessing.shared_memory import SharedMemory
66
from typing import TYPE_CHECKING
77

@@ -38,7 +38,9 @@ def test_memory_estimation_does_not_overestimate_due_to_shared_memory() -> None:
3838
equal to additional_memory_size_estimate_per_unshared_memory_child where the additional shared memory is exactly
3939
the same as the unshared memory.
4040
"""
41-
estimated_memory_expectation = Value('b', False) # noqa: FBT003 # Common usage pattern for multiprocessing.Value
41+
42+
ctx = get_context('fork')
43+
estimated_memory_expectation = ctx.Value('b', False) # noqa: FBT003 # Common usage pattern for multiprocessing.Value
4244

4345
def parent_process() -> None:
4446
extra_memory_size = 1024 * 1024 * 100 # 100 MB
@@ -70,8 +72,8 @@ def get_additional_memory_estimation_while_running_processes(
7072
*, target: Callable, count: int = 1, use_shared_memory: bool = False
7173
) -> float:
7274
processes = []
73-
ready = Barrier(parties=count + 1)
74-
measured = Barrier(parties=count + 1)
75+
ready = ctx.Barrier(parties=count + 1)
76+
measured = ctx.Barrier(parties=count + 1)
7577
shared_memory: None | SharedMemory = None
7678
memory_before = get_memory_info().current_size
7779

@@ -83,7 +85,7 @@ def get_additional_memory_estimation_while_running_processes(
8385
extra_args = []
8486

8587
for _ in range(count):
86-
p = Process(target=target, args=[ready, measured, *extra_args])
88+
p = ctx.Process(target=target, args=[ready, measured, *extra_args])
8789
p.start()
8890
processes.append(p)
8991

@@ -129,7 +131,7 @@ def get_additional_memory_estimation_while_running_processes(
129131
f'{memory_estimation_difference_ratio=}'
130132
)
131133

132-
process = Process(target=parent_process)
134+
process = ctx.Process(target=parent_process)
133135
process.start()
134136
process.join()
135137

tests/unit/storage_clients/_redis/test_redis_rq_client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
import json
5+
import sys
56
from typing import TYPE_CHECKING
67

78
import pytest
@@ -25,6 +26,10 @@ async def rq_client(
2526
suppress_user_warning: None, # noqa: ARG001
2627
) -> AsyncGenerator[RedisRequestQueueClient, None]:
2728
"""A fixture for a Redis RQ client."""
29+
# TODO: https://github.com/apify/crawlee-python/issues/1554
30+
if request.param == 'bloom' and sys.platform == 'win32' and sys.version_info >= (3, 14):
31+
pytest.skip('Bloom filters not supported on Windows with Python 3.14 and fakeredis')
32+
2833
client = await RedisStorageClient(redis=redis_client, queue_dedup_strategy=request.param).create_rq_client(
2934
name='test_request_queue'
3035
)

uv.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)