Repository navigation
Expand file tree
/
Copy pathconftest.py
More file actions
137 lines (108 loc) · 4.22 KB
/
Copy pathconftest.py
File metadata and controls
137 lines (108 loc) · 4.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""Runs the ```python blocks under docs/ as tests (`just test-docs`).
Blocks on one page share a namespace, in page order. A block that is not meant to run carries
`<!-- skip: next "reason" -->` above its fence; hidden setup goes in `<!-- invisible-code-block: python -->`.
A block that demonstrates an error carries `<!-- raises: ErrorClassName -->` and passes only if it raises that.
"""
import ast
import asyncio
import importlib.machinery
import importlib.util
import inspect
import pathlib
import re
import sys
import types
from collections.abc import Iterator
import pytest
from sybil import Document, Example, Region, Sybil
from sybil.parsers.markdown import ClearNamespaceParser, CodeBlockParser, SkipParser
from sybil.parsers.markdown.lexers import DirectiveInHTMLCommentLexer
_UNINSTALLED_PACKAGES = frozenset(
{
"aiohttp",
"fastapi",
"faststream",
"litestar",
"modern_di_litestar",
"modern_di_pytest",
"pydantic_ai",
"redis",
"sqlalchemy",
"uvicorn",
}
)
_EXCLUDED_PAGES = (
"adr/*",
"agents/*",
"migration/*",
)
_FRAMEWORK_INDEPENDENT_INTEGRATION_PAGES = frozenset({"writing-integrations.md"})
_EXPECTED_ERROR_KEY = "__docs_expected_error__"
_PAGE_MODULE_KEY = "__docs_page_module__"
_RUN_OPTIONS = {"docs_integrations": False}
_RAISES_LEXER = DirectiveInHTMLCommentLexer("raises", arguments=r"\w+")
def _expect_error(example: Example) -> None:
example.namespace[_EXPECTED_ERROR_KEY] = example.parsed
def _parse_raises(document: Document) -> Iterator[Region]:
for lexed in _RAISES_LEXER(document):
yield Region(lexed.start, lexed.end, lexed.lexemes["arguments"], _expect_error)
def _page_module(example: Example) -> types.ModuleType:
module = example.namespace.get(_PAGE_MODULE_KEY)
if module is None:
page = pathlib.Path(example.path)
name = re.sub(r"\W", "_", f"docs_example_{page.parent.name}_{page.stem}")
spec = importlib.machinery.ModuleSpec(
name, importlib.machinery.SourceFileLoader(name, str(page)), origin=str(page)
)
spec.has_location = True
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
example.namespace[_PAGE_MODULE_KEY] = module
return module
def _run(example: Example) -> None:
source = "\n" * (example.line + example.parsed.line_offset) + example.parsed
code = compile(source, example.path, "exec", flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT, dont_inherit=True)
result = eval(code, vars(_page_module(example))) # noqa: S307
if inspect.iscoroutine(result):
asyncio.run(result)
def _evaluate_python(example: Example) -> None:
expected_error = example.namespace.pop(_EXPECTED_ERROR_KEY, None)
try:
_run(example)
except ModuleNotFoundError as error:
if _RUN_OPTIONS["docs_integrations"] or (error.name or "").partition(".")[0] not in _UNINSTALLED_PACKAGES:
raise
pytest.skip(f"{error.name} is not installed in this repo")
except Exception as error:
if type(error).__name__ != expected_error:
raise
return
if expected_error:
pytest.fail(f"expected the block to raise {expected_error}, it raised nothing")
_collect_docs = Sybil(
parsers=[
CodeBlockParser("python", _evaluate_python),
SkipParser(),
ClearNamespaceParser(),
_parse_raises,
],
path="docs",
patterns=["*.md"],
excludes=_EXCLUDED_PAGES,
).pytest()
def pytest_addoption(parser: pytest.Parser) -> None:
parser.addoption(
"--docs-integrations",
action="store_true",
help="also run the framework pages under docs/integrations/, and fail instead of skip on a missing package",
)
def pytest_configure(config: pytest.Config) -> None:
_RUN_OPTIONS["docs_integrations"] = config.getoption("--docs-integrations")
def pytest_collect_file(file_path: pathlib.Path, parent: pytest.Collector) -> pytest.Collector | None:
if (
file_path.parent.name == "integrations"
and file_path.name not in _FRAMEWORK_INDEPENDENT_INTEGRATION_PAGES
and not parent.config.getoption("--docs-integrations")
):
return None
return _collect_docs(file_path, parent)