Skip to content

Commit 2506375

Browse files
feat: python 3.14 support (#6712)
## 📝 Summary Add python 3.14 (🥧!) to our ci (https://docs.python.org/3.14/whatsnew/3.14.html) <img width="447" height="140" alt="image" src="https://github.com/user-attachments/assets/2dbd0183-0abe-4464-9b56-fed17fd0d80d" /> Also add a note to remove 3.9 in a month (https://devguide.python.org/versions/) --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 437f681 commit 2506375

File tree

7 files changed

+63
-32
lines changed

7 files changed

+63
-32
lines changed

.github/workflows/test_be.yaml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ jobs:
5959
matrix:
6060
os: [ubuntu-latest, macos-latest, windows-latest]
6161
dependencies: ["core", "core,optional"]
62-
python-version: ["3.9"]
62+
python-version: ["3.10"]
6363
include:
6464
- os: ubuntu-latest
6565
python-version: "3.10"
66-
dependencies: "core"
66+
dependencies: "minimal"
6767
- os: ubuntu-latest
6868
python-version: "3.11"
6969
dependencies: "core"
@@ -74,8 +74,8 @@ jobs:
7474
python-version: "3.13"
7575
dependencies: "core"
7676
- os: ubuntu-latest
77-
python-version: "3.9"
78-
dependencies: "core,optional"
77+
python-version: "3.14"
78+
dependencies: "core"
7979
- os: ubuntu-latest
8080
python-version: "3.10"
8181
dependencies: "core,optional"
@@ -88,9 +88,7 @@ jobs:
8888
- os: ubuntu-latest
8989
python-version: "3.13"
9090
dependencies: "core,optional"
91-
- os: ubuntu-latest
92-
python-version: "3.9"
93-
dependencies: "minimal"
91+
# TODO: Add in 3.14 optional once there is broader wheel support
9492
steps:
9593
- name: 🛑 Cancel Previous Runs
9694
uses: styfle/cancel-workflow-action@0.12.1
@@ -115,6 +113,11 @@ jobs:
115113
if: ${{ matrix.python-version == '3.12' }}
116114
run: hatch run typecheck:check
117115

116+
# Required since python3.14 not pulled automatically for now.
117+
- uses: actions/setup-python@v6
118+
with:
119+
python-version: ${{ matrix.python-version }}
120+
118121
# Test with base dependencies
119122
- name: Test with base dependencies
120123
if: ${{ matrix.dependencies == 'core' }}

marimo/_messaging/ops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ class ColumnPreview(msgspec.Struct):
614614

615615

616616
# We shouldn't need to make table_name and column_name have default values.
617-
# We can use kw_only=True once we drop support for Python 3.9.
617+
# We can use kw_only=True once we drop support for Python 3.9 (25-11-01).
618618
class DataColumnPreview(Op, ColumnPreview, tag="data-column-preview"):
619619
"""Preview of a column in a dataset."""
620620

marimo/_plugins/ui/_core/registry.py

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

2323
T = TypeVar("T")
2424

25-
# Recursive types don't support | or dict[] in py3.8/3.9
25+
# Recursive types don't support | or dict[] in py3.8/3.9 (25-11-01)
2626
LensValue: TypeAlias = Union[T, dict[str, "LensValue[T]"]]
2727

2828

pixi.lock

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

pyproject.toml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ dependencies = [
2020
# Pinned to specific version for introduction of codeblock handling.
2121
"pymdown-extensions>=10.15,<11",
2222
# syntax highlighting of code in markdown
23-
"pygments>=2.13,<3",
23+
"pygments>=2.19,<3",
2424
# for reading, writing configs
2525
"tomlkit>= 0.12.0",
2626
# for managing frontmatter headers in markdown
@@ -36,7 +36,7 @@ dependencies = [
3636
# websockets for use with starlette, and for lsp
3737
"websockets >= 14.2.0",
3838
# loro for collaborative editing
39-
"loro>=1.5.0; python_version >= '3.11'",
39+
"loro>=1.5.0; python_version >= '3.11' and python_version < '3.14'",
4040
# python <=3.10 compatibility
4141
"typing_extensions>=4.4.0; python_version < '3.11'",
4242
# for rst parsing; lowerbound determined by awscli requiring < 0.17,
@@ -68,6 +68,7 @@ classifiers = [
6868
"Programming Language :: Python :: 3.11",
6969
"Programming Language :: Python :: 3.12",
7070
"Programming Language :: Python :: 3.13",
71+
"Programming Language :: Python :: 3.14",
7172
"Programming Language :: Python :: 3 :: Only",
7273
]
7374

@@ -195,18 +196,21 @@ extra-dependencies = [
195196
"hypothesis~=6.102.1",
196197
# For server testing
197198
"httpx~=0.27.0",
198-
"matplotlib~=3.9.2",
199+
"urllib3~=2.5.0",
200+
"matplotlib~=3.10.7",
201+
# Forced for modern matplotlib.
202+
"pillow>=9",
199203
"pytest~=8.3.4",
200204
"pytest-timeout~=2.3.1",
201-
"pytest-codecov~=0.6.1",
205+
"pytest-codecov~=0.7.0",
202206
"pytest-rerunfailures~=15.1",
203207
"pytest-asyncio~=0.26.0",
204208
"pytest-picked>=0.5.1",
205209
"inline-snapshot~=0.29.0",
206210
]
207211

208212
[[tool.hatch.envs.test.matrix]]
209-
python = ["3.9", "3.10", "3.11", "3.12", "3.13"]
213+
python = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
210214

211215
[tool.hatch.envs.test.scripts]
212216
test = "pytest{env:HATCH_TEST_ARGS:} {args:tests}"
@@ -284,7 +288,7 @@ extra-dependencies = [
284288
]
285289

286290
[[tool.hatch.envs.test-optional.matrix]]
287-
python = ["3.9", "3.10", "3.11", "3.12", "3.13"]
291+
python = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
288292

289293
[tool.hatch.envs.docs]
290294
dependencies = [

tests/_server/api/endpoints/test_ws_rtc.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ def rtc_enabled(config: UserConfigManager):
110110
ws_2_sync = "/ws_sync?session_id=456&access_token=fake-token"
111111

112112

113-
@pytest.mark.skipif("sys.version_info < (3, 11)")
113+
@pytest.mark.skipif(
114+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
115+
)
114116
async def test_loro_sync(client: TestClient) -> None:
115117
"""Test that Loro-CRDT sync works between multiple clients"""
116118

@@ -147,7 +149,9 @@ async def test_loro_sync(client: TestClient) -> None:
147149
client.post("/api/kernel/shutdown", headers=HEADERS)
148150

149151

150-
@pytest.mark.skipif("sys.version_info < (3, 11)")
152+
@pytest.mark.skipif(
153+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
154+
)
151155
async def test_loro_cleanup_on_session_close(
152156
client: TestClient,
153157
) -> None:
@@ -192,7 +196,9 @@ async def test_loro_cleanup_on_session_close(
192196
client.post("/api/kernel/shutdown", headers=HEADERS)
193197

194198

195-
@pytest.mark.skipif("sys.version_info < (3, 11)")
199+
@pytest.mark.skipif(
200+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
201+
)
196202
async def test_loro_persistence(client: TestClient) -> None:
197203
"""Test that cell content persists between connections"""
198204
from loro import ExportMode, LoroDoc

tests/_server/rtc/test_rtc_doc.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from marimo._server.rtc.doc import LoroDocManager
1010
from marimo._types.ids import CellId_t
1111

12-
if sys.version_info >= (3, 11):
12+
if sys.version_info >= (3, 11) and sys.version_info < (3, 14):
1313
from loro import LoroDoc, LoroText
1414

1515
doc_manager = LoroDocManager()
@@ -29,7 +29,9 @@ async def setup_doc_manager() -> AsyncGenerator[None, None]:
2929
doc_manager.loro_docs_cleaners.clear()
3030

3131

32-
@pytest.mark.skipif("sys.version_info < (3, 11)")
32+
@pytest.mark.skipif(
33+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
34+
)
3335
async def test_quick_reconnection(setup_doc_manager: None) -> None:
3436
"""Test that quick reconnection properly handles cleanup task cancellation"""
3537
del setup_doc_manager
@@ -65,7 +67,9 @@ async def test_quick_reconnection(setup_doc_manager: None) -> None:
6567
) # Original client + reconnected client
6668

6769

68-
@pytest.mark.skipif("sys.version_info < (3, 11)")
70+
@pytest.mark.skipif(
71+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
72+
)
6973
async def test_two_users_sync(setup_doc_manager: None) -> None:
7074
"""Test that two users can connect and sync text properly without duplicates"""
7175
del setup_doc_manager
@@ -111,7 +115,9 @@ async def test_two_users_sync(setup_doc_manager: None) -> None:
111115
assert lang_text_typed.to_string() == "python"
112116

113117

114-
@pytest.mark.skipif("sys.version_info < (3, 11)")
118+
@pytest.mark.skipif(
119+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
120+
)
115121
async def test_concurrent_doc_creation(setup_doc_manager: None) -> None:
116122
"""Test concurrent doc creation doesn't cause issues"""
117123
del setup_doc_manager
@@ -130,7 +136,9 @@ async def test_concurrent_doc_creation(setup_doc_manager: None) -> None:
130136
assert len(doc_manager.loro_docs) == 1
131137

132138

133-
@pytest.mark.skipif("sys.version_info < (3, 11)")
139+
@pytest.mark.skipif(
140+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
141+
)
134142
async def test_concurrent_client_operations(
135143
setup_doc_manager: None,
136144
) -> None:
@@ -157,7 +165,9 @@ async def client_operation(queue: asyncio.Queue[bytes]) -> None:
157165
assert len(doc_manager.loro_docs_clients[file_key]) == 0
158166

159167

160-
@pytest.mark.skipif("sys.version_info < (3, 11)")
168+
@pytest.mark.skipif(
169+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
170+
)
161171
async def test_cleanup_task_management(setup_doc_manager: None) -> None:
162172
"""Test cleanup task management and cancellation"""
163173
del setup_doc_manager
@@ -189,7 +199,9 @@ async def test_cleanup_task_management(setup_doc_manager: None) -> None:
189199
await doc_manager.remove_client(file_key, new_queue)
190200

191201

192-
@pytest.mark.skipif("sys.version_info < (3, 11)")
202+
@pytest.mark.skipif(
203+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
204+
)
193205
async def test_broadcast_update(setup_doc_manager: None) -> None:
194206
"""Test broadcast update functionality"""
195207
del setup_doc_manager
@@ -215,7 +227,9 @@ async def test_broadcast_update(setup_doc_manager: None) -> None:
215227
assert await queue.get() == message
216228

217229

218-
@pytest.mark.skipif("sys.version_info < (3, 11)")
230+
@pytest.mark.skipif(
231+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
232+
)
219233
async def test_remove_nonexistent_doc(setup_doc_manager: None) -> None:
220234
"""Test removing a doc that doesn't exist"""
221235
del setup_doc_manager
@@ -226,7 +240,9 @@ async def test_remove_nonexistent_doc(setup_doc_manager: None) -> None:
226240
assert file_key not in doc_manager.loro_docs_cleaners
227241

228242

229-
@pytest.mark.skipif("sys.version_info < (3, 11)")
243+
@pytest.mark.skipif(
244+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
245+
)
230246
async def test_remove_nonexistent_client(setup_doc_manager: None) -> None:
231247
"""Test removing a client that doesn't exist"""
232248
del setup_doc_manager
@@ -236,7 +252,9 @@ async def test_remove_nonexistent_client(setup_doc_manager: None) -> None:
236252
assert file_key not in doc_manager.loro_docs_clients
237253

238254

239-
@pytest.mark.skipif("sys.version_info < (3, 11)")
255+
@pytest.mark.skipif(
256+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
257+
)
240258
async def test_concurrent_doc_removal(setup_doc_manager: None) -> None:
241259
"""Test concurrent doc removal doesn't cause issues"""
242260
del setup_doc_manager
@@ -255,7 +273,7 @@ async def test_concurrent_doc_removal(setup_doc_manager: None) -> None:
255273

256274

257275
@pytest.mark.skipif(
258-
sys.version_info < (3, 11), reason="Python 3.10+ required for Barrier"
276+
"sys.version_info < (3, 11) or sys.version_info >= (3, 14)"
259277
)
260278
async def test_prevent_lock_deadlock(setup_doc_manager: None) -> None:
261279
"""Test that our deadlock prevention measures work correctly.

0 commit comments

Comments
 (0)