pytest-codeblock 0.5.2__tar.gz → 0.5.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.5.2/src/pytest_codeblock.egg-info → pytest_codeblock-0.5.4}/PKG-INFO +1 -1
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/pyproject.toml +55 -1
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/__init__.py +8 -2
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/constants.py +6 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/md.py +19 -4
- pytest_codeblock-0.5.4/src/pytest_codeblock/pytestrun.py +63 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/rst.py +20 -7
- pytest_codeblock-0.5.4/src/pytest_codeblock/tests/test_pytestrun_marker.py +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4/src/pytest_codeblock.egg-info}/PKG-INFO +1 -1
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock.egg-info/SOURCES.txt +3 -1
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/LICENSE +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/README.rst +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/setup.cfg +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/collector.py +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/config.py +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/helpers.py +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/tests/__init__.py +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/tests/test_customisation.py +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/tests/test_integration.py +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/tests/test_nameless_codeblocks.py +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/tests/test_pytest_codeblock.py +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock.egg-info/dependency_links.txt +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock.egg-info/entry_points.txt +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock.egg-info/requires.txt +0 -0
- {pytest_codeblock-0.5.2 → pytest_codeblock-0.5.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.5.
|
|
3
|
+
Version: 0.5.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>
|
|
@@ -2,7 +2,7 @@
|
|
|
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.5.
|
|
5
|
+
version = "0.5.4"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"pytest",
|
|
@@ -268,6 +268,60 @@ ignore = [
|
|
|
268
268
|
"docs/code_of_conduct.rst",
|
|
269
269
|
"docs/security.rst",
|
|
270
270
|
"docs/source_tree.rst",
|
|
271
|
+
"docs/source_tree_full.rst",
|
|
271
272
|
"docs/make.bat",
|
|
272
273
|
"docs/Makefile",
|
|
273
274
|
]
|
|
275
|
+
|
|
276
|
+
[[tool.sphinx-source-tree.files]]
|
|
277
|
+
output = "docs/source_tree_full.rst"
|
|
278
|
+
title = "Full project source-tree"
|
|
279
|
+
|
|
280
|
+
[[tool.sphinx-source-tree.files]]
|
|
281
|
+
output = "docs/source_tree.rst"
|
|
282
|
+
title = "Project source-tree"
|
|
283
|
+
ignore = [
|
|
284
|
+
"__pycache__",
|
|
285
|
+
"*.pyc",
|
|
286
|
+
"*.pyo",
|
|
287
|
+
"*.py,cover",
|
|
288
|
+
".git",
|
|
289
|
+
".hg",
|
|
290
|
+
".svn",
|
|
291
|
+
".tox",
|
|
292
|
+
".nox",
|
|
293
|
+
".venv",
|
|
294
|
+
"venv",
|
|
295
|
+
"env",
|
|
296
|
+
"*.egg-info",
|
|
297
|
+
"dist",
|
|
298
|
+
"build",
|
|
299
|
+
"node_modules",
|
|
300
|
+
".mypy_cache",
|
|
301
|
+
".pytest_cache",
|
|
302
|
+
".coverage",
|
|
303
|
+
"htmlcov",
|
|
304
|
+
".idea",
|
|
305
|
+
".vscode",
|
|
306
|
+
".DS_Store",
|
|
307
|
+
"Thumbs.db",
|
|
308
|
+
".ruff_cache",
|
|
309
|
+
".coverage.*",
|
|
310
|
+
".secrets.baseline",
|
|
311
|
+
".pre-commit-config.yaml",
|
|
312
|
+
".pre-commit-hooks.yaml",
|
|
313
|
+
".readthedocs.yaml",
|
|
314
|
+
"CHANGELOG.rst",
|
|
315
|
+
"CODE_OF_CONDUCT.rst",
|
|
316
|
+
"LICENSE",
|
|
317
|
+
"SECURITY.rst",
|
|
318
|
+
"docs/changelog.rst",
|
|
319
|
+
"docs/code_of_conduct.rst",
|
|
320
|
+
"docs/security.rst",
|
|
321
|
+
"docs/source_tree.rst",
|
|
322
|
+
"docs/source_tree_full.rst",
|
|
323
|
+
"docs/make.bat",
|
|
324
|
+
"docs/Makefile",
|
|
325
|
+
"examples",
|
|
326
|
+
"docs",
|
|
327
|
+
]
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
from .config import get_config
|
|
4
|
-
from .constants import CODEBLOCK_MARK
|
|
4
|
+
from .constants import CODEBLOCK_MARK, PYTESTRUN_MARK
|
|
5
5
|
from .md import MarkdownFile
|
|
6
6
|
from .rst import RSTFile
|
|
7
7
|
|
|
8
8
|
__title__ = "pytest-codeblock"
|
|
9
|
-
__version__ = "0.5.
|
|
9
|
+
__version__ = "0.5.4"
|
|
10
10
|
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
|
|
11
11
|
__copyright__ = "2025-2026 Artur Barseghyan"
|
|
12
12
|
__license__ = "MIT"
|
|
@@ -42,3 +42,9 @@ def pytest_configure(config):
|
|
|
42
42
|
"markers",
|
|
43
43
|
f"{CODEBLOCK_MARK}: pytest-codeblock markers (auto-registered)",
|
|
44
44
|
)
|
|
45
|
+
# Only register if not already present
|
|
46
|
+
if PYTESTRUN_MARK not in marker_names:
|
|
47
|
+
config.addinivalue_line(
|
|
48
|
+
"markers",
|
|
49
|
+
f"{PYTESTRUN_MARK}: pytest-codeblock markers (auto-registered)",
|
|
50
|
+
)
|
|
@@ -4,6 +4,7 @@ __license__ = "MIT"
|
|
|
4
4
|
__all__ = (
|
|
5
5
|
"CODEBLOCK_MARK",
|
|
6
6
|
"DJANGO_DB_MARKS",
|
|
7
|
+
"PYTESTRUN_MARK",
|
|
7
8
|
"TEST_PREFIX",
|
|
8
9
|
)
|
|
9
10
|
|
|
@@ -16,3 +17,8 @@ DJANGO_DB_MARKS = {
|
|
|
16
17
|
TEST_PREFIX = "test_"
|
|
17
18
|
|
|
18
19
|
CODEBLOCK_MARK = "codeblock"
|
|
20
|
+
|
|
21
|
+
# When this mark is present on a code block, the plugin will exec() the block
|
|
22
|
+
# and then discover and run any Test* classes / test_* functions found in it,
|
|
23
|
+
# rather than treating the whole block as a single test body.
|
|
24
|
+
PYTESTRUN_MARK = "pytestrun"
|
|
@@ -9,8 +9,14 @@ import pytest
|
|
|
9
9
|
|
|
10
10
|
from .collector import CodeSnippet, group_snippets
|
|
11
11
|
from .config import get_config
|
|
12
|
-
from .constants import
|
|
12
|
+
from .constants import (
|
|
13
|
+
CODEBLOCK_MARK,
|
|
14
|
+
DJANGO_DB_MARKS,
|
|
15
|
+
PYTESTRUN_MARK,
|
|
16
|
+
TEST_PREFIX,
|
|
17
|
+
)
|
|
13
18
|
from .helpers import contains_top_level_await, wrap_async_code
|
|
19
|
+
from .pytestrun import run_pytest_style_code
|
|
14
20
|
|
|
15
21
|
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
|
|
16
22
|
__copyright__ = "2025-2026 Artur Barseghyan"
|
|
@@ -25,14 +31,12 @@ def parse_markdown(text: str) -> list[CodeSnippet]:
|
|
|
25
31
|
"""
|
|
26
32
|
Parse Markdown text and extract Python code snippets as CodeSnippet
|
|
27
33
|
objects.
|
|
28
|
-
|
|
29
34
|
Supports:
|
|
30
35
|
- <!-- pytestmark: <mark> --> comments immediately before a code fence
|
|
31
36
|
- <!-- codeblock-name: <name> --> comments for naming
|
|
32
37
|
- <!-- continue: <name> --> comments for grouping with a named snippet
|
|
33
38
|
- Fenced code blocks with ```python (and optional name=<name> in the
|
|
34
39
|
info string)
|
|
35
|
-
|
|
36
40
|
Captures each snippet's name, code, starting line, and any pytest marks.
|
|
37
41
|
"""
|
|
38
42
|
config = get_config()
|
|
@@ -195,6 +199,7 @@ class MarkdownFile(pytest.File):
|
|
|
195
199
|
# Bind the values we need so we don't close over `sn` itself
|
|
196
200
|
_sn_name = sn.name
|
|
197
201
|
_fpath = str(self.path)
|
|
202
|
+
_is_pytestrun = PYTESTRUN_MARK in sn.marks
|
|
198
203
|
|
|
199
204
|
# Build list of fixture names requested by this snippet
|
|
200
205
|
_fixture_names: list[str] = list(sn.fixtures)
|
|
@@ -213,14 +218,24 @@ class MarkdownFile(pytest.File):
|
|
|
213
218
|
sn_name=_sn_name,
|
|
214
219
|
fpath=_fpath,
|
|
215
220
|
fixture_names=_fixture_names,
|
|
221
|
+
is_pytestrun=_is_pytestrun,
|
|
216
222
|
):
|
|
217
223
|
# This inner function *actually* has a **fixtures signature,
|
|
218
224
|
# but we override __signature__ so pytest passes the right
|
|
219
225
|
# fixtures and names.
|
|
220
226
|
def test_block(**fixtures):
|
|
221
|
-
|
|
227
|
+
if is_pytestrun:
|
|
228
|
+
run_pytest_style_code(
|
|
229
|
+
code=code,
|
|
230
|
+
snippet_name=sn_name,
|
|
231
|
+
path=fpath,
|
|
232
|
+
)
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
# Normal (non-pytestrun) execution path
|
|
222
236
|
ex_code = code
|
|
223
237
|
if contains_top_level_await(code):
|
|
238
|
+
# Auto-wrap async code
|
|
224
239
|
ex_code = wrap_async_code(code)
|
|
225
240
|
|
|
226
241
|
try:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper module for running pytest-style tests found inside executed code blocks.
|
|
3
|
+
When a code block is marked with `pytestrun`, its code is written to a
|
|
4
|
+
temporary file and executed by pytest as a subprocess, so that fixtures,
|
|
5
|
+
markers, setup/teardown, and assertions all work correctly.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import tempfile
|
|
11
|
+
|
|
12
|
+
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
|
|
13
|
+
__copyright__ = "2025-2026 Artur Barseghyan"
|
|
14
|
+
__license__ = "MIT"
|
|
15
|
+
__all__ = ("run_pytest_style_code",)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run_pytest_style_code(
|
|
19
|
+
code: str,
|
|
20
|
+
snippet_name: str,
|
|
21
|
+
path: str,
|
|
22
|
+
) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Write the code block to a temporary file and run it with pytest.
|
|
25
|
+
Raises AssertionError on any test failures.
|
|
26
|
+
"""
|
|
27
|
+
project_root = os.getcwd()
|
|
28
|
+
# Place the temp directory alongside the source file so that pytest walks
|
|
29
|
+
# up through its real directory hierarchy and discovers conftest.py files
|
|
30
|
+
# (including project-root ones that define fixtures).
|
|
31
|
+
source_dir = os.path.dirname(os.path.abspath(path))
|
|
32
|
+
pytest_cache_dir = os.path.join(source_dir, ".pytest_cache")
|
|
33
|
+
os.makedirs(pytest_cache_dir, exist_ok=True)
|
|
34
|
+
tmpdir = tempfile.mkdtemp(prefix="pytest_codeblock_", dir=pytest_cache_dir)
|
|
35
|
+
tmpfile = os.path.join(tmpdir, f"{snippet_name}.py")
|
|
36
|
+
try:
|
|
37
|
+
with open(tmpfile, "w") as f:
|
|
38
|
+
f.write(code)
|
|
39
|
+
env = os.environ.copy()
|
|
40
|
+
env["PYTHONPATH"] = os.pathsep.join(sys.path)
|
|
41
|
+
result = subprocess.run(
|
|
42
|
+
[
|
|
43
|
+
sys.executable, "-m", "pytest", tmpfile,
|
|
44
|
+
f"--rootdir={project_root}",
|
|
45
|
+
"--no-header", "-q",
|
|
46
|
+
],
|
|
47
|
+
capture_output=True,
|
|
48
|
+
text=True,
|
|
49
|
+
cwd=project_root,
|
|
50
|
+
env=env,
|
|
51
|
+
)
|
|
52
|
+
if result.returncode != 0:
|
|
53
|
+
output = (result.stdout + result.stderr).strip()
|
|
54
|
+
raise AssertionError(
|
|
55
|
+
f"pytestrun block `{snippet_name}` in {path} failed:\n\n"
|
|
56
|
+
f"{output}"
|
|
57
|
+
)
|
|
58
|
+
finally:
|
|
59
|
+
try:
|
|
60
|
+
os.unlink(tmpfile)
|
|
61
|
+
os.rmdir(tmpdir)
|
|
62
|
+
except OSError:
|
|
63
|
+
pass
|
|
@@ -10,8 +10,14 @@ import pytest
|
|
|
10
10
|
|
|
11
11
|
from .collector import CodeSnippet, group_snippets
|
|
12
12
|
from .config import get_config
|
|
13
|
-
from .constants import
|
|
13
|
+
from .constants import (
|
|
14
|
+
CODEBLOCK_MARK,
|
|
15
|
+
DJANGO_DB_MARKS,
|
|
16
|
+
PYTESTRUN_MARK,
|
|
17
|
+
TEST_PREFIX,
|
|
18
|
+
)
|
|
14
19
|
from .helpers import contains_top_level_await, wrap_async_code
|
|
20
|
+
from .pytestrun import run_pytest_style_code
|
|
15
21
|
|
|
16
22
|
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
|
|
17
23
|
__copyright__ = "2025-2026 Artur Barseghyan"
|
|
@@ -129,10 +135,6 @@ def parse_rst(text: str, base_dir: Path) -> list[CodeSnippet]:
|
|
|
129
135
|
)
|
|
130
136
|
snippets.append(snippet)
|
|
131
137
|
|
|
132
|
-
# TODO: Is this needed?
|
|
133
|
-
# pending_marks.clear()
|
|
134
|
-
# pending_fixtures.clear()
|
|
135
|
-
|
|
136
138
|
i = j + 1
|
|
137
139
|
continue
|
|
138
140
|
|
|
@@ -211,7 +213,7 @@ def parse_rst(text: str, base_dir: Path) -> list[CodeSnippet]:
|
|
|
211
213
|
sn_marks = pending_marks.copy()
|
|
212
214
|
sn_fixtures = pending_fixtures.copy()
|
|
213
215
|
pending_name = None
|
|
214
|
-
pending_marks = [CODEBLOCK_MARK]
|
|
216
|
+
pending_marks = [CODEBLOCK_MARK] # clear pending marks
|
|
215
217
|
pending_fixtures.clear()
|
|
216
218
|
|
|
217
219
|
snippets.append(CodeSnippet(
|
|
@@ -241,7 +243,7 @@ def parse_rst(text: str, base_dir: Path) -> list[CodeSnippet]:
|
|
|
241
243
|
sn_marks = pending_marks.copy()
|
|
242
244
|
sn_fixtures = pending_fixtures.copy()
|
|
243
245
|
pending_name = None
|
|
244
|
-
pending_marks = [CODEBLOCK_MARK]
|
|
246
|
+
pending_marks = [CODEBLOCK_MARK] # clear pending marks
|
|
245
247
|
pending_fixtures.clear()
|
|
246
248
|
j = i + 1
|
|
247
249
|
if j < n and not lines[j].strip():
|
|
@@ -317,6 +319,7 @@ class RSTFile(pytest.File):
|
|
|
317
319
|
# Bind the values we need so we don't close over `sn` itself
|
|
318
320
|
_sn_name = sn.name
|
|
319
321
|
_fpath = str(self.path)
|
|
322
|
+
_is_pytestrun = PYTESTRUN_MARK in sn.marks
|
|
320
323
|
|
|
321
324
|
# Build list of fixture names requested by this snippet
|
|
322
325
|
_fixture_names: list[str] = list(sn.fixtures)
|
|
@@ -334,11 +337,21 @@ class RSTFile(pytest.File):
|
|
|
334
337
|
sn_name=_sn_name,
|
|
335
338
|
fpath=_fpath,
|
|
336
339
|
fixture_names=_fixture_names,
|
|
340
|
+
is_pytestrun=_is_pytestrun,
|
|
337
341
|
):
|
|
338
342
|
# This inner function *actually* has a **fixtures signature,
|
|
339
343
|
# but we override __signature__ so pytest passes the right
|
|
340
344
|
# fixtures and names.
|
|
341
345
|
def test_block(**fixtures):
|
|
346
|
+
if is_pytestrun:
|
|
347
|
+
run_pytest_style_code(
|
|
348
|
+
code=code,
|
|
349
|
+
snippet_name=sn_name,
|
|
350
|
+
path=fpath,
|
|
351
|
+
)
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
# Normal (non-pytestrun) execution path
|
|
342
355
|
ex_code = code
|
|
343
356
|
if contains_top_level_await(code):
|
|
344
357
|
ex_code = wrap_async_code(code)
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-codeblock
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.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>
|
|
@@ -7,6 +7,7 @@ src/pytest_codeblock/config.py
|
|
|
7
7
|
src/pytest_codeblock/constants.py
|
|
8
8
|
src/pytest_codeblock/helpers.py
|
|
9
9
|
src/pytest_codeblock/md.py
|
|
10
|
+
src/pytest_codeblock/pytestrun.py
|
|
10
11
|
src/pytest_codeblock/rst.py
|
|
11
12
|
src/pytest_codeblock.egg-info/PKG-INFO
|
|
12
13
|
src/pytest_codeblock.egg-info/SOURCES.txt
|
|
@@ -18,4 +19,5 @@ src/pytest_codeblock/tests/__init__.py
|
|
|
18
19
|
src/pytest_codeblock/tests/test_customisation.py
|
|
19
20
|
src/pytest_codeblock/tests/test_integration.py
|
|
20
21
|
src/pytest_codeblock/tests/test_nameless_codeblocks.py
|
|
21
|
-
src/pytest_codeblock/tests/test_pytest_codeblock.py
|
|
22
|
+
src/pytest_codeblock/tests/test_pytest_codeblock.py
|
|
23
|
+
src/pytest_codeblock/tests/test_pytestrun_marker.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/tests/test_customisation.py
RENAMED
|
File without changes
|
{pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock/tests/test_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock.egg-info/requires.txt
RENAMED
|
File without changes
|
{pytest_codeblock-0.5.2 → pytest_codeblock-0.5.4}/src/pytest_codeblock.egg-info/top_level.txt
RENAMED
|
File without changes
|