From 8893929b5c9ea33e826e30218c79dc9a2f972d22 Mon Sep 17 00:00:00 2001 From: cyphercodes Date: Fri, 8 May 2026 18:30:03 +0300 Subject: [PATCH 1/3] Fall back from sqlite cache when sqlite is unavailable --- mypy/build.py | 19 +++++++++++++------ mypy/test/testmetastore.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 mypy/test/testmetastore.py diff --git a/mypy/build.py b/mypy/build.py index 21a5559b329a..6ea2300702ec 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1912,14 +1912,21 @@ def exclude_from_backups(target_dir: str) -> None: def create_metastore(options: Options, parallel_worker: bool) -> MetadataStore: """Create the appropriate metadata store.""" + cache_dir_prefix = _cache_dir_prefix(options) if options.sqlite_cache: - mds: MetadataStore = SqliteMetadataStore( - _cache_dir_prefix(options), - set_journal_mode=not parallel_worker, - num_shards=options.sqlite_num_shards, - ) + try: + mds: MetadataStore = SqliteMetadataStore( + cache_dir_prefix, + set_journal_mode=not parallel_worker, + num_shards=options.sqlite_num_shards, + ) + except ImportError: + # The sqlite3 Python module can be present even when the _sqlite3 + # extension module is not available. Fall back to the filesystem + # cache instead of crashing when initializing the default cache. + mds = FilesystemMetadataStore(cache_dir_prefix) else: - mds = FilesystemMetadataStore(_cache_dir_prefix(options)) + mds = FilesystemMetadataStore(cache_dir_prefix) return mds diff --git a/mypy/test/testmetastore.py b/mypy/test/testmetastore.py new file mode 100644 index 000000000000..1624af45e4d7 --- /dev/null +++ b/mypy/test/testmetastore.py @@ -0,0 +1,33 @@ +"""Unit tests for metadata stores.""" + +from __future__ import annotations + +import tempfile +import unittest +from typing import NoReturn +from unittest.mock import patch + +from mypy import build +from mypy.metastore import FilesystemMetadataStore +from mypy.options import Options + + +class TestMetadataStore(unittest.TestCase): + def test_create_metastore_falls_back_to_filesystem_when_sqlite_missing(self) -> None: + options = Options() + options.sqlite_cache = True + + def missing_sqlite(db_file: str, set_journal_mode: bool) -> NoReturn: + raise ModuleNotFoundError("No module named '_sqlite3'") + + with tempfile.TemporaryDirectory() as tmpdir: + options.cache_dir = tmpdir + with patch("mypy.metastore.connect_db", missing_sqlite): + store = build.create_metastore(options, parallel_worker=False) + + try: + assert isinstance(store, FilesystemMetadataStore) + assert store.write("example.meta.json", b"{}") + assert store.read("example.meta.json") == b"{}" + finally: + store.close() From c7616ac3ea065258c108da2d3a47a29953055ae6 Mon Sep 17 00:00:00 2001 From: cyphercodes Date: Fri, 8 May 2026 22:36:00 +0300 Subject: [PATCH 2/3] test: make sqlite fallback test mypyc-safe --- mypy/test/testmetastore.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mypy/test/testmetastore.py b/mypy/test/testmetastore.py index 1624af45e4d7..04a8e2f3e8cc 100644 --- a/mypy/test/testmetastore.py +++ b/mypy/test/testmetastore.py @@ -4,7 +4,6 @@ import tempfile import unittest -from typing import NoReturn from unittest.mock import patch from mypy import build @@ -17,12 +16,12 @@ def test_create_metastore_falls_back_to_filesystem_when_sqlite_missing(self) -> options = Options() options.sqlite_cache = True - def missing_sqlite(db_file: str, set_journal_mode: bool) -> NoReturn: - raise ModuleNotFoundError("No module named '_sqlite3'") - with tempfile.TemporaryDirectory() as tmpdir: options.cache_dir = tmpdir - with patch("mypy.metastore.connect_db", missing_sqlite): + with patch( + "mypy.build.SqliteMetadataStore", + side_effect=ModuleNotFoundError("No module named '_sqlite3'"), + ): store = build.create_metastore(options, parallel_worker=False) try: From 1244b63f65eadbcb8a0727bb5dd9477ddcd75d0b Mon Sep 17 00:00:00 2001 From: cyphercodes Date: Fri, 8 May 2026 23:08:04 +0300 Subject: [PATCH 3/3] test: skip sqlite fallback mock under compiled mypy --- mypy/test/testmetastore.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy/test/testmetastore.py b/mypy/test/testmetastore.py index 04a8e2f3e8cc..7a7d4ae7aada 100644 --- a/mypy/test/testmetastore.py +++ b/mypy/test/testmetastore.py @@ -12,6 +12,9 @@ class TestMetadataStore(unittest.TestCase): + @unittest.skipUnless( + build.__file__.endswith(".py"), "mock patching is unreliable for compiled mypy" + ) def test_create_metastore_falls_back_to_filesystem_when_sqlite_missing(self) -> None: options = Options() options.sqlite_cache = True