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
29 changes: 29 additions & 0 deletions .github/scripts/assert-unchanged.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash

# Assert that there are no changes in a given directory compared to HEAD.
# Expects a relative directory as the one and only argument.

set -e

CHECK_DIR=$1

# Find untracked files
UNTRACKED=$(git ls-files --others --exclude-standard "$CHECK_DIR")
# and display their content by comparing with '/dev/null'
echo "$UNTRACKED" | xargs -I _ git --no-pager diff --no-index /dev/null _

# Display changes in tracked files and capture non-zero exit code if so
set +e
git diff --exit-code HEAD "$CHECK_DIR"
GIT_DIFF_HEAD_EXIT_CODE=$?
set -e

# Display changes in tracked files and capture exit status
if [ $GIT_DIFF_HEAD_EXIT_CODE -ne 0 ] || [ -n "$UNTRACKED" ]; then
echo "::error::Uncommited changes in directory '$CHECK_DIR'"
exit 1
else
echo "::notice::No Uncommited changes, directory '$CHECK_DIR' is clean"
fi

set +e
14 changes: 9 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ env:
# Many color libraries just need this to be set to any value, but at least
# one distinguishes color depth, where "3" -> "256-bit color".
FORCE_COLOR: 3
MYPYPATH: ${{ github.workspace }}/stubs

defaults:
run:
Expand Down Expand Up @@ -78,17 +77,22 @@ jobs:

# TODO upload coverage statistics, and fail on decrease?

- name: Compare example stubs
- name: Check example_pkg-stubs
# Check that stubs for example_pkg are up-to-date by regenerating them
# with docstub and looking for differences.
run: |
python -m docstub run -v \
--config=examples/docstub.toml \
--out-dir=examples/example_pkg-stubs \
examples/example_pkg
git diff --exit-code examples/ && echo "Stubs for example_pkg did not change"
.github/scripts/assert-unchanged.sh examples/

- name: Generate stubs for docstub
- name: Check docstub-stubs
# Check that stubs for docstub are up-to-date by regenerating them
# with docstub and looking for differences.
run: |
python -m docstub run -v src/docstub -o ${MYPYPATH}/docstub
python -m docstub run -v src/docstub -o src/docstub-stubs
.github/scripts/assert-unchanged.sh src/docstub-stubs/

- name: Check with mypy.stubtest
run: |
Expand Down
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
exclude: |
(?x)^(
examples/.*-stubs/.*|
src/docstub-stubs/.*|
)$

repos:
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -163,5 +163,4 @@ ignore_missing_imports = true


[tool.basedpyright]
stubPath = "stubs/"
typeCheckingMode = "standard"
5 changes: 5 additions & 0 deletions src/docstub-stubs/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# File generated with docstub

from ._version import __version__

__all__ = ["__version__"]
6 changes: 6 additions & 0 deletions src/docstub-stubs/__main__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# File generated with docstub

from ._cli import cli

if __name__ == "__main__":
pass
88 changes: 88 additions & 0 deletions src/docstub-stubs/_analysis.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# File generated with docstub

import builtins
import importlib
import json
import logging
import re
from collections.abc import Iterable
from dataclasses import asdict, dataclass
from functools import cache
from pathlib import Path
from typing import Any, ClassVar

import libcst as cst
import libcst.matchers as cstm

from ._utils import accumulate_qualname, module_name_from_path, pyfile_checksum

logger: logging.Logger

def _shared_leading_qualname(*qualnames: tuple[str]) -> str: ...
@dataclass(slots=True, frozen=True)
class PyImport:

import_: str | None = ...
from_: str | None = ...
as_: str | None = ...
implicit: str | None = ...

@classmethod
def typeshed_Incomplete(cls) -> PyImport: ...
def format_import(self, relative_to: str | None = ...) -> str: ...
@property
def target(self) -> str: ...
@property
def has_import(self) -> None: ...
def __post_init__(self) -> None: ...
def __repr__(self) -> str: ...
def __str__(self) -> str: ...

def _is_type(value: Any) -> bool: ...
def _builtin_types() -> dict[str, PyImport]: ...
def _runtime_types_in_module(module_name: str) -> dict[str, PyImport]: ...
def common_known_types() -> dict[str, PyImport]: ...

class TypeCollector(cst.CSTVisitor):
class ImportSerializer:

suffix: ClassVar[str]
encoding: ClassVar[str]

def hash_args(self, path: Path) -> str: ...
def serialize(
self, data: tuple[dict[str, PyImport], dict[str, PyImport]]
) -> bytes: ...
def deserialize(
self, raw: bytes
) -> tuple[dict[str, PyImport], dict[str, PyImport]]: ...

@classmethod
def collect(cls, file: Path) -> tuple[dict[str, PyImport], dict[str, PyImport]]: ...
def __init__(self, *, module_name: str) -> None: ...
def visit_ClassDef(self, node: cst.ClassDef) -> bool: ...
def leave_ClassDef(self, original_node: cst.ClassDef) -> None: ...
def visit_FunctionDef(self, node: cst.FunctionDef) -> bool: ...
def visit_TypeAlias(self, node: cst.TypeAlias) -> bool: ...
def visit_AnnAssign(self, node: cst.AnnAssign) -> bool: ...
def visit_ImportFrom(self, node: cst.ImportFrom) -> bool: ...
def visit_Import(self, node: cst.Import) -> bool: ...
def _collect_type_annotation(self, stack: Iterable[str]) -> None: ...

class TypeMatcher:
types: dict[str, PyImport]
type_prefixes: dict[str, PyImport]
type_nicknames: dict[str, str]
successful_queries: int
unknown_qualnames: list
current_file: Path | None

def __init__(
self,
*,
types: dict[str, PyImport] | None = ...,
type_prefixes: dict[str, PyImport] | None = ...,
type_nicknames: dict[str, str] | None = ...,
) -> None: ...
def _resolve_nickname(self, name: str) -> str: ...
def match(self, search: str) -> tuple[str | None, PyImport | None]: ...
49 changes: 49 additions & 0 deletions src/docstub-stubs/_cache.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# File generated with docstub

import logging
from collections.abc import Callable
from functools import cached_property
from pathlib import Path
from typing import Any, Protocol

logger: logging.Logger

CACHE_DIR_NAME: str

CACHEDIR_TAG_CONTENT: str

GITHUB_IGNORE_CONTENT: str

def _directory_size(path: Path) -> int: ...
def create_cache(path: Path) -> None: ...
def validate_cache(path: Path) -> None: ...

class FuncSerializer[T](Protocol):

suffix: str

def hash_args(self, *args: Any, **kwargs: Any) -> str: ...
def serialize(self, data: T) -> bytes: ...
def deserialize(self, raw: bytes) -> T: ...

class FileCache:
func: Callable
serializer: FuncSerializer
sub_dir: str
cache_hits: int
cache_misses: int
cached_last_call: bool | None

def __init__(
self,
*,
func: Callable,
serializer: FuncSerializer,
cache_dir: Path,
sub_dir: str | None = ...,
) -> None: ...
@cached_property
def cache_dir(self) -> Path: ...
@property
def cache_sub_dir(self) -> None: ...
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
57 changes: 57 additions & 0 deletions src/docstub-stubs/_cli.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# File generated with docstub

import logging
import shutil
import sys
import time
from collections import Counter
from collections.abc import Iterable, Sequence
from contextlib import contextmanager
from pathlib import Path
from typing import Literal

import click

from ._analysis import PyImport, TypeCollector, TypeMatcher, common_known_types
from ._cache import CACHE_DIR_NAME, FileCache, validate_cache
from ._config import Config
from ._path_utils import (
STUB_HEADER_COMMENT,
find_package_root,
walk_source_and_targets,
walk_source_package,
)
from ._report import setup_logging
from ._stubs import Py2StubTransformer, try_format_stub
from ._version import __version__

logger: logging.Logger

def _cache_dir_in_cwd() -> Path: ...
def _load_configuration(config_paths: list[Path] | None = ...) -> Config: ...
def _calc_verbosity(
*, verbose: Literal[0, 1, 2], quiet: Literal[0, 1, 2]
) -> Literal[-2, -1, 0, 1, 2]: ...
def _collect_type_info(
root_path: Path, *, ignore: Sequence[str] = ..., cache: bool = ...
) -> tuple[dict[str, PyImport], dict[str, PyImport]]: ...
def _format_unknown_names(unknown_names: Iterable[str]) -> str: ...
def log_execution_time() -> None: ...
@click.group()
def cli() -> None: ...
@cli.command()
def run(
*,
root_path: Path,
out_dir: Path,
config_paths: Sequence[Path],
ignore: Sequence[str],
group_errors: bool,
allow_errors: int,
fail_on_warning: bool,
no_cache: bool,
verbose: int,
quiet: int,
) -> None: ...
@cli.command()
def clean(verbose: int, quiet: int) -> None: ...
31 changes: 31 additions & 0 deletions src/docstub-stubs/_config.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# File generated with docstub

import dataclasses
import logging
import tomllib
from collections.abc import Mapping
from pathlib import Path
from typing import ClassVar, Self

logger: logging.Logger

@dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
class Config:
TEMPLATE_PATH: ClassVar[Path]
NUMPY_PATH: ClassVar[Path]

types: dict[str, str] = ...
type_prefixes: dict[str, str] = ...
type_nicknames: dict[str, str] = ...
ignore_files: list[str] = ...

config_paths: tuple[Path, ...] = ...

@classmethod
def from_toml(cls, path: Path | str) -> Self: ...
def merge(self, other: Self) -> Self: ...
def to_dict(self) -> None: ...
def __post_init__(self) -> None: ...
def __repr__(self) -> str: ...
@staticmethod
def validate(mapping: Mapping) -> None: ...
Loading
Loading