Use mypy.stubtest in CI by lagru · Pull Request #25 · scientific-python/docstub · GitHub
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
12 changes: 11 additions & 1 deletion .github/workflows/ci.yml
2 changes: 1 addition & 1 deletion examples/example_pkg-stubs/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated with docstub. Manual edits will be overwritten!
# File generated with docstub

import _numpy as np_
from _basic import func_contains
Expand Down
3 changes: 2 additions & 1 deletion examples/example_pkg-stubs/_basic.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Generated with docstub. Manual edits will be overwritten!
# File generated with docstub

import configparser
import logging
from collections.abc import Sequence
Expand Down
3 changes: 2 additions & 1 deletion examples/example_pkg-stubs/_numpy.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Generated with docstub. Manual edits will be overwritten!
# File generated with docstub

import numpy as np
from numpy.typing import ArrayLike, NDArray

Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ dev = [
test = [
"pytest >=5.0.0",
"pytest-cov >= 5.0.0",
"mypy",
]

[project.urls]
Expand Down Expand Up @@ -80,6 +81,7 @@ extend-select = [
"UP", # pyupgrade
"YTT", # flake8-2020
"EXE", # flake8-executable
# "PYI", # flake8-pyi
]
ignore = [
"PLR09", # Too many <...>
Expand Down
25 changes: 16 additions & 9 deletions src/docstub/_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ class KnownImport:
<KnownImport 'from numpy import uint8 as ui8'>
"""

import_name: str = None
import_path: str = None
import_alias: str = None
builtin_name: str = None
# docstub: off
import_name: str | None = None
import_path: str | None = None
import_alias: str | None = None
builtin_name: str | None = None
# docstub: on

@classmethod
@cache
Expand Down Expand Up @@ -194,15 +196,15 @@ def __post_init__(self):
elif self.import_name is None:
raise ValueError("non builtin must at least define an `import_name`")

def __repr__(self):
def __repr__(self) -> str:
if self.builtin_name:
info = f"{self.target} (builtin)"
else:
info = f"{self.format_import()!r}"
out = f"<{type(self).__name__} {info}>"
return out

def __str__(self):
def __str__(self) -> str:
out = self.format_import()
return out

Expand Down Expand Up @@ -406,7 +408,10 @@ class TypesDatabase:

Attributes
----------
current_source : ~.PackageFile | None
current_source : Path | None
source_pkgs : list[Path]
known_imports: dict[str, KnownImport]
stats : dict[str, Any]

Examples
--------
Expand All @@ -427,11 +432,13 @@ def __init__(
----------
source_pkgs: list[Path], optional
known_imports: dict[str, KnownImport], optional
If not provided, defaults to imports returned by
:func:`common_known_imports`.
"""
if source_pkgs is None:
source_pkgs = []
if known_imports is None:
known_imports = {}
known_imports = common_known_imports()

self.current_source = None
self.source_pkgs = source_pkgs
Expand Down Expand Up @@ -524,6 +531,6 @@ def query(self, search_name):

return annotation_name, known_import

def __repr__(self):
def __repr__(self) -> str:
repr = f"{type(self).__name__}({self.source_pkgs})"
return repr
2 changes: 1 addition & 1 deletion src/docstub/_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def create_cache(path):

gitignore_path = path / ".gitignore"
gitignore_content = (
"# This file is a cache directory tag automatically created by docstub.\n" "*\n"
"# This file is a cache directory automatically created by docstub.\n" "*\n"
)
if not gitignore_path.is_file():
with open(gitignore_path, "w") as fp:
Expand Down
28 changes: 23 additions & 5 deletions src/docstub/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@
)
from ._cache import FileCache
from ._config import Config
from ._stubs import Py2StubTransformer, walk_source, walk_source_and_targets
from ._stubs import (
Py2StubTransformer,
try_format_stub,
walk_source,
walk_source_and_targets,
)
from ._version import __version__

logger = logging.getLogger(__name__)


STUB_HEADER_COMMENT = "# File generated with docstub"


def _load_configuration(config_path=None):
"""Load and merge configuration from CWD and optional files.

Expand Down Expand Up @@ -139,6 +147,14 @@ def report_execution_time():
@click.help_option("-h", "--help")
@report_execution_time()
def main(source_dir, out_dir, config_path, verbose):
"""
Parameters
----------
source_dir : Path
out_dir : Path
config_path : Path
verbose : str
"""
_setup_logging(verbose=verbose)

source_dir = Path(source_dir)
Expand Down Expand Up @@ -171,6 +187,8 @@ def main(source_dir, out_dir, config_path, verbose):
stub_content = stub_transformer.python_to_stub(
py_content, module_path=source_path
)
stub_content = f"{STUB_HEADER_COMMENT}\n\n{stub_content}"
stub_content = try_format_stub(stub_content)
except (SystemExit, KeyboardInterrupt):
raise
except Exception as e:
Expand All @@ -185,14 +203,14 @@ def main(source_dir, out_dir, config_path, verbose):
successful_queries = types_db.stats["successful_queries"]
click.secho(f"{successful_queries} matched annotations", fg="green")

grammar_errors = stub_transformer.transformer.stats["grammar_errors"]
if grammar_errors:
click.secho(f"{grammar_errors} grammar violations", fg="red")
grammar_error_count = stub_transformer.transformer.stats["grammar_errors"]
if grammar_error_count:
click.secho(f"{grammar_error_count} grammar violations", fg="red")

unknown_doctypes = types_db.stats["unknown_doctypes"]
if unknown_doctypes:
click.secho(f"{len(unknown_doctypes)} unknown doctypes:", fg="red")
click.echo(" " + "\n ".join(set(unknown_doctypes)))

if unknown_doctypes or grammar_errors:
if unknown_doctypes or grammar_error_count:
sys.exit(1)
32 changes: 28 additions & 4 deletions src/docstub/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,17 @@ class Config:
_source: tuple[Path, ...] = ()

@classmethod
def from_toml(cls, path: Path | str) -> "Config":
"""Return configuration options in local TOML file if they exist."""
def from_toml(cls, path):
"""Return configuration options in local TOML file if they exist.

Parameters
----------
path : Path or str

Returns
-------
config : Self
"""
path = Path(path)
with open(path, "rb") as fp:
raw = tomllib.load(fp)
Expand All @@ -29,11 +38,26 @@ def from_toml(cls, path: Path | str) -> "Config":

@classmethod
def from_default(cls):
"""Create a configuration with default values.

Returns
-------
config : Self
"""
config = cls.from_toml(cls.DEFAULT_CONFIG_PATH)
return config

def merge(self, other):
"""Merge contents with other and return a new Config instance."""
"""Merge contents with other and return a new Config instance.

Parameters
----------
other : Self

Returns
-------
merged : Self
"""
if not isinstance(other, type(self)):
return NotImplemented
new = Config(
Expand All @@ -56,7 +80,7 @@ def __post_init__(self):
if not isinstance(self.replace_doctypes, dict):
raise TypeError("replace_doctypes must be a string")

def __repr__(self):
def __repr__(self) -> str:
sources = " | ".join(str(s) for s in self._source)
formatted = f"<{type(self).__name__}: {sources}>"
return formatted
29 changes: 19 additions & 10 deletions src/docstub/_docstrings.py
Loading