diff --git a/.cursor/rules/dev-loop.mdc b/.cursor/rules/dev-loop.mdc new file mode 100644 index 000000000..1886aa702 --- /dev/null +++ b/.cursor/rules/dev-loop.mdc @@ -0,0 +1,37 @@ +--- +description: QA every edit +globs: *.py +--- + +# First: QA Every edit + +Run these commands between edits: + +Check typings: + +``` +uv run mypy +``` + +Lint: + +``` +uv run ruff check . --fix; uv run ruff format .; +``` + +Check tests: + +``` +uv run py.test +``` + +Between every edit, rerun: +- Type checks +- Lint +- Tests + +If there's any failures *due to the edits*, fix them first, then: + +# When your edit is complete: Commit it + +Make an atomic commit for the edit, using conventional commits. diff --git a/.cursor/rules/git-commits.mdc b/.cursor/rules/git-commits.mdc new file mode 100644 index 000000000..0a5fa1184 --- /dev/null +++ b/.cursor/rules/git-commits.mdc @@ -0,0 +1,82 @@ +--- +description: git-commits: Git commit message standards and AI assistance +globs: git-commits: Git commit message standards and AI assistance | *.git/* .gitignore .github/* CHANGELOG.md CHANGES.md +--- +# Git Commit Standards + +## Format +``` +type(scope[component]): concise description + +why: explanation of necessity/impact +what: +- technical changes made +- keep focused on single topic + +refs: #issue-number, breaking changes, links +``` + +## Commit Types +- `feat`: New features/enhancements +- `fix`: Bug fixes +- `refactor`: Code restructuring +- `docs`: Documentation changes +- `chore`: Maintenance tasks (deps, tooling) +- `test`: Test-related changes +- `style`: Code style/formatting + +## Guidelines +- Subject line: max 50 chars +- Body lines: max 72 chars +- Use imperative mood ("Add" not "Added") +- Single topic per commit +- Blank line between subject and body +- Mark breaking changes with "BREAKING:" +- Use "See also:" for external links + +## AI Assistance in Cursor +- Stage changes with `git add` +- Use `@commit` to generate initial message +- Review and adjust the generated message +- Ensure it follows format above + +## Examples + +Good commit: +``` +feat(subprocess[run]): Switch to unicode-only text handling + +why: Improve consistency and type safety in subprocess handling +what: +- BREAKING: Changed run() to use text=True by default +- Removed console_to_str() helper and encoding logic +- Simplified output handling +- Updated type hints for better safety + +refs: #485 +See also: https://docs.python.org/3/library/subprocess.html +``` + +Bad commit: +``` +updated some stuff and fixed bugs +``` + +Cursor Rules: Add development QA and git commit standards (#cursor-rules) + +- Add dev-loop.mdc: QA process for code edits + - Type checking with mypy + - Linting with ruff + - Test validation with pytest + - Ensures edits are validated before commits + +- Add git-commits.mdc: Commit message standards + - Structured format with why/what sections + - Defined commit types and guidelines + - Examples of good/bad commits + - AI assistance instructions + +Note: These rules help maintain code quality and commit history +consistency across the project. + +See also: https://docs.cursor.com/context/rules-for-ai \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 44c1a4ce6..affbc0b0e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,6 +23,15 @@ jobs: - name: Set up Python ${{ matrix.python-version }} run: uv python install ${{ matrix.python-version }} + - name: Test runtime dependencies + run: | + uv run --no-dev -p python${{ matrix.python-version }} -- python -c ' + from libtmux import common, constants, exc, formats, neo, pane, server, session, window, __version__ + server = server.Server() + print("libtmux version:", __version__) + print("libtmux Server:", server) + ' + - name: Install dependencies run: uv sync --all-extras --dev diff --git a/.tool-versions b/.tool-versions index 5a3a79a5a..ff78fd6a6 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -uv 0.6.0 +uv 0.6.1 python 3.13.2 3.12.9 3.11.11 3.10.16 3.9.21 3.8.20 3.7.17 diff --git a/CHANGES b/CHANGES index 320670151..cfb7f923d 100644 --- a/CHANGES +++ b/CHANGES @@ -9,12 +9,56 @@ To install via [pip](https://pip.pypa.io/en/stable/), use: $ pip install --user --upgrade --pre libtmux ``` -## libtmux 0.45.x (Yet to be released) +## libtmux 0.46.x (Yet to be released) - _Future release notes will be placed here_ +## libtmux 0.45.0 (2025-02-23) + +### Breaking Changes + +#### Test helpers: Refactor + +Test helper functionality has been split into focused modules (#578): + +- `libtmux.test` module split into: + - `libtmux.test.constants`: Test-related constants (`TEST_SESSION_PREFIX`, etc.) + - `libtmux.test.environment`: Environment variable mocking + - `libtmux.test.random`: Random string generation utilities + - `libtmux.test.temporary`: Temporary session/window management + +**Breaking**: Import paths have changed. Update imports: + +```python +# Old (0.44.x and earlier) +from libtmux.test import ( + TEST_SESSION_PREFIX, + get_test_session_name, + get_test_window_name, + namer, + temp_session, + temp_window, + EnvironmentVarGuard, +) +``` + +```python +# New (0.45.0+) +from libtmux.test.constants import TEST_SESSION_PREFIX +from libtmux.test.environment import EnvironmentVarGuard +from libtmux.test.random import get_test_session_name, get_test_window_name, namer +from libtmux.test.temporary import temp_session, temp_window +``` + +### Development + +- CI: Check for runtime dependencies (#574) + + Kudos @ppentchev for inspiration on the command + ([comment](https://github.com/tmux-python/libtmux/pull/572#issuecomment-2663642923)). + ## libtmux 0.44.2 (2025-02-17) ### Bug fix diff --git a/MIGRATION b/MIGRATION index 2c88341b9..e3b097e50 100644 --- a/MIGRATION +++ b/MIGRATION @@ -25,6 +25,41 @@ _Detailed migration steps for the next version will be posted here._ +## libtmux 0.45.0 (2025-02-23) + +### Test helpers: Module moves + +Test helper functionality has been split into focused modules (#578): + +- `libtmux.test` module split into: + - `libtmux.test.constants`: Test-related constants (`TEST_SESSION_PREFIX`, etc.) + - `libtmux.test.environment`: Environment variable mocking + - `libtmux.test.random`: Random string generation utilities + - `libtmux.test.temporary`: Temporary session/window management + +**Breaking**: Import paths have changed. Update imports: + +```python +# Old (0.44.x and earlier) +from libtmux.test import ( + TEST_SESSION_PREFIX, + get_test_session_name, + get_test_window_name, + namer, + temp_session, + temp_window, + EnvironmentVarGuard, +) +``` + +```python +# New (0.45.0+) +from libtmux.test.constants import TEST_SESSION_PREFIX +from libtmux.test.environment import EnvironmentVarGuard +from libtmux.test.random import get_test_session_name, get_test_window_name, namer +from libtmux.test.temporary import temp_session, temp_window +``` + ## 0.35.0: Commands require explicit targets (2024-03-17) ### Commands require explicit targets (#535) @@ -64,13 +99,11 @@ _Detailed migration steps for the next version will be posted here._ ### Renamings (#527) - `Session.attached_window` renamed to {meth}`Session.active_window` - - `Session.attached_window` deprecated + - `Session.attached_window` deprecated - `Session.attached_pane` renamed to {meth}`Session.active_pane` - - `Session.attached_pane` deprecated + - `Session.attached_pane` deprecated - `Window.attached_pane` renamed to {meth}`Window.active_pane` - - `Window.attached_pane` deprecated - - + - `Window.attached_pane` deprecated ## 0.28.0: Resizing and detached by default (2024-02-15) @@ -88,6 +121,7 @@ _Detailed migration steps for the next version will be posted here._ - `Pane.resize_pane()` renamed to {meth}`Pane.resize()` (via #523) This convention will be more consistent with {meth}`Window.resize()`. + - {meth}`Pane.resize_pane()`'s params changed (#523) - No longer accepts `-U`, `-D`, `-L`, `-R` directly, instead accepts @@ -129,9 +163,11 @@ _Detailed migration steps for the next version will be posted here._ - 0.16 and below: `window['id']` 0.17 and after: `window.id` + - 0.16 and below: `window.get('id')` 0.17 and after: `window.id` + - 0.16 and below: `window.get('id', None)` 0.17 and after: `getattr(window, 'id', None)` diff --git a/docs/index.md b/docs/index.md index 390a2a46d..76c4796b6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,6 +20,7 @@ about topics/index api/index pytest-plugin/index +test-helpers/index ``` ```{toctree} diff --git a/docs/pytest-plugin/index.md b/docs/pytest-plugin/index.md index cdcdf5a82..8f8dca41d 100644 --- a/docs/pytest-plugin/index.md +++ b/docs/pytest-plugin/index.md @@ -147,11 +147,3 @@ def set_home( :show-inheritance: :member-order: bysource ``` - -## Test utilities - -```{toctree} -:maxdepth: 1 - -test -``` diff --git a/docs/pytest-plugin/test.md b/docs/pytest-plugin/test.md deleted file mode 100644 index 8fbff818b..000000000 --- a/docs/pytest-plugin/test.md +++ /dev/null @@ -1,6 +0,0 @@ -# Test helpers - -```{eval-rst} -.. automodule:: libtmux.test - :members: -``` diff --git a/docs/redirects.txt b/docs/redirects.txt index 1f20db7c0..afff787ad 100644 --- a/docs/redirects.txt +++ b/docs/redirects.txt @@ -15,3 +15,4 @@ "reference/servers.md" "api/servers.md" "reference/sessions.md" "api/sessions.md" "reference/windows.md" "api/windows.md" +"pytest-plugin/test.md" "test-helpers/index.md" diff --git a/docs/test-helpers/constants.md b/docs/test-helpers/constants.md new file mode 100644 index 000000000..facbfb871 --- /dev/null +++ b/docs/test-helpers/constants.md @@ -0,0 +1,10 @@ +# Constants + +Test-related constants used across libtmux test helpers. + +```{eval-rst} +.. automodule:: libtmux.test.constants + :members: + :undoc-members: + :show-inheritance: +``` \ No newline at end of file diff --git a/docs/test-helpers/environment.md b/docs/test-helpers/environment.md new file mode 100644 index 000000000..e385193a6 --- /dev/null +++ b/docs/test-helpers/environment.md @@ -0,0 +1,10 @@ +# Environment + +Environment variable mocking utilities for tests. + +```{eval-rst} +.. automodule:: libtmux.test.environment + :members: + :undoc-members: + :show-inheritance: +``` \ No newline at end of file diff --git a/docs/test-helpers/index.md b/docs/test-helpers/index.md new file mode 100644 index 000000000..b27fa8d3e --- /dev/null +++ b/docs/test-helpers/index.md @@ -0,0 +1,17 @@ +# Test helpers + +Test helpers for libtmux and downstream libraries. + +```{toctree} +:maxdepth: 2 + +constants +environment +random +temporary +``` + +```{eval-rst} +.. automodule:: libtmux.test + :members: +``` \ No newline at end of file diff --git a/docs/test-helpers/random.md b/docs/test-helpers/random.md new file mode 100644 index 000000000..2222a6cee --- /dev/null +++ b/docs/test-helpers/random.md @@ -0,0 +1,10 @@ +# Random + +Random string generation utilities for test names. + +```{eval-rst} +.. automodule:: libtmux.test.random + :members: + :undoc-members: + :show-inheritance: +``` \ No newline at end of file diff --git a/docs/test-helpers/temporary.md b/docs/test-helpers/temporary.md new file mode 100644 index 000000000..f1ee07b2f --- /dev/null +++ b/docs/test-helpers/temporary.md @@ -0,0 +1,10 @@ +# Temporary Objects + +Context managers for temporary tmux objects (sessions, windows). + +```{eval-rst} +.. automodule:: libtmux.test.temporary + :members: + :undoc-members: + :show-inheritance: +``` \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index a1752d1c2..f3dc1aa14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "libtmux" -version = "0.44.2" +version = "0.45.0" description = "Typed library that provides an ORM wrapper for tmux, a terminal multiplexer." requires-python = ">=3.9,<4.0" authors = [ @@ -134,6 +134,8 @@ parallel = true omit = [ "*/_compat.py", "docs/conf.py", + "tests/test_*.py", + "tests/*/test_*.py", ] [tool.coverage.report] @@ -195,6 +197,7 @@ required-imports = [ [tool.ruff.lint.flake8-builtins] builtins-allowed-modules = [ "dataclasses", + "random", "types", ] diff --git a/src/libtmux/__about__.py b/src/libtmux/__about__.py index 9f27b720e..4de25b74b 100644 --- a/src/libtmux/__about__.py +++ b/src/libtmux/__about__.py @@ -4,7 +4,7 @@ __title__ = "libtmux" __package_name__ = "libtmux" -__version__ = "0.44.2" +__version__ = "0.45.0" __description__ = "Typed scripting library / ORM / API wrapper for tmux" __email__ = "tony@git-pull.com" __author__ = "Tony Narlock" diff --git a/src/libtmux/pytest_plugin.py b/src/libtmux/pytest_plugin.py index 320d31ca2..92da4676d 100644 --- a/src/libtmux/pytest_plugin.py +++ b/src/libtmux/pytest_plugin.py @@ -13,7 +13,8 @@ from libtmux import exc from libtmux.server import Server -from libtmux.test import TEST_SESSION_PREFIX, get_test_session_name, namer +from libtmux.test.constants import TEST_SESSION_PREFIX +from libtmux.test.random import get_test_session_name, namer if t.TYPE_CHECKING: import pathlib diff --git a/src/libtmux/test.py b/src/libtmux/test.py deleted file mode 100644 index 2bbaa2e53..000000000 --- a/src/libtmux/test.py +++ /dev/null @@ -1,356 +0,0 @@ -"""Helper methods for libtmux and downstream libtmux libraries.""" - -from __future__ import annotations - -import contextlib -import logging -import os -import pathlib -import random -import time -import typing as t - -from .exc import WaitTimeout - -logger = logging.getLogger(__name__) - -if t.TYPE_CHECKING: - import sys - import types - from collections.abc import Callable, Generator - - from libtmux.server import Server - from libtmux.session import Session - from libtmux.window import Window - - if sys.version_info >= (3, 11): - from typing import Self - else: - from typing_extensions import Self - - -TEST_SESSION_PREFIX = "libtmux_" -RETRY_TIMEOUT_SECONDS = int(os.getenv("RETRY_TIMEOUT_SECONDS", 8)) -RETRY_INTERVAL_SECONDS = float(os.getenv("RETRY_INTERVAL_SECONDS", 0.05)) - - -class RandomStrSequence: - """Factory to generate random string.""" - - def __init__( - self, - characters: str = "abcdefghijklmnopqrstuvwxyz0123456789_", - ) -> None: - """Create a random letter / number generator. 8 chars in length. - - >>> rng = RandomStrSequence() - >>> next(rng) - '...' - >>> len(next(rng)) - 8 - >>> type(next(rng)) - - """ - self.characters: str = characters - - def __iter__(self) -> RandomStrSequence: - """Return self.""" - return self - - def __next__(self) -> str: - """Return next random string.""" - return "".join(random.sample(self.characters, k=8)) - - -namer = RandomStrSequence() -current_dir = pathlib.Path(__file__) -example_dir = current_dir.parent / "examples" -fixtures_dir = current_dir / "fixtures" - - -def retry_until( - fun: Callable[[], bool], - seconds: float = RETRY_TIMEOUT_SECONDS, - *, - interval: float = RETRY_INTERVAL_SECONDS, - raises: bool | None = True, -) -> bool: - """ - Retry a function until a condition meets or the specified time passes. - - Parameters - ---------- - fun : callable - A function that will be called repeatedly until it returns ``True`` or - the specified time passes. - seconds : float - Seconds to retry. Defaults to ``8``, which is configurable via - ``RETRY_TIMEOUT_SECONDS`` environment variables. - interval : float - Time in seconds to wait between calls. Defaults to ``0.05`` and is - configurable via ``RETRY_INTERVAL_SECONDS`` environment variable. - raises : bool - Whether or not to raise an exception on timeout. Defaults to ``True``. - - Examples - -------- - >>> def fn(): - ... p = session.active_window.active_pane - ... return p.pane_current_path is not None - - >>> retry_until(fn) - True - - In pytest: - - >>> assert retry_until(fn, raises=False) - """ - ini = time.time() - - while not fun(): - end = time.time() - if end - ini >= seconds: - if raises: - raise WaitTimeout - return False - time.sleep(interval) - return True - - -def get_test_session_name(server: Server, prefix: str = TEST_SESSION_PREFIX) -> str: - """ - Faker to create a session name that doesn't exist. - - Parameters - ---------- - server : :class:`libtmux.Server` - libtmux server - prefix : str - prefix for sessions (e.g. ``libtmux_``). Defaults to - ``TEST_SESSION_PREFIX``. - - Returns - ------- - str - Random session name guaranteed to not collide with current ones. - - Examples - -------- - >>> get_test_session_name(server=server) - 'libtmux_...' - - Never the same twice: - >>> get_test_session_name(server=server) != get_test_session_name(server=server) - True - """ - while True: - session_name = prefix + next(namer) - if not server.has_session(session_name): - break - return session_name - - -def get_test_window_name( - session: Session, - prefix: str | None = TEST_SESSION_PREFIX, -) -> str: - """ - Faker to create a window name that doesn't exist. - - Parameters - ---------- - session : :class:`libtmux.Session` - libtmux session - prefix : str - prefix for windows (e.g. ``libtmux_``). Defaults to - ``TEST_SESSION_PREFIX``. - - ATM we reuse the test session prefix here. - - Returns - ------- - str - Random window name guaranteed to not collide with current ones. - - Examples - -------- - >>> get_test_window_name(session=session) - 'libtmux_...' - - Never the same twice: - >>> get_test_window_name(session=session) != get_test_window_name(session=session) - True - """ - assert prefix is not None - while True: - window_name = prefix + next(namer) - if len(session.windows.filter(window_name=window_name)) == 0: - break - return window_name - - -@contextlib.contextmanager -def temp_session( - server: Server, - *args: t.Any, - **kwargs: t.Any, -) -> Generator[Session, t.Any, t.Any]: - """ - Return a context manager with a temporary session. - - If no ``session_name`` is entered, :func:`get_test_session_name` will make - an unused session name. - - The session will destroy itself upon closing with :meth:`Session.session()`. - - Parameters - ---------- - server : :class:`libtmux.Server` - - Other Parameters - ---------------- - args : list - Arguments passed into :meth:`Server.new_session` - kwargs : dict - Keyword arguments passed into :meth:`Server.new_session` - - Yields - ------ - :class:`libtmux.Session` - Temporary session - - Examples - -------- - >>> with temp_session(server) as session: - ... session.new_window(window_name='my window') - Window(@3 2:my window, Session($... ...)) - """ - if "session_name" in kwargs: - session_name = kwargs.pop("session_name") - else: - session_name = get_test_session_name(server) - - session = server.new_session(session_name, *args, **kwargs) - - try: - yield session - finally: - if server.has_session(session_name): - session.kill() - return - - -@contextlib.contextmanager -def temp_window( - session: Session, - *args: t.Any, - **kwargs: t.Any, -) -> Generator[Window, t.Any, t.Any]: - """ - Return a context manager with a temporary window. - - The window will destroy itself upon closing with :meth:`window. - kill()`. - - If no ``window_name`` is entered, :func:`get_test_window_name` will make - an unused window name. - - Parameters - ---------- - session : :class:`libtmux.Session` - - Other Parameters - ---------------- - args : list - Arguments passed into :meth:`Session.new_window` - kwargs : dict - Keyword arguments passed into :meth:`Session.new_window` - - Yields - ------ - :class:`libtmux.Window` - temporary window - - Examples - -------- - >>> with temp_window(session) as window: - ... window - Window(@2 2:... Session($1 libtmux_...)) - - - >>> with temp_window(session) as window: - ... window.split() - Pane(%4 Window(@3 2:libtmux_..., Session($1 libtmux_...))) - """ - if "window_name" not in kwargs: - window_name = get_test_window_name(session) - else: - window_name = kwargs.pop("window_name") - - window = session.new_window(window_name, *args, **kwargs) - - # Get ``window_id`` before returning it, it may be killed within context. - window_id = window.window_id - assert window_id is not None - assert isinstance(window_id, str) - - try: - yield window - finally: - if len(session.windows.filter(window_id=window_id)) > 0: - window.kill() - return - - -class EnvironmentVarGuard: - """Mock environmental variables safely. - - Helps rotect the environment variable properly. Can be used as context - manager. - - Notes - ----- - Vendorized to fix issue with Anaconda Python 2 not including test module, - see #121 [1]_ - - References - ---------- - .. [1] Just installed, "ImportError: cannot import name test_support". - GitHub issue for tmuxp. https://github.com/tmux-python/tmuxp/issues/121. - Created October 12th, 2015. Accessed April 7th, 2018. - """ - - def __init__(self) -> None: - self._environ = os.environ - self._unset: set[str] = set() - self._reset: dict[str, str] = {} - - def set(self, envvar: str, value: str) -> None: - """Set environment variable.""" - if envvar not in self._environ: - self._unset.add(envvar) - else: - self._reset[envvar] = self._environ[envvar] - self._environ[envvar] = value - - def unset(self, envvar: str) -> None: - """Unset environment variable.""" - if envvar in self._environ: - self._reset[envvar] = self._environ[envvar] - del self._environ[envvar] - - def __enter__(self) -> Self: - """Return context for for context manager.""" - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_value: BaseException | None, - exc_tb: types.TracebackType | None, - ) -> None: - """Cleanup to run after context manager finishes.""" - for envvar, value in self._reset.items(): - self._environ[envvar] = value - for unset in self._unset: - del self._environ[unset] diff --git a/src/libtmux/test/__init__.py b/src/libtmux/test/__init__.py new file mode 100644 index 000000000..8fba9fa82 --- /dev/null +++ b/src/libtmux/test/__init__.py @@ -0,0 +1,36 @@ +"""Helper methods for libtmux and downstream libtmux libraries.""" + +from __future__ import annotations + +import contextlib +import logging +import os +import pathlib +import random +import time +import typing as t + +from libtmux.exc import WaitTimeout +from libtmux.test.constants import ( + RETRY_INTERVAL_SECONDS, + RETRY_TIMEOUT_SECONDS, + TEST_SESSION_PREFIX, +) + +from .random import namer + +logger = logging.getLogger(__name__) + +if t.TYPE_CHECKING: + import sys + import types + from collections.abc import Callable, Generator + + from libtmux.server import Server + from libtmux.session import Session + from libtmux.window import Window + + if sys.version_info >= (3, 11): + from typing import Self + else: + from typing_extensions import Self diff --git a/src/libtmux/test/constants.py b/src/libtmux/test/constants.py new file mode 100644 index 000000000..63d644da3 --- /dev/null +++ b/src/libtmux/test/constants.py @@ -0,0 +1,9 @@ +"""Constants for libtmux test helpers.""" + +from __future__ import annotations + +import os + +TEST_SESSION_PREFIX = "libtmux_" +RETRY_TIMEOUT_SECONDS = int(os.getenv("RETRY_TIMEOUT_SECONDS", 8)) +RETRY_INTERVAL_SECONDS = float(os.getenv("RETRY_INTERVAL_SECONDS", 0.05)) diff --git a/src/libtmux/test/environment.py b/src/libtmux/test/environment.py new file mode 100644 index 000000000..9023c1f83 --- /dev/null +++ b/src/libtmux/test/environment.py @@ -0,0 +1,72 @@ +"""Helper methods for libtmux and downstream libtmux libraries.""" + +from __future__ import annotations + +import logging +import os +import typing as t + +logger = logging.getLogger(__name__) + +if t.TYPE_CHECKING: + import sys + import types + + if sys.version_info >= (3, 11): + from typing import Self + else: + from typing_extensions import Self + + +class EnvironmentVarGuard: + """Mock environmental variables safely. + + Helps rotect the environment variable properly. Can be used as context + manager. + + Notes + ----- + Vendorized to fix issue with Anaconda Python 2 not including test module, + see #121 [1]_ + + References + ---------- + .. [1] Just installed, "ImportError: cannot import name test_support". + GitHub issue for tmuxp. https://github.com/tmux-python/tmuxp/issues/121. + Created October 12th, 2015. Accessed April 7th, 2018. + """ + + def __init__(self) -> None: + self._environ = os.environ + self._unset: set[str] = set() + self._reset: dict[str, str] = {} + + def set(self, envvar: str, value: str) -> None: + """Set environment variable.""" + if envvar not in self._environ: + self._unset.add(envvar) + else: + self._reset[envvar] = self._environ[envvar] + self._environ[envvar] = value + + def unset(self, envvar: str) -> None: + """Unset environment variable.""" + if envvar in self._environ: + self._reset[envvar] = self._environ[envvar] + del self._environ[envvar] + + def __enter__(self) -> Self: + """Return context for for context manager.""" + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> None: + """Cleanup to run after context manager finishes.""" + for envvar, value in self._reset.items(): + self._environ[envvar] = value + for unset in self._unset: + del self._environ[unset] diff --git a/src/libtmux/test/random.py b/src/libtmux/test/random.py new file mode 100644 index 000000000..abcb95bce --- /dev/null +++ b/src/libtmux/test/random.py @@ -0,0 +1,134 @@ +"""Random helpers for libtmux and downstream libtmux libraries.""" + +from __future__ import annotations + +import logging +import random +import typing as t + +from libtmux.test.constants import ( + TEST_SESSION_PREFIX, +) + +logger = logging.getLogger(__name__) + +if t.TYPE_CHECKING: + import sys + + from libtmux.server import Server + from libtmux.session import Session + + if sys.version_info >= (3, 11): + pass + + +logger = logging.getLogger(__name__) + +if t.TYPE_CHECKING: + import sys + + if sys.version_info >= (3, 11): + pass + + +class RandomStrSequence: + """Factory to generate random string.""" + + def __init__( + self, + characters: str = "abcdefghijklmnopqrstuvwxyz0123456789_", + ) -> None: + """Create a random letter / number generator. 8 chars in length. + + >>> rng = RandomStrSequence() + >>> next(rng) + '...' + >>> len(next(rng)) + 8 + >>> type(next(rng)) + + """ + self.characters: str = characters + + def __iter__(self) -> RandomStrSequence: + """Return self.""" + return self + + def __next__(self) -> str: + """Return next random string.""" + return "".join(random.sample(self.characters, k=8)) + + +namer = RandomStrSequence() + + +def get_test_session_name(server: Server, prefix: str = TEST_SESSION_PREFIX) -> str: + """ + Faker to create a session name that doesn't exist. + + Parameters + ---------- + server : :class:`libtmux.Server` + libtmux server + prefix : str + prefix for sessions (e.g. ``libtmux_``). Defaults to + ``TEST_SESSION_PREFIX``. + + Returns + ------- + str + Random session name guaranteed to not collide with current ones. + + Examples + -------- + >>> get_test_session_name(server=server) + 'libtmux_...' + + Never the same twice: + >>> get_test_session_name(server=server) != get_test_session_name(server=server) + True + """ + while True: + session_name = prefix + next(namer) + if not server.has_session(session_name): + break + return session_name + + +def get_test_window_name( + session: Session, + prefix: str | None = TEST_SESSION_PREFIX, +) -> str: + """ + Faker to create a window name that doesn't exist. + + Parameters + ---------- + session : :class:`libtmux.Session` + libtmux session + prefix : str + prefix for windows (e.g. ``libtmux_``). Defaults to + ``TEST_SESSION_PREFIX``. + + ATM we reuse the test session prefix here. + + Returns + ------- + str + Random window name guaranteed to not collide with current ones. + + Examples + -------- + >>> get_test_window_name(session=session) + 'libtmux_...' + + Never the same twice: + >>> get_test_window_name(session=session) != get_test_window_name(session=session) + True + """ + assert prefix is not None + while True: + window_name = prefix + next(namer) + if len(session.windows.filter(window_name=window_name)) == 0: + break + return window_name diff --git a/src/libtmux/test/retry.py b/src/libtmux/test/retry.py new file mode 100644 index 000000000..1f989e73a --- /dev/null +++ b/src/libtmux/test/retry.py @@ -0,0 +1,71 @@ +"""Retry helpers for libtmux and downstream libtmux libraries.""" + +from __future__ import annotations + +import logging +import time +import typing as t + +from libtmux.exc import WaitTimeout +from libtmux.test.constants import ( + RETRY_INTERVAL_SECONDS, + RETRY_TIMEOUT_SECONDS, +) + +logger = logging.getLogger(__name__) + +if t.TYPE_CHECKING: + import sys + from collections.abc import Callable + + if sys.version_info >= (3, 11): + pass + + +def retry_until( + fun: Callable[[], bool], + seconds: float = RETRY_TIMEOUT_SECONDS, + *, + interval: float = RETRY_INTERVAL_SECONDS, + raises: bool | None = True, +) -> bool: + """ + Retry a function until a condition meets or the specified time passes. + + Parameters + ---------- + fun : callable + A function that will be called repeatedly until it returns ``True`` or + the specified time passes. + seconds : float + Seconds to retry. Defaults to ``8``, which is configurable via + ``RETRY_TIMEOUT_SECONDS`` environment variables. + interval : float + Time in seconds to wait between calls. Defaults to ``0.05`` and is + configurable via ``RETRY_INTERVAL_SECONDS`` environment variable. + raises : bool + Whether or not to raise an exception on timeout. Defaults to ``True``. + + Examples + -------- + >>> def fn(): + ... p = session.active_window.active_pane + ... return p.pane_current_path is not None + + >>> retry_until(fn) + True + + In pytest: + + >>> assert retry_until(fn, raises=False) + """ + ini = time.time() + + while not fun(): + end = time.time() + if end - ini >= seconds: + if raises: + raise WaitTimeout + return False + time.sleep(interval) + return True diff --git a/src/libtmux/test/temporary.py b/src/libtmux/test/temporary.py new file mode 100644 index 000000000..cc2106edd --- /dev/null +++ b/src/libtmux/test/temporary.py @@ -0,0 +1,135 @@ +"""Temporary object helpers for libtmux and downstream libtmux libraries.""" + +from __future__ import annotations + +import contextlib +import logging +import typing as t + +from libtmux.test.random import get_test_session_name, get_test_window_name + +logger = logging.getLogger(__name__) + +if t.TYPE_CHECKING: + import sys + from collections.abc import Generator + + from libtmux.server import Server + from libtmux.session import Session + from libtmux.window import Window + + if sys.version_info >= (3, 11): + pass + + +@contextlib.contextmanager +def temp_session( + server: Server, + *args: t.Any, + **kwargs: t.Any, +) -> Generator[Session, t.Any, t.Any]: + """ + Return a context manager with a temporary session. + + If no ``session_name`` is entered, :func:`get_test_session_name` will make + an unused session name. + + The session will destroy itself upon closing with :meth:`Session.session()`. + + Parameters + ---------- + server : :class:`libtmux.Server` + + Other Parameters + ---------------- + args : list + Arguments passed into :meth:`Server.new_session` + kwargs : dict + Keyword arguments passed into :meth:`Server.new_session` + + Yields + ------ + :class:`libtmux.Session` + Temporary session + + Examples + -------- + >>> with temp_session(server) as session: + ... session.new_window(window_name='my window') + Window(@3 2:my window, Session($... ...)) + """ + if "session_name" in kwargs: + session_name = kwargs.pop("session_name") + else: + session_name = get_test_session_name(server) + + session = server.new_session(session_name, *args, **kwargs) + + try: + yield session + finally: + if server.has_session(session_name): + session.kill() + return + + +@contextlib.contextmanager +def temp_window( + session: Session, + *args: t.Any, + **kwargs: t.Any, +) -> Generator[Window, t.Any, t.Any]: + """ + Return a context manager with a temporary window. + + The window will destroy itself upon closing with :meth:`window. + kill()`. + + If no ``window_name`` is entered, :func:`get_test_window_name` will make + an unused window name. + + Parameters + ---------- + session : :class:`libtmux.Session` + + Other Parameters + ---------------- + args : list + Arguments passed into :meth:`Session.new_window` + kwargs : dict + Keyword arguments passed into :meth:`Session.new_window` + + Yields + ------ + :class:`libtmux.Window` + temporary window + + Examples + -------- + >>> with temp_window(session) as window: + ... window + Window(@2 2:... Session($1 libtmux_...)) + + + >>> with temp_window(session) as window: + ... window.split() + Pane(%4 Window(@3 2:libtmux_..., Session($1 libtmux_...))) + """ + if "window_name" not in kwargs: + window_name = get_test_window_name(session) + else: + window_name = kwargs.pop("window_name") + + window = session.new_window(window_name, *args, **kwargs) + + # Get ``window_id`` before returning it, it may be killed within context. + window_id = window.window_id + assert window_id is not None + assert isinstance(window_id, str) + + try: + yield window + finally: + if len(session.windows.filter(window_id=window_id)) > 0: + window.kill() + return diff --git a/tests/legacy_api/test_session.py b/tests/legacy_api/test_session.py index ffe115ae2..c756999ea 100644 --- a/tests/legacy_api/test_session.py +++ b/tests/legacy_api/test_session.py @@ -12,7 +12,8 @@ from libtmux.common import has_gte_version, has_lt_version from libtmux.pane import Pane from libtmux.session import Session -from libtmux.test import TEST_SESSION_PREFIX, namer +from libtmux.test.constants import TEST_SESSION_PREFIX +from libtmux.test.random import namer from libtmux.window import Window if t.TYPE_CHECKING: diff --git a/tests/legacy_api/test_tmuxobject.py b/tests/legacy_api/test_tmuxobject.py index 790b8cebb..dc023d24b 100644 --- a/tests/legacy_api/test_tmuxobject.py +++ b/tests/legacy_api/test_tmuxobject.py @@ -7,7 +7,8 @@ from libtmux.pane import Pane from libtmux.session import Session -from libtmux.test import TEST_SESSION_PREFIX, namer +from libtmux.test.constants import TEST_SESSION_PREFIX +from libtmux.test.random import namer from libtmux.window import Window if t.TYPE_CHECKING: diff --git a/tests/test_test.py b/tests/test/test_retry.py similarity index 97% rename from tests/test_test.py rename to tests/test/test_retry.py index a9471363c..36e35930d 100644 --- a/tests/test_test.py +++ b/tests/test/test_retry.py @@ -7,7 +7,7 @@ import pytest from libtmux.exc import WaitTimeout -from libtmux.test import retry_until +from libtmux.test.retry import retry_until def test_retry_three_times() -> None: diff --git a/tests/test_pane.py b/tests/test_pane.py index 21d0cbb87..746467851 100644 --- a/tests/test_pane.py +++ b/tests/test_pane.py @@ -10,7 +10,7 @@ from libtmux.common import has_gte_version, has_lt_version, has_lte_version from libtmux.constants import PaneDirection, ResizeAdjustmentDirection -from libtmux.test import retry_until +from libtmux.test.retry import retry_until if t.TYPE_CHECKING: from libtmux.session import Session diff --git a/tests/test_session.py b/tests/test_session.py index 33b82dd72..88d5f79e6 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -13,7 +13,8 @@ from libtmux.constants import WindowDirection from libtmux.pane import Pane from libtmux.session import Session -from libtmux.test import TEST_SESSION_PREFIX, namer +from libtmux.test.constants import TEST_SESSION_PREFIX +from libtmux.test.random import namer from libtmux.window import Window if t.TYPE_CHECKING: diff --git a/tests/test_tmuxobject.py b/tests/test_tmuxobject.py index 5c77e29e4..e8c8be9ec 100644 --- a/tests/test_tmuxobject.py +++ b/tests/test_tmuxobject.py @@ -7,7 +7,8 @@ from libtmux.pane import Pane from libtmux.session import Session -from libtmux.test import TEST_SESSION_PREFIX, namer +from libtmux.test.constants import TEST_SESSION_PREFIX +from libtmux.test.random import namer from libtmux.window import Window if t.TYPE_CHECKING: diff --git a/uv.lock b/uv.lock index 10fc21123..eb2387c12 100644 --- a/uv.lock +++ b/uv.lock @@ -2,7 +2,8 @@ version = 1 revision = 1 requires-python = ">=3.9, <4.0" resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", "python_full_version < '3.10'", ] @@ -23,7 +24,8 @@ name = "alabaster" version = "1.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210 } wheels = [ @@ -294,7 +296,8 @@ dependencies = [ { name = "beautifulsoup4" }, { name = "pygments" }, { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-basic-ng" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a0/e2/d351d69a9a9e4badb4a5be062c2d0e87bd9e6c23b5e57337fef14bef34c8/furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01", size = 1661506 } @@ -304,16 +307,16 @@ wheels = [ [[package]] name = "gp-libs" -version = "0.0.10" +version = "0.0.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, { name = "myst-parser", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/27/8f/02465fe771a79649ec3b01f13cbd5cc980438a7c59e5c8c6a0203cc5706e/gp_libs-0.0.10.tar.gz", hash = "sha256:83e5bf7107465fdba10369e63de90eed0577b5c80c0f21044499f2e703ba225c", size = 14497 } +sdist = { url = "https://files.pythonhosted.org/packages/a6/e2/262a8af11c5a4435506822cf63662365a9ea40de110cdb76fa8ec00ea64b/gp_libs-0.0.11.tar.gz", hash = "sha256:0a5c43f47c9d459bd9ba80cacecaede2f47a17b6821e4f222b52513158b7e429", size = 14712 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/33/80adaf9da81532a7b3ee45fe0eaaa9ed96fb25eddaae75267406dd24f49d/gp_libs-0.0.10-py3-none-any.whl", hash = "sha256:0ad6cfdddc0974454456fb1bcaec55212f1b95fe6c2c4383ac4665fb47541094", size = 15565 }, + { url = "https://files.pythonhosted.org/packages/bd/6d/e91e25fbe57cb2e09bbb4f89a113a9e96dacc7a74fb42f4a25cfab55aaba/gp_libs-0.0.11-py3-none-any.whl", hash = "sha256:c7699af49907d5c7a4cb96093c9f6e192e566abbfd5dbc6d3f58bbac2ac731b3", size = 15699 }, ] [[package]] @@ -378,7 +381,7 @@ wheels = [ [[package]] name = "libtmux" -version = "0.44.2" +version = "0.45.0" source = { editable = "." } [package.dev-dependencies] @@ -404,10 +407,12 @@ dev = [ { name = "pytest-xdist" }, { name = "ruff" }, { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-autobuild" }, { name = "sphinx-autodoc-typehints", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx-autodoc-typehints", version = "3.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-copybutton" }, { name = "sphinx-inline-tabs" }, { name = "sphinxext-opengraph" }, @@ -421,10 +426,12 @@ docs = [ { name = "myst-parser", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-autobuild" }, { name = "sphinx-autodoc-typehints", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx-autodoc-typehints", version = "3.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-copybutton" }, { name = "sphinx-inline-tabs" }, { name = "sphinxext-opengraph" }, @@ -694,7 +701,8 @@ name = "myst-parser" version = "4.0.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", ] dependencies = [ { name = "docutils", marker = "python_full_version >= '3.10'" }, @@ -702,7 +710,8 @@ dependencies = [ { name = "markdown-it-py", marker = "python_full_version >= '3.10'" }, { name = "mdit-py-plugins", marker = "python_full_version >= '3.10'" }, { name = "pyyaml", marker = "python_full_version >= '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985 } wheels = [ @@ -885,29 +894,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742 }, +] + [[package]] name = "ruff" -version = "0.9.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/e1/e265aba384343dd8ddd3083f5e33536cd17e1566c41453a5517b5dd443be/ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9", size = 3639454 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/e3/3d2c022e687e18cf5d93d6bfa2722d46afc64eaa438c7fbbdd603b3597be/ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba", size = 11714128 }, - { url = "https://files.pythonhosted.org/packages/e1/22/aff073b70f95c052e5c58153cba735748c9e70107a77d03420d7850710a0/ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504", size = 11682539 }, - { url = "https://files.pythonhosted.org/packages/75/a7/f5b7390afd98a7918582a3d256cd3e78ba0a26165a467c1820084587cbf9/ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83", size = 11132512 }, - { url = "https://files.pythonhosted.org/packages/a6/e3/45de13ef65047fea2e33f7e573d848206e15c715e5cd56095589a7733d04/ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc", size = 11929275 }, - { url = "https://files.pythonhosted.org/packages/7d/f2/23d04cd6c43b2e641ab961ade8d0b5edb212ecebd112506188c91f2a6e6c/ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b", size = 11466502 }, - { url = "https://files.pythonhosted.org/packages/b5/6f/3a8cf166f2d7f1627dd2201e6cbc4cb81f8b7d58099348f0c1ff7b733792/ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e", size = 12676364 }, - { url = "https://files.pythonhosted.org/packages/f5/c4/db52e2189983c70114ff2b7e3997e48c8318af44fe83e1ce9517570a50c6/ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666", size = 13335518 }, - { url = "https://files.pythonhosted.org/packages/66/44/545f8a4d136830f08f4d24324e7db957c5374bf3a3f7a6c0bc7be4623a37/ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5", size = 12823287 }, - { url = "https://files.pythonhosted.org/packages/c5/26/8208ef9ee7431032c143649a9967c3ae1aae4257d95e6f8519f07309aa66/ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5", size = 14592374 }, - { url = "https://files.pythonhosted.org/packages/31/70/e917781e55ff39c5b5208bda384fd397ffd76605e68544d71a7e40944945/ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217", size = 12500173 }, - { url = "https://files.pythonhosted.org/packages/84/f5/e4ddee07660f5a9622a9c2b639afd8f3104988dc4f6ba0b73ffacffa9a8c/ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6", size = 11906555 }, - { url = "https://files.pythonhosted.org/packages/f1/2b/6ff2fe383667075eef8656b9892e73dd9b119b5e3add51298628b87f6429/ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897", size = 11538958 }, - { url = "https://files.pythonhosted.org/packages/3c/db/98e59e90de45d1eb46649151c10a062d5707b5b7f76f64eb1e29edf6ebb1/ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08", size = 12117247 }, - { url = "https://files.pythonhosted.org/packages/ec/bc/54e38f6d219013a9204a5a2015c09e7a8c36cedcd50a4b01ac69a550b9d9/ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656", size = 12554647 }, - { url = "https://files.pythonhosted.org/packages/a5/7d/7b461ab0e2404293c0627125bb70ac642c2e8d55bf590f6fce85f508f1b2/ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d", size = 9949214 }, - { url = "https://files.pythonhosted.org/packages/ee/30/c3cee10f915ed75a5c29c1e57311282d1a15855551a64795c1b2bbe5cf37/ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa", size = 10999914 }, - { url = "https://files.pythonhosted.org/packages/e8/a8/d71f44b93e3aa86ae232af1f2126ca7b95c0f515ec135462b3e1f351441c/ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a", size = 10177499 }, +version = "0.9.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/8b/a86c300359861b186f18359adf4437ac8e4c52e42daa9eedc731ef9d5b53/ruff-0.9.7.tar.gz", hash = "sha256:643757633417907510157b206e490c3aa11cab0c087c912f60e07fbafa87a4c6", size = 3669813 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/f3/3a1d22973291226df4b4e2ff70196b926b6f910c488479adb0eeb42a0d7f/ruff-0.9.7-py3-none-linux_armv6l.whl", hash = "sha256:99d50def47305fe6f233eb8dabfd60047578ca87c9dcb235c9723ab1175180f4", size = 11774588 }, + { url = "https://files.pythonhosted.org/packages/8e/c9/b881f4157b9b884f2994fd08ee92ae3663fb24e34b0372ac3af999aa7fc6/ruff-0.9.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d59105ae9c44152c3d40a9c40d6331a7acd1cdf5ef404fbe31178a77b174ea66", size = 11746848 }, + { url = "https://files.pythonhosted.org/packages/14/89/2f546c133f73886ed50a3d449e6bf4af27d92d2f960a43a93d89353f0945/ruff-0.9.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f313b5800483770bd540cddac7c90fc46f895f427b7820f18fe1822697f1fec9", size = 11177525 }, + { url = "https://files.pythonhosted.org/packages/d7/93/6b98f2c12bf28ab9def59c50c9c49508519c5b5cfecca6de871cf01237f6/ruff-0.9.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042ae32b41343888f59c0a4148f103208bf6b21c90118d51dc93a68366f4e903", size = 11996580 }, + { url = "https://files.pythonhosted.org/packages/8e/3f/b3fcaf4f6d875e679ac2b71a72f6691a8128ea3cb7be07cbb249f477c061/ruff-0.9.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87862589373b33cc484b10831004e5e5ec47dc10d2b41ba770e837d4f429d721", size = 11525674 }, + { url = "https://files.pythonhosted.org/packages/f0/48/33fbf18defb74d624535d5d22adcb09a64c9bbabfa755bc666189a6b2210/ruff-0.9.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a17e1e01bee0926d351a1ee9bc15c445beae888f90069a6192a07a84af544b6b", size = 12739151 }, + { url = "https://files.pythonhosted.org/packages/63/b5/7e161080c5e19fa69495cbab7c00975ef8a90f3679caa6164921d7f52f4a/ruff-0.9.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7c1f880ac5b2cbebd58b8ebde57069a374865c73f3bf41f05fe7a179c1c8ef22", size = 13416128 }, + { url = "https://files.pythonhosted.org/packages/4e/c8/b5e7d61fb1c1b26f271ac301ff6d9de5e4d9a9a63f67d732fa8f200f0c88/ruff-0.9.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e63fc20143c291cab2841dbb8260e96bafbe1ba13fd3d60d28be2c71e312da49", size = 12870858 }, + { url = "https://files.pythonhosted.org/packages/da/cb/2a1a8e4e291a54d28259f8fc6a674cd5b8833e93852c7ef5de436d6ed729/ruff-0.9.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91ff963baed3e9a6a4eba2a02f4ca8eaa6eba1cc0521aec0987da8d62f53cbef", size = 14786046 }, + { url = "https://files.pythonhosted.org/packages/ca/6c/c8f8a313be1943f333f376d79724260da5701426c0905762e3ddb389e3f4/ruff-0.9.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88362e3227c82f63eaebf0b2eff5b88990280fb1ecf7105523883ba8c3aaf6fb", size = 12550834 }, + { url = "https://files.pythonhosted.org/packages/9d/ad/f70cf5e8e7c52a25e166bdc84c082163c9c6f82a073f654c321b4dff9660/ruff-0.9.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0372c5a90349f00212270421fe91874b866fd3626eb3b397ede06cd385f6f7e0", size = 11961307 }, + { url = "https://files.pythonhosted.org/packages/52/d5/4f303ea94a5f4f454daf4d02671b1fbfe2a318b5fcd009f957466f936c50/ruff-0.9.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d76b8ab60e99e6424cd9d3d923274a1324aefce04f8ea537136b8398bbae0a62", size = 11612039 }, + { url = "https://files.pythonhosted.org/packages/eb/c8/bd12a23a75603c704ce86723be0648ba3d4ecc2af07eecd2e9fa112f7e19/ruff-0.9.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0c439bdfc8983e1336577f00e09a4e7a78944fe01e4ea7fe616d00c3ec69a3d0", size = 12168177 }, + { url = "https://files.pythonhosted.org/packages/cc/57/d648d4f73400fef047d62d464d1a14591f2e6b3d4a15e93e23a53c20705d/ruff-0.9.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:115d1f15e8fdd445a7b4dc9a30abae22de3f6bcabeb503964904471691ef7606", size = 12610122 }, + { url = "https://files.pythonhosted.org/packages/49/79/acbc1edd03ac0e2a04ae2593555dbc9990b34090a9729a0c4c0cf20fb595/ruff-0.9.7-py3-none-win32.whl", hash = "sha256:e9ece95b7de5923cbf38893f066ed2872be2f2f477ba94f826c8defdd6ec6b7d", size = 9988751 }, + { url = "https://files.pythonhosted.org/packages/6d/95/67153a838c6b6ba7a2401241fd8a00cd8c627a8e4a0491b8d853dedeffe0/ruff-0.9.7-py3-none-win_amd64.whl", hash = "sha256:3770fe52b9d691a15f0b87ada29c45324b2ace8f01200fb0c14845e499eb0c2c", size = 11002987 }, + { url = "https://files.pythonhosted.org/packages/63/6a/aca01554949f3a401991dc32fe22837baeaccb8a0d868256cbb26a029778/ruff-0.9.7-py3-none-win_arm64.whl", hash = "sha256:b075a700b2533feb7a01130ff656a4ec0d5f340bb540ad98759b8401c32c2037", size = 10177763 }, ] [[package]] @@ -974,25 +992,25 @@ name = "sphinx" version = "8.1.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version == '3.10.*'", ] dependencies = [ - { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "babel", marker = "python_full_version >= '3.10'" }, - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version >= '3.10'" }, - { name = "imagesize", marker = "python_full_version >= '3.10'" }, - { name = "jinja2", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pygments", marker = "python_full_version >= '3.10'" }, - { name = "requests", marker = "python_full_version >= '3.10'" }, - { name = "snowballstemmer", marker = "python_full_version >= '3.10'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.10'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.10'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.10'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.10'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.10'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.10'" }, + { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "babel", marker = "python_full_version == '3.10.*'" }, + { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version == '3.10.*'" }, + { name = "imagesize", marker = "python_full_version == '3.10.*'" }, + { name = "jinja2", marker = "python_full_version == '3.10.*'" }, + { name = "packaging", marker = "python_full_version == '3.10.*'" }, + { name = "pygments", marker = "python_full_version == '3.10.*'" }, + { name = "requests", marker = "python_full_version == '3.10.*'" }, + { name = "snowballstemmer", marker = "python_full_version == '3.10.*'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.10.*'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.10.*'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.10.*'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.10.*'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.10.*'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.10.*'" }, { name = "tomli", marker = "python_full_version == '3.10.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611 } @@ -1000,6 +1018,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125 }, ] +[[package]] +name = "sphinx" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "babel", marker = "python_full_version >= '3.11'" }, + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version >= '3.11'" }, + { name = "imagesize", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "requests", marker = "python_full_version >= '3.11'" }, + { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/4b/95bdb36eaee30698f2d244d52e1b9e58642af56525d4b02fcd0f7312c27c/sphinx-8.2.1.tar.gz", hash = "sha256:e4b932951b9c18b039f73b72e4e63afe967d90408700ec222b981ac24647c01e", size = 8321376 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/aa/282768cff0039b227a923cb65686539bb606e448c594d4fdee4d2c7765a1/sphinx-8.2.1-py3-none-any.whl", hash = "sha256:b5d2bb3cdf6207fcacde9f92085d2b97667b05b9c346eaec426ca4be8af505e9", size = 3589415 }, +] + [[package]] name = "sphinx-autobuild" version = "2024.10.3" @@ -1007,7 +1056,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "starlette" }, { name = "uvicorn" }, { name = "watchfiles" }, @@ -1038,23 +1088,39 @@ name = "sphinx-autodoc-typehints" version = "3.0.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version == '3.10.*'", ] dependencies = [ - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/26/f0/43c6a5ff3e7b08a8c3b32f81b859f1b518ccc31e45f22e2b41ced38be7b9/sphinx_autodoc_typehints-3.0.1.tar.gz", hash = "sha256:b9b40dd15dee54f6f810c924f863f9cf1c54f9f3265c495140ea01be7f44fa55", size = 36282 } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/dc/dc46c5c7c566b7ec5e8f860f9c89533bf03c0e6aadc96fb9b337867e4460/sphinx_autodoc_typehints-3.0.1-py3-none-any.whl", hash = "sha256:4b64b676a14b5b79cefb6628a6dc8070e320d4963e8ff640a2f3e9390ae9045a", size = 20245 }, ] +[[package]] +name = "sphinx-autodoc-typehints" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "sphinx", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/cc/d38e7260b1bd3af0c84ad8285dfd78236584b74544510584e07963e000ec/sphinx_autodoc_typehints-3.1.0.tar.gz", hash = "sha256:a6b7b0b6df0a380783ce5b29150c2d30352746f027a3e294d37183995d3f23ed", size = 36528 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/bc5bed0677ae00b9ca7919968ea675e2f696b6b20f1648262f26a7a6c6b4/sphinx_autodoc_typehints-3.1.0-py3-none-any.whl", hash = "sha256:67bdee7e27ba943976ce92ebc5647a976a7a08f9f689a826c54617b96a423913", size = 20404 }, +] + [[package]] name = "sphinx-basic-ng" version = "1.0.0b2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736 } wheels = [ @@ -1067,7 +1133,8 @@ version = "0.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039 } wheels = [ @@ -1080,7 +1147,8 @@ version = "2023.4.21" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/48/f5/f8a2be63ed7be9f91a4c2bea0e25bcb56aa4c5cc37ec4d8ead8065f926b1/sphinx_inline_tabs-2023.4.21.tar.gz", hash = "sha256:5df2f13f602c158f3f5f6c509e008aeada199a8c76d97ba3aa2822206683bebc", size = 42664 } wheels = [ @@ -1147,7 +1215,8 @@ version = "0.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/5b/4302fe33c88dbfb572e2c1cad26735164c23f16fb8dba94ddb1867d0ef06/sphinxext-opengraph-0.9.1.tar.gz", hash = "sha256:dd2868a1e7c9497977fbbf44cc0844a42af39ca65fe1bb0272518af225d06fc5", size = 1034511 } wheels = [ @@ -1160,7 +1229,8 @@ version = "0.2.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1f/b4/e5fbb493f796430230189a1ce5f9beff1ac1b98619fc71ed35deca6059a5/sphinxext-rediraffe-0.2.7.tar.gz", hash = "sha256:651dcbfae5ffda9ffd534dfb8025f36120e5efb6ea1a33f5420023862b9f725d", size = 8735 } wheels = [ @@ -1169,15 +1239,15 @@ wheels = [ [[package]] name = "starlette" -version = "0.45.3" +version = "0.46.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/fb/2984a686808b89a6781526129a4b51266f678b2d2b97ab2d325e56116df8/starlette-0.45.3.tar.gz", hash = "sha256:2cbcba2a75806f8a41c722141486f37c28e30a0921c5f6fe4346cb0dcee1302f", size = 2574076 } +sdist = { url = "https://files.pythonhosted.org/packages/44/b6/fb9a32e3c5d59b1e383c357534c63c2d3caa6f25bf3c59dd89d296ecbaec/starlette-0.46.0.tar.gz", hash = "sha256:b359e4567456b28d473d0193f34c0de0ed49710d75ef183a74a5ce0499324f50", size = 2575568 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/61/f2b52e107b1fc8944b33ef56bf6ac4ebbe16d91b94d2b87ce013bf63fb84/starlette-0.45.3-py3-none-any.whl", hash = "sha256:dfb6d332576f136ec740296c7e8bb8c8a7125044e7c6da30744718880cdd059d", size = 71507 }, + { url = "https://files.pythonhosted.org/packages/41/94/8af675a62e3c91c2dee47cf92e602cfac86e8767b1a1ac3caf1b327c2ab0/starlette-0.46.0-py3-none-any.whl", hash = "sha256:913f0798bd90ba90a9156383bcf1350a17d6259451d0d8ee27fc0cf2db609038", size = 71991 }, ] [[package]]