pax_global_header00006660000000000000000000000064152626274640014530gustar00rootroot0000000000000052 comment=1a565b09f029323f4570388ea01117a3d505b084 pytest-examples-0.0.18/000077500000000000000000000000001526262746400147625ustar00rootroot00000000000000pytest-examples-0.0.18/.github/000077500000000000000000000000001526262746400163225ustar00rootroot00000000000000pytest-examples-0.0.18/.github/workflows/000077500000000000000000000000001526262746400203575ustar00rootroot00000000000000pytest-examples-0.0.18/.github/workflows/ci.yml000066400000000000000000000052271526262746400215030ustar00rootroot00000000000000name: CI on: push: branches: - main tags: - '**' pull_request: {} env: COLUMNS: 150 jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v3 with: enable-cache: true - run: uv sync --python 3.12 --frozen --no-dev --group lint - uses: pre-commit/action@v3.0.0 with: extra_args: --all-files --verbose env: SKIP: no-commit-to-branch test: name: test py${{ matrix.python-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] env: UV_PYTHON: ${{ matrix.python-version }} steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v3 with: enable-cache: true - run: mkdir coverage - run: uv run --frozen coverage run -m pytest env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} - name: store coverage files uses: actions/upload-artifact@v4 with: name: coverage-${{ matrix.python-version }} path: coverage include-hidden-files: true coverage: runs-on: ubuntu-latest needs: [test] steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v3 with: enable-cache: true - name: get coverage files uses: actions/download-artifact@v4 with: merge-multiple: true path: coverage - run: uv run --frozen coverage combine coverage - run: uv run --frozen coverage report --fail-under 85 # https://github.com/marketplace/actions/alls-green#why used for branch protection checks check: if: always() needs: [lint, test, coverage] runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} release: name: Deploy needs: [check] if: "success() && startsWith(github.ref, 'refs/tags/')" runs-on: ubuntu-latest environment: release permissions: id-token: write steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v3 with: enable-cache: true - name: check GITHUB_REF matches package version uses: samuelcolvin/check-python-version@v4.1 with: version_file_path: pyproject.toml - run: uv build - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: skip-existing: true pytest-examples-0.0.18/.gitignore000066400000000000000000000004011526262746400167450ustar00rootroot00000000000000.idea/ env/ venv/ .venv/ env3*/ Pipfile *.py[cod] *.egg-info/ .python-version /build/ dist/ .cache/ .mypy_cache/ test.py .coverage /htmlcov/ /site/ /site.zip .pytest_cache/ .vscode/ _build/ .auto-format /sandbox/ /.ghtopdep_cache/ /worktrees/ /.ruff_cache/ pytest-examples-0.0.18/.pre-commit-config.yaml000066400000000000000000000013221526262746400212410ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 hooks: - id: no-commit-to-branch # prevent direct commits to the `main` branch - id: check-yaml - id: check-toml - id: end-of-file-fixer - id: trailing-whitespace - repo: local hooks: - id: format name: Format entry: make args: [format] language: system types: [python] pass_filenames: false - id: lint name: Lint entry: make args: [lint] types: [python] language: system pass_filenames: false - id: typecheck name: Typecheck entry: make args: [typecheck] language: system types: [python] pass_filenames: false pytest-examples-0.0.18/LICENSE000066400000000000000000000021211526262746400157630ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2023 Samuel Colvin & Pydantic Services inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pytest-examples-0.0.18/Makefile000066400000000000000000000025321526262746400164240ustar00rootroot00000000000000.DEFAULT_GOAL := all sources = pytest_examples tests example .PHONY: .uv # Check that uv is installed .uv: @uv --version || echo 'Please install uv: https://docs.astral.sh/uv/getting-started/installation/' .PHONY: install # Install the package, dependencies, and pre-commit for local development install: .uv uv sync --frozen --group lint uv run pre-commit install --install-hooks .PHONY: format # Format the code format: uv run ruff format uv run ruff check --fix --fix-only .PHONY: lint # Lint the code lint: uv run ruff format --check uv run ruff check .PHONY: typecheck typecheck: uv run pyright .PHONY: test test: uv run pytest .PHONY: test-all-python # Run tests on Python 3.9 to 3.13 test-all-python: UV_PROJECT_ENVIRONMENT=.venv39 uv run --python 3.9 coverage run -p -m pytest UV_PROJECT_ENVIRONMENT=.venv310 uv run --python 3.10 coverage run -p -m pytest UV_PROJECT_ENVIRONMENT=.venv311 uv run --python 3.11 coverage run -p -m pytest UV_PROJECT_ENVIRONMENT=.venv312 uv run --python 3.12 coverage run -p -m pytest UV_PROJECT_ENVIRONMENT=.venv313 uv run --python 3.13 coverage run -p -m pytest @uv run coverage combine @uv run coverage report .PHONY: testcov # Run tests and collect coverage data testcov: uv run coverage run -m pytest @uv run coverage report @uv run coverage html .PHONY: all all: format lint typecheck testcov pytest-examples-0.0.18/README.md000066400000000000000000000060701526262746400162440ustar00rootroot00000000000000# pytest-examples [![CI](https://github.com/pydantic/pytest-examples/actions/workflows/ci.yml/badge.svg)](https://github.com/pydantic/pytest-examples/actions/workflows/ci.yml?query=event%3Apush+branch%3Amain) [![PyPI](https://img.shields.io/pypi/v/pytest-examples.svg)](https://pypi.python.org/pypi/pytest-examples) [![versions](https://img.shields.io/pypi/pyversions/pytest-examples.svg)](https://github.com/pydantic/pytest-examples) [![license](https://img.shields.io/github/license/pydantic/pytest-examples.svg)](https://github.com/pydantic/pytest-examples/blob/main/LICENSE) Pytest plugin for testing Python code examples in docstrings and markdown files. `pytest-examples` can: * lint code examples using `ruff` and `black` * run code examples * run code examples and check print statements are inlined correctly in the code It can also update code examples in place to format them and insert or update print statements. ## Installation ```bash pip install -U pytest-examples ``` ## Usage ### Basic usage Here's an example basic usage - lint then run examples in the `foo_dir` directory and the `bar_file.py` file. ```py import pytest from pytest_examples import find_examples, CodeExample, EvalExample @pytest.mark.parametrize('example', find_examples('foo_dir', 'bar_file.py'), ids=str) def test_docstrings(example: CodeExample, eval_example: EvalExample): eval_example.lint(example) eval_example.run(example) ``` ### Check print statements `pytest-examples` can also check print statements are inserted correctly. There's the expected format of prints statemints in docstrings: ```py def add_two_things(a, b): """ ```py from my_lib import add_two_things print(add_two_things(1, 2)) #> 3 ``` """ return a + b ``` And here's an example of a markdown file, again documenting `add_two_things`: ````markdown # How `add_two_things` works ```py from my_lib import add_two_things print(add_two_things(1, 2)) #> 3 ``` ```` `pytest-examples` can then run the code and check the print statements are correct: ```py import pytest from pytest_examples import find_examples, CodeExample, EvalExample @pytest.mark.parametrize('example', find_examples('foo_dir'), ids=str) def test_docstrings(example: CodeExample, eval_example: EvalExample): eval_example.run_print_check(example) ``` ### Updating files As well as checking linting and print statements, are correct, we can also update files. This requires the `--update-examples` flags **AND** use of the `format()` and `run_print_update()` methods. Here's a full example of a unit test that checks code when called normally, but can update it when the flag is set: ```py import pytest from pytest_examples import find_examples, CodeExample, EvalExample @pytest.mark.parametrize('example', find_examples('README.md'), ids=str) def test_readme(example: CodeExample, eval_example: EvalExample): if eval_example.update_examples: eval_example.format(example) eval_example.run_print_update(example) else: eval_example.lint(example) eval_example.run_print_check(example) ``` pytest-examples-0.0.18/example/000077500000000000000000000000001526262746400164155ustar00rootroot00000000000000pytest-examples-0.0.18/example/README.md000066400000000000000000000016371526262746400177030ustar00rootroot00000000000000# Example of `pytest-examples` Usage ```py x = 123 print( 'this is an example of how pytest-examples is used', x, [1, 2, 3], ) """ this is an example of how pytest-examples is used 123 [1, 2, 3] """ # something more complex if True: print({i: f'this is {i}' for i in range(10)}) """ { 0: 'this is 0', 1: 'this is 1', 2: 'this is 2', 3: 'this is 3', 4: 'this is 4', 5: 'this is 5', 6: 'this is 6', 7: 'this is 7', 8: 'this is 8', 9: 'this is 9', } """ ``` # Another example ```py print('this is another example') #> this is another example ``` # And another ```py print(b'hello world') #> b'hello world' ``` ```py import string print( [ string.ascii_letters[: i + 10] for i in range(4) ] ) """ [ 'abcdefghij', 'abcdefghijk', 'abcdefghijkl', 'abcdefghijklm', ] """ ``` pytest-examples-0.0.18/example/error.md000066400000000000000000000003611526262746400200700ustar00rootroot00000000000000# Example of `pytest-examples` Usage ```py description = 'this is an example of how pytest-examples is used' a = 1 b = 2 # assert a + b == 4 # c = 1 / 0 def foo(): """ This will fail """ x = 1 return x / 0 foo() ``` pytest-examples-0.0.18/example/test_example.py000066400000000000000000000030321526262746400214570ustar00rootroot00000000000000import pytest from pytest_examples import CodeExample, EvalExample, find_examples @pytest.mark.xfail(reason='This is an expected failure due to errors in the code', strict=True) @pytest.mark.parametrize('example', find_examples('example/error.md'), ids=str) def test_will_error(example: CodeExample, eval_example: EvalExample): eval_example.lint(example) eval_example.run(example) @pytest.mark.parametrize('example', find_examples('example/README.md'), ids=str) def test_insert_print(example: CodeExample, eval_example: EvalExample): eval_example.set_config(line_length=50) if eval_example.update_examples: eval_example.format(example) eval_example.run_print_update(example) else: eval_example.lint(example) eval_example.run_print_check(example) @pytest.mark.parametrize('example', find_examples('example/test_example.py'), ids=str) def test_python_self(example: CodeExample, eval_example: EvalExample): """ Test this code. ```py print('this is introspection!') #> this is introspection! ``` """ eval_example.lint(example) eval_example.run_print_check(example) @pytest.mark.parametrize('example', find_examples('example/test_example.py'), ids=str) def test_python_self_change_docstyle(example: CodeExample, eval_example: EvalExample): """Test this code (no line break at beginning of docstring). ```py print('this is introspection!') #> this is introspection! ``` """ eval_example.lint(example) eval_example.run_print_check(example) pytest-examples-0.0.18/pyproject.toml000066400000000000000000000061241526262746400177010ustar00rootroot00000000000000[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.sdist] # limit which files are included in the sdist (.tar.gz) asset, # see https://github.com/pydantic/pydantic/pull/4542 include = ["/README.md", "/Makefile", "/pytest_examples", "/tests"] [project] name = "pytest-examples" version = "0.0.18" description = "Pytest plugin for testing examples in docstrings and markdown files." authors = [{ name = "Samuel Colvin", email = "s@muelcolvin.com" }] license = "MIT" readme = "README.md" classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "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", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: Unix", "Operating System :: POSIX :: Linux", "Environment :: Console", "Environment :: MacOS X", "Framework :: Pytest", "Topic :: Software Development :: Libraries :: Python Modules", ] requires-python = ">=3.8" dependencies = ["pytest>=7", "black>=23", "ruff>=0.5.0"] [project.entry-points.pytest11] examples = "pytest_examples" [project.urls] repository = "https://github.com/pydantic/pytest-examples" [dependency-groups] dev = ["coverage[toml]>=7.6.1", "pytest-pretty>=1.2.0"] lint = ["pre-commit>=3.5.0", "pyright>=1.1.389", "ruff>=0.7.4"] [tool.pytest.ini_options] testpaths = ["tests", "example"] filterwarnings = "error" xfail_strict = true [tool.ruff] line-length = 120 target-version = "py39" include = ["pytest_examples/**/*.py", "tests/**/*.py", "examples/**/*.py"] exclude = ["tests/cases_update/*.py"] [tool.ruff.lint] extend-select = ["Q", "RUF100", "C90", "UP", "I", "D"] flake8-quotes = { inline-quotes = "single", multiline-quotes = "double" } isort = { combine-as-imports = true, known-first-party = ["pytest_examples"] } mccabe = { max-complexity = 15 } ignore = [ "D100", # ignore missing docstring in module "D101", # ignore missing docstring in public class "D102", # ignore missing docstring in public method "D103", # ignore missing docstring in public function "D104", # ignore missing docstring in public package "D105", # ignore missing docstring in magic methods "D107", # ignore missing docstring in __init__ methods ] [tool.ruff.lint.pydocstyle] convention = "google" [tool.ruff.format] docstring-code-format = true quote-style = "single" [tool.coverage.run] source = ["pytest_examples"] branch = true [tool.coverage.report] precision = 2 exclude_lines = [ "pragma: no cover", "raise NotImplementedError", "if TYPE_CHECKING:", "@overload", ] [tool.pyright] #typeCheckingMode = "strict" reportUnnecessaryTypeIgnoreComment = true reportMissingTypeStubs = false reportUnusedCallResult = false reportExplicitAny = false reportAny = false include = ["pytest_examples"] venvPath = ".venv" pytest-examples-0.0.18/pytest_examples/000077500000000000000000000000001526262746400202105ustar00rootroot00000000000000pytest-examples-0.0.18/pytest_examples/__init__.py000066400000000000000000000041041526262746400223200ustar00rootroot00000000000000from __future__ import annotations as _annotations from collections.abc import Iterator from importlib.metadata import version from pathlib import Path import pytest from .eval_example import EvalExample from .find_examples import CodeExample, find_examples __version__ = version('pytest_examples') __all__ = 'find_examples', 'CodeExample', 'EvalExample' def pytest_addoption(parser) -> None: """Add options to the pytest command line.""" group = parser.getgroup('examples') group.addoption( '--update-examples', action='store_true', help=( '[WARNING: this will allow files to be changed in place!] ' 'Update code examples to reflect print output and linting.' ), ) group.addoption( '--update-examples-disable-summary', action='store_true', help='Disable the summary of updated examples at the end of the test run.', ) summary: str | None = None @pytest.fixture(scope='session') def _examples_to_update(pytestconfig: pytest.Config) -> Iterator[list[CodeExample]]: """Don't use this directly, it's just used by.""" global summary examples_to_update: list[CodeExample] = [] yield examples_to_update if pytestconfig.getoption('update_examples') and examples_to_update: from .modify_files import _modify_files summary_ = _modify_files(examples_to_update) if not pytestconfig.getoption('update_examples_disable_summary'): summary = summary_ @pytest.fixture(name='eval_example') def eval_example(tmp_path: Path, request: pytest.FixtureRequest, _examples_to_update) -> Iterator[EvalExample]: """Fixture to return a `EvalExample` instance for running and linting examples.""" eval_ex = EvalExample(tmp_path=tmp_path, pytest_request=request) yield eval_ex if request.config.getoption('update_examples'): _examples_to_update.extend(eval_ex.to_update) def pytest_terminal_summary() -> None: """Customise pytest to print the summary of updated examples at the end of the test run.""" if summary: print(summary) pytest-examples-0.0.18/pytest_examples/config.py000066400000000000000000000062401526262746400220310ustar00rootroot00000000000000from __future__ import annotations as _annotations import hashlib import tempfile from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING from black.const import DEFAULT_LINE_LENGTH from black.mode import Mode as BlackMode, TargetVersion as BlackTargetVersion if TYPE_CHECKING: from typing import Literal __all__ = 'ExamplesConfig', 'DEFAULT_LINE_LENGTH' @dataclass class ExamplesConfig: line_length: int = DEFAULT_LINE_LENGTH quotes: Literal['single', 'double', 'either'] = 'either' magic_trailing_comma: bool = True target_version: Literal['py37', 'py38', 'py39', 'py310', 'py311'] = 'py37' upgrade: bool = False isort: bool = False ruff_line_length: int | None = None ruff_select: list[str] | None = None ruff_ignore: list[str] | None = None white_space_dot: bool = False """If True, replace spaces with `·` in example diffs.""" def black_mode(self): return BlackMode( line_length=self.line_length, target_versions={BlackTargetVersion[self.target_version.upper()]} if self.target_version else set(), string_normalization=self.quotes == 'double', magic_trailing_comma=self.magic_trailing_comma, ) def hash(self) -> str: # str(self) should be a good identifier of a specific config return hashlib.md5(str(self).encode()).hexdigest() def ruff_config(self) -> tuple[str, ...]: config_lines: list[str] = [] select: list[str] = [] ignore: list[str] = [] args: list[str] = [] # line length is enforced by black if self.ruff_line_length is None: # if not ruff line length, ignore E501 which is line length errors # by default, ruff sets the line length to 88 ignore.append('E501') else: args.append(f'--line-length={self.ruff_line_length}') if self.ruff_select: select.extend(self.ruff_select) if self.quotes == 'single': # enforce single quotes using ruff, black will enforce double quotes select.append('Q') config_lines.append("flake8-quotes = {inline-quotes = 'single', multiline-quotes = 'double'}") if self.target_version: args.append(f'--target-version={self.target_version}') if self.upgrade: select.append('UP') if self.isort: select.append('I') if self.ruff_ignore: ignore.extend(self.ruff_ignore) if select: # use extend to not disable default select args.append(f'--extend-select={",".join(select)}') if ignore: args.append(f'--ignore={",".join(ignore)}') if config_lines: config_toml = '\n'.join(config_lines) config_file = Path(tempfile.gettempdir()) / 'pytest-examples-ruff-config' / self.hash() / 'ruff.toml' if not config_file.exists() or config_file.read_text() != config_toml: config_file.parent.mkdir(parents=True, exist_ok=True) config_file.write_text(config_toml) args.append(f'--config={config_file}') return tuple(args) pytest-examples-0.0.18/pytest_examples/eval_example.py000066400000000000000000000230401526262746400232230ustar00rootroot00000000000000from __future__ import annotations as _annotations from pathlib import Path from typing import TYPE_CHECKING, Any, Callable import pytest from _pytest.assertion.rewrite import AssertionRewritingHook from _pytest.outcomes import Failed as PytestFailed from .config import DEFAULT_LINE_LENGTH, ExamplesConfig from .lint import FormatError, black_check, black_format, ruff_check, ruff_format from .run_code import IncludePrint, InsertPrintStatements, run_code if TYPE_CHECKING: from typing import Literal from .find_examples import CodeExample __all__ = ('EvalExample',) class EvalExample: """Class to run and lint examples.""" def __init__(self, *, tmp_path: Path, pytest_request: pytest.FixtureRequest): self.tmp_path = tmp_path self._pytest_config = pytest_request.config self._test_id = pytest_request.node.nodeid self.to_update: list[CodeExample] = [] self.config: ExamplesConfig = ExamplesConfig() self.print_callback: Callable[[str], str] | None = None self.include_print: IncludePrint | None = None def set_config( self, *, line_length: int = DEFAULT_LINE_LENGTH, quotes: Literal['single', 'double', 'either'] = 'either', magic_trailing_comma: bool = True, target_version: Literal['py37', 'py38', 'py39', 'py310', 'py310'] = 'py37', upgrade: bool = False, isort: bool = False, ruff_line_length: int | None = None, ruff_select: list[str] | None = None, ruff_ignore: list[str] | None = None, ): """Set the config for lints. Args: line_length: The line length to use when wrapping print statements, defaults to 88. quotes: The quote to use, defaults to "either". magic_trailing_comma: If True, add a trailing comma to magic methods, defaults to True. target_version: The target version to use when upgrading code, defaults to "py37". upgrade: If True, upgrade the code to the target version, defaults to False. isort: If True, run ruff's isort extension on the code, defaults to False. ruff_line_length: In general, we disable line-length checks in ruff, to let black take care of them. ruff_select: Ruff rules to select ruff_ignore: Ruff rules to ignore """ self.config = ExamplesConfig( line_length=line_length, quotes=quotes, magic_trailing_comma=magic_trailing_comma, target_version=target_version, upgrade=upgrade, isort=isort, ruff_line_length=ruff_line_length, ruff_select=ruff_select, ruff_ignore=ruff_ignore, ) @property def update_examples(self) -> bool: return bool(self._pytest_config.getoption('update_examples')) def run( self, example: CodeExample, *, module_globals: dict[str, Any] | None = None, rewrite_assertions: bool = True, call: str | None = None, ) -> dict[str, Any]: """Run the example, print is not mocked and print statements are not checked. Args: example: The example to run. module_globals: The globals to use when running the example. rewrite_assertions: If True, rewrite assertions in the example using pytest's assertion rewriting. call: If not None, method to check for and call if it exists. """ __tracebackhide__ = True example.test_id = self._test_id _, module_dict = self._run(example, None, module_globals, rewrite_assertions, call) return module_dict def run_print_check( self, example: CodeExample, *, module_globals: dict[str, Any] | None = None, rewrite_assertions: bool = True, call: str | None = None, ) -> dict[str, Any]: """Run the example and check print statements. Args: example: The example to run. module_globals: The globals to use when running the example. rewrite_assertions: If True, rewrite assertions in the example using pytest's assertion rewriting. call: If not None, method to check for and call if it exists. """ __tracebackhide__ = True example.test_id = self._test_id insert_print, module_dict = self._run(example, 'check', module_globals, rewrite_assertions, call) insert_print.check_print_statements(example) return module_dict def run_print_update( self, example: CodeExample, *, module_globals: dict[str, Any] | None = None, rewrite_assertions: bool = True, call: str | None = None, ) -> dict[str, Any]: """Run the example and update print statements, requires `--update-examples`. Args: example: The example to run. module_globals: The globals to use when running the example. rewrite_assertions: If True, rewrite assertions in the example using pytest's assertion rewriting. call: If not None, method to check for and call if it exists. """ __tracebackhide__ = True self._check_update(example) insert_print, module_dict = self._run(example, 'update', module_globals, rewrite_assertions, call) new_code = insert_print.updated_print_statements(example) if new_code: example.source = new_code self._mark_for_update(example) return module_dict def _run( self, example: CodeExample, insert_print_statements: Literal['check', 'update', None], module_globals: dict[str, Any] | None, rewrite_assertions: bool, call: str | None, ) -> tuple[InsertPrintStatements, dict[str, Any]]: __tracebackhide__ = True if rewrite_assertions: loader = AssertionRewritingHook(config=self._pytest_config) loader.mark_rewrite(example.module_name) else: loader = None if insert_print_statements == 'check': enable_print_mock = True elif insert_print_statements == 'update': enable_print_mock = True else: enable_print_mock = False python_file = self._write_file(example) return run_code( example=example, python_file=python_file, loader=loader, config=self.config, enable_print_mock=enable_print_mock, print_callback=self.print_callback, include_print=self.include_print, module_globals=module_globals, call=call, ) def lint(self, example: CodeExample) -> None: """Lint the example with black and ruff. Args: example: The example to lint. """ self.lint_black(example) self.lint_ruff(example) def lint_black(self, example: CodeExample) -> None: """Lint the example using black. Args: example: The example to lint. """ example.test_id = self._test_id try: black_check(example, self.config) except FormatError as exc: raise PytestFailed(str(exc), pytrace=False) from None def lint_ruff( self, example: CodeExample, ) -> None: """Lint the example using ruff. Args: example: The example to lint. """ example.test_id = self._test_id try: ruff_check(example, self.config) except FormatError as exc: raise PytestFailed(str(exc), pytrace=False) from None def format(self, example: CodeExample) -> None: """Format the example with black and ruff, requires `--update-examples`. Args: example: The example to format. """ self.format_ruff(example) self.format_black(example) def format_black(self, example: CodeExample) -> None: """Format the example using black, requires `--update-examples`. Args: example: The example to lint. """ self._check_update(example) new_content = black_format(example.source, self.config, remove_double_blank=example.in_py_file()) if new_content != example.source: example.source = new_content self._mark_for_update(example) def format_ruff( self, example: CodeExample, ) -> None: """Format the example using ruff, requires `--update-examples`. Args: example: The example to lint. """ self._check_update(example) try: new_content = ruff_format(example, self.config) except FormatError as exc: raise PytestFailed(str(exc), pytrace=False) from None else: if new_content != example.source: example.source = new_content self._mark_for_update(example) def _check_update(self, example: CodeExample) -> None: if not self.update_examples: raise RuntimeError('Cannot update examples without --update-examples') example.test_id = self._test_id def _mark_for_update(self, example: CodeExample) -> None: """Add the example to self.to_update IF it's not already there.""" s = str(example) if not any(s == str(ex) for ex in self.to_update): self.to_update.append(example) def _write_file(self, example: CodeExample) -> Path: python_file = self.tmp_path / f'{example.module_name}.py' python_file.write_text(example.source) return python_file pytest-examples-0.0.18/pytest_examples/find_examples.py000066400000000000000000000137561526262746400234140ustar00rootroot00000000000000from __future__ import annotations as _annotations import re import shlex from collections.abc import Iterable from dataclasses import dataclass from pathlib import Path from textwrap import dedent from uuid import UUID, uuid4 __all__ = 'CodeExample', 'find_examples' @dataclass class CodeExample: """Information about a Python code example.""" source: str """The source code of the example, this is has any indent removed.""" path: Path """The path to the file containing the example.""" start_line: int """The line number of the first line of the example.""" end_line: int """The line number of the last line of the example.""" start_index: int """Index of the start of the example.""" end_index: int """Index of the end of the example.""" prefix: str """The prefix of the code block, e.g. `py`, can also contain rules for skipping some tests.""" indent: int """The indentation of the example, number of spaces.""" group: UUID | None = None """A unique identifier for the example group.""" test_id: str | None = None """ID of the test this example was generated for.""" @classmethod def create( cls, source: str, *, path: Path = Path('testing.md'), start_line: int = 0, end_line: int | None = None, start_index: int = 0, end_index: int | None = None, prefix: str = '', indent: int = 0, ): """Create a `CodeExample`, mostly for testing.""" if end_line is None: end_line = start_line + source.count('\n') if end_index is None: end_index = start_index + len(source) return cls( source=source, path=path, start_line=start_line, end_line=end_line, start_index=start_index, end_index=end_index, prefix=prefix, indent=indent, ) @property def module_name(self) -> str: """A suitable Python module name for testing the example.""" return f'{self.path.stem}_{self.start_line}_{self.end_line}' def prefix_settings(self) -> dict[str, str]: """Key/value pairs from the prefix line. This works on the format `py foo="bar" spam="with space"`. """ settings = {} for m in re.finditer(r'([^{\s]+?)=([\'"])(.+?)\2', self.prefix): settings[m.group(1)] = m.group(3) return settings def prefix_tags(self) -> set[str]: """Extract tags from the prefix, alternative logic to `prefix_settings`. This works on the format `py .foo .bar` or `{.py .foo .bar}`. """ tags = shlex.split(self.prefix.strip(' {}')) tags = {p.lstrip('.') for p in tags if p} try: tags.remove('py') except KeyError: pass return tags def in_py_file(self) -> bool: """Whether the example is in a Python file.""" return self.path.suffix == '.py' def __str__(self): try: path = self.path.relative_to(Path.cwd()) except ValueError: path = self.path return f'{path}:{self.start_line}-{self.end_line}' def find_examples(*paths: str | Path, skip: bool = False) -> Iterable[CodeExample]: """Find Python code examples in markdown files and python file docstrings. :param paths: Directories or files to search for examples in. :param skip: Whether to exit early and not search for examples, useful when running on windows where search fails. :return: A generator of `CodeExample` objects. """ if skip: return for s in paths: path = Path(s) if path.is_file(): sub_paths = [path] elif path.is_dir(): sub_paths = path.glob('**/*') else: raise ValueError(f'Not a file or directory: {s!r}') for path in sub_paths: group = uuid4() if path.suffix == '.py': code = path.read_text('utf-8') for m_docstring in re.finditer(r'(^ *)(r?""")(.+?)"""', code, flags=re.M | re.S): start_line = code[: m_docstring.start()].count('\n') docstring = m_docstring.group(3) index_offset = m_docstring.start() + len(m_docstring.group(1)) + len(m_docstring.group(2)) yield from _extract_code_chunks( path, docstring, group, line_offset=start_line, index_offset=index_offset ) elif path.suffix == '.md': code = path.read_text('utf-8') yield from _extract_code_chunks(path, code, group) def _extract_code_chunks( path: Path, text: str, group: UUID, *, line_offset: int = 0, index_offset: int = 0 ) -> Iterable[CodeExample]: for m_code in re.finditer(r'(^ *```)( *)(.*?)\n(.+?)\1', text, flags=re.M | re.S): group1, group2, prefix, source = m_code.groups() prefix = prefix.lower() if prefix.startswith(('py', '{.py')): start_line = line_offset + text[: m_code.start()].count('\n') + 1 source_dedent, indent = remove_indent(source) # 1 for the newline start_index = index_offset + m_code.start() + len(group1) + len(group2) + len(prefix) + 1 yield CodeExample( source=source_dedent, path=path, start_line=start_line, end_line=start_line + source.count('\n') + 1, start_index=start_index, end_index=start_index + len(source), prefix=prefix, indent=indent, group=group, ) def remove_indent(text: str) -> tuple[str, int]: """Remove the given indent from each line of text, return the dedented text and the indent.""" first_line_before = text[: text.strip('\n').find('\n')] text = dedent(text) first_line_after = text[: text.strip('\n').find('\n')] return text, len(first_line_before) - len(first_line_after) pytest-examples-0.0.18/pytest_examples/lint.py000066400000000000000000000067131526262746400215370ustar00rootroot00000000000000from __future__ import annotations as _annotations import re from subprocess import PIPE, Popen from textwrap import indent from typing import TYPE_CHECKING from black import format_str as black_format_str from black.output import diff as black_diff from ruff.__main__ import find_ruff_bin from .config import ExamplesConfig if TYPE_CHECKING: from .find_examples import CodeExample __all__ = 'ruff_check', 'ruff_format', 'black_check', 'black_format', 'code_diff', 'FormatError' class FormatError(ValueError): pass def ruff_format( example: CodeExample, config: ExamplesConfig, *, ignore_errors: bool = False, ) -> str: args = ('--fix',) if ignore_errors: args += ('--exit-zero',) try: return ruff_check(example, config, extra_ruff_args=args) except FormatError: # this is a workaround for https://github.com/charliermarsh/ruff/issues/3694#issuecomment-1483388856 try: ruff_check(example, config) except FormatError as e2: raise e2 from None else: raise Exception('ruff failed in Fix mode but not in Check mode, please report this') def ruff_check( example: CodeExample, config: ExamplesConfig, *, extra_ruff_args: tuple[str, ...] = (), ) -> str: ruff = find_ruff_bin() args = ruff, 'check', '-', *config.ruff_config(), *extra_ruff_args p = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True) stdout, stderr = p.communicate(example.source, timeout=10) if p.returncode == 1 and stdout: def replace_offset(m: re.Match[str]): line_number = int(m.group(1)) return f'{example.path}:{line_number + example.start_line}' output = re.sub(r'^-:(\d+)', replace_offset, stdout, flags=re.M) raise FormatError(f'ruff failed:\n{indent(output, " ")}') elif p.returncode != 0: raise RuntimeError(f'Error running ruff, return code {p.returncode}:\n{stderr or stdout}') else: return stdout def black_format(source: str, config: ExamplesConfig, *, remove_double_blank: bool = False) -> str: # hack to avoid black complaining about our print output format before_black = re.sub(r'^( *#)> ', r'\1 > ', source, flags=re.M) after_black = black_format_str(before_black, mode=config.black_mode()) # then revert it back after_black = re.sub(r'^( *#) > ', r'\1> ', after_black, flags=re.M) if remove_double_blank: after_black = re.sub(r'\n{3}', '\n\n', after_black) return after_black def black_check(example: CodeExample, config: ExamplesConfig) -> None: after_black = black_format(example.source, config, remove_double_blank=example.in_py_file()) if example.source != after_black: diff = code_diff(example, after_black, config) raise FormatError(f'black failed:\n{indent(diff, " ")}') def code_diff(example: CodeExample, after: str, config: ExamplesConfig) -> str: diff = black_diff(sub_space(example.source, config), sub_space(after, config), 'before', 'after') def replace_at_line(match: re.Match[str]) -> str: offset = re.sub(r'\d+', lambda m: str(int(m.group(0)) + example.start_line), match.group(2)) return f'{match.group(1)}{offset}{match.group(3)}' return re.sub(r'^(@@\s*)(.*)(\s*@@)$', replace_at_line, diff, flags=re.M) def sub_space(text: str, config: ExamplesConfig) -> str: if config.white_space_dot: return text.replace(' ', '·') else: return text pytest-examples-0.0.18/pytest_examples/modify_files.py000066400000000000000000000035511526262746400232370ustar00rootroot00000000000000from __future__ import annotations as _annotations from itertools import groupby from pathlib import Path from textwrap import indent from typing import TYPE_CHECKING if TYPE_CHECKING: from .find_examples import CodeExample def _modify_files(examples: list[CodeExample]) -> str: """Internal use only, update examples in place.""" # The same example shouldn't appear more than once unique_examples: set[str] = set() for ex in examples: s = str(ex) if s in unique_examples: examples_str = '\n'.join(f' {ex} (test: {ex.test_id})' for ex in examples) raise RuntimeError(f'Cannot update the same example in separate tests!\nexamples:\n{examples_str}') unique_examples.add(s) # same file should not appear in more than one group files: set[Path] = set() # order by line number descending so the earlier change doesn't mess up line numbers for later changes examples.sort(key=lambda x: (x.group, x.start_line), reverse=True) for _, g in groupby(examples, key=lambda x: x.group): new_files = {ex.path for ex in g} if new_files & files: raise RuntimeError('Cannot update the same file in separate groups!') files |= new_files msg = [f'pytest-examples: {len(examples)} examples to update in {len(files)} file(s)...'] for path, g in groupby(examples, key=lambda ex: ex.path): content = path.read_text() count = 0 for ex in g: example: CodeExample = ex new_source = example.source if example.indent: new_source = indent(new_source, ' ' * example.indent) content = content[: example.start_index] + new_source + content[example.end_index :] count += 1 msg.append(f' {path} {count} examples updated') path.write_text(content) return '\n'.join(msg) pytest-examples-0.0.18/pytest_examples/py.typed000066400000000000000000000000001526262746400216750ustar00rootroot00000000000000pytest-examples-0.0.18/pytest_examples/run_code.py000066400000000000000000000322061526262746400223630ustar00rootroot00000000000000from __future__ import annotations as _annotations import ast import asyncio import dataclasses import importlib.util import inspect import re import sys from collections.abc import Sequence from dataclasses import dataclass from importlib.abc import Loader from pathlib import Path from textwrap import indent from typing import TYPE_CHECKING, Any, Callable from unittest.mock import patch import pytest from black.parsing import InvalidInput from .lint import black_format, code_diff from .traceback import create_example_traceback if TYPE_CHECKING: from .config import ExamplesConfig from .find_examples import CodeExample __all__ = 'run_code', 'InsertPrintStatements', 'IncludePrint' parent_frame_id = 4 if sys.version_info >= (3, 8) else 3 IncludePrint = Callable[[Path, inspect.FrameInfo, Sequence[Any]], bool] def run_code( *, example: CodeExample, python_file: Path, loader: Loader | None, config: ExamplesConfig, enable_print_mock: bool, print_callback: Callable[[str], str] | None, include_print: IncludePrint | None, module_globals: dict[str, Any] | None, call: str | None, ) -> tuple[InsertPrintStatements, dict[str, Any]]: """Run the code example. Args: example: The `CodeExample` to run. python_file: The path to the python file. loader: optional loader to use to load the module. config: The `ExamplesConfig` to use. enable_print_mock: If True, mock the `print` function. print_callback: If not None, a callback to call on `print`. include_print: If not None, a function to call to determine if the print statement should be included. module_globals: The extra globals to add before calling the module. call: If not None, a (coroutine) function to call in the module. Returns: A tuple of the `InsertPrintStatements` instance and the module's globals. """ __tracebackhide__ = True spec = importlib.util.spec_from_file_location('__main__', str(python_file), loader=loader) assert spec is not None, f'Could not load {python_file}' assert spec.loader is not None, f'Loader is None for {python_file}' module = importlib.util.module_from_spec(spec) # does nothing if insert_print_statements is False insert_print = InsertPrintStatements(python_file, config, enable_print_mock, print_callback, include_print) if module_globals: module.__dict__.update(module_globals) try: with insert_print: sys.modules[spec.name] = module spec.loader.exec_module(module) if call: to_call = getattr(module, call, None) if to_call is not None: if inspect.iscoroutinefunction(to_call): asyncio.run(to_call()) else: to_call() except KeyboardInterrupt: print('KeyboardInterrupt in example') except Exception as exc: example_tb = create_example_traceback(exc, str(python_file), example) if example_tb: raise exc.with_traceback(example_tb) else: raise exc return insert_print, {k: v for k, v in module.__dict__.items() if not k.startswith(('__', '@'))} @dataclass(init=False) class Arg: """A single argument to a print statement.""" data: str is_str: bool = False def __init__(self, v: Any): if isinstance(v, str): self.data = v self.is_str = True elif isinstance(v, set): # NOTE! this is not recursive ordered = ', '.join(repr(x) for x in sorted(v)) self.data = f'{{{ordered}}}' else: self.data = re.sub('0x[a-f0-9]{8,12}>', '0x0123456789ab>', str(v)) def __str__(self) -> str: return self.data def format(self, config: ExamplesConfig) -> str: if self.is_str: return self.data else: try: return black_format(self.data, config) except InvalidInput: return self.data @dataclass class PrintStatement: """A single print statement.""" line_no: int sep: str args: list[Arg] def __str__(self): return self.sep.join(map(str, self.args)) def not_print(*args): import sys sys.stdout.write(' '.join(map(str, args)) + '\n') class MockPrintFunction: __slots__ = 'file', 'statements', 'include_print' def __init__(self, file: Path, include_print: IncludePrint | None) -> None: self.file = file self.statements: list[PrintStatement] = [] self.include_print = include_print def __call__(self, *args: Any, sep: str = ' ', **kwargs: Any) -> None: frame = inspect.stack()[parent_frame_id] if self._include_file(frame, args): lineno = self._find_line_number(frame) s = PrintStatement(lineno, sep, [Arg(arg) for arg in args]) self.statements.append(s) def _include_file(self, frame: inspect.FrameInfo, args: Sequence[Any]) -> bool: if self.include_print: return self.include_print(self.file, frame, args) else: return self.file.samefile(frame.filename) def _find_line_number(self, inspect_frame: inspect.FrameInfo) -> int: """Find the line number of the print statement in the file that is being executed.""" frame = inspect_frame.frame while True: if self.file.samefile(frame.f_code.co_filename): return frame.f_lineno elif frame.f_back: frame = frame.f_back else: raise RuntimeError(f'Could not find line number of print statement at {inspect_frame}') class InsertPrintStatements: def __init__( self, python_path: Path, config: ExamplesConfig, enable: bool, print_callback: Callable[[str], str] | None, include_print: IncludePrint | None, ): self.file = python_path self.config = config self.print_func = MockPrintFunction(python_path, include_print) if enable else None self.print_callback = print_callback self.patch = None def __enter__(self) -> None: if self.print_func: self.patch = patch('builtins.print', side_effect=self.print_func) self.patch.start() def __exit__(self, *args) -> None: if self.patch is not None: self.patch.stop() def check_print_statements(self, example: CodeExample) -> None: new_code = self.updated_print_statements(example) if new_code is not None: diff = code_diff(example, new_code, self.config) pytest.fail(f'Print output changed code:\n{indent(diff, " ")}', pytrace=False) def updated_print_statements(self, example: CodeExample) -> str | None: with_prints = self._insert_print_statements(example) # we check against the raw `with_prints` and `with_prints` with trailing whitespace removed # since trailing white space will have already been stripped by pre-commit in `example.source` if example.source not in (with_prints, re.sub(r'[ \t]+\n', '\n', with_prints)): return with_prints def print_statements(self) -> list[PrintStatement]: return self.print_func.statements if self.print_func else [] def _insert_print_statements(self, example: CodeExample) -> str: assert self.print_func is not None, 'print statements not being inserted' lines = example.source.splitlines() old_line_no = -1 for s in reversed(self.print_func.statements): line_no, col = find_print_location(example, s.line_no) # switch from 1-indexed line number to 0-indexed indexes into lines line_index = line_no - 1 if s.line_no != old_line_no: remove_old_print(lines, line_index) self._insert_print_args(lines, s, example.in_py_file(), line_index, col) old_line_no = s.line_no return '\n'.join(lines) + '\n' def _insert_print_args( self, lines: list[str], statement: PrintStatement, in_python: bool, line_index: int, col: int ) -> None: formatted_args = [self.print_callback(str(arg)) if self.print_callback else str(arg) for arg in statement.args] single_line = statement.sep.join(formatted_args) indent_str = ' ' * col max_single_length = self.config.line_length - len(indent_str) if '\n' not in single_line and len(single_line) + len(comment_prefix) < max_single_length: lines.insert(line_index + 1, f'{indent_str}{comment_prefix}{single_line}') else: # if the statement is too long to go on one line, print each arg on its own line formatted with black sep = f'{statement.sep}\n' indent_config = dataclasses.replace(self.config, line_length=max_single_length) output = sep.join(arg.format(indent_config).strip('\n') for arg in statement.args) if self.print_callback: output = self.print_callback(output) # remove trailing whitespace output = re.sub(r' +$', '', output, flags=re.MULTILINE) # have to use triple single quotes in python since we're already in a double quotes docstring quote = "'''" if in_python else '"""' lines.insert(line_index + 1, indent(f'{quote}\n{output}\n{quote}', indent_str)) comment_prefix = '#> ' comment_prefix_re = re.compile(f'^ *{re.escape(comment_prefix)}', re.MULTILINE) triple_quotes_prefix_re = re.compile('^ *(?:"{3}|\'{3})', re.MULTILINE) def remove_old_print(lines: list[str], line_index: int) -> None: """Remove the old print statement.""" try: next_line = lines[line_index + 1] except IndexError: # end of file return if triple_quotes_prefix_re.search(next_line): for i in range(2, 10_000): if triple_quotes_prefix_re.search(lines[line_index + i]): del lines[line_index + 1 : line_index + i + 1] return raise ValueError('Could not find end of triple quotes') else: try: while comment_prefix_re.search(lines[line_index + 1]): del lines[line_index + 1] except IndexError: # end of file pass def find_print_location(example: CodeExample, line_no: int) -> tuple[int, int]: """Find the line and column of the print statement. Args: example: the `CodeExample` line_no: The line number on which the print statement starts or approx Return: tuple if `(line, column)` of the print statement """ m = ast.parse(example.source, filename=example.path.name) return find_print(m, line_no) or (line_no, 0) # ast nodes that have a body with_body = ( ast.Module, ast.FunctionDef, ast.If, ast.Try, ast.ExceptHandler, ast.With, ast.For, ast.AsyncFor, ast.AsyncFunctionDef, ast.AsyncWith, ast.While, ast.ClassDef, ) def find_print(node: Any, line: int) -> tuple[int, int] | None: if isinstance(node, with_body): found_loc = find_print_in_body(node.body, line) if found_loc is not None: return found_loc if isinstance(node, ast.If): found_loc = find_print_in_body(node.orelse, line) if found_loc is not None: return found_loc elif isinstance(node, ast.Try): for node in node.handlers: found_loc = find_print(node, line) if found_loc is not None: return found_loc elif isinstance(node, ast.Expr): return find_print(node.value, line) elif isinstance(node, ast.Call): if isinstance(node.func, ast.Name) and node.func.id == 'print' and node.lineno == line: return expr_last_line(node), node.col_offset return find_print(node.func, line) def find_print_in_body(body: list[ast.stmt], line: int) -> tuple[int, int] | None: for node in body: found_loc = find_print(node, line) if found_loc is not None: return found_loc def expr_last_line(c: ast.expr) -> int: if isinstance(c, ast.Constant): return c.lineno if isinstance(c, ast.Call): if c.keywords: return maybe_plus_1(c, expr_last_line(c.keywords[-1].value)) elif c.args: return maybe_plus_1(c, expr_last_line(c.args[-1])) else: return c.lineno elif isinstance(c, (ast.List, ast.Tuple, ast.Set)): if c.elts: return maybe_plus_1(c, expr_last_line(c.elts[-1])) else: return c.lineno elif isinstance(c, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)): gen = c.generators[-1] if gen.ifs: return maybe_plus_1(c, expr_last_line(gen.ifs[-1])) else: return maybe_plus_1(c, expr_last_line(gen.iter)) else: return c.lineno def maybe_plus_1(c: ast.expr, last_arg_line: int) -> int: if c.lineno == last_arg_line: # all args are on the same line, assume the basic `print(x, y)` case return c.lineno else: # args are on multiple lines, assume the following format an extra line with `)` return last_arg_line + 1 pytest-examples-0.0.18/pytest_examples/traceback.py000066400000000000000000000103041526262746400224770ustar00rootroot00000000000000from __future__ import annotations as _annotations import sys from types import CodeType, FrameType, TracebackType from typing import TYPE_CHECKING if TYPE_CHECKING: from .find_examples import CodeExample def create_example_traceback(exc: Exception, module_path: str, example: CodeExample) -> TracebackType | None: """Create a new traceback with the filename and line numbers altered to match `example`. This involves lots of horrible hacking, but (somewhat miraculously) seems to work. Frames outside the example are not included in the new traceback. """ frames = [] tb = exc.__traceback__ while tb is not None: frame = tb.tb_frame if frame.f_code.co_filename == module_path: frames.append((create_custom_frame(frame, example), tb.tb_lasti, tb.tb_lineno + example.start_line)) tb = tb.tb_next frames.reverse() new_tb = None for altered_frame, lasti, lineno in frames: new_tb = TracebackType(tb_next=new_tb, tb_frame=altered_frame, tb_lasti=lasti, tb_lineno=lineno) return new_tb def create_custom_frame(frame: FrameType, example: CodeExample) -> FrameType: """Create a new frame that mostly matches `frame` but with filename and line number faked. Create a new frame that mostly matches `frame` but with a code object that has a filename from `example` and adjusted first line number so that pytest shows the correct code context in the traceback. Taken mostly from https://naleraphael.github.io/blog/posts/devlog_create_a_builtin_frame_object/ With the CodeType creation inspired by https://stackoverflow.com/a/16123158/949890. """ import ctypes P_SIZE = ctypes.sizeof(ctypes.c_void_p) IS_X64 = P_SIZE == 8 P_MEM_TYPE = ctypes.POINTER(ctypes.c_ulong if IS_X64 else ctypes.c_uint) ctypes.pythonapi.PyFrame_New.argtypes = ( P_MEM_TYPE, # PyThreadState *tstate P_MEM_TYPE, # PyCodeObject *code ctypes.py_object, # PyObject *globals ctypes.py_object, # PyObject *locals ) ctypes.pythonapi.PyFrame_New.restype = ctypes.py_object # PyFrameObject* ctypes.pythonapi.PyThreadState_Get.argtypes = None # type: ignore ctypes.pythonapi.PyThreadState_Get.restype = P_MEM_TYPE f_code = frame.f_code if sys.version_info >= (3, 12): code = CodeType( f_code.co_argcount, f_code.co_posonlyargcount, f_code.co_kwonlyargcount, f_code.co_nlocals, f_code.co_stacksize, f_code.co_flags, f_code.co_code, f_code.co_consts, f_code.co_names, f_code.co_varnames, str(example.path), f_code.co_name, f_code.co_qualname, f_code.co_firstlineno + example.start_line, f_code.co_linetable, f_code.co_exceptiontable, ) elif sys.version_info >= (3, 11): code = CodeType( f_code.co_argcount, f_code.co_posonlyargcount, f_code.co_kwonlyargcount, f_code.co_nlocals, f_code.co_stacksize, f_code.co_flags, f_code.co_code, f_code.co_consts, f_code.co_names, f_code.co_varnames, str(example.path), f_code.co_name, f_code.co_qualname, f_code.co_firstlineno + example.start_line, f_code.co_lnotab, f_code.co_exceptiontable, ) else: code = CodeType( f_code.co_argcount, f_code.co_posonlyargcount, f_code.co_kwonlyargcount, f_code.co_nlocals, f_code.co_stacksize, f_code.co_flags, f_code.co_code, f_code.co_consts, f_code.co_names, f_code.co_varnames, str(example.path), f_code.co_name, f_code.co_firstlineno + example.start_line, f_code.co_lnotab, ) return ctypes.pythonapi.PyFrame_New( ctypes.pythonapi.PyThreadState_Get(), # thread state ctypes.cast(id(code), P_MEM_TYPE), # a code object frame.f_globals, # a dict of globals frame.f_locals, # a dict of locals ) pytest-examples-0.0.18/tests/000077500000000000000000000000001526262746400161245ustar00rootroot00000000000000pytest-examples-0.0.18/tests/cases_update/000077500000000000000000000000001526262746400205645ustar00rootroot00000000000000pytest-examples-0.0.18/tests/cases_update/call_twice.md000066400000000000000000000011321526262746400232110ustar00rootroot00000000000000```py def foobar(x): print(f'x={x}') foobar(1) foobar(2) ``` ################### OUTPUT ################### ```py def foobar(x): print(f'x={x}') #> x=1 #> x=2 foobar(1) foobar(2) ``` ################### TEST ##################### ```py from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_examples(example: CodeExample, eval_example: EvalExample): if eval_example.update_examples: eval_example.format(example) eval_example.run_print_update(example) ``` pytest-examples-0.0.18/tests/cases_update/dataclass_indent.md000066400000000000000000000014751526262746400244150ustar00rootroot00000000000000```py from dataclasses import dataclass @dataclass class User: foobar: int def __post_init__(self): print(self.foobar) User(foobar=123) ``` ################### OUTPUT ################### ```py from dataclasses import dataclass @dataclass class User: foobar: int def __post_init__(self): print(self.foobar) #> 123 User(foobar=123) ``` ################### TEST ##################### ```py from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_examples(example: CodeExample, eval_example: EvalExample): eval_example.set_config(quotes='double') if eval_example.update_examples: eval_example.format(example) eval_example.run_print_update(example) ``` pytest-examples-0.0.18/tests/cases_update/long_python_lines.py000066400000000000000000000014731526262746400246750ustar00rootroot00000000000000def foobar(): """ ```py print({i: 'x' * (5 * i) for i in range(3)}) ``` """ pass ################### OUTPUT ################### def foobar(): """ ```py print( { i: 'x' * (5 * i) for i in range(3) } ) ''' { 0: '', 1: 'xxxxx', 2: 'xxxxxxxxxx', } ''' ``` """ pass ################### TEST ##################### import pytest from pytest_examples import CodeExample, EvalExample, find_examples @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_examples(example: CodeExample, eval_example: EvalExample): eval_example.set_config(line_length=30) if eval_example.update_examples: eval_example.format(example) eval_example.run_print_update(example) pytest-examples-0.0.18/tests/cases_update/python_class.py000066400000000000000000000012261526262746400236450ustar00rootroot00000000000000def foobar(): """ ```py x = 4 class A: pass print(x) ``` """ pass ################### OUTPUT ################### def foobar(): """ ```py x = 4 class A: pass print(x) #> 4 ``` """ pass ################### TEST ##################### import pytest from pytest_examples import CodeExample, EvalExample, find_examples @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_examples(example: CodeExample, eval_example: EvalExample): if eval_example.update_examples: eval_example.format(example) eval_example.run_print_update(example) pytest-examples-0.0.18/tests/cases_update/simple.md000066400000000000000000000015311526262746400223770ustar00rootroot00000000000000# this is an example ```py import string print([string.ascii_letters[: i + 10] for i in range(4)]) ``` ################### OUTPUT ################### # this is an example ```py import string print( [ string.ascii_letters[ : i + 10 ] for i in range(4) ] ) """ [ "abcdefghij", "abcdefghijk", "abcdefghijkl", "abcdefghijklm", ] """ ``` ################### TEST ##################### ```py from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_examples(example: CodeExample, eval_example: EvalExample): eval_example.set_config(line_length=30, quotes='double') if eval_example.update_examples: eval_example.format(example) eval_example.run_print_update(example) ``` pytest-examples-0.0.18/tests/cases_update/simple.py000066400000000000000000000011201526262746400224210ustar00rootroot00000000000000def foobar(): """ ```py print(123) ``` """ pass ################### OUTPUT ################### def foobar(): """ ```py print(123) #> 123 ``` """ pass ################### TEST ##################### import pytest from pytest_examples import CodeExample, EvalExample, find_examples @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_examples(example: CodeExample, eval_example: EvalExample): if eval_example.update_examples: eval_example.lint(example) eval_example.run_print_update(example) pytest-examples-0.0.18/tests/cases_update/use_globals.md000066400000000000000000000011601526262746400234030ustar00rootroot00000000000000```py x = 1 y = 2 ``` ```py print(x, y) ``` ################### OUTPUT ################### ```py x = 1 y = 2 ``` ```py print(x, y) #> 1 2 ``` ################### TEST ##################### ```py test_count = 2 from pytest_examples import find_examples, CodeExample, EvalExample import pytest examples_globals = {} @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_examples(example: CodeExample, eval_example: EvalExample): if eval_example.update_examples: m_dict = eval_example.run_print_update(example, module_globals=examples_globals) examples_globals.update(m_dict) ``` pytest-examples-0.0.18/tests/conftest.py000066400000000000000000000000361526262746400203220ustar00rootroot00000000000000pytest_plugins = ['pytester'] pytest-examples-0.0.18/tests/test_find_examples.py000066400000000000000000000155551526262746400223660ustar00rootroot00000000000000import pytest from pytest_examples import CodeExample, find_examples def test_find_md_example(pytester: pytest.Pytester): pytester.makefile( '.md', # language=Markdown my_file=""" # My file ```py a = 1 ``` ``` py b = 2 ``` ```{.py .test="skip"} c = 3 ``` ``` {.py} d = 4 ``` """, ) pytester.makepyfile( # language=Python """ from pathlib import Path from pytest_examples import find_examples import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_examples_str(example): assert example.indent == 0 assert example.end_line == example.start_line + 2 @pytest.mark.parametrize('example', find_examples(Path('.')), ids=str) def test_find_examples_path(example): assert example.indent == 0 assert example.end_line == example.start_line + 2 """ ) result = pytester.runpytest('-p', 'no:pretty', '-v') output = '\n'.join(result.outlines) result.assert_outcomes(passed=8) assert 'test_find_examples_str[my_file.md:3-5] PASSED' in output assert 'test_find_examples_str[my_file.md:7-9] PASSED' in output assert 'test_find_examples_str[my_file.md:11-13] PASSED' in output assert 'test_find_examples_str[my_file.md:15-17] PASSED' in output assert 'test_find_examples_path[my_file.md:3-5] PASSED' in output assert 'test_find_examples_path[my_file.md:7-9] PASSED' in output assert 'test_find_examples_path[my_file.md:11-13] PASSED' in output assert 'test_find_examples_path[my_file.md:15-17] PASSED' in output def test_find_py_example(pytester: pytest.Pytester): pytester.makefile( '.py', # language=Python my_file=''' def func_a(): """ ```py a = 1 b = 2 assert a + b == 3 ``` """ pass def func_b(): """ ```py c = 3 d = 4 assert c + d == 7 ``` ```py e = 5 f = 6 assert e + f == 11 ``` """ pass def func_c(): """Does cool things. ```py g = 7 h = 8 assert g + h == 15 ``` """ ''', ) pytester.makepyfile( # language=Python """ from pytest_examples import find_examples import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_examples(example): assert example.indent == 4 """ ) result = pytester.runpytest('-p', 'no:pretty', '-vs') result.assert_outcomes(passed=4) output = '\n'.join(result.outlines) assert 'test_find_examples[my_file.py:3-7] PASSED' in output assert 'test_find_examples[my_file.py:14-18] PASSED' in output assert 'test_find_examples[my_file.py:20-24] PASSED' in output assert 'test_find_examples[my_file.py:30-34] PASSED' in output def test_find_py_example_with_module_docstring(pytester: pytest.Pytester): pytester.makefile( '.py', # language=Python my_file=''' """Module docstring.""" def func_a(): """ ```py a = 1 b = 2 assert a + b == 3 ``` """ pass def func_b(): """ ```py c = 3 d = 4 assert c + d == 7 ``` ```py e = 5 f = 6 assert e + f == 11 ``` """ pass def func_c(): """Does cool things. ```py g = 7 h = 8 assert g + h == 15 ``` """ ''', ) pytester.makepyfile( # language=Python """ from pytest_examples import find_examples import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_examples(example): assert example.indent == 4 """ ) result = pytester.runpytest('-p', 'no:pretty', '-vs') result.assert_outcomes(passed=4) output = '\n'.join(result.outlines) assert 'test_find_examples[my_file.py:4-8] PASSED' in output assert 'test_find_examples[my_file.py:15-19] PASSED' in output assert 'test_find_examples[my_file.py:21-25] PASSED' in output assert 'test_find_examples[my_file.py:31-35] PASSED' in output def test_find_file_example(pytester: pytest.Pytester): pytester.makefile( '.md', # language=Markdown my_file=""" # My file ```py a = 1 b = 2 assert a + b == 3 ``` """, ) pytester.makepyfile( # language=Python """ from pytest_examples import find_examples import pytest @pytest.mark.parametrize('example', find_examples('my_file.md'), ids=str) def test_find_examples(example): pass """ ) result = pytester.runpytest('-p', 'no:pretty', '-v', '-s') result.assert_outcomes(passed=1) output = '\n'.join(result.outlines) assert 'test_find_examples[my_file.md:3-7] PASSED' in output def test_find_missing(pytester: pytest.Pytester): pytester.makepyfile( # language=Python """ from pytest_examples import find_examples import pytest @pytest.mark.parametrize('example', find_examples('missing.md'), ids=str) def test_find_examples(example): pass """ ) result = pytester.runpytest('-p', 'no:pretty', '-v', '-s') result.assert_outcomes(errors=1) assert "Not a file or directory: 'missing.md'" in '\n'.join(result.outlines) def test_find_index_markdown(tmp_path): # language=Markdown code = """ foobar ```py title="a.py" a = 1 b = 2 assert a + b == 3 ``` """ (tmp_path / 'a.md').write_text(code) examples = list(find_examples(str(tmp_path))) assert len(examples) == 1 example: CodeExample = examples[0] assert code[example.start_index : example.end_index] == 'a = 1\nb = 2\nassert a + b == 3\n' assert example.source == 'a = 1\nb = 2\nassert a + b == 3\n' def test_find_index_python(tmp_path): # language=Python code = ''' def foo(): """Single line docstring""" pass def func_a(): """ prefix. ```py a = 1 b = 2 assert a + b == 3 ``` """ pass ''' (tmp_path / 'a.py').write_text(code) examples = list(find_examples(str(tmp_path))) assert len(examples) == 1 example: CodeExample = examples[0] assert code[example.start_index : example.end_index] == ' a = 1\n b = 2\n assert a + b == 3\n' assert example.source == 'a = 1\nb = 2\nassert a + b == 3\n' @pytest.mark.parametrize( 'prefix,prefix_settings', [ ('py test="skip"', {'test': 'skip'}), ('py test="skip" foo="B ar"', {'test': 'skip', 'foo': 'B ar'}), ("py test='require-3.8'", {'test': 'require-3.8'}), ], ) def test_prefix_settings(prefix, prefix_settings): ex = CodeExample.create('foobar', prefix=prefix) assert ex.prefix_settings() == prefix_settings @pytest.mark.parametrize( 'prefix,prefix_tags', [ ('{.py .foobar}', {'foobar'}), ("{.py 'foo bar'", {'foo bar'}), ('py mytag', {'mytag'}), ('py test="skip" foo="B ar"', {'test=skip', 'foo=B ar'}), ], ) def test_prefix_tags(prefix, prefix_tags): ex = CodeExample.create('foobar', prefix=prefix) assert ex.prefix_tags() == prefix_tags pytest-examples-0.0.18/tests/test_insert_print.py000066400000000000000000000220711526262746400222570ustar00rootroot00000000000000from __future__ import annotations as _annotations import sys import pytest from _pytest.outcomes import Failed from pytest_examples import CodeExample from pytest_examples.run_code import find_print_location # separate list to hopefully make it easier to read print_last_lines = [ pytest.param( """\ print(1, 2, [3, 4, 5], "hello") """, 1, (1, 0), id='simple case', ), pytest.param( """\ print(1, 2, [3, 4, 5], "hello") x = 123 """, 1, (1, 0), id='content after print output', ), pytest.param( """\ if True: print((1, 2, 3)) """, 2, (2, 4), id='single-line-indent', ), pytest.param( """\ def foobar(): a = 4 if True: print( 1, 2, 3 ) x=4 foobar() """, 4, (8, 8), id='print-over-lines', ), pytest.param( """\ import string print( [ string.ascii_letters[ : i + 10 ] for i in range(4) ] ) """, 2, (9, 0), id='multiline-comprehension', ), pytest.param( """\ print(1, 2, 3 ) """, 1, (1, 0), id='ill-formed-print-1', ), pytest.param( """\ print( 1, 2, 3) """, 1, (3, 0), id='ill-formed-print-2', ), pytest.param( """\ try: raise ValueError('boom') except ValueError as e: print(e) #> boom """, 4, (4, 4), id='try-except', ), pytest.param( """\ a.b.c(1, 2, 3) print(4) """, 2, (2, 0), id='call-attribute', ), pytest.param( """\ @dataclass class User: foobar: int def __post_init__(self): print(self.foobar) #> 123 """, 6, (6, 8), id='class-method', ), pytest.param( """\ async with foobar(): print( 1 ) """, 2, (4, 4), id='async-function', ), ] @pytest.mark.parametrize('python_code,print_line,expected_last_loc', print_last_lines) def test_find_end_of_print(python_code: str, print_line: int, expected_last_loc: tuple[int, int]): last_loc = find_print_location(CodeExample.create(python_code), print_line) assert last_loc == expected_last_loc # separate list to hopefully make it easier to read unchanged_code: list = [ pytest.param( # language=Python """\ print(1, 2, [3, 4, 5], "hello") #> 1 2 [3, 4, 5] hello """, id='simple case', ), pytest.param( # language=Python """\ print(1, 2, [3, 4, 5], "hello") #> 1 2 [3, 4, 5] hello x = 123 """, id='content after print output', ), pytest.param( # language=Python '''\ print({i: f'i value is {i}' for i in range(3)}) """ { 0: 'i value is 0', 1: 'i value is 1', 2: 'i value is 2', } """ ''', id='multiline', ), pytest.param( # language=Python """\ if True: print((1, 2, 3)) #> (1, 2, 3) """, id='single-line-indent', ), pytest.param( # language=Python '''\ if True: print(['x' * i * 8 for i in range(3)]) """ [ '', 'xxxxxxxx', 'xxxxxxxxxxxxxxxx', ] """ ''', id='multi-line-indent', ), pytest.param( # language=Python """\ def foobar(): a = 4 if True: print( 1, 2, 3 ) #> 1 2 3 x=4 foobar() """, id='print-over-lines', ), pytest.param( # language=Python '''\ import string print( [ string.ascii_letters[ : i + 10 ] for i in range(4) ] ) """ [ 'abcdefghij', 'abcdefghijk', 'abcdefghijkl', 'abcdefghijklm', ] """ ''', id='multiline-comprehension', ), pytest.param( # language=Python """\ print(1, 2, 3 #> 1 2 3 ) """, id='ill-formed-print-1', ), pytest.param( # language=Python """\ print( 1, 2, 3) #> 1 2 3 #> 1 2 3 """, id='ill-formed-print-2', ), pytest.param( # language=Python '''\ if True: if True: if True: print([1, 2, 3, 4]) #> [1, 2, 3, 4] print([1, 2, 3, 4, 5]) """ [1, 2, 3, 4, 5] """ print([1_000_000, 2_000_000, 3_000_000]) """ [ 1000000, 2000000, 3000000, ] """ ''', id='big-indent', ), pytest.param( '''\ print('this is not\\npython code') """ this is not python code """ ''', id='not-python-code', ), pytest.param( """\ print(ValueError('this is not python code')) #> this is not python code """, id='non-python-repr', ), pytest.param( '''\ print(ValueError('this is not\\npython code')) """ this is not python code """ ''', id='non-python-repr', ), pytest.param( """\ try: raise ValueError('boom') except ValueError as e: print(e) #> boom """, id='print-in-except', ), pytest.param( """\ print({1, 3, 2, 4}) #> {1, 2, 3, 4} """, id='set-order', ), pytest.param( '''\ class Foo: pass print(Foo()) """ <__main__.Foo object at 0x0123456789ab> """ ''', id='hex_id', ), pytest.param( """\ for i in range(3): print(i) #> 0 #> 1 #> 2 """, id='look', ), pytest.param( """print('foobar ')\n#> foobar \n""", id='trailing-spaces', ), ] @pytest.mark.parametrize('python_code', unchanged_code) def test_insert_print_check_unchanged(tmp_path, eval_example, python_code: str): # note this file is no written here as it's not required md_file = tmp_path / 'test.md' example = CodeExample.create(python_code, path=md_file) eval_example.set_config(line_length=30) eval_example.run_print_check(example) def test_insert_print_check_change(tmp_path, eval_example): # language=Python python_code = 'print(1, 2, [3, 4, 5], "hello")\n' # note this file is no written here as it's not required md_file = tmp_path / 'test.md' example = CodeExample.create(python_code, path=md_file, start_line=3) with pytest.raises(Failed) as exc_info: eval_example.run_print_check(example) assert str(exc_info.value) == ( 'Print output changed code:\n' ' --- before\n' ' +++ after\n' ' @@ -4 +4,5 @@\n' ' print(1, 2, [3, 4, 5], "hello")\n' ' +#> 1 2 [3, 4, 5] hello\n' ) def test_run_main(tmp_path, eval_example): # note this file is no written here as it's not required md_file = tmp_path / 'test.md' python_code = """ def main(): 1 / 0 """ example = CodeExample.create(python_code, path=md_file) eval_example.set_config(line_length=30) eval_example.run_print_check(example) with pytest.raises(ZeroDivisionError): eval_example.run_print_check(example, call='main') def test_run_main_print(tmp_path, eval_example): python_code = """ main_called = False def main(): global main_called main_called = True print(1, 2, 3) #> 1 2 3 """ # note this file is no written here as it's not required example = CodeExample.create(python_code, path=tmp_path / 'test.md') eval_example.set_config(line_length=30) module_dict = eval_example.run_print_check(example, call='main') assert module_dict['main_called'] def test_run_main_print_async(tmp_path, eval_example): python_code = """ main_called = False async def main(): global main_called main_called = True print(1, 2, 3) #> 1 2 3 """ # note this file is no written here as it's not required example = CodeExample.create(python_code, path=tmp_path / 'test.md') eval_example.set_config(line_length=30) module_dict = eval_example.run_print_check(example, call='main') assert module_dict['main_called'] def test_custom_include_print(tmp_path, eval_example): python_code = """ print('yes') #> yes print('no') """ # note this file is no written here as it's not required example = CodeExample.create(python_code, path=tmp_path / 'test.md') eval_example.set_config(line_length=30) def custom_include_print(path, frame, args): return 'yes' in args eval_example.include_print = custom_include_print eval_example.run_print_check(example, call='main') def test_print_different_file(tmp_path, eval_example): other_file = tmp_path / 'other.py' other_code = """ def does_print(): print('hello') """ other_file.write_text(other_code) sys.path.append(str(tmp_path)) python_code = """ import other other.does_print() #> hello """ example = CodeExample.create(python_code, path=tmp_path / 'test.md') eval_example.include_print = lambda p, f, a: True eval_example.run_print_check(example, call='main') del sys.modules['other'] other_file.write_text(('\n' * 30) + other_code) eval_example.run_print_check(example, call='main') pytest-examples-0.0.18/tests/test_lint.py000066400000000000000000000025441526262746400205100ustar00rootroot00000000000000import pytest from pytest_examples import CodeExample from pytest_examples.config import ExamplesConfig from pytest_examples.lint import FormatError, black_check, ruff_check long_function = 'def this_is_a_very_long_function_name_to_cause_errors(the_argument): pass\n' def test_ruff(): example = CodeExample.create(long_function) ruff_check(example, ExamplesConfig()) def test_ruff_config(): code = 'from typing import Union\n\n\ndef foo(x: Union[int, str]):\n pass\n' example = CodeExample.create(code) ruff_check(example, ExamplesConfig()) with pytest.raises(FormatError, match='real/file.py:4:12: UP007 [*] Use `X | Y` for type annotations'): ruff_check(example, ExamplesConfig(target_version='py311', upgrade=True)) def test_ruff_offset(): code = 'print(x)\n' example = CodeExample.create(code) with pytest.raises(FormatError, match='testing.md:1:7: F821 Undefined name'): ruff_check(example, ExamplesConfig()) example = CodeExample.create(code, start_line=10) with pytest.raises(FormatError, match='testing.md:11:7: F821 Undefined name'): ruff_check(example, ExamplesConfig()) def test_black_line_length(): example = CodeExample.create(long_function, start_line=4) with pytest.raises(FormatError, match='^black failed:\n'): black_check(example, ExamplesConfig(line_length=40)) pytest-examples-0.0.18/tests/test_run_examples.py000066400000000000000000000225501526262746400222430ustar00rootroot00000000000000import re import sys import pytest from pytest_examples import CodeExample # language=Python python_code = """ from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_run_examples(example: CodeExample, eval_example: EvalExample): eval_example.run(example) """ @pytest.mark.skipif(sys.version_info < (3, 8), reason='traceback different on 3.7') def test_run_example_ok_fail(pytester: pytest.Pytester): pytester.makefile( '.md', # language=Markdown my_file=""" # My file ```py a = 1 b = 2 assert a + b == 3 ``` ```py a = 1 b = 2 assert a + b == 4 ``` """, ) # language=Python pytester.makepyfile( """ from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_run_examples(example: CodeExample, eval_example: EvalExample): eval_example.run(example, rewrite_assertions=False) """ ) result = pytester.runpytest('-p', 'no:pretty', '-v') result.assert_outcomes(passed=1, failed=1) assert result.outlines[-11].startswith('_ _ _ _ ') assert result.outlines[-10:-3] == [ '', ' a = 1', ' b = 2', '> assert a + b == 4', 'E AssertionError', '', 'my_file.md:12: AssertionError', ] def test_ruff_ok(pytester: pytest.Pytester): pytester.makefile( '.md', my_file='```py\nimport sys\nprint(sys.platform)\n```', ) # language=Python pytester.makepyfile( """ from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_run_examples(example: CodeExample, eval_example: EvalExample): eval_example.lint_ruff(example) """ ) result = pytester.runpytest('-p', 'no:pretty', '-v') result.assert_outcomes(passed=1) def test_ruff_error(pytester: pytest.Pytester): pytester.makefile( '.md', my_file='```py\nimport sys\nprint(missing)\n```', ) # language=Python pytester.makepyfile( """ from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_run_examples(example: CodeExample, eval_example: EvalExample): eval_example.lint_ruff(example) """ ) result = pytester.runpytest('-p', 'no:pretty', '-v') result.assert_outcomes(failed=1) output = '\n'.join(result.outlines) output = re.sub(r'(=|_){3,}', r'\1\1\1', output) for phrase in [ '=== FAILURES ===\n', '___ test_find_run_examples[my_file.md:1-4] ___\n', 'ruff failed:\n', ' my_file.md:2:8: F401 [*] `sys` imported but unused\n', ' my_file.md:3:7: F821 Undefined name `missing`\n', ' Found 2 errors.\n', ' [*] 1 fixable with the `--fix` option.\n', '=== short test summary info ===\n', ]: assert phrase in output def test_black_ok(pytester: pytest.Pytester): pytester.makefile( '.md', my_file='```py\nx = [1, 2, 3]\n```', ) # language=Python pytester.makepyfile( """ from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_run_examples(example: CodeExample, eval_example: EvalExample): eval_example.lint_black(example) """ ) result = pytester.runpytest('-p', 'no:pretty', '-v') result.assert_outcomes(passed=1) def test_black_error(pytester: pytest.Pytester): pytester.makefile( '.md', my_file='line 1\nline 2\n```py\nx =[1,2, 3]\n```', ) # language=Python pytester.makepyfile( """ from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_run_examples(example: CodeExample, eval_example: EvalExample): eval_example.lint_black(example) """ ) result = pytester.runpytest('-p', 'no:pretty', '-v') result.assert_outcomes(failed=1) failures_start = next(index for index, line in enumerate(result.outlines) if 'FAILURES' in line) failures_end = next(index for index, line in enumerate(result.outlines) if 'short test summary' in line) e_lines = [line.strip() for line in result.outlines[failures_start + 2 : failures_end]] assert e_lines == [ 'black failed:', '--- before', '+++ after', '@@ -4 +4 @@', '-x =[1,2, 3]', '+x = [1, 2, 3]', '', ] def test_black_error_dot_space(pytester: pytest.Pytester): pytester.makefile( '.md', my_file='line 1\nline 2\n```py\nx =[1,2, 3]\n```', ) # language=Python pytester.makepyfile( """ from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_run_examples(example: CodeExample, eval_example: EvalExample): eval_example.config.white_space_dot = True eval_example.lint_black(example) """ ) result = pytester.runpytest('-p', 'no:pretty', '-v') result.assert_outcomes(failed=1) failures_start = next(index for index, line in enumerate(result.outlines) if 'FAILURES' in line) failures_end = next(index for index, line in enumerate(result.outlines) if 'short test summary' in line) e_lines = [line.strip() for line in result.outlines[failures_start + 2 : failures_end]] assert e_lines == [ 'black failed:', '--- before', '+++ after', '@@ -4 +4 @@', '-x·=[1,2,·3]', '+x·=·[1,·2,·3]', '', ] def test_black_error_multiline(pytester: pytest.Pytester): pytester.makefile( '.md', my_file=""" line 1 line 2 ```py x =[ 1, 2, 3 ] ```""", ) # language=Python pytester.makepyfile( """ from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_run_examples(example: CodeExample, eval_example: EvalExample): eval_example.lint_black(example) """ ) result = pytester.runpytest('-p', 'no:pretty', '-v') result.assert_outcomes(failed=1) failures_start = next(index for index, line in enumerate(result.outlines) if 'FAILURES' in line) failures_end = next(index for index, line in enumerate(result.outlines) if 'short test summary' in line) e_lines = [line.strip() for line in result.outlines[failures_start + 2 : failures_end]] assert e_lines == [ 'black failed:', '--- before', '+++ after', '@@ -4,8 +4 @@', '-x =[', '- 1,', '- 2,', '- 3', '-]', '+x = [1, 2, 3]', '', ] @pytest.mark.skipif(sys.version_info < (3, 8), reason='traceback different on 3.7') def test_run_directly(tmp_path, eval_example): # language=Python python_code = """\ x = 4 def div(y): try: return x / y finally: str(y) div(2) div(0)""" # language=Markdown markdown = f"""\ # this is markdown ```py {python_code} ``` """ md_file = tmp_path / 'test.md' md_file.write_text(markdown) example = CodeExample.create(python_code, path=md_file, start_line=3, end_line=6) with pytest.raises(ZeroDivisionError) as exc_info: eval_example.run(example) # debug(exc_info.traceback) assert exc_info.traceback[-1].frame.code.path == md_file assert exc_info.traceback[-1].lineno == 7 assert exc_info.traceback[-2].frame.code.path == md_file assert exc_info.traceback[-2].lineno == 12 def test_print_sub(pytester: pytest.Pytester): pytester.makefile( '.md', # language=Markdown my_file=''' # My file ```py print('hello') #> hello print('1/2/3') #> X/X/X print({f'{i} key': i for i in range(8)}) """ { 'X key': X, 'X key': X, 'X key': X, 'X key': X, 'X key': X, 'X key': X, 'X key': X, 'X key': X, } """ ``` ''', ) # language=Python pytester.makepyfile( r""" import re from pytest_examples import find_examples, CodeExample, EvalExample import pytest def print_sub(print_statement): return re.sub(r'[0-9]+', 'X', print_statement) @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_run_examples(example: CodeExample, eval_example: EvalExample): eval_example.print_callback = print_sub eval_example.run_print_check(example, rewrite_assertions=False) """ ) result = pytester.runpytest('-p', 'no:pretty', '-v') result.assert_outcomes(passed=1) def test_print_check_spaces(pytester: pytest.Pytester): pytester.makefile( '.md', # language=Markdown my_file=""" # My file ```py # note trailing space print('hello ') #> hello ```""", ) # language=Python pytester.makepyfile( r""" from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_run_examples(example: CodeExample, eval_example: EvalExample): eval_example.run_print_check(example, rewrite_assertions=False) """ ) result = pytester.runpytest('-p', 'no:pretty', '-v') result.assert_outcomes(passed=1) pytest-examples-0.0.18/tests/test_update.py000066400000000000000000000111741526262746400210230ustar00rootroot00000000000000import pytest def test_update_files(pytester: pytest.Pytester): md_file = pytester.makefile( '.md', # language=Markdown my_file=""" # My file ```py print("this is the first example") ``` ```py async def main(): print(["first things", "second things", "third things"]) ``` """, ) py_file = pytester.makefile( '.py', # language=Python my_file=''' ################################# start of demo code ################################# def func_a(): """ Func A docstring. ```py print(1) ``` """ pass ################################# end of demo code ################################# ''', ) pytester.makepyfile( # language=Python """ ################################# start of test code ################################# from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_find_examples(example: CodeExample, eval_example: EvalExample): if eval_example.update_examples: eval_example.lint(example) eval_example.run_print_update(example, call='main') else: eval_example.lint(example) # insert_print_statements='check' would fail here eval_example.run(example) ################################# end of test code ################################# """ ) result = pytester.runpytest('-p', 'no:pretty') result.assert_outcomes(passed=3) result = pytester.runpytest('-p', 'no:pretty', '--update-examples', '--update-examples-disable-summary') result.assert_outcomes(passed=3) assert md_file.read_text() == ( """\ # My file ```py print("this is the first example") #> this is the first example ``` ```py async def main(): print(["first things", "second things", "third things"]) #> ['first things', 'second things', 'third things'] ```""" ) assert ( py_file.read_text() == '''\ ################################# start of demo code ################################# def func_a(): """ Func A docstring. ```py print(1) #> 1 ``` """ pass ################################# end of demo code #################################''' ) def test_update_repeat_example(pytester: pytest.Pytester): pytester.makefile( '.md', # language=Markdown my_file=""" # My file ```py print("this is the first example") ``` """, ) pytester.makepyfile( # language=Python """ ################################# start of test code ################################# from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_a(example: CodeExample, eval_example: EvalExample): if eval_example.update_examples: eval_example.run_print_update(example) @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_b(example: CodeExample, eval_example: EvalExample): if eval_example.update_examples: eval_example.run_print_update(example) ################################# end of test code ################################# """ ) result = pytester.runpytest('-p', 'no:pretty', '--update-examples') result.assert_outcomes(errors=1, passed=2) assert 'RuntimeError: Cannot update the same example in separate tests!' in '\n'.join(result.outlines) def test_update_repeat_file(pytester: pytest.Pytester): pytester.makefile( '.md', # language=Markdown my_file=""" ```py print("example 1") ``` ```py print("example 2") ``` """, ) pytester.makepyfile( # language=Python """ ################################# start of test code ################################# from pytest_examples import find_examples, CodeExample, EvalExample import pytest @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_a(example: CodeExample, eval_example: EvalExample): if 'example 1' in example.source: eval_example.run_print_update(example) @pytest.mark.parametrize('example', find_examples('.'), ids=str) def test_b(example: CodeExample, eval_example: EvalExample): if 'example 2' in example.source: eval_example.run_print_update(example) ################################# end of test code ################################# """ ) result = pytester.runpytest('-p', 'no:pretty', '--update-examples') result.assert_outcomes(errors=1, passed=4) assert 'RuntimeError: Cannot update the same file in separate groups!' in '\n'.join(result.outlines) pytest-examples-0.0.18/tests/test_update_examples_dir.py000066400000000000000000000036401526262746400235560ustar00rootroot00000000000000import re from pathlib import Path import pytest def find_cases(): root_dir = Path(__file__).parent / 'cases_update' for f in root_dir.iterdir(): if not f.is_file(): continue if f.suffix in {'.py', '.md'}: content = f.read_text() m = re.search(r'(.+?)^#{5,}', content, flags=re.M | re.I | re.S) assert m, f'No EXAMPLE match found for {f}' example = m.group(1).strip('\n') m = re.search(r'^#{5,}\s*output\s*#{5,}\n(.+?)^#{5,}', content, flags=re.M | re.I | re.S) assert m, f'No OUTPUT match found for {f}' output = m.group(1).strip('\n') m = re.search(r'^#{5,}\s*test\s*#{5,}\n(.+)', content, flags=re.M | re.I | re.S) assert m, f'No TEST match found for {f}' test = m.group(1).strip('\n') m_test_count = re.search(r'^test_count *= *(\d+)', test, flags=re.M) if m_test_count: test_count = int(m_test_count.group(1)) else: test_count = 1 if f.suffix == '.md': m = re.search(r'^```.*?^(.+?)^```', test, flags=re.M | re.S) if m: test = m.group(1) yield pytest.param(f, example, output, test, test_count, id=f.name) @pytest.mark.parametrize('file_path,example,output,test_code,test_count', find_cases()) def test_cases_update( pytester: pytest.Pytester, file_path: Path, example: str, output: str, test_code: str, test_count: int ): input_file = pytester.makefile(file_path.suffix, **{f'case_{file_path.stem}': example}) pytester.makepyfile(test_code) result = pytester.runpytest('-p', 'no:pretty', '-vs', '--update-examples', '--update-examples-disable-summary') result.assert_outcomes(passed=test_count) # debug(input_file.read_text(), output) # print(input_file.read_text()) assert input_file.read_text() == output pytest-examples-0.0.18/uv.lock000066400000000000000000002163251526262746400162770ustar00rootroot00000000000000version = 1 revision = 2 requires-python = ">=3.8" [[package]] name = "black" version = "24.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "mypy-extensions" }, { name = "packaging" }, { name = "pathspec" }, { name = "platformdirs" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/04/b0/46fb0d4e00372f4a86a6f8efa3cb193c9f64863615e39010b1477e010578/black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f", size = 644810, upload-time = "2024-08-02T17:43:18.405Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/47/6e/74e29edf1fba3887ed7066930a87f698ffdcd52c5dbc263eabb06061672d/black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6", size = 1632092, upload-time = "2024-08-02T17:47:26.911Z" }, { url = "https://files.pythonhosted.org/packages/ab/49/575cb6c3faee690b05c9d11ee2e8dba8fbd6d6c134496e644c1feb1b47da/black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb", size = 1457529, upload-time = "2024-08-02T17:47:29.109Z" }, { url = "https://files.pythonhosted.org/packages/7a/b4/d34099e95c437b53d01c4aa37cf93944b233066eb034ccf7897fa4e5f286/black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42", size = 1757443, upload-time = "2024-08-02T17:46:20.306Z" }, { url = "https://files.pythonhosted.org/packages/87/a0/6d2e4175ef364b8c4b64f8441ba041ed65c63ea1db2720d61494ac711c15/black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a", size = 1418012, upload-time = "2024-08-02T17:47:20.33Z" }, { url = "https://files.pythonhosted.org/packages/08/a6/0a3aa89de9c283556146dc6dbda20cd63a9c94160a6fbdebaf0918e4a3e1/black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1", size = 1615080, upload-time = "2024-08-02T17:48:05.467Z" }, { url = "https://files.pythonhosted.org/packages/db/94/b803d810e14588bb297e565821a947c108390a079e21dbdcb9ab6956cd7a/black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af", size = 1438143, upload-time = "2024-08-02T17:47:30.247Z" }, { url = "https://files.pythonhosted.org/packages/a5/b5/f485e1bbe31f768e2e5210f52ea3f432256201289fd1a3c0afda693776b0/black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4", size = 1738774, upload-time = "2024-08-02T17:46:17.837Z" }, { url = "https://files.pythonhosted.org/packages/a8/69/a000fc3736f89d1bdc7f4a879f8aaf516fb03613bb51a0154070383d95d9/black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af", size = 1427503, upload-time = "2024-08-02T17:46:22.654Z" }, { url = "https://files.pythonhosted.org/packages/a2/a8/05fb14195cfef32b7c8d4585a44b7499c2a4b205e1662c427b941ed87054/black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368", size = 1646132, upload-time = "2024-08-02T17:49:52.843Z" }, { url = "https://files.pythonhosted.org/packages/41/77/8d9ce42673e5cb9988f6df73c1c5c1d4e9e788053cccd7f5fb14ef100982/black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed", size = 1448665, upload-time = "2024-08-02T17:47:54.479Z" }, { url = "https://files.pythonhosted.org/packages/cc/94/eff1ddad2ce1d3cc26c162b3693043c6b6b575f538f602f26fe846dfdc75/black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018", size = 1762458, upload-time = "2024-08-02T17:46:19.384Z" }, { url = "https://files.pythonhosted.org/packages/28/ea/18b8d86a9ca19a6942e4e16759b2fa5fc02bbc0eb33c1b866fcd387640ab/black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2", size = 1436109, upload-time = "2024-08-02T17:46:52.97Z" }, { url = "https://files.pythonhosted.org/packages/9f/d4/ae03761ddecc1a37d7e743b89cccbcf3317479ff4b88cfd8818079f890d0/black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd", size = 1617322, upload-time = "2024-08-02T17:51:20.203Z" }, { url = "https://files.pythonhosted.org/packages/14/4b/4dfe67eed7f9b1ddca2ec8e4418ea74f0d1dc84d36ea874d618ffa1af7d4/black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2", size = 1442108, upload-time = "2024-08-02T17:50:40.824Z" }, { url = "https://files.pythonhosted.org/packages/97/14/95b3f91f857034686cae0e73006b8391d76a8142d339b42970eaaf0416ea/black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e", size = 1745786, upload-time = "2024-08-02T17:46:02.939Z" }, { url = "https://files.pythonhosted.org/packages/95/54/68b8883c8aa258a6dde958cd5bdfada8382bec47c5162f4a01e66d839af1/black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920", size = 1426754, upload-time = "2024-08-02T17:46:38.603Z" }, { url = "https://files.pythonhosted.org/packages/13/b2/b3f24fdbb46f0e7ef6238e131f13572ee8279b70f237f221dd168a9dba1a/black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c", size = 1631706, upload-time = "2024-08-02T17:49:57.606Z" }, { url = "https://files.pythonhosted.org/packages/d9/35/31010981e4a05202a84a3116423970fd1a59d2eda4ac0b3570fbb7029ddc/black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e", size = 1457429, upload-time = "2024-08-02T17:49:12.764Z" }, { url = "https://files.pythonhosted.org/packages/27/25/3f706b4f044dd569a20a4835c3b733dedea38d83d2ee0beb8178a6d44945/black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47", size = 1756488, upload-time = "2024-08-02T17:46:08.067Z" }, { url = "https://files.pythonhosted.org/packages/63/72/79375cd8277cbf1c5670914e6bd4c1b15dea2c8f8e906dc21c448d0535f0/black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb", size = 1417721, upload-time = "2024-08-02T17:46:42.637Z" }, { url = "https://files.pythonhosted.org/packages/27/1e/83fa8a787180e1632c3d831f7e58994d7aaf23a0961320d21e84f922f919/black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed", size = 206504, upload-time = "2024-08-02T17:43:15.747Z" }, ] [[package]] name = "cfgv" version = "3.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, ] [[package]] name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "coverage" version = "7.6.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791, upload-time = "2024-08-04T19:45:30.9Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/61/eb7ce5ed62bacf21beca4937a90fe32545c91a3c8a42a30c6616d48fc70d/coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", size = 206690, upload-time = "2024-08-04T19:43:07.695Z" }, { url = "https://files.pythonhosted.org/packages/7d/73/041928e434442bd3afde5584bdc3f932fb4562b1597629f537387cec6f3d/coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", size = 207127, upload-time = "2024-08-04T19:43:10.15Z" }, { url = "https://files.pythonhosted.org/packages/c7/c8/6ca52b5147828e45ad0242388477fdb90df2c6cbb9a441701a12b3c71bc8/coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", size = 235654, upload-time = "2024-08-04T19:43:12.405Z" }, { url = "https://files.pythonhosted.org/packages/d5/da/9ac2b62557f4340270942011d6efeab9833648380109e897d48ab7c1035d/coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc", size = 233598, upload-time = "2024-08-04T19:43:14.078Z" }, { url = "https://files.pythonhosted.org/packages/53/23/9e2c114d0178abc42b6d8d5281f651a8e6519abfa0ef460a00a91f80879d/coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", size = 234732, upload-time = "2024-08-04T19:43:16.632Z" }, { url = "https://files.pythonhosted.org/packages/0f/7e/a0230756fb133343a52716e8b855045f13342b70e48e8ad41d8a0d60ab98/coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", size = 233816, upload-time = "2024-08-04T19:43:19.049Z" }, { url = "https://files.pythonhosted.org/packages/28/7c/3753c8b40d232b1e5eeaed798c875537cf3cb183fb5041017c1fdb7ec14e/coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", size = 232325, upload-time = "2024-08-04T19:43:21.246Z" }, { url = "https://files.pythonhosted.org/packages/57/e3/818a2b2af5b7573b4b82cf3e9f137ab158c90ea750a8f053716a32f20f06/coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", size = 233418, upload-time = "2024-08-04T19:43:22.945Z" }, { url = "https://files.pythonhosted.org/packages/c8/fb/4532b0b0cefb3f06d201648715e03b0feb822907edab3935112b61b885e2/coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", size = 209343, upload-time = "2024-08-04T19:43:25.121Z" }, { url = "https://files.pythonhosted.org/packages/5a/25/af337cc7421eca1c187cc9c315f0a755d48e755d2853715bfe8c418a45fa/coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", size = 210136, upload-time = "2024-08-04T19:43:26.851Z" }, { url = "https://files.pythonhosted.org/packages/ad/5f/67af7d60d7e8ce61a4e2ddcd1bd5fb787180c8d0ae0fbd073f903b3dd95d/coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", size = 206796, upload-time = "2024-08-04T19:43:29.115Z" }, { url = "https://files.pythonhosted.org/packages/e1/0e/e52332389e057daa2e03be1fbfef25bb4d626b37d12ed42ae6281d0a274c/coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", size = 207244, upload-time = "2024-08-04T19:43:31.285Z" }, { url = "https://files.pythonhosted.org/packages/aa/cd/766b45fb6e090f20f8927d9c7cb34237d41c73a939358bc881883fd3a40d/coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", size = 239279, upload-time = "2024-08-04T19:43:33.581Z" }, { url = "https://files.pythonhosted.org/packages/70/6c/a9ccd6fe50ddaf13442a1e2dd519ca805cbe0f1fcd377fba6d8339b98ccb/coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", size = 236859, upload-time = "2024-08-04T19:43:35.301Z" }, { url = "https://files.pythonhosted.org/packages/14/6f/8351b465febb4dbc1ca9929505202db909c5a635c6fdf33e089bbc3d7d85/coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", size = 238549, upload-time = "2024-08-04T19:43:37.578Z" }, { url = "https://files.pythonhosted.org/packages/68/3c/289b81fa18ad72138e6d78c4c11a82b5378a312c0e467e2f6b495c260907/coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", size = 237477, upload-time = "2024-08-04T19:43:39.92Z" }, { url = "https://files.pythonhosted.org/packages/ed/1c/aa1efa6459d822bd72c4abc0b9418cf268de3f60eeccd65dc4988553bd8d/coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", size = 236134, upload-time = "2024-08-04T19:43:41.453Z" }, { url = "https://files.pythonhosted.org/packages/fb/c8/521c698f2d2796565fe9c789c2ee1ccdae610b3aa20b9b2ef980cc253640/coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", size = 236910, upload-time = "2024-08-04T19:43:43.037Z" }, { url = "https://files.pythonhosted.org/packages/7d/30/033e663399ff17dca90d793ee8a2ea2890e7fdf085da58d82468b4220bf7/coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", size = 209348, upload-time = "2024-08-04T19:43:44.787Z" }, { url = "https://files.pythonhosted.org/packages/20/05/0d1ccbb52727ccdadaa3ff37e4d2dc1cd4d47f0c3df9eb58d9ec8508ca88/coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", size = 210230, upload-time = "2024-08-04T19:43:46.707Z" }, { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983, upload-time = "2024-08-04T19:43:49.082Z" }, { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221, upload-time = "2024-08-04T19:43:52.15Z" }, { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342, upload-time = "2024-08-04T19:43:53.746Z" }, { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371, upload-time = "2024-08-04T19:43:55.993Z" }, { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455, upload-time = "2024-08-04T19:43:57.618Z" }, { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924, upload-time = "2024-08-04T19:44:00.012Z" }, { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252, upload-time = "2024-08-04T19:44:01.713Z" }, { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897, upload-time = "2024-08-04T19:44:03.898Z" }, { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606, upload-time = "2024-08-04T19:44:05.532Z" }, { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373, upload-time = "2024-08-04T19:44:07.079Z" }, { url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007, upload-time = "2024-08-04T19:44:09.453Z" }, { url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269, upload-time = "2024-08-04T19:44:11.045Z" }, { url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886, upload-time = "2024-08-04T19:44:12.83Z" }, { url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037, upload-time = "2024-08-04T19:44:15.393Z" }, { url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038, upload-time = "2024-08-04T19:44:17.466Z" }, { url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690, upload-time = "2024-08-04T19:44:19.336Z" }, { url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765, upload-time = "2024-08-04T19:44:20.994Z" }, { url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611, upload-time = "2024-08-04T19:44:22.616Z" }, { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671, upload-time = "2024-08-04T19:44:24.418Z" }, { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368, upload-time = "2024-08-04T19:44:26.276Z" }, { url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758, upload-time = "2024-08-04T19:44:29.028Z" }, { url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035, upload-time = "2024-08-04T19:44:30.673Z" }, { url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839, upload-time = "2024-08-04T19:44:32.412Z" }, { url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569, upload-time = "2024-08-04T19:44:34.547Z" }, { url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927, upload-time = "2024-08-04T19:44:36.313Z" }, { url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401, upload-time = "2024-08-04T19:44:38.155Z" }, { url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301, upload-time = "2024-08-04T19:44:39.883Z" }, { url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598, upload-time = "2024-08-04T19:44:41.59Z" }, { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307, upload-time = "2024-08-04T19:44:43.301Z" }, { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453, upload-time = "2024-08-04T19:44:45.677Z" }, { url = "https://files.pythonhosted.org/packages/81/d0/d9e3d554e38beea5a2e22178ddb16587dbcbe9a1ef3211f55733924bf7fa/coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", size = 206674, upload-time = "2024-08-04T19:44:47.694Z" }, { url = "https://files.pythonhosted.org/packages/38/ea/cab2dc248d9f45b2b7f9f1f596a4d75a435cb364437c61b51d2eb33ceb0e/coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", size = 207101, upload-time = "2024-08-04T19:44:49.32Z" }, { url = "https://files.pythonhosted.org/packages/ca/6f/f82f9a500c7c5722368978a5390c418d2a4d083ef955309a8748ecaa8920/coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", size = 236554, upload-time = "2024-08-04T19:44:51.631Z" }, { url = "https://files.pythonhosted.org/packages/a6/94/d3055aa33d4e7e733d8fa309d9adf147b4b06a82c1346366fc15a2b1d5fa/coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", size = 234440, upload-time = "2024-08-04T19:44:53.464Z" }, { url = "https://files.pythonhosted.org/packages/e4/6e/885bcd787d9dd674de4a7d8ec83faf729534c63d05d51d45d4fa168f7102/coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", size = 235889, upload-time = "2024-08-04T19:44:55.165Z" }, { url = "https://files.pythonhosted.org/packages/f4/63/df50120a7744492710854860783d6819ff23e482dee15462c9a833cc428a/coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", size = 235142, upload-time = "2024-08-04T19:44:57.269Z" }, { url = "https://files.pythonhosted.org/packages/3a/5d/9d0acfcded2b3e9ce1c7923ca52ccc00c78a74e112fc2aee661125b7843b/coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", size = 233805, upload-time = "2024-08-04T19:44:59.033Z" }, { url = "https://files.pythonhosted.org/packages/c4/56/50abf070cb3cd9b1dd32f2c88f083aab561ecbffbcd783275cb51c17f11d/coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", size = 234655, upload-time = "2024-08-04T19:45:01.398Z" }, { url = "https://files.pythonhosted.org/packages/25/ee/b4c246048b8485f85a2426ef4abab88e48c6e80c74e964bea5cd4cd4b115/coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", size = 209296, upload-time = "2024-08-04T19:45:03.819Z" }, { url = "https://files.pythonhosted.org/packages/5c/1c/96cf86b70b69ea2b12924cdf7cabb8ad10e6130eab8d767a1099fbd2a44f/coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", size = 210137, upload-time = "2024-08-04T19:45:06.25Z" }, { url = "https://files.pythonhosted.org/packages/19/d3/d54c5aa83268779d54c86deb39c1c4566e5d45c155369ca152765f8db413/coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", size = 206688, upload-time = "2024-08-04T19:45:08.358Z" }, { url = "https://files.pythonhosted.org/packages/a5/fe/137d5dca72e4a258b1bc17bb04f2e0196898fe495843402ce826a7419fe3/coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", size = 207120, upload-time = "2024-08-04T19:45:11.526Z" }, { url = "https://files.pythonhosted.org/packages/78/5b/a0a796983f3201ff5485323b225d7c8b74ce30c11f456017e23d8e8d1945/coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", size = 235249, upload-time = "2024-08-04T19:45:13.202Z" }, { url = "https://files.pythonhosted.org/packages/4e/e1/76089d6a5ef9d68f018f65411fcdaaeb0141b504587b901d74e8587606ad/coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", size = 233237, upload-time = "2024-08-04T19:45:14.961Z" }, { url = "https://files.pythonhosted.org/packages/9a/6f/eef79b779a540326fee9520e5542a8b428cc3bfa8b7c8f1022c1ee4fc66c/coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", size = 234311, upload-time = "2024-08-04T19:45:16.924Z" }, { url = "https://files.pythonhosted.org/packages/75/e1/656d65fb126c29a494ef964005702b012f3498db1a30dd562958e85a4049/coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", size = 233453, upload-time = "2024-08-04T19:45:18.672Z" }, { url = "https://files.pythonhosted.org/packages/68/6a/45f108f137941a4a1238c85f28fd9d048cc46b5466d6b8dda3aba1bb9d4f/coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", size = 231958, upload-time = "2024-08-04T19:45:20.63Z" }, { url = "https://files.pythonhosted.org/packages/9b/e7/47b809099168b8b8c72ae311efc3e88c8d8a1162b3ba4b8da3cfcdb85743/coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", size = 232938, upload-time = "2024-08-04T19:45:23.062Z" }, { url = "https://files.pythonhosted.org/packages/52/80/052222ba7058071f905435bad0ba392cc12006380731c37afaf3fe749b88/coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", size = 209352, upload-time = "2024-08-04T19:45:25.042Z" }, { url = "https://files.pythonhosted.org/packages/b8/d8/1b92e0b3adcf384e98770a00ca095da1b5f7b483e6563ae4eb5e935d24a1/coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", size = 210153, upload-time = "2024-08-04T19:45:27.079Z" }, { url = "https://files.pythonhosted.org/packages/a5/2b/0354ed096bca64dc8e32a7cbcae28b34cb5ad0b1fe2125d6d99583313ac0/coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", size = 198926, upload-time = "2024-08-04T19:45:28.875Z" }, ] [package.optional-dependencies] toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] [[package]] name = "distlib" version = "0.3.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, ] [[package]] name = "exceptiongroup" version = "1.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, ] [[package]] name = "filelock" version = "3.16.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037, upload-time = "2024-09-17T19:02:01.779Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163, upload-time = "2024-09-17T19:02:00.268Z" }, ] [[package]] name = "identify" version = "2.6.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/29/bb/25024dbcc93516c492b75919e76f389bac754a3e4248682fba32b250c880/identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98", size = 99097, upload-time = "2024-09-14T23:50:32.513Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7d/0c/4ef72754c050979fdcc06c744715ae70ea37e734816bb6514f79df77a42f/identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0", size = 98972, upload-time = "2024-09-14T23:50:30.747Z" }, ] [[package]] name = "iniconfig" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, ] [[package]] name = "markdown-it-py" version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] name = "mypy-extensions" version = "1.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload-time = "2023-02-04T12:11:27.157Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload-time = "2023-02-04T12:11:25.002Z" }, ] [[package]] name = "nodeenv" version = "1.9.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] [[package]] name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, ] [[package]] name = "pathspec" version = "0.12.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] [[package]] name = "platformdirs" version = "4.3.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302, upload-time = "2024-09-17T19:06:50.688Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439, upload-time = "2024-09-17T19:06:49.212Z" }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] [[package]] name = "pre-commit" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, { name = "identify" }, { name = "nodeenv" }, { name = "pyyaml" }, { name = "virtualenv" }, ] sdist = { url = "https://files.pythonhosted.org/packages/04/b3/4ae08d21eb097162f5aad37f4585f8069a86402ed7f5362cc9ae097f9572/pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32", size = 177079, upload-time = "2023-10-13T15:57:48.334Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6c/75/526915fedf462e05eeb1c75ceaf7e3f9cde7b5ce6f62740fe5f7f19a0050/pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660", size = 203698, upload-time = "2023-10-13T15:57:46.378Z" }, ] [[package]] name = "pygments" version = "2.18.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905, upload-time = "2024-05-04T13:42:02.013Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513, upload-time = "2024-05-04T13:41:57.345Z" }, ] [[package]] name = "pyright" version = "1.1.389" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/72/4e/9a5ab8745e7606b88c2c7ca223449ac9d82a71fd5e31df47b453f2cb39a1/pyright-1.1.389.tar.gz", hash = "sha256:716bf8cc174ab8b4dcf6828c3298cac05c5ed775dda9910106a5dcfe4c7fe220", size = 21940, upload-time = "2024-11-13T16:35:41.84Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1b/26/c288cabf8cfc5a27e1aa9e5029b7682c0f920b8074f45d22bf844314d66a/pyright-1.1.389-py3-none-any.whl", hash = "sha256:41e9620bba9254406dc1f621a88ceab5a88af4c826feb4f614d95691ed243a60", size = 18581, upload-time = "2024-11-13T16:35:40.689Z" }, ] [[package]] name = "pytest" version = "8.3.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, ] [[package]] name = "pytest-examples" version = "0.0.18" source = { editable = "." } dependencies = [ { name = "black" }, { name = "pytest" }, { name = "ruff" }, ] [package.dev-dependencies] dev = [ { name = "coverage", extra = ["toml"] }, { name = "pytest-pretty" }, ] lint = [ { name = "pre-commit" }, { name = "pyright" }, { name = "ruff" }, ] [package.metadata] requires-dist = [ { name = "black", specifier = ">=23" }, { name = "pytest", specifier = ">=7" }, { name = "ruff", specifier = ">=0.5.0" }, ] [package.metadata.requires-dev] dev = [ { name = "coverage", extras = ["toml"], specifier = ">=7.6.1" }, { name = "pytest-pretty", specifier = ">=1.2.0" }, ] lint = [ { name = "pre-commit", specifier = ">=3.5.0" }, { name = "pyright", specifier = ">=1.1.389" }, { name = "ruff", specifier = ">=0.7.4" }, ] [[package]] name = "pytest-pretty" version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, { name = "rich" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a5/18/30ad0408295f3157f7a4913f0eaa51a0a377ebad0ffa51ff239e833c6c72/pytest_pretty-1.2.0.tar.gz", hash = "sha256:105a355f128e392860ad2c478ae173ff96d2f03044692f9818ff3d49205d3a60", size = 6542, upload-time = "2023-04-05T17:11:50.917Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/bf/fe/d44d391312c1b8abee2af58ee70fabb1c00b6577ac4e0bdf25b70c1caffb/pytest_pretty-1.2.0-py3-none-any.whl", hash = "sha256:6f79122bf53864ae2951b6c9e94d7a06a87ef753476acd4588aeac018f062036", size = 6180, upload-time = "2023-04-05T17:11:49.801Z" }, ] [[package]] name = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, { url = "https://files.pythonhosted.org/packages/74/d9/323a59d506f12f498c2097488d80d16f4cf965cee1791eab58b56b19f47a/PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", size = 183218, upload-time = "2024-08-06T20:33:06.411Z" }, { url = "https://files.pythonhosted.org/packages/74/cc/20c34d00f04d785f2028737e2e2a8254e1425102e730fee1d6396f832577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", size = 728067, upload-time = "2024-08-06T20:33:07.879Z" }, { url = "https://files.pythonhosted.org/packages/20/52/551c69ca1501d21c0de51ddafa8c23a0191ef296ff098e98358f69080577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", size = 757812, upload-time = "2024-08-06T20:33:12.542Z" }, { url = "https://files.pythonhosted.org/packages/fd/7f/2c3697bba5d4aa5cc2afe81826d73dfae5f049458e44732c7a0938baa673/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", size = 746531, upload-time = "2024-08-06T20:33:14.391Z" }, { url = "https://files.pythonhosted.org/packages/8c/ab/6226d3df99900e580091bb44258fde77a8433511a86883bd4681ea19a858/PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", size = 800820, upload-time = "2024-08-06T20:33:16.586Z" }, { url = "https://files.pythonhosted.org/packages/a0/99/a9eb0f3e710c06c5d922026f6736e920d431812ace24aae38228d0d64b04/PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", size = 145514, upload-time = "2024-08-06T20:33:22.414Z" }, { url = "https://files.pythonhosted.org/packages/75/8a/ee831ad5fafa4431099aa4e078d4c8efd43cd5e48fbc774641d233b683a9/PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", size = 162702, upload-time = "2024-08-06T20:33:23.813Z" }, { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" }, { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" }, { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" }, { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" }, { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" }, { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" }, { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" }, { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" }, { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" }, ] [[package]] name = "rich" version = "13.9.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149, upload-time = "2024-11-01T16:43:57.873Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424, upload-time = "2024-11-01T16:43:55.817Z" }, ] [[package]] name = "ruff" version = "0.7.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0b/8b/bc4e0dfa1245b07cf14300e10319b98e958a53ff074c1dd86b35253a8c2a/ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2", size = 3275547, upload-time = "2024-11-15T11:33:11.853Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e6/4b/f5094719e254829766b807dadb766841124daba75a37da83e292ae5ad12f/ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478", size = 10447512, upload-time = "2024-11-15T11:32:27.812Z" }, { url = "https://files.pythonhosted.org/packages/9e/1d/3d2d2c9f601cf6044799c5349ff5267467224cefed9b35edf5f1f36486e9/ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63", size = 10235436, upload-time = "2024-11-15T11:32:30.6Z" }, { url = "https://files.pythonhosted.org/packages/62/83/42a6ec6216ded30b354b13e0e9327ef75a3c147751aaf10443756cb690e9/ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20", size = 9888936, upload-time = "2024-11-15T11:32:33.287Z" }, { url = "https://files.pythonhosted.org/packages/4d/26/e1e54893b13046a6ad05ee9b89ee6f71542ba250f72b4c7a7d17c3dbf73d/ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109", size = 10697353, upload-time = "2024-11-15T11:32:35.895Z" }, { url = "https://files.pythonhosted.org/packages/21/24/98d2e109c4efc02bfef144ec6ea2c3e1217e7ce0cfddda8361d268dfd499/ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452", size = 10228078, upload-time = "2024-11-15T11:32:40.929Z" }, { url = "https://files.pythonhosted.org/packages/ad/b7/964c75be9bc2945fc3172241b371197bb6d948cc69e28bc4518448c368f3/ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea", size = 11264823, upload-time = "2024-11-15T11:32:43.31Z" }, { url = "https://files.pythonhosted.org/packages/12/8d/20abdbf705969914ce40988fe71a554a918deaab62c38ec07483e77866f6/ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7", size = 11951855, upload-time = "2024-11-15T11:32:46.038Z" }, { url = "https://files.pythonhosted.org/packages/b8/fc/6519ce58c57b4edafcdf40920b7273dfbba64fc6ebcaae7b88e4dc1bf0a8/ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05", size = 11516580, upload-time = "2024-11-15T11:32:48.17Z" }, { url = "https://files.pythonhosted.org/packages/37/1a/5ec1844e993e376a86eb2456496831ed91b4398c434d8244f89094758940/ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06", size = 12692057, upload-time = "2024-11-15T11:32:50.623Z" }, { url = "https://files.pythonhosted.org/packages/50/90/76867152b0d3c05df29a74bb028413e90f704f0f6701c4801174ba47f959/ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc", size = 11085137, upload-time = "2024-11-15T11:32:52.819Z" }, { url = "https://files.pythonhosted.org/packages/c8/eb/0a7cb6059ac3555243bd026bb21785bbc812f7bbfa95a36c101bd72b47ae/ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172", size = 10681243, upload-time = "2024-11-15T11:32:55.902Z" }, { url = "https://files.pythonhosted.org/packages/5e/76/2270719dbee0fd35780b05c08a07b7a726c3da9f67d9ae89ef21fc18e2e5/ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a", size = 10319187, upload-time = "2024-11-15T11:32:58.255Z" }, { url = "https://files.pythonhosted.org/packages/9f/e5/39100f72f8ba70bec1bd329efc880dea8b6c1765ea1cb9d0c1c5f18b8d7f/ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd", size = 10803715, upload-time = "2024-11-15T11:33:00.88Z" }, { url = "https://files.pythonhosted.org/packages/a5/89/40e904784f305fb56850063f70a998a64ebba68796d823dde67e89a24691/ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a", size = 11162912, upload-time = "2024-11-15T11:33:03.097Z" }, { url = "https://files.pythonhosted.org/packages/8d/1b/dd77503b3875c51e3dbc053fd8367b845ab8b01c9ca6d0c237082732856c/ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac", size = 8702767, upload-time = "2024-11-15T11:33:05.15Z" }, { url = "https://files.pythonhosted.org/packages/63/76/253ddc3e89e70165bba952ecca424b980b8d3c2598ceb4fc47904f424953/ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6", size = 9497534, upload-time = "2024-11-15T11:33:07.359Z" }, { url = "https://files.pythonhosted.org/packages/aa/70/f8724f31abc0b329ca98b33d73c14020168babcf71b0cba3cded5d9d0e66/ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f", size = 8851590, upload-time = "2024-11-15T11:33:09.664Z" }, ] [[package]] name = "tomli" version = "2.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1e/e4/1b6cbcc82d8832dd0ce34767d5c560df8a3547ad8cbc427f34601415930a/tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8", size = 16622, upload-time = "2024-11-11T18:38:01.76Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/de/f7/4da0ffe1892122c9ea096c57f64c2753ae5dd3ce85488802d11b0992cc6d/tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391", size = 13750, upload-time = "2024-11-11T18:38:00.19Z" }, ] [[package]] name = "typing-extensions" version = "4.12.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" }, ] [[package]] name = "virtualenv" version = "20.27.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8c/b3/7b6a79c5c8cf6d90ea681310e169cf2db2884f4d583d16c6e1d5a75a4e04/virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba", size = 6491145, upload-time = "2024-10-28T18:00:22.706Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ae/92/78324ff89391e00c8f4cf6b8526c41c6ef36b4ea2d2c132250b1a6fc2b8d/virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4", size = 3117838, upload-time = "2024-10-28T18:00:19.994Z" }, ]