Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,32 @@ jobs:
- name: Run tests
run: pytest

test-mypy-compat:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
mypy-version:
- "~=1.3.0" # Lowest supported mypy version
- "~=1.19.1"
- "~=2.0.0"

steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
# 3.11 is the highest Python supported by the lowest mypy in the matrix.
python-version: "3.11"
- name: Install dependencies
run: |
pip install -U pip setuptools wheel
pip install -e .
pip install -U "mypy${{ matrix.mypy-version }}"
- name: Run cache-cleanup tests
run: pytest pytest_mypy_plugins/tests/test_mypy_cache.py

lint:
runs-on: ubuntu-latest

Expand Down
8 changes: 4 additions & 4 deletions pytest_mypy_plugins/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def join_toml_configs(
# useful for overrides further.
toml_config = tomlkit.document()

if "tool" not in toml_config or "mypy" not in toml_config["tool"]: # type: ignore[operator]
if "tool" not in toml_config or "mypy" not in toml_config["tool"]:
tool = tomlkit.table(is_super_table=True)
tool.append("mypy", tomlkit.table())
toml_config.append("tool", tool)
Expand All @@ -46,15 +46,15 @@ def join_toml_configs(
additional_mypy_config = f"{_TOML_TABLE_NAME}\n{dedent(additional_mypy_config)}"

additional_data = tomlkit.parse(additional_mypy_config)
toml_config["tool"]["mypy"].update( # type: ignore[index, union-attr]
additional_data["tool"]["mypy"].value.items(), # type: ignore[index]
toml_config["tool"]["mypy"].update(
additional_data["tool"]["mypy"].value.items(),
)

mypy_config_file_path = execution_path / "pyproject.toml"
with mypy_config_file_path.open("w") as f:
# We don't want the whole config file, because it can contain
# other sections like `[tool.isort]`, we only need `[tool.mypy]` part.
tool_mypy = toml_config["tool"]["mypy"] # type: ignore[index]
tool_mypy = toml_config["tool"]["mypy"]

# construct toml output
min_toml = tomlkit.document()
Expand Down
26 changes: 24 additions & 2 deletions pytest_mypy_plugins/item.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import glob
import importlib
import io
import os
Expand Down Expand Up @@ -374,7 +375,17 @@ def remove_cache_files(self, fpath_no_suffix: Path) -> None:

# Build entry names to remove for each path component so namespace-package
# cache entries are also cleaned (e.g. "pkg.*", "pkg/sub.*", "pkg/sub/mod.*").
suffixes = (".meta.json", ".data.json", ".err.json", ".meta.ff", ".data.ff", ".err.ff")
suffixes = (
".meta.json",
".data.json",
".err.json",
".meta.ff",
".data.ff",
".err.ff",
# mypy >= 2.0 also writes a `.meta_ex` companion file
".meta_ex.json",
".meta_ex.ff",
)
entries: list[str] = []
accumulated = ""
for i, part in enumerate(fpath_no_suffix.parts):
Expand All @@ -388,7 +399,18 @@ def remove_cache_files(self, fpath_no_suffix: Path) -> None:
entries.extend(accumulated + s for s in suffixes)

stores: list[MetadataStore] = []
if os.path.isfile(os.path.join(cache_dir, "cache.db")):

# mypy >= 2.0 shards the sqlite cache across `cache.{i}.db` files instead of one `cache.db` file
# https://github.com/python/mypy/pull/21292
shard_dbs = glob.glob(os.path.join(cache_dir, "cache.*.db"))
if shard_dbs:
try:
# `num_shards` was added in mypy 2.0; pinned lint mypy is older.
stores.append(SqliteMetadataStore(cache_dir, num_shards=len(shard_dbs))) # type: ignore[call-arg]
except TypeError:
# For backward compatibility with `SqliteMetadataStore` with no `num_shards` arg.
stores.append(SqliteMetadataStore(cache_dir))
elif os.path.isfile(os.path.join(cache_dir, "cache.db")):
stores.append(SqliteMetadataStore(cache_dir))
stores.append(FilesystemMetadataStore(cache_dir))

Expand Down
14 changes: 12 additions & 2 deletions pytest_mypy_plugins/tests/test_mypy_cache.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import glob
import os
import subprocess
from collections.abc import Generator
from pathlib import Path
Expand Down Expand Up @@ -212,7 +214,15 @@ def make_yaml_test_file(
def get_created_cache_files(cache_dir: Path, module_rel_paths_no_suffix: tuple[str, ...]) -> list[str]:
stores: list[MetadataStore] = []
cache_dir_str = str(cache_dir)
if (cache_dir / "cache.db").is_file():
# mypy >= 2.0 shards the sqlite cache across `cache.{i}.db` files instead of one `cache.db` file
shard_dbs = glob.glob(os.path.join(cache_dir_str, "cache.*.db"))
if shard_dbs:
try:
# `num_shards` was added in mypy 2.0; pinned lint mypy is older.
stores.append(SqliteMetadataStore(cache_dir_str, num_shards=len(shard_dbs))) # type: ignore[call-arg]
except TypeError:
stores.append(SqliteMetadataStore(cache_dir_str))
elif (cache_dir / "cache.db").is_file():
stores.append(SqliteMetadataStore(cache_dir_str))
if cache_dir.is_dir():
stores.append(FilesystemMetadataStore(cache_dir_str))
Expand All @@ -221,7 +231,7 @@ def get_created_cache_files(cache_dir: Path, module_rel_paths_no_suffix: tuple[s
for store in stores:
try:
for entry in store.list_all():
if any(entry.startswith(rel_path + ".") for rel_path in module_rel_paths_no_suffix):
if any(os.path.normpath(entry).startswith(rel_path + ".") for rel_path in module_rel_paths_no_suffix):
created.append(entry)
finally:
# See PR #188: mypy < 1.20 does not have MetadataStore.close
Expand Down