pytest-codeblock 0.3.4__tar.gz → 0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pytest_codeblock-0.3.4/src/pytest_codeblock.egg-info → pytest_codeblock-0.4}/PKG-INFO +28 -8
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/README.rst +25 -5
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/pyproject.toml +7 -3
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/__init__.py +5 -3
- pytest_codeblock-0.4/src/pytest_codeblock/config.py +133 -0
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/md.py +8 -6
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/rst.py +3 -1
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/tests/conftest.py +13 -0
- pytest_codeblock-0.4/src/pytest_codeblock/tests/test_customisation.py +96 -0
- pytest_codeblock-0.4/src/pytest_codeblock/tests/test_integration.py +1053 -0
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/tests/test_pytest_codeblock.py +4 -4
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4/src/pytest_codeblock.egg-info}/PKG-INFO +28 -8
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock.egg-info/SOURCES.txt +3 -0
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock.egg-info/requires.txt +3 -0
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/LICENSE +0 -0
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/setup.cfg +0 -0
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/collector.py +0 -0
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/constants.py +0 -0
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/helpers.py +0 -0
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/tests/__init__.py +0 -0
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock.egg-info/dependency_links.txt +0 -0
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock.egg-info/entry_points.txt +0 -0
- {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-codeblock
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
4
4
|
Summary: Pytest plugin to collect and test code blocks in reStructuredText and Markdown files.
|
|
5
5
|
Author-email: Artur Barseghyan <artur.barseghyan@gmail.com>
|
|
6
6
|
Maintainer-email: Artur Barseghyan <artur.barseghyan@gmail.com>
|
|
@@ -15,7 +15,6 @@ Classifier: Framework :: Pytest
|
|
|
15
15
|
Classifier: Development Status :: 4 - Beta
|
|
16
16
|
Classifier: Intended Audience :: Developers
|
|
17
17
|
Classifier: Operating System :: OS Independent
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -24,10 +23,11 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
24
23
|
Classifier: Programming Language :: Python
|
|
25
24
|
Classifier: Topic :: Software Development :: Testing
|
|
26
25
|
Classifier: Topic :: Software Development
|
|
27
|
-
Requires-Python: >=3.
|
|
26
|
+
Requires-Python: >=3.10
|
|
28
27
|
Description-Content-Type: text/x-rst
|
|
29
28
|
License-File: LICENSE
|
|
30
29
|
Requires-Dist: pytest
|
|
30
|
+
Requires-Dist: tomli; python_version < "3.11"
|
|
31
31
|
Provides-Extra: all
|
|
32
32
|
Requires-Dist: pytest-codeblock[build,dev,docs,test]; extra == "all"
|
|
33
33
|
Provides-Extra: dev
|
|
@@ -75,12 +75,14 @@ pytest-codeblock
|
|
|
75
75
|
.. _moto: https://github.com/getmoto/moto
|
|
76
76
|
.. _openai: https://github.com/openai/openai-python
|
|
77
77
|
.. _Ollama: https://github.com/ollama/ollama
|
|
78
|
+
.. _tomli: https://pypi.org/project/tomli/
|
|
78
79
|
|
|
79
80
|
.. Internal references
|
|
80
81
|
|
|
81
82
|
.. _pytest-codeblock: https://github.com/barseghyanartur/pytest-codeblock/
|
|
82
83
|
.. _Read the Docs: http://pytest-codeblock.readthedocs.io/
|
|
83
84
|
.. _Examples: https://github.com/barseghyanartur/pytest-codeblock/tree/main/examples
|
|
85
|
+
.. _Customisation docs: https://pytest-codeblock.readthedocs.io/en/latest/customisation.html
|
|
84
86
|
.. _Contributor guidelines: https://pytest-codeblock.readthedocs.io/en/latest/contributor_guidelines.html
|
|
85
87
|
.. _reStructuredText docs: https://pytest-codeblock.readthedocs.io/en/latest/restructured_text.html
|
|
86
88
|
.. _Markdown docs: https://pytest-codeblock.readthedocs.io/en/latest/markdown.html
|
|
@@ -128,7 +130,7 @@ Features
|
|
|
128
130
|
The only requirement here is that your code blocks shall
|
|
129
131
|
have a name starting with ``test_``. Async code snippets are supported as
|
|
130
132
|
well.
|
|
131
|
-
- **Grouping
|
|
133
|
+
- **Grouping**: Split a single example across multiple code blocks;
|
|
132
134
|
the plugin concatenates them into one test.
|
|
133
135
|
- **Pytest markers support**: Add existing or custom `pytest`_ markers
|
|
134
136
|
to the code blocks and hook into the tests life-cycle using ``conftest.py``.
|
|
@@ -137,8 +139,9 @@ Features
|
|
|
137
139
|
|
|
138
140
|
Prerequisites
|
|
139
141
|
=============
|
|
140
|
-
- Python 3.
|
|
141
|
-
- `pytest`_ is the only required dependency
|
|
142
|
+
- Python 3.10+
|
|
143
|
+
- `pytest`_ is the only required dependency (on Python 3.11+; for Python 3.10
|
|
144
|
+
`tomli`_ is also required).
|
|
142
145
|
|
|
143
146
|
Documentation
|
|
144
147
|
=============
|
|
@@ -168,8 +171,25 @@ Or install with `uv`_:
|
|
|
168
171
|
|
|
169
172
|
Configuration
|
|
170
173
|
=============
|
|
171
|
-
|
|
172
|
-
automatically.
|
|
174
|
+
For most use cases, no configuration needed. All your `.rst` and `.md` files
|
|
175
|
+
shall be picked automatically.
|
|
176
|
+
|
|
177
|
+
However, if you need to add another file extension or use or another language
|
|
178
|
+
identifier for python in codeblock, you could configure that.
|
|
179
|
+
|
|
180
|
+
See the following example of `pyproject.toml` configuration:
|
|
181
|
+
|
|
182
|
+
*Filename: pyproject.toml*
|
|
183
|
+
|
|
184
|
+
.. code-block:: toml
|
|
185
|
+
|
|
186
|
+
[tool.pytest-codeblock]
|
|
187
|
+
rst_user_codeblocks = ["c_py"]
|
|
188
|
+
rst_user_extensions = [".rst.txt"]
|
|
189
|
+
md_user_codeblocks = ["c_py"]
|
|
190
|
+
md_user_extensions = [".md.txt"]
|
|
191
|
+
|
|
192
|
+
See `customisation docs`_ for more.
|
|
173
193
|
|
|
174
194
|
Usage
|
|
175
195
|
=====
|
|
@@ -14,12 +14,14 @@ pytest-codeblock
|
|
|
14
14
|
.. _moto: https://github.com/getmoto/moto
|
|
15
15
|
.. _openai: https://github.com/openai/openai-python
|
|
16
16
|
.. _Ollama: https://github.com/ollama/ollama
|
|
17
|
+
.. _tomli: https://pypi.org/project/tomli/
|
|
17
18
|
|
|
18
19
|
.. Internal references
|
|
19
20
|
|
|
20
21
|
.. _pytest-codeblock: https://github.com/barseghyanartur/pytest-codeblock/
|
|
21
22
|
.. _Read the Docs: http://pytest-codeblock.readthedocs.io/
|
|
22
23
|
.. _Examples: https://github.com/barseghyanartur/pytest-codeblock/tree/main/examples
|
|
24
|
+
.. _Customisation docs: https://pytest-codeblock.readthedocs.io/en/latest/customisation.html
|
|
23
25
|
.. _Contributor guidelines: https://pytest-codeblock.readthedocs.io/en/latest/contributor_guidelines.html
|
|
24
26
|
.. _reStructuredText docs: https://pytest-codeblock.readthedocs.io/en/latest/restructured_text.html
|
|
25
27
|
.. _Markdown docs: https://pytest-codeblock.readthedocs.io/en/latest/markdown.html
|
|
@@ -67,7 +69,7 @@ Features
|
|
|
67
69
|
The only requirement here is that your code blocks shall
|
|
68
70
|
have a name starting with ``test_``. Async code snippets are supported as
|
|
69
71
|
well.
|
|
70
|
-
- **Grouping
|
|
72
|
+
- **Grouping**: Split a single example across multiple code blocks;
|
|
71
73
|
the plugin concatenates them into one test.
|
|
72
74
|
- **Pytest markers support**: Add existing or custom `pytest`_ markers
|
|
73
75
|
to the code blocks and hook into the tests life-cycle using ``conftest.py``.
|
|
@@ -76,8 +78,9 @@ Features
|
|
|
76
78
|
|
|
77
79
|
Prerequisites
|
|
78
80
|
=============
|
|
79
|
-
- Python 3.
|
|
80
|
-
- `pytest`_ is the only required dependency
|
|
81
|
+
- Python 3.10+
|
|
82
|
+
- `pytest`_ is the only required dependency (on Python 3.11+; for Python 3.10
|
|
83
|
+
`tomli`_ is also required).
|
|
81
84
|
|
|
82
85
|
Documentation
|
|
83
86
|
=============
|
|
@@ -107,8 +110,25 @@ Or install with `uv`_:
|
|
|
107
110
|
|
|
108
111
|
Configuration
|
|
109
112
|
=============
|
|
110
|
-
|
|
111
|
-
automatically.
|
|
113
|
+
For most use cases, no configuration needed. All your `.rst` and `.md` files
|
|
114
|
+
shall be picked automatically.
|
|
115
|
+
|
|
116
|
+
However, if you need to add another file extension or use or another language
|
|
117
|
+
identifier for python in codeblock, you could configure that.
|
|
118
|
+
|
|
119
|
+
See the following example of `pyproject.toml` configuration:
|
|
120
|
+
|
|
121
|
+
*Filename: pyproject.toml*
|
|
122
|
+
|
|
123
|
+
.. code-block:: toml
|
|
124
|
+
|
|
125
|
+
[tool.pytest-codeblock]
|
|
126
|
+
rst_user_codeblocks = ["c_py"]
|
|
127
|
+
rst_user_extensions = [".rst.txt"]
|
|
128
|
+
md_user_codeblocks = ["c_py"]
|
|
129
|
+
md_user_extensions = [".md.txt"]
|
|
130
|
+
|
|
131
|
+
See `customisation docs`_ for more.
|
|
112
132
|
|
|
113
133
|
Usage
|
|
114
134
|
=====
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
name = "pytest-codeblock"
|
|
3
3
|
description = "Pytest plugin to collect and test code blocks in reStructuredText and Markdown files."
|
|
4
4
|
readme = "README.rst"
|
|
5
|
-
version = "0.
|
|
6
|
-
requires-python = ">=3.
|
|
5
|
+
version = "0.4"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"pytest",
|
|
9
|
+
"tomli; python_version < '3.11'",
|
|
9
10
|
]
|
|
10
11
|
authors = [
|
|
11
12
|
{ name = "Artur Barseghyan", email = "artur.barseghyan@gmail.com" },
|
|
@@ -19,7 +20,6 @@ classifiers = [
|
|
|
19
20
|
"Development Status :: 4 - Beta",
|
|
20
21
|
"Intended Audience :: Developers",
|
|
21
22
|
"Operating System :: OS Independent",
|
|
22
|
-
"Programming Language :: Python :: 3.9",
|
|
23
23
|
"Programming Language :: Python :: 3.10",
|
|
24
24
|
"Programming Language :: Python :: 3.11",
|
|
25
25
|
"Programming Language :: Python :: 3.12",
|
|
@@ -156,6 +156,10 @@ known-third-party = []
|
|
|
156
156
|
ignore-path = [
|
|
157
157
|
"docs/requirements.txt",
|
|
158
158
|
"src/pytest-codeblock.egg-info/SOURCES.txt",
|
|
159
|
+
"examples/customisation_example/*.rst",
|
|
160
|
+
"examples/customisation_example/*.rst.txt",
|
|
161
|
+
"examples/customisation_example/*.md",
|
|
162
|
+
"examples/customisation_example/*.md.txt",
|
|
159
163
|
]
|
|
160
164
|
|
|
161
165
|
[tool.pytest.ini_options]
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
+
from .config import get_config
|
|
3
4
|
from .md import MarkdownFile
|
|
4
5
|
from .rst import RSTFile
|
|
5
6
|
|
|
6
7
|
__title__ = "pytest-codeblock"
|
|
7
|
-
__version__ = "0.
|
|
8
|
+
__version__ = "0.4"
|
|
8
9
|
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
|
|
9
10
|
__copyright__ = "2025-2026 Artur Barseghyan"
|
|
10
11
|
__license__ = "MIT"
|
|
@@ -15,12 +16,13 @@ __all__ = (
|
|
|
15
16
|
|
|
16
17
|
def pytest_collect_file(parent, path):
|
|
17
18
|
"""Collect .md and .rst files for codeblock tests."""
|
|
19
|
+
config = get_config()
|
|
18
20
|
# Determine file extension (works for py.path or pathlib.Path)
|
|
19
21
|
file_name = str(path).lower()
|
|
20
|
-
if file_name.endswith(
|
|
22
|
+
if any(file_name.endswith(ext) for ext in config.all_md_extensions):
|
|
21
23
|
# Use the MarkdownFile collector for Markdown files
|
|
22
24
|
return MarkdownFile.from_parent(parent=parent, path=Path(path))
|
|
23
|
-
if file_name.endswith(
|
|
25
|
+
if any(file_name.endswith(ext) for ext in config.all_rst_extensions):
|
|
24
26
|
# Use the RSTFile collector for reStructuredText files
|
|
25
27
|
return RSTFile.from_parent(parent=parent, path=Path(path))
|
|
26
28
|
return None
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Configuration loading from pyproject.toml."""
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
if sys.version_info >= (3, 11):
|
|
7
|
+
import tomllib
|
|
8
|
+
else:
|
|
9
|
+
try:
|
|
10
|
+
import tomli as tomllib
|
|
11
|
+
except ImportError:
|
|
12
|
+
tomllib = None # type: ignore[assignment]
|
|
13
|
+
|
|
14
|
+
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
|
|
15
|
+
__copyright__ = "2025-2026 Artur Barseghyan"
|
|
16
|
+
__license__ = "MIT"
|
|
17
|
+
__all__ = (
|
|
18
|
+
"get_config",
|
|
19
|
+
"Config",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Default values
|
|
23
|
+
DEFAULT_RST_CODEBLOCKS = ("py", "python", "python3")
|
|
24
|
+
DEFAULT_MD_CODEBLOCKS = ("py", "python", "python3")
|
|
25
|
+
DEFAULT_RST_EXTENSIONS = (".rst",)
|
|
26
|
+
DEFAULT_MD_EXTENSIONS = (".md", ".markdown")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Config:
|
|
30
|
+
"""Configuration container for pytest-codeblock."""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
rst_codeblocks: tuple[str, ...] = DEFAULT_RST_CODEBLOCKS,
|
|
35
|
+
rst_user_codeblocks: tuple[str, ...] = (),
|
|
36
|
+
md_codeblocks: tuple[str, ...] = DEFAULT_MD_CODEBLOCKS,
|
|
37
|
+
md_user_codeblocks: tuple[str, ...] = (),
|
|
38
|
+
rst_extensions: tuple[str, ...] = DEFAULT_RST_EXTENSIONS,
|
|
39
|
+
rst_user_extensions: tuple[str, ...] = (),
|
|
40
|
+
md_extensions: tuple[str, ...] = DEFAULT_MD_EXTENSIONS,
|
|
41
|
+
md_user_extensions: tuple[str, ...] = (),
|
|
42
|
+
):
|
|
43
|
+
self.rst_codeblocks = rst_codeblocks
|
|
44
|
+
self.rst_user_codeblocks = rst_user_codeblocks
|
|
45
|
+
self.md_codeblocks = md_codeblocks
|
|
46
|
+
self.md_user_codeblocks = md_user_codeblocks
|
|
47
|
+
self.rst_extensions = rst_extensions
|
|
48
|
+
self.rst_user_extensions = rst_user_extensions
|
|
49
|
+
self.md_extensions = md_extensions
|
|
50
|
+
self.md_user_extensions = md_user_extensions
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def all_rst_codeblocks(self) -> tuple[str, ...]:
|
|
54
|
+
"""Combined RST codeblocks (system + user)."""
|
|
55
|
+
return self.rst_codeblocks + self.rst_user_codeblocks
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def all_md_codeblocks(self) -> tuple[str, ...]:
|
|
59
|
+
"""Combined MD codeblocks (system + user)."""
|
|
60
|
+
return self.md_codeblocks + self.md_user_codeblocks
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def all_rst_extensions(self) -> tuple[str, ...]:
|
|
64
|
+
"""Combined RST extensions (system + user)."""
|
|
65
|
+
return self.rst_extensions + self.rst_user_extensions
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def all_md_extensions(self) -> tuple[str, ...]:
|
|
69
|
+
"""Combined MD extensions (system + user)."""
|
|
70
|
+
return self.md_extensions + self.md_user_extensions
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
_cached_config: Optional[Config] = None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _find_pyproject_toml() -> Optional[Path]:
|
|
77
|
+
"""Find pyproject.toml starting from cwd and going up."""
|
|
78
|
+
cwd = Path.cwd()
|
|
79
|
+
for parent in [cwd, *cwd.parents]:
|
|
80
|
+
candidate = parent / "pyproject.toml"
|
|
81
|
+
if candidate.is_file():
|
|
82
|
+
return candidate
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _load_config_from_pyproject(path: Path) -> dict:
|
|
87
|
+
"""Load [tool.pytest-codeblock] section from pyproject.toml."""
|
|
88
|
+
if tomllib is None:
|
|
89
|
+
return {}
|
|
90
|
+
try:
|
|
91
|
+
with open(path, "rb") as f:
|
|
92
|
+
data = tomllib.load(f)
|
|
93
|
+
return data.get("tool", {}).get("pytest-codeblock", {})
|
|
94
|
+
except Exception:
|
|
95
|
+
return {}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_config(*, force_reload: bool = False) -> Config:
|
|
99
|
+
"""Get the configuration, loading from pyproject.toml if available."""
|
|
100
|
+
global _cached_config
|
|
101
|
+
|
|
102
|
+
if _cached_config is not None and not force_reload:
|
|
103
|
+
return _cached_config
|
|
104
|
+
|
|
105
|
+
pyproject_path = _find_pyproject_toml()
|
|
106
|
+
if pyproject_path is None:
|
|
107
|
+
_cached_config = Config()
|
|
108
|
+
return _cached_config
|
|
109
|
+
|
|
110
|
+
raw = _load_config_from_pyproject(pyproject_path)
|
|
111
|
+
|
|
112
|
+
def to_tuple(val, default: tuple[str, ...]) -> tuple[str, ...]:
|
|
113
|
+
if val is None:
|
|
114
|
+
return default
|
|
115
|
+
if isinstance(val, (list, tuple)):
|
|
116
|
+
return tuple(val)
|
|
117
|
+
return default
|
|
118
|
+
|
|
119
|
+
_cached_config = Config(
|
|
120
|
+
rst_codeblocks=to_tuple(
|
|
121
|
+
raw.get("rst_codeblocks"), DEFAULT_RST_CODEBLOCKS
|
|
122
|
+
),
|
|
123
|
+
rst_user_codeblocks=to_tuple(raw.get("rst_user_codeblocks"), ()),
|
|
124
|
+
md_codeblocks=to_tuple(raw.get("md_codeblocks"), DEFAULT_MD_CODEBLOCKS),
|
|
125
|
+
md_user_codeblocks=to_tuple(raw.get("md_user_codeblocks"), ()),
|
|
126
|
+
rst_extensions=to_tuple(
|
|
127
|
+
raw.get("rst_extensions"), DEFAULT_RST_EXTENSIONS
|
|
128
|
+
),
|
|
129
|
+
rst_user_extensions=to_tuple(raw.get("rst_user_extensions"), ()),
|
|
130
|
+
md_extensions=to_tuple(raw.get("md_extensions"), DEFAULT_MD_EXTENSIONS),
|
|
131
|
+
md_user_extensions=to_tuple(raw.get("md_user_extensions"), ()),
|
|
132
|
+
)
|
|
133
|
+
return _cached_config
|
|
@@ -8,6 +8,7 @@ from typing import Optional
|
|
|
8
8
|
import pytest
|
|
9
9
|
|
|
10
10
|
from .collector import CodeSnippet, group_snippets
|
|
11
|
+
from .config import get_config
|
|
11
12
|
from .constants import CODEBLOCK_MARK, DJANGO_DB_MARKS, TEST_PREFIX
|
|
12
13
|
from .helpers import contains_top_level_await, wrap_async_code
|
|
13
14
|
|
|
@@ -28,22 +29,23 @@ def parse_markdown(text: str) -> list[CodeSnippet]:
|
|
|
28
29
|
Supports:
|
|
29
30
|
- <!-- pytestmark: <mark> --> comments immediately before a code fence
|
|
30
31
|
- <!-- codeblock-name: <name> --> comments for naming
|
|
31
|
-
- <!-- continue:
|
|
32
|
+
- <!-- continue: <name> --> comments for grouping with a named snippet
|
|
32
33
|
- Fenced code blocks with ```python (and optional name=<name> in the
|
|
33
34
|
info string)
|
|
34
35
|
|
|
35
36
|
Captures each snippet's name, code, starting line, and any pytest marks.
|
|
36
37
|
"""
|
|
37
|
-
|
|
38
|
+
config = get_config()
|
|
39
|
+
snippets: list[CodeSnippet] = []
|
|
38
40
|
lines = text.splitlines()
|
|
39
|
-
pending_name:
|
|
41
|
+
pending_name: Optional[str] = None
|
|
40
42
|
pending_continue: Optional[str] = None
|
|
41
43
|
pending_marks: list[str] = [CODEBLOCK_MARK]
|
|
42
44
|
pending_fixtures: list[str] = []
|
|
43
45
|
in_block = False
|
|
44
46
|
fence = ""
|
|
45
47
|
block_indent = 0
|
|
46
|
-
code_buffer:
|
|
48
|
+
code_buffer: list[str] = []
|
|
47
49
|
snippet_name: Optional[str] = None
|
|
48
50
|
start_line = 0
|
|
49
51
|
|
|
@@ -92,7 +94,7 @@ def parse_markdown(text: str) -> list[CodeSnippet]:
|
|
|
92
94
|
parts = info.split(None, 1)
|
|
93
95
|
lang = parts[0].lower() if parts else ""
|
|
94
96
|
extra = parts[1] if len(parts) > 1 else ""
|
|
95
|
-
if lang in
|
|
97
|
+
if lang in config.all_md_codeblocks:
|
|
96
98
|
in_block = True
|
|
97
99
|
block_indent = indent
|
|
98
100
|
start_line = idx + 1
|
|
@@ -173,7 +175,7 @@ class MarkdownFile(pytest.File):
|
|
|
173
175
|
_fpath = str(self.path)
|
|
174
176
|
|
|
175
177
|
# Build list of fixture names requested by this snippet
|
|
176
|
-
_fixture_names:
|
|
178
|
+
_fixture_names: list[str] = list(sn.fixtures)
|
|
177
179
|
|
|
178
180
|
# If snippet is marked as needing DB, also request the `db`
|
|
179
181
|
# fixture, unless user already added it explicitly.
|
|
@@ -9,6 +9,7 @@ from typing import Optional, Union
|
|
|
9
9
|
import pytest
|
|
10
10
|
|
|
11
11
|
from .collector import CodeSnippet, group_snippets
|
|
12
|
+
from .config import get_config
|
|
12
13
|
from .constants import CODEBLOCK_MARK, DJANGO_DB_MARKS, TEST_PREFIX
|
|
13
14
|
from .helpers import contains_top_level_await, wrap_async_code
|
|
14
15
|
|
|
@@ -69,6 +70,7 @@ def parse_rst(text: str, base_dir: Path) -> list[CodeSnippet]:
|
|
|
69
70
|
- .. codeblock-name: <name>
|
|
70
71
|
- .. code-block:: python
|
|
71
72
|
"""
|
|
73
|
+
config = get_config()
|
|
72
74
|
snippets: list[CodeSnippet] = []
|
|
73
75
|
lines = text.splitlines()
|
|
74
76
|
n = len(lines)
|
|
@@ -159,7 +161,7 @@ def parse_rst(text: str, base_dir: Path) -> list[CodeSnippet]:
|
|
|
159
161
|
if m:
|
|
160
162
|
base_indent = len(m.group(1))
|
|
161
163
|
lang = m.group(2).lower()
|
|
162
|
-
if lang in
|
|
164
|
+
if lang in config.all_rst_codeblocks:
|
|
163
165
|
# Parse :name: option
|
|
164
166
|
name_val: Optional[str] = None
|
|
165
167
|
j = i + 1
|
|
@@ -13,6 +13,9 @@ __all__ = (
|
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
pytest_plugins = ["pytester"]
|
|
17
|
+
|
|
18
|
+
|
|
16
19
|
@pytest.fixture
|
|
17
20
|
def http_request_factory():
|
|
18
21
|
"""
|
|
@@ -47,3 +50,13 @@ def markdown_with_pytest_mark():
|
|
|
47
50
|
```python name=test_db
|
|
48
51
|
from django.db import models
|
|
49
52
|
```"""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.fixture
|
|
56
|
+
def pytester_subprocess(pytester):
|
|
57
|
+
"""
|
|
58
|
+
Wrapper that forces subprocess mode to avoid deprecation warning conflicts
|
|
59
|
+
when the plugin uses the old `path` argument signature.
|
|
60
|
+
"""
|
|
61
|
+
pytester.runpytest = pytester.runpytest_subprocess
|
|
62
|
+
return pytester
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Tests for customisation of languages and extensions."""
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
from ..config import get_config
|
|
5
|
+
from ..md import parse_markdown
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestCustomLanguages:
|
|
9
|
+
"""Test custom language support in markdown."""
|
|
10
|
+
|
|
11
|
+
def test_custom_md_language_recognized(self):
|
|
12
|
+
"""Test that custom markdown language is recognised when configured."""
|
|
13
|
+
# Mock config to include custom language
|
|
14
|
+
mock_config = {
|
|
15
|
+
"all_md_codeblocks": ["python", "djc_py"], # djc_py is custom
|
|
16
|
+
"all_rst_codeblocks": ["python"],
|
|
17
|
+
"all_md_extensions": [".md"],
|
|
18
|
+
"all_rst_extensions": [".rst"],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
text = """
|
|
22
|
+
```djc_py name=custom_lang
|
|
23
|
+
x = 1
|
|
24
|
+
```
|
|
25
|
+
"""
|
|
26
|
+
with patch("pytest_codeblock.md.get_config") as mock_get_config:
|
|
27
|
+
mock_config_obj = type("Config", (), mock_config)()
|
|
28
|
+
mock_get_config.return_value = mock_config_obj
|
|
29
|
+
|
|
30
|
+
snippets = parse_markdown(text)
|
|
31
|
+
assert len(snippets) == 1
|
|
32
|
+
assert snippets[0].name == "custom_lang"
|
|
33
|
+
assert "x = 1" in snippets[0].code
|
|
34
|
+
|
|
35
|
+
# -------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
def test_unknown_language_ignored(self):
|
|
38
|
+
"""Test that unknown language fence is ignored."""
|
|
39
|
+
text = """
|
|
40
|
+
```unknown_lang
|
|
41
|
+
x = 1
|
|
42
|
+
```
|
|
43
|
+
"""
|
|
44
|
+
snippets = parse_markdown(text)
|
|
45
|
+
assert len(snippets) == 0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TestCustomExtensions:
|
|
49
|
+
"""Test custom file extension support."""
|
|
50
|
+
|
|
51
|
+
def test_config_includes_custom_extensions(self):
|
|
52
|
+
"""Test that config can include custom extensions."""
|
|
53
|
+
config = get_config()
|
|
54
|
+
# By default should include .md and .rst
|
|
55
|
+
assert ".md" in config.all_md_extensions
|
|
56
|
+
assert ".rst" in config.all_rst_extensions
|
|
57
|
+
|
|
58
|
+
def test_python_as_custom_md_extension(self):
|
|
59
|
+
"""Test that .py files can be configured as markdown sources."""
|
|
60
|
+
# This test verifies the config structure supports it
|
|
61
|
+
mock_config = {
|
|
62
|
+
"all_md_codeblocks": ["python"],
|
|
63
|
+
"all_rst_codeblocks": ["python"],
|
|
64
|
+
"all_md_extensions": [".md", ".txt"], # .txt added
|
|
65
|
+
"all_rst_extensions": [".rst"],
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
mock_config_obj = type("Config", (), mock_config)()
|
|
69
|
+
# Verify .txt extension is in the list
|
|
70
|
+
assert ".txt" in mock_config_obj.all_md_extensions
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TestDefaults:
|
|
74
|
+
"""Test that defaults are preserved."""
|
|
75
|
+
|
|
76
|
+
def test_default_python_language_works(self):
|
|
77
|
+
"""Test that default Python language is always available."""
|
|
78
|
+
text = """
|
|
79
|
+
```python
|
|
80
|
+
x = 1
|
|
81
|
+
```
|
|
82
|
+
"""
|
|
83
|
+
snippets = parse_markdown(text)
|
|
84
|
+
assert len(snippets) == 1
|
|
85
|
+
|
|
86
|
+
# -------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
def test_default_md_extension(self):
|
|
89
|
+
"""Test that .md is always a supported extension."""
|
|
90
|
+
config = get_config()
|
|
91
|
+
assert ".md" in config.all_md_extensions
|
|
92
|
+
|
|
93
|
+
def test_default_rst_extension(self):
|
|
94
|
+
"""Test that .rst is always a supported extension."""
|
|
95
|
+
config = get_config()
|
|
96
|
+
assert ".rst" in config.all_rst_extensions
|