Skip to content
Merged

0.17 #18

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.12'
- uses: pre-commit/action@v3.0.1
4 changes: 2 additions & 2 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ jobs:
id-token: write

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
ref: main
- name: Set up Python 3.12
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.12'

Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/run_tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ jobs:
name: pre-commit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.12'
- uses: pre-commit/action@v3.0.1
Expand All @@ -19,12 +19,12 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ repos:
- id: trailing-whitespace

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.2
rev: v0.14.6
hooks:
- id: ruff
name: ruff unused imports
Expand Down
4 changes: 2 additions & 2 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ formats: all


build:
os: ubuntu-22.04
os: ubuntu-24.04
tools:
python: "3.10"
python: "3.12"

python:
install:
Expand Down
19 changes: 15 additions & 4 deletions .ruff.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
indent-width = 4
line-length = 120

target-version = "py38"
target-version = "py39"

src = [
"src",
Expand Down Expand Up @@ -30,9 +30,6 @@ ignore = [
# https://docs.astral.sh/ruff/rules/#flake8-bandit-s
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes

# https://docs.astral.sh/ruff/rules/#pyupgrade-up
"UP038", # Use X | Y in {} call instead of (X, Y)

# https://docs.astral.sh/ruff/rules/#flake8-annotations-ann
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed in {name}

Expand Down Expand Up @@ -85,6 +82,10 @@ lines-after-imports = 2 # https://docs.astral.sh/ruff/settings/#lint_isort_l
[lint.per-file-ignores]
"setup.py" = ["PTH123"]

"__init__.py" = [
"F401" # {name} imported but unused https://docs.astral.sh/ruff/rules/unused-import/
]

"tests/*" = [
"ANN", # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann

Expand All @@ -94,4 +95,14 @@ lines-after-imports = 2 # https://docs.astral.sh/ruff/settings/#lint_isort_l
# https://docs.astral.sh/ruff/rules/#refactor-r
"PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
"PLR0913", # Too many arguments in function definition ({c_args} > {max_args})

# https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt
"FBT003", # Boolean positional value in function call

# https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg
"ARG001", # Unused function argument {name}
"ARG002", # Unused method argument {name}
"ARG003", # Unused class method argument: {name}
"ARG004", # Unused static method argument: {name}
"ARG005", # Unused lambda argument: {name}
]
4 changes: 4 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ This code will be executed
```

# Changelog
#### 0.17 (2026-03-18)
- Dropped support for Python 3.8
- Added some type hints and updated dependencies

#### 0.16 (2025-04-01)
- Fixed an issue where the ``hide`` and ``hide_output`` options were not working correctly

Expand Down
11 changes: 5 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# Dependencies for old python versions
pre-commit == 3.5.0; python_version < '3.9'
pytest == 7.4.4; python_version < '3.10'
sphinx == 7.4.7; python_version == '3.9'
sphinx == 7.1.2; python_version == '3.8'
sphinx == 8.1.3; python_version == '3.10'

# Current dependencies
pre-commit == 4.0.1; python_version >= '3.9'
pytest == 8.3.4; python_version >= '3.10'
sphinx == 8.1.3; python_version >= '3.10'
sphinx == 8.2.0; python_version >= '3.11'

ruff == 0.8.2
pre-commit == 4.0.1
ruff == 0.15.6

sphinx-rtd-theme == 3.0.2
sphinx-rtd-theme == 3.1.0
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import typing
from pathlib import Path

from setuptools import find_packages, setup


def load_version() -> str:
version: typing.Dict[str, str] = {}
version: dict[str, str] = {}
with open('src/sphinx_exec_code/__version__.py') as fp:
exec(fp.read(), version)
assert version['__version__'], version
Expand All @@ -15,7 +14,7 @@ def load_version() -> str:
__version__ = load_version()

print(f'Version: {__version__}')
print('')
print()

# When we run tox tests we don't have these files available so we skip them
readme = Path(__file__).with_name('readme.md')
Expand Down Expand Up @@ -45,19 +44,20 @@ def load_version() -> str:
},
package_dir={'': 'src'},
packages=find_packages('src', exclude=['tests*']),
install_requires=['typing-extensions'],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Framework :: Sphinx :: Extension',
'License :: OSI Approved :: Apache Software License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.14',
'Programming Language :: Python :: 3 :: Only',
],
)
2 changes: 1 addition & 1 deletion src/sphinx_exec_code/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.16'
__version__ = '0.17'
12 changes: 9 additions & 3 deletions src/sphinx_exec_code/code_exec.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
from __future__ import annotations

import os
import subprocess
import sys
from itertools import dropwhile
from pathlib import Path
from typing import TYPE_CHECKING

from sphinx_exec_code.code_exec_error import CodeExceptionError
from sphinx_exec_code.configuration import PYTHONPATH_FOLDERS, SET_UTF8_ENCODING, WORKING_DIR


if TYPE_CHECKING:
from pathlib import Path


def execute_code(code: str, file: Path, first_loc: int) -> str:
cwd: Path = WORKING_DIR.value
encoding = 'utf-8' if SET_UTF8_ENCODING.value else None
Expand All @@ -21,14 +27,14 @@ def execute_code(code: str, file: Path, first_loc: int) -> str:
except KeyError:
env['PYTHONPATH'] = os.pathsep.join(python_folders)

run = subprocess.run([sys.executable, '-c', code.encode('utf-8')], capture_output=True, text=True,
run = subprocess.run([sys.executable, '-c', code.encode('utf-8')], capture_output=True, text=True, # noqa: S603
encoding=encoding, cwd=cwd, env=env, check=False)

if run.returncode != 0:
raise CodeExceptionError(code, file, first_loc, run.returncode, run.stderr) from None

# decode output and drop tailing spaces
ret_str = (run.stdout if run.stdout is not None else '' + run.stderr if run.stderr is not None else '').rstrip()
ret_str = ((run.stdout or '') + (run.stderr or '')).rstrip()

# drop leading empty lines
ret_lines = list(dropwhile(lambda x: not x.strip(), ret_str.splitlines()))
Expand Down
13 changes: 8 additions & 5 deletions src/sphinx_exec_code/code_exec_error.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from __future__ import annotations

import re
from pathlib import Path
from typing import List
from typing import TYPE_CHECKING


if TYPE_CHECKING:
from pathlib import Path

re_line = re.compile(r'^\s*File "(<string>)", line (\d+), in <module>', re.MULTILINE)

Expand All @@ -16,14 +20,14 @@ def __init__(self, code: str, file: Path, first_loc: int, ret: int, stderr: str)
self.exec_ret = ret
self.exec_err = stderr

def _err_line(self, lines: List[str]) -> int:
def _err_line(self, lines: list[str]) -> int:
# Find the last line where the error happened
err_line = len(lines)
for m in re_line.finditer(self.exec_err):
err_line = int(m.group(2))
return err_line - 1

def pformat(self) -> List[str]:
def pformat(self) -> list[str]:
filename = self.file.name
code_lines = self.code.splitlines()
err_line = self._err_line(code_lines)
Expand All @@ -41,7 +45,6 @@ def pformat(self) -> List[str]:
for tb_line in self.exec_err.splitlines():
m = re_line.search(tb_line)
if m:
tb_line = tb_line.replace('File "<string>"', f'File "{filename}"')
tb_line = tb_line.replace('File "<string>"', f'File "{filename}"')
tb_line = tb_line.replace(f', line {m.group(2)}, in <module>',
f', line {int(m.group(2)) + self.first_loc - 1}')
Expand Down
14 changes: 9 additions & 5 deletions src/sphinx_exec_code/code_format.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from __future__ import annotations

from textwrap import dedent
from typing import List, Tuple
from typing import TYPE_CHECKING


from docutils.statemachine import StringList
if TYPE_CHECKING:
from docutils.statemachine import StringList


class VisibilityMarkerError(Exception):
Expand All @@ -19,7 +23,7 @@ def __init__(self, marker: str) -> None:

self.do_add = True
self.skip_empty = False
self.lines: List[str] = []
self.lines: list[str] = []

def is_marker(self, line: str) -> bool:
if line == self.start:
Expand Down Expand Up @@ -54,7 +58,7 @@ def add_line(self, line: str) -> None:

self.lines.append(line)

def get_lines(self) -> List[str]:
def get_lines(self) -> list[str]:
# remove leading and tailing empty lines of the code
code_lines = self.lines
while code_lines and not code_lines[0].strip():
Expand All @@ -64,7 +68,7 @@ def get_lines(self) -> List[str]:
return code_lines


def get_show_exec_code(code_lines: StringList) -> Tuple[str, str]:
def get_show_exec_code(code_lines: StringList | list[str]) -> tuple[str, str]:
shown = CodeMarker('hide')
executed = CodeMarker('skip')

Expand Down
17 changes: 11 additions & 6 deletions src/sphinx_exec_code/configuration/base.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
from typing import Any, Final, Generic, Optional, Tuple, Type, TypeVar, Union
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Final, Generic, TypeVar

from sphinx.application import Sphinx as SphinxApp
from sphinx.errors import ConfigError

from sphinx_exec_code.__const__ import log


if TYPE_CHECKING:
from sphinx.application import Sphinx as SphinxApp


TYPE_VALUE = TypeVar('TYPE_VALUE')


class SphinxConfigValue(Generic[TYPE_VALUE]):
SPHINX_TYPE: Union[Tuple[Type[Any], ...], Type[Any]]
SPHINX_TYPE: tuple[type[Any], ...] | type[Any] | tuple[()]

def __init__(self, sphinx_name: str, initial_value: Optional[TYPE_VALUE] = None) -> None:
def __init__(self, sphinx_name: str, initial_value: TYPE_VALUE | None = None) -> None:
self.sphinx_name: Final = sphinx_name
self._value: Optional[TYPE_VALUE] = initial_value
self._value: TYPE_VALUE | None = initial_value

@property
def value(self) -> TYPE_VALUE:
Expand All @@ -26,7 +31,7 @@ def value(self) -> TYPE_VALUE:
def transform_value(self, app: SphinxApp, value: Any) -> TYPE_VALUE:
return value

def validate_value(self, value) -> TYPE_VALUE:
def validate_value(self, value: Any) -> TYPE_VALUE:
return value

def from_app(self, app: SphinxApp) -> TYPE_VALUE:
Expand Down
16 changes: 13 additions & 3 deletions src/sphinx_exec_code/configuration/flag_config.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
from sphinx.application import Sphinx as SphinxApp
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from typing_extensions import override

from sphinx_exec_code.configuration.base import SphinxConfigValue


if TYPE_CHECKING:
from sphinx.application import Sphinx as SphinxApp


class SphinxConfigFlag(SphinxConfigValue[bool]):
SPHINX_TYPE = bool

def validate_value(self, value) -> bool:
@override
def validate_value(self, value: Any) -> bool:
if not isinstance(value, bool):
raise TypeError()
return value

def transform_value(self, app: SphinxApp, value) -> bool:
@override
def transform_value(self, app: SphinxApp, value: Any) -> bool:
return bool(value)
Loading
Loading