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.
Files changed (23) hide show
  1. {pytest_codeblock-0.3.4/src/pytest_codeblock.egg-info → pytest_codeblock-0.4}/PKG-INFO +28 -8
  2. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/README.rst +25 -5
  3. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/pyproject.toml +7 -3
  4. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/__init__.py +5 -3
  5. pytest_codeblock-0.4/src/pytest_codeblock/config.py +133 -0
  6. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/md.py +8 -6
  7. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/rst.py +3 -1
  8. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/tests/conftest.py +13 -0
  9. pytest_codeblock-0.4/src/pytest_codeblock/tests/test_customisation.py +96 -0
  10. pytest_codeblock-0.4/src/pytest_codeblock/tests/test_integration.py +1053 -0
  11. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/tests/test_pytest_codeblock.py +4 -4
  12. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4/src/pytest_codeblock.egg-info}/PKG-INFO +28 -8
  13. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock.egg-info/SOURCES.txt +3 -0
  14. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock.egg-info/requires.txt +3 -0
  15. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/LICENSE +0 -0
  16. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/setup.cfg +0 -0
  17. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/collector.py +0 -0
  18. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/constants.py +0 -0
  19. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/helpers.py +0 -0
  20. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock/tests/__init__.py +0 -0
  21. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock.egg-info/dependency_links.txt +0 -0
  22. {pytest_codeblock-0.3.4 → pytest_codeblock-0.4}/src/pytest_codeblock.egg-info/entry_points.txt +0 -0
  23. {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.4
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.9
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 by name**: Split a single example across multiple code blocks;
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.9+
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
- No configuration needed. All your `.rst` and `.md` files shall be picked
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 by name**: Split a single example across multiple code blocks;
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.9+
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
- No configuration needed. All your `.rst` and `.md` files shall be picked
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.3.4"
6
- requires-python = ">=3.9"
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.3.4"
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((".md", ".markdown")):
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(".rst"):
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: <name> --> comments for grouping with a named snippet
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
- snippets: list[CodeSnippet] = []
38
+ config = get_config()
39
+ snippets: list[CodeSnippet] = []
38
40
  lines = text.splitlines()
39
- pending_name: Optional[str] = None
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: list[str] = []
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 ("python", "py", "python3"):
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: list[str] = list(sn.fixtures)
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 ("python", "py", "python3"):
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