Skip to content

Commit

Permalink
chore: use mypy (MODFLOW-ORG#194)
Browse files Browse the repository at this point in the history
Add mypy to lint deps, fix some typing issues, type check in CI
  • Loading branch information
wpbonelli authored Mar 9, 2025
1 parent 3858230 commit ed9c4e9
Show file tree
Hide file tree
Showing 13 changed files with 51 additions and 35 deletions.
11 changes: 7 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,21 @@ jobs:
with:
cache-dependency-glob: "**/pyproject.toml"

- name: Install project
- name: Install
run: uv sync --group lint

- name: Run ruff check
- name: Lint
run: uvx ruff check

- name: Run ruff format
- name: Format
run: uvx ruff format --check

- name: Check spelling
- name: Spelling
run: uvx codespell

- name: Typing
run: uv run mypy modflow_devtools

build:
name: Build
runs-on: ubuntu-latest
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ Python development tools for MODFLOW 6 and related projects.
- [`MODFLOW-ORG/modflow6-examples`](https://github.com/MODFLOW-ORG/modflow6-examples)
- [`MODFLOW-ORG/modflow6-testmodels`](https://github.com/MODFLOW-ORG/modflow6-testmodels)
- [`MODFLOW-ORG/modflow6-largetestmodels`](https://github.com/MODFLOW-ORG/modflow6-largetestmodels)
* a parser for MODFLOW 6 [definition files](https://modflow6.readthedocs.io/en/stable/_dev/dfn.html)

## Requirements

Expand Down
4 changes: 4 additions & 0 deletions modflow_devtools/dfn.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@

from modflow_devtools.download import download_and_unzip

# TODO: use dataclasses instead of typed dicts, static
# methods on typed dicts are evidently not allowed
# mypy: ignore-errors


def _try_literal_eval(value: str) -> Any:
"""
Expand Down
2 changes: 2 additions & 0 deletions modflow_devtools/dfn2toml.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from modflow_devtools.dfn import Dfn

# mypy: ignore-errors


def convert(indir: PathLike, outdir: PathLike):
indir = Path(indir).expanduser().absolute()
Expand Down
8 changes: 1 addition & 7 deletions modflow_devtools/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ def get_release(repo, tag="latest", retries=3, verbose=False) -> dict:
else f"{req_url}/releases/tags/{tag}"
)
request = get_request(req_url)
releases = None
num_tries = 0

while True:
Expand All @@ -169,12 +168,7 @@ def get_release(repo, tag="latest", retries=3, verbose=False) -> dict:
f"use GITHUB_TOKEN env to bypass rate limit ({err})"
) from err
elif err.code == 404:
if releases is None:
releases = get_releases(repo, verbose=verbose)
if tag not in releases:
raise ValueError(
f"Release {tag} not found (choose from {', '.join(releases)})"
)
raise ValueError(f"Release {tag} not found")
elif err.code == 503 and num_tries < retries:
# GitHub sometimes returns this error for valid URLs, so retry
warn(f"URL request {num_tries} failed ({err})")
Expand Down
3 changes: 2 additions & 1 deletion modflow_devtools/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ def session_tmpdir(tmpdir_factory, request) -> Generator[Path, None, None]:
@pytest.fixture
def repos_path() -> Path | None:
"""Path to directory containing test model and example repositories"""
return environ.get("REPOS_PATH", None)
path = environ.get("REPOS_PATH", None)
return Path(path) if path else None


@pytest.fixture
Expand Down
3 changes: 3 additions & 0 deletions modflow_devtools/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@

from packaging.version import Version

# mypy: ignore-errors


VERSIONS = {}


Expand Down
8 changes: 4 additions & 4 deletions modflow_devtools/latex.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from collections.abc import Iterable
from collections.abc import Iterable, Sequence
from os import PathLike
from pathlib import Path

Expand All @@ -7,8 +7,8 @@ def build_table(
caption: str,
fpth: str | PathLike,
arr,
headings: Iterable[str] | None = None,
col_widths: Iterable[float] | None = None,
headings: Sequence[str] | None = None,
col_widths: Sequence[float] | None = None,
):
"""
Build a LaTeX table from the given NumPy array.
Expand Down Expand Up @@ -55,7 +55,7 @@ def build_table(
def get_header(
caption: str,
label: str,
headings: Iterable[str],
headings: Sequence[str],
col_widths: Iterable[float] | None = None,
center: bool = True,
firsthead: bool = False,
Expand Down
2 changes: 1 addition & 1 deletion modflow_devtools/make_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def write_registry(
if not registry_path.exists():
registry_path.parent.mkdir(parents=True, exist_ok=True)

models = {}
models: dict[str, list[str]] = {}
exclude = [".DS_Store"]
with registry_path.open("a+" if append else "w") as f:
if not path.is_dir():
Expand Down
18 changes: 10 additions & 8 deletions modflow_devtools/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def get_packages(namefile_path: PathLike) -> list[str]:
a list of packages used by the simulation or model
"""

packages = []
packages: list[str] = []
path = Path(namefile_path).expanduser().resolve()
lines = path.read_text().splitlines()
gwf_lines = [ln for ln in lines if ln.strip().lower().startswith("gwf6 ")]
Expand All @@ -188,22 +188,22 @@ def parse_model_namefile(line):

for line in lines:
# Skip over blank and commented lines
line = line.strip().split()
if len(line) < 2:
words = line.strip().split()
if len(words) < 2:
continue

line = line[0].lower()
if any(line.startswith(c) for c in ["#", "!", "data", "list"]) or line in [
word = words[0].lower()
if any(word.startswith(c) for c in ["#", "!", "data", "list"]) or word in [
"begin",
"end",
"memory_print_option",
]:
continue

# strip "6" from package name
line = line.replace("6", "")
word = word.replace("6", "")

packages.append(line.lower())
packages.append(word.lower())

return list(set(packages))

Expand Down Expand Up @@ -293,6 +293,7 @@ def keyfunc(v):
else:
return 1

path = Path(path).expanduser().absolute()
model_paths = []
globbed = path.rglob(f"{prefix if prefix else ''}*")
example_paths = [p for p in globbed if p.is_dir()]
Expand Down Expand Up @@ -499,7 +500,8 @@ def get_env(name: str, default: object = None) -> object | None:
otherwise the default value if the environment variable is not set.
"""
try:
v = environ.get(name)
if (v := environ.get(name, None)) is None:
return default
if isinstance(default, bool):
v = v.lower().title()
v = literal_eval(v)
Expand Down
11 changes: 6 additions & 5 deletions modflow_devtools/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import importlib.resources as pkg_resources
from collections.abc import Callable
from io import IOBase
from pathlib import Path

Expand Down Expand Up @@ -29,13 +30,13 @@
)

try:
with pkg_resources.open_text(DATA_ANCHOR, REGISTRY_NAME) as f:
FETCHER.load_registry(f)
with pkg_resources.open_text(DATA_ANCHOR, REGISTRY_NAME) as rf:
FETCHER.load_registry(rf)
except: # noqa: E722
print(f"Could not load registry from {DATA_PATH}/{REGISTRY_NAME}.")


def _generate_function(model_name, files) -> callable:
def _generate_function(model_name, files) -> Callable:
def model_function():
return [Path(FETCHER.fetch(file)) for file in files]

Expand All @@ -59,7 +60,7 @@ def model_map() -> dict[str, list[Path]]:


try:
with pkg_resources.open_binary(DATA_ANCHOR, MODELMAP_NAME) as f:
_attach_functions(f)
with pkg_resources.open_binary(DATA_ANCHOR, MODELMAP_NAME) as mf:
_attach_functions(mf)
except: # noqa: E722
print(f"Could not load model mapping from {DATA_PATH}/{MODELMAP_NAME}.")
4 changes: 2 additions & 2 deletions modflow_devtools/snapshots.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from io import BytesIO, StringIO
from typing import Optional
from typing import Optional, Union

from modflow_devtools.imports import import_optional_dependency

Expand Down Expand Up @@ -107,7 +107,7 @@ def snapshot_disable(pytestconfig) -> bool:


@pytest.fixture
def snapshot(request, snapshot_disable) -> "SnapshotAssertion":
def snapshot(request, snapshot_disable) -> Union[MatchAnything, "SnapshotAssertion"]:
return (
MatchAnything()
if snapshot_disable
Expand Down
11 changes: 9 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ build = [
]
lint = [
"codespell[toml]",
"ruff"
"ruff",
"mypy"
]
test = [
"modflow-devtools[lint]",
Expand Down Expand Up @@ -87,7 +88,8 @@ build = [
]
lint = [
"codespell[toml]",
"ruff"
"ruff",
"mypy",
]
test = [
"modflow-devtools[lint]",
Expand Down Expand Up @@ -178,3 +180,8 @@ select = [

[tool.pytest.ini_options]
addopts = ["--import-mode=importlib"]

[tool.mypy]
mypy_path = "modflow_devtools"
ignore_missing_imports = true
warn_unreachable = true

0 comments on commit ed9c4e9

Please sign in to comment.