Skip to content

Commit 7da9616

Browse files
committed
fix mypy/ruff violations
1 parent 9da583c commit 7da9616

File tree

5 files changed

+37
-21
lines changed

5 files changed

+37
-21
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,5 +83,6 @@ dev = [
8383
"pytest-subprocess>=1.5.3",
8484
"ruff>=0.11.10",
8585
"sphinx-rtd-theme>=3.0.2",
86+
"types-docutils>=0.21.0.20250604",
8687
"types-pyyaml>=6.0.12.20250516",
8788
]

structured_tutorials/runners/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,13 @@ def run(self) -> None:
7272
for part in self.tutorial.parts:
7373
if isinstance(part, File):
7474
self.handle_file_step(part, context)
75+
self._performed_steps.append(part)
7576
elif isinstance(part, Commands):
7677
for command in part.commands:
7778
self.handle_command(command, context)
79+
self._performed_steps.append(command)
7880
else: # pragma: no cover
7981
raise ValueError(f"{part}: Unknown step type.")
80-
self._performed_steps.append(part)
8182
finally:
8283
for performed_step in reversed(self._performed_steps):
8384
if isinstance(performed_step, Command):

structured_tutorials/sphinx/__init__.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
"""Sphinx extension."""
22

3-
import copy
43
import shlex
54
from pathlib import Path
6-
from typing import Any
5+
from typing import TYPE_CHECKING, Any
76

87
from docutils.nodes import Body, Node, paragraph
8+
from docutils.parsers.rst.states import RSTState
99
from docutils.statemachine import StringList
1010
from jinja2 import Environment
1111
from sphinx.application import Sphinx
1212
from sphinx.config import Config
13+
from sphinx.environment import BuildEnvironment
1314
from sphinx.errors import ConfigError, ExtensionError, SphinxError
1415
from sphinx.util.docutils import SphinxDirective
1516
from sphinx.util.typing import ExtensionMetadata
@@ -22,24 +23,32 @@
2223

2324
LAST_PART = "__end__"
2425

26+
if TYPE_CHECKING:
27+
from sphinx.environment import _CurrentDocument
28+
2529

2630
class CurrentDocumentMixin:
31+
"""Mixin adding the current document property and context."""
32+
33+
if TYPE_CHECKING:
34+
env: BuildEnvironment
35+
2736
# NOTE: sphinx 8.2.0 introduced "current_document", temp_data is deprecated and kept only for
2837
# backwards compatability: https://github.com/sphinx-doc/sphinx/pull/13151
2938
@property
30-
def current_document(self):
39+
def current_document(self) -> "_CurrentDocument":
3140
if hasattr(self.env, "current_document"):
3241
return self.env.current_document
3342
else:
3443
return self.env.temp_data
3544

3645
@property
37-
def context(self):
38-
return self.current_document["tutorial-context"]
46+
def context(self) -> dict[str, Any]:
47+
return self.current_document["tutorial-context"] # type: ignore[no-any-return]
3948

4049
@property
4150
def jinja(self) -> Environment:
42-
return self.current_document["tutorial-env"]
51+
return self.current_document["tutorial-env"] # type: ignore[no-any-return]
4352

4453

4554
class TutorialDirective(CurrentDocumentMixin, SphinxDirective):
@@ -49,7 +58,7 @@ class TutorialDirective(CurrentDocumentMixin, SphinxDirective):
4958
required_arguments = 1
5059
optional_arguments = 0
5160

52-
def get_tutorial_path(self, tutorial: str) -> str:
61+
def get_tutorial_path(self, tutorial: str) -> Path:
5362
root: Path = self.config.tutorial_root
5463
tutorial_path = Path(tutorial)
5564
if tutorial_path.is_absolute():
@@ -60,9 +69,9 @@ def get_tutorial_path(self, tutorial: str) -> str:
6069
return absolute_path
6170

6271
def run(self) -> list[Node]:
63-
tutorial = self.arguments[0].strip()
72+
tutorial_arg = self.arguments[0].strip()
6473

65-
tutorial_path = self.get_tutorial_path(tutorial)
74+
tutorial_path = self.get_tutorial_path(tutorial_arg)
6675
tutorial = load_tutorial(tutorial_path)
6776

6877
self.current_document["tutorial"] = tutorial
@@ -84,10 +93,10 @@ class PartDirective(CurrentDocumentMixin, SphinxDirective):
8493
required_arguments = 0
8594
optional_arguments = 1 # Next part to display
8695

87-
def render(self, template: str, **extra_context: dict[str, Any]) -> str:
96+
def render(self, template: str, **extra_context: Any) -> str:
8897
return self.jinja.from_string(template).render({**self.context, **extra_context})
8998

90-
def get_command(self, command: Command) -> dict:
99+
def get_command(self, command: Command) -> dict[str, Any]:
91100
if isinstance(command.command, str):
92101
args = self.jinja.from_string(command.command).render(self.context)
93102
else:
@@ -128,13 +137,6 @@ def render_file(self, file: File) -> str:
128137
{file}
129138
"""
130139

131-
@property
132-
def current_document(self):
133-
if hasattr(self.env, "current_document"):
134-
return self.env.current_document
135-
else:
136-
return self.env.temp_data
137-
138140
def run(self) -> list[paragraph]:
139141
node = paragraph()
140142

@@ -166,7 +168,7 @@ def run(self) -> list[paragraph]:
166168
raise ExtensionError(f"{next_part_id}: Part not found.")
167169

168170
lines = StringList(text.splitlines())
169-
state: Body = self.state
171+
state: RSTState = self.state
170172
state.nested_parse(lines, 0, node)
171173
return [node]
172174

tests/test_local_runner.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytest
66
from pytest_subprocess.fake_process import FakeProcess
77

8-
from structured_tutorials.models import Command
8+
from structured_tutorials.models import Command, Commands
99
from structured_tutorials.runners.base import TutorialError
1010
from structured_tutorials.tutorial import load_tutorial, run_tutorial
1111
from tests.conftest import DATA_DIR, ROOT_DIR
@@ -32,6 +32,7 @@ def test_minimal_with_expected_error(fp: FakeProcess) -> None:
3232
"""Test the minimal tutorial that throws an expected error."""
3333
fp.register(["ls"], returncode=1)
3434
tutorial = load_tutorial(DATA_DIR / "minimal.yaml")
35+
assert isinstance(tutorial.parts[0], Commands)
3536
step = tutorial.parts[0].commands[0]
3637
assert isinstance(step, Command)
3738
step.returncode = 1

uv.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)