diff --git a/.github/workflows/test_be.yaml b/.github/workflows/test_be.yaml index 30c733f72e2..7ea6115430c 100644 --- a/.github/workflows/test_be.yaml +++ b/.github/workflows/test_be.yaml @@ -59,11 +59,11 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] dependencies: ["core", "core,optional"] - python-version: ["3.9"] + python-version: ["3.10"] include: - os: ubuntu-latest python-version: "3.10" - dependencies: "core" + dependencies: "minimal" - os: ubuntu-latest python-version: "3.11" dependencies: "core" @@ -74,8 +74,8 @@ jobs: python-version: "3.13" dependencies: "core" - os: ubuntu-latest - python-version: "3.9" - dependencies: "core,optional" + python-version: "3.14" + dependencies: "core" - os: ubuntu-latest python-version: "3.10" dependencies: "core,optional" @@ -88,9 +88,7 @@ jobs: - os: ubuntu-latest python-version: "3.13" dependencies: "core,optional" - - os: ubuntu-latest - python-version: "3.9" - dependencies: "minimal" + # TODO: Add in 3.14 optional once there is broader wheel support steps: - name: 🛑 Cancel Previous Runs uses: styfle/cancel-workflow-action@0.12.1 @@ -115,6 +113,11 @@ jobs: if: ${{ matrix.python-version == '3.12' }} run: hatch run typecheck:check + # Required since python3.14 not pulled automatically for now. + - uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + # Test with base dependencies - name: Test with base dependencies if: ${{ matrix.dependencies == 'core' }} diff --git a/marimo/_messaging/ops.py b/marimo/_messaging/ops.py index 6ffcb27cc7a..4f3844f3d67 100644 --- a/marimo/_messaging/ops.py +++ b/marimo/_messaging/ops.py @@ -614,7 +614,7 @@ class ColumnPreview(msgspec.Struct): # We shouldn't need to make table_name and column_name have default values. -# We can use kw_only=True once we drop support for Python 3.9. +# We can use kw_only=True once we drop support for Python 3.9 (25-11-01). class DataColumnPreview(Op, ColumnPreview, tag="data-column-preview"): """Preview of a column in a dataset.""" diff --git a/marimo/_plugins/ui/_core/registry.py b/marimo/_plugins/ui/_core/registry.py index bf3b20b3056..f6c742bb66e 100644 --- a/marimo/_plugins/ui/_core/registry.py +++ b/marimo/_plugins/ui/_core/registry.py @@ -22,7 +22,7 @@ T = TypeVar("T") -# Recursive types don't support | or dict[] in py3.8/3.9 +# Recursive types don't support | or dict[] in py3.8/3.9 (25-11-01) LensValue: TypeAlias = Union[T, dict[str, "LensValue[T]"]] diff --git a/pixi.lock b/pixi.lock index 1dccd201342..12dcf9b355e 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1101,19 +1101,19 @@ packages: - pypi: ./ name: marimo version: 0.16.5 - sha256: 5558c60bca6ce981e35b3b1a108f3e8f289b0e9df1a04a4020be1edfc92f795b + sha256: 00660bfd80ed86fe2742590ac0f59cbd50d764290e29293c6a16beb233d9632b requires_dist: - click>=8.0,<9 - jedi>=0.18.0 - markdown>=3.6,<4 - pymdown-extensions>=10.15,<11 - - pygments>=2.13,<3 + - pygments>=2.19,<3 - tomlkit>=0.12.0 - pyyaml>=6.0 - uvicorn>=0.22.0,<0.36.0 - starlette>=0.35.0,!=0.36.0 - websockets>=14.2.0 - - loro>=1.5.0 ; python_full_version >= '3.11' + - loro>=1.5.0 ; python_full_version >= '3.11' and python_full_version < '3.14' - typing-extensions>=4.4.0 ; python_full_version < '3.11' - docutils>=0.16.0 - psutil>=5.0 diff --git a/pyproject.toml b/pyproject.toml index e463727beed..322676522ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ # Pinned to specific version for introduction of codeblock handling. "pymdown-extensions>=10.15,<11", # syntax highlighting of code in markdown - "pygments>=2.13,<3", + "pygments>=2.19,<3", # for reading, writing configs "tomlkit>= 0.12.0", # for managing frontmatter headers in markdown @@ -36,7 +36,7 @@ dependencies = [ # websockets for use with starlette, and for lsp "websockets >= 14.2.0", # loro for collaborative editing - "loro>=1.5.0; python_version >= '3.11'", + "loro>=1.5.0; python_version >= '3.11' and python_version < '3.14'", # python <=3.10 compatibility "typing_extensions>=4.4.0; python_version < '3.11'", # for rst parsing; lowerbound determined by awscli requiring < 0.17, @@ -68,6 +68,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3 :: Only", ] @@ -195,10 +196,13 @@ extra-dependencies = [ "hypothesis~=6.102.1", # For server testing "httpx~=0.27.0", - "matplotlib~=3.9.2", + "urllib3~=2.5.0", + "matplotlib~=3.10.7", + # Forced for modern matplotlib. + "pillow>=9", "pytest~=8.3.4", "pytest-timeout~=2.3.1", - "pytest-codecov~=0.6.1", + "pytest-codecov~=0.7.0", "pytest-rerunfailures~=15.1", "pytest-asyncio~=0.26.0", "pytest-picked>=0.5.1", @@ -206,7 +210,7 @@ extra-dependencies = [ ] [[tool.hatch.envs.test.matrix]] -python = ["3.9", "3.10", "3.11", "3.12", "3.13"] +python = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] [tool.hatch.envs.test.scripts] test = "pytest{env:HATCH_TEST_ARGS:} {args:tests}" @@ -284,7 +288,7 @@ extra-dependencies = [ ] [[tool.hatch.envs.test-optional.matrix]] -python = ["3.9", "3.10", "3.11", "3.12", "3.13"] +python = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] [tool.hatch.envs.docs] dependencies = [ diff --git a/tests/_server/api/endpoints/test_ws_rtc.py b/tests/_server/api/endpoints/test_ws_rtc.py index 760a6b1aaf2..0d74c2a5e1e 100644 --- a/tests/_server/api/endpoints/test_ws_rtc.py +++ b/tests/_server/api/endpoints/test_ws_rtc.py @@ -110,7 +110,9 @@ def rtc_enabled(config: UserConfigManager): ws_2_sync = "/ws_sync?session_id=456&access_token=fake-token" -@pytest.mark.skipif("sys.version_info < (3, 11)") +@pytest.mark.skipif( + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" +) async def test_loro_sync(client: TestClient) -> None: """Test that Loro-CRDT sync works between multiple clients""" @@ -147,7 +149,9 @@ async def test_loro_sync(client: TestClient) -> None: client.post("/api/kernel/shutdown", headers=HEADERS) -@pytest.mark.skipif("sys.version_info < (3, 11)") +@pytest.mark.skipif( + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" +) async def test_loro_cleanup_on_session_close( client: TestClient, ) -> None: @@ -192,7 +196,9 @@ async def test_loro_cleanup_on_session_close( client.post("/api/kernel/shutdown", headers=HEADERS) -@pytest.mark.skipif("sys.version_info < (3, 11)") +@pytest.mark.skipif( + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" +) async def test_loro_persistence(client: TestClient) -> None: """Test that cell content persists between connections""" from loro import ExportMode, LoroDoc diff --git a/tests/_server/rtc/test_rtc_doc.py b/tests/_server/rtc/test_rtc_doc.py index c96cec72eb4..64831a70c88 100644 --- a/tests/_server/rtc/test_rtc_doc.py +++ b/tests/_server/rtc/test_rtc_doc.py @@ -9,7 +9,7 @@ from marimo._server.rtc.doc import LoroDocManager from marimo._types.ids import CellId_t -if sys.version_info >= (3, 11): +if sys.version_info >= (3, 11) and sys.version_info < (3, 14): from loro import LoroDoc, LoroText doc_manager = LoroDocManager() @@ -29,7 +29,9 @@ async def setup_doc_manager() -> AsyncGenerator[None, None]: doc_manager.loro_docs_cleaners.clear() -@pytest.mark.skipif("sys.version_info < (3, 11)") +@pytest.mark.skipif( + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" +) async def test_quick_reconnection(setup_doc_manager: None) -> None: """Test that quick reconnection properly handles cleanup task cancellation""" del setup_doc_manager @@ -65,7 +67,9 @@ async def test_quick_reconnection(setup_doc_manager: None) -> None: ) # Original client + reconnected client -@pytest.mark.skipif("sys.version_info < (3, 11)") +@pytest.mark.skipif( + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" +) async def test_two_users_sync(setup_doc_manager: None) -> None: """Test that two users can connect and sync text properly without duplicates""" del setup_doc_manager @@ -111,7 +115,9 @@ async def test_two_users_sync(setup_doc_manager: None) -> None: assert lang_text_typed.to_string() == "python" -@pytest.mark.skipif("sys.version_info < (3, 11)") +@pytest.mark.skipif( + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" +) async def test_concurrent_doc_creation(setup_doc_manager: None) -> None: """Test concurrent doc creation doesn't cause issues""" del setup_doc_manager @@ -130,7 +136,9 @@ async def test_concurrent_doc_creation(setup_doc_manager: None) -> None: assert len(doc_manager.loro_docs) == 1 -@pytest.mark.skipif("sys.version_info < (3, 11)") +@pytest.mark.skipif( + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" +) async def test_concurrent_client_operations( setup_doc_manager: None, ) -> None: @@ -157,7 +165,9 @@ async def client_operation(queue: asyncio.Queue[bytes]) -> None: assert len(doc_manager.loro_docs_clients[file_key]) == 0 -@pytest.mark.skipif("sys.version_info < (3, 11)") +@pytest.mark.skipif( + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" +) async def test_cleanup_task_management(setup_doc_manager: None) -> None: """Test cleanup task management and cancellation""" del setup_doc_manager @@ -189,7 +199,9 @@ async def test_cleanup_task_management(setup_doc_manager: None) -> None: await doc_manager.remove_client(file_key, new_queue) -@pytest.mark.skipif("sys.version_info < (3, 11)") +@pytest.mark.skipif( + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" +) async def test_broadcast_update(setup_doc_manager: None) -> None: """Test broadcast update functionality""" del setup_doc_manager @@ -215,7 +227,9 @@ async def test_broadcast_update(setup_doc_manager: None) -> None: assert await queue.get() == message -@pytest.mark.skipif("sys.version_info < (3, 11)") +@pytest.mark.skipif( + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" +) async def test_remove_nonexistent_doc(setup_doc_manager: None) -> None: """Test removing a doc that doesn't exist""" del setup_doc_manager @@ -226,7 +240,9 @@ async def test_remove_nonexistent_doc(setup_doc_manager: None) -> None: assert file_key not in doc_manager.loro_docs_cleaners -@pytest.mark.skipif("sys.version_info < (3, 11)") +@pytest.mark.skipif( + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" +) async def test_remove_nonexistent_client(setup_doc_manager: None) -> None: """Test removing a client that doesn't exist""" del setup_doc_manager @@ -236,7 +252,9 @@ async def test_remove_nonexistent_client(setup_doc_manager: None) -> None: assert file_key not in doc_manager.loro_docs_clients -@pytest.mark.skipif("sys.version_info < (3, 11)") +@pytest.mark.skipif( + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" +) async def test_concurrent_doc_removal(setup_doc_manager: None) -> None: """Test concurrent doc removal doesn't cause issues""" del setup_doc_manager @@ -255,7 +273,7 @@ async def test_concurrent_doc_removal(setup_doc_manager: None) -> None: @pytest.mark.skipif( - sys.version_info < (3, 11), reason="Python 3.10+ required for Barrier" + "sys.version_info < (3, 11) or sys.version_info >= (3, 14)" ) async def test_prevent_lock_deadlock(setup_doc_manager: None) -> None: """Test that our deadlock prevention measures work correctly.