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
7 changes: 4 additions & 3 deletions config/coverage.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ parallel = true
source =
packages/griffelib/src/griffe
packages/griffecli/src/griffecli
tests/
packages/griffecli/tests/
packages/griffelib/tests/

[coverage:paths]
equivalent =
Expand All @@ -17,8 +18,8 @@ precision = 2
omit =
src/*/__init__.py
src/*/__main__.py
tests/__init__.py
tests/tmp/*
packages/*/tests/__init__.py
packages/*/tests/tmp/*
exclude_lines =
pragma: no cover
if TYPE_CHECKING
Expand Down
4 changes: 2 additions & 2 deletions config/pytest.ini
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[pytest]
python_files =
test_*.py
packages/*/tests/**/test_*.py
addopts =
--cov
--cov-config config/coverage.ini
testpaths =
tests
packages/*/tests

# action:message_regex:warning_class:module_regex:line
filterwarnings =
Expand Down
8 changes: 4 additions & 4 deletions config/ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ output-format = "concise"

[lint]
exclude = [
"tests/fixtures/*.py",
"packages/*/tests/fixtures/*.py",
]
select = ["ALL"]
ignore = [
Expand Down Expand Up @@ -65,11 +65,11 @@ logger-objects = ["griffe.logger"]
"INP001", # File is part of an implicit namespace package
"T201", # Print statement
]
"tests/test_git.py" = [
"packages/griffelib/tests/test_git.py" = [
"S603", # `subprocess` call: check for execution of untrusted input
"S607", # Starting a process with a partial executable path
]
"tests/**.py" = [
"packages/*/tests/**.py" = [
"ARG005", # Unused lambda argument
"FBT001", # Boolean positional arg in function definition
"PLC1901", # a == "" can be simplified to not a
Expand All @@ -91,7 +91,7 @@ convention = "google"

[format]
exclude = [
"tests/fixtures/*.py",
"packages/*/tests/fixtures/*.py",
]
docstring-code-format = true
docstring-code-line-length = 80
2 changes: 1 addition & 1 deletion config/ty.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[src]
exclude = ["tests/fixtures"]
exclude = ["packages/*/tests/fixtures"]

[terminal]
error-on-warning = true
Expand Down
3 changes: 1 addition & 2 deletions config/vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
"-vvv",
"--no-cov",
"--dist=no",
"tests",
"-k=${input:tests_selection}"
]
}
Expand All @@ -54,4 +53,4 @@
"default": ""
}
]
}
}
3 changes: 2 additions & 1 deletion docs/guide/contributors/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ descriptions = {
"src": "The source of our Python package(s). See [Sources](#sources) and [Program structure](#program-structure).",
"src/griffe": "Our public API, exposed to users. See [Program structure](#program-structure).",
"packages/griffelib/src/griffe/_internal": "Our internal API, hidden from users. See [Program structure](#program-structure).",
"tests": "Our test suite. See [Tests](#tests).",
"packages/griffecli/tests": "Our test suite. See [Tests](#tests).",
"packages/griffelib/tests": "Our test suite. See [Tests](#tests).",
".copier-answers.yml": "The answers file generated by [Copier](https://copier.readthedocs.io/en/stable/). See [Boilerplate](#boilerplate).",
"devdeps.txt": "Our development dependencies specification. See [`make setup`][command-setup] command.",
"duties.py": "Our project tasks, written with [duty](https://pawamoy.github.io/duty). See [Tasks][tasks].",
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/users/recommendations/public-apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ In this script, we find our entrypoint, `griffe.main`, used programmatically.

The second user of your CLI as API is... you again. When you write tests for your CLI, you import your entrypoints and call them by passing CLI options and arguments, maybe asserting the exit code raised with a `SystemExit` or the standard output/error thanks to [pytest's capture fixtures](https://docs.pytest.org/en/6.2.x/capture.html). Some simplified examples from our own test suite:

```python title="tests/test_cli.py"
```python
import pytest
import griffe

Expand Down
5 changes: 2 additions & 3 deletions duties.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from duty.context import Context


PY_SRC_PATHS = (Path(_) for _ in ("packages/griffecli/src", "packages/griffelib/src", "tests", "duties.py", "scripts"))
PY_SRC_PATHS = (Path(_) for _ in ("packages", "duties.py", "scripts"))
PY_SRC_LIST = tuple(str(_) for _ in PY_SRC_PATHS)
PY_SRC = " ".join(PY_SRC_LIST)
CI = os.environ.get("CI", "0") in {"1", "true", "yes", ""}
Expand All @@ -41,7 +41,7 @@ def _pyprefix(title: str) -> str:
def _get_changelog_version() -> str:
changelog_version_re = re.compile(r"^## \[(\d+\.\d+\.\d+)\].*$")
with Path(__file__).parent.joinpath("CHANGELOG.md").open("r", encoding="utf8") as file:
return next(filter(bool, map(changelog_version_re.match, file))).group(1) # ty:ignore[invalid-argument-type,unresolved-attribute]
return next(filter(bool, map(changelog_version_re.match, file))).group(1) # ty:ignore[unresolved-attribute]


@duty
Expand Down Expand Up @@ -460,7 +460,6 @@ def test(ctx: Context, *cli_args: str) -> None:
os.environ["PYTHONWARNDEFAULTENCODING"] = "1"
ctx.run(
tools.pytest(
"tests",
config_file="config/pytest.ini",
color="yes",
).add_args("-n", "auto", *cli_args),
Expand Down
1 change: 1 addition & 0 deletions packages/griffecli/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests suite for `griffecli`."""
File renamed without changes.
6 changes: 3 additions & 3 deletions packages/griffelib/src/griffe/_internal/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ def canonical_path(self) -> str:
return f"{self.parent.canonical_path}.{self.name}"
if isinstance(self.parent, str):
return f"{self.parent}.{self.name}"
parent = self.parent.members.get(self.member, self.parent) # ty:ignore[no-matching-overload]
parent = self.parent.members.get(self.member, self.parent)
try:
return parent.resolve(self.name)
except NameResolutionError:
Expand Down Expand Up @@ -1108,7 +1108,7 @@ def iterate(self, *, flat: bool = True) -> Iterator[str | Expr]:


def _get_precedence(expr: Expr) -> _OperatorPrecedence:
return _precedence_map.get(type(expr), lambda _: _OperatorPrecedence.NONE)(expr) # ty:ignore[no-matching-overload]
return _precedence_map.get(type(expr), lambda _: _OperatorPrecedence.NONE)(expr)


def _build_attribute(node: ast.Attribute, parent: Module | Class, **kwargs: Any) -> Expr:
Expand Down Expand Up @@ -1198,7 +1198,7 @@ def _build_constant(
)
else:
return _build(parsed.body, parent, **kwargs) # ty:ignore[unresolved-attribute]
return {type(...): lambda _: "..."}.get(type(node.value), repr)(node.value) # ty:ignore[no-matching-overload]
return {type(...): lambda _: "..."}.get(type(node.value), repr)(node.value)


def _build_dict(node: ast.Dict, parent: Module | Class, **kwargs: Any) -> Expr:
Expand Down
4 changes: 2 additions & 2 deletions packages/griffelib/src/griffe/_internal/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ def expand_exports(self, module: Module, seen: set | None = None) -> None:
if module.exports is None:
return

expanded = []
expanded: list[str | ExprName] = []
for export in module.exports:
# It's a name: we resolve it, get the module it comes from,
# recurse into it, and add its exports to the current ones.
Expand Down Expand Up @@ -416,7 +416,7 @@ def expand_wildcards(
if already_present:
old_member = obj.get_member(new_member.name)
old_lineno = old_member.alias_lineno if old_member.is_alias else old_member.lineno
overwrite = alias_lineno > (old_lineno or 0)
overwrite = (alias_lineno or 0) > (old_lineno or 0)

# 1. If the expanded member is an alias with a target path equal to its own path, we stop.
# This situation can arise because of Griffe's mishandling of (abusive) wildcard imports.
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import json
import sys
from pathlib import Path

import pytest
from jsonschema import ValidationError, validate
Expand Down Expand Up @@ -91,7 +92,10 @@ def test_minimal_light_data_is_enough(symbol: str) -> None:

# YORE: EOL 3.12: Remove block.
# YORE: EOL 3.11: Remove line.
@pytest.mark.skipif(sys.version_info < (3, 12), reason="Python less than 3.12 does not have PEP 695 generics")
@pytest.mark.skipif(
sys.version_info < (3, 12),
reason="Python less than 3.12 does not have PEP 695 generics",
)
def test_encoding_pep695_generics_without_defaults() -> None:
"""Test serialization and de-serialization of PEP 695 generics without defaults.

Expand Down Expand Up @@ -159,6 +163,10 @@ def _validate(obj: dict, schema: dict) -> None:
raise


@pytest.mark.skipif(
not Path("docs", "schema.json").exists(),
reason="schema.json not available",
)
def test_json_schema() -> None:
"""Assert that our serialized data matches our JSON schema."""
loader = GriffeLoader()
Expand All @@ -172,7 +180,14 @@ def test_json_schema() -> None:

# YORE: EOL 3.12: Remove block.
# YORE: EOL 3.11: Remove line.
@pytest.mark.skipif(sys.version_info < (3, 12), reason="Python less than 3.12 does not have PEP 695 generics")
@pytest.mark.skipif(
sys.version_info < (3, 12),
reason="Python less than 3.12 does not have PEP 695 generics",
)
@pytest.mark.skipif(
not Path("docs", "schema.json").exists(),
reason="schema.json not available",
)
def test_json_schema_for_pep695_generics_without_defaults() -> None:
"""Assert that serialized PEP 695 generics without defaults match our JSON schema.

Expand All @@ -193,6 +208,10 @@ def func[**P, T, *R](arg: T, *args: P.args, **kwargs: P.kwargs) -> tuple[*R]: pa

# YORE: EOL 3.12: Remove line.
@pytest.mark.skipif(sys.version_info < (3, 13), reason="Python less than 3.13 does not have defaults in PEP 695 generics") # fmt: skip
@pytest.mark.skipif(
not Path("docs", "schema.json").exists(),
reason="schema.json not available",
)
def test_json_schema_for_pep695_generics() -> None:
"""Assert that serialized PEP 695 generics with defaults match our JSON schema.

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from __future__ import annotations

import os
import sys
from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING, Any

Expand All @@ -14,17 +16,40 @@
GriffeLoader,
ObjectNode,
load_extensions,
sys_path,
temporary_visited_module,
temporary_visited_package,
)
from griffe._internal.models import Attribute, Class, Function, Module, Object, TypeAlias
from griffe._internal.models import (
Attribute,
Class,
Function,
Module,
Object,
TypeAlias,
)

if TYPE_CHECKING:
import ast
from collections.abc import Iterator

from griffe import Attribute, Class, Function, Module, Object, ObjectNode, TypeAlias


PACKAGE_ROOT = Path(__file__).parent.parent.parent


@contextmanager
def cd(path: str | Path) -> Iterator[None]:
"""Change directory to `path` and restore the previous directory on exit."""
old_path = Path.cwd()
os.chdir(path)
try:
yield
finally:
os.chdir(old_path)


class AnalysisEventsTest(Extension): # noqa: D101
def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D107
super().__init__()
Expand Down Expand Up @@ -101,17 +126,20 @@ def on_alias_instance(self, *, node: ast.AST | ObjectNode, alias: Alias, **kwarg
# With class.
AnalysisEventsTest,
# With absolute paths (esp. important to test for Windows).
Path("tests/test_extensions/test_base.py").absolute().as_posix(),
Path("tests/test_extensions/test_base.py:AnalysisEventsTest").absolute().as_posix(),
PACKAGE_ROOT.joinpath("tests/test_extensions/test_base.py").absolute().as_posix(),
PACKAGE_ROOT.joinpath("tests/test_extensions/test_base.py:AnalysisEventsTest").absolute().as_posix(),
],
)
def test_loading_extensions(extension: str | dict[str, dict[str, Any]] | Extension | type[Extension]) -> None:
def test_loading_extensions(
extension: str | dict[str, dict[str, Any]] | Extension | type[Extension],
) -> None:
"""Test the extensions loading mechanisms.

Parameters:
extension: Extension specification (parametrized).
"""
extensions = load_extensions(extension)
with cd(PACKAGE_ROOT), sys_path(PACKAGE_ROOT, *sys.path):
extensions = load_extensions(extension)
loaded: AnalysisEventsTest = extensions._extensions[0] # ty:ignore[invalid-assignment]
# We cannot use isinstance here,
# because loading from a filepath drops the parent `tests` package,
Expand Down Expand Up @@ -157,7 +185,10 @@ def method(self): ...


# YORE: EOL 3.11: Remove line.
@pytest.mark.skipif(sys.version_info < (3, 12), reason="Python less than 3.12 does not have PEP 695 type aliases")
@pytest.mark.skipif(
sys.version_info < (3, 12),
reason="Python less than 3.12 does not have PEP 695 type aliases",
)
def test_analysis_events() -> None:
"""Test analysis events triggering."""
extension = AnalysisEventsTest()
Expand Down Expand Up @@ -259,7 +290,10 @@ def method(self): ...


# YORE: EOL 3.11: Remove line.
@pytest.mark.skipif(sys.version_info < (3, 12), reason="Python less than 3.12 does not have PEP 695 type aliases")
@pytest.mark.skipif(
sys.version_info < (3, 12),
reason="Python less than 3.12 does not have PEP 695 type aliases",
)
def test_load_events() -> None:
"""Test load events triggering."""
extension = LoadEventsTest()
Expand Down
Loading
Loading