Compare commits

...

4 Commits

Author SHA1 Message Date
Gud Boi fc5e80fea5 Drop subint-family gate from `main_thread_forkserver`
`main_thread_forkserver` doesn't actually need py3.14
`concurrent.interpreters` (PEP 734) — it forks from a
non-trio worker thread and runs `_trio_main` in the child,
same shape as `trio_proc`. The previous `_has_subints`
gate + subint-family `case` arm were a copy-paste error.

In `tractor.spawn._main_thread_forkserver`,
- drop the `_has_subints` import + the `RuntimeError`
  raise in `main_thread_forkserver_proc()`.
- drop the now-unused `import sys` (only used by the
  prior error msg).

In `tractor.spawn._spawn.try_set_start_method()`,
- pull `'main_thread_forkserver'` out of the subint-
  family arm (which still gates on `_has_subints`).
- merge it into the `'trio'` arm — both set `_ctx = None`
  bc neither needs an `mp.context`.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-29 18:13:46 -04:00
Gud Boi b7115fc875 Drop test-local timeouts, +`sync_pause` to dev
In `pyproject.toml`,
- include the `sync_pause` group from `dev`, so dev
  installs ship `greenback` for `pause_from_sync()`.

Comment out per-test `@pytest.mark.timeout(...)`
markers in,
- `tests/devx/test_debugger.py`
- `tests/discovery/test_registrar.py`
- `tests/spawn/test_main_thread_forkserver.py`
- `tests/spawn/test_subint_cancellation.py`
- `tests/test_advanced_streaming.py`
- `tests/test_cancellation.py`

The global cap was already dropped (3c366cac); these
were the leftover per-test caps which now block
interactive `pdb` flows under the new spawn backends.

In `uv.lock`,
- pull `greenback` into the resolved `dev` deps
  (per the `sync_pause` include above).
- catch up the prior `xonsh` editable→PyPI switch
  (from the `pyproject.toml` `tool.uv.sources` edit).

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-29 18:10:40 -04:00
Gud Boi 208e7c0926 Honor `TRACTOR_LOGLEVEL`+`TRACTOR_SPAWN_METHOD` env-vars
Add env-var overrides inside `._root.open_root_actor()` so
devs/test-runs can swap the actor-spawn backend or crank
console verbosity *without* touching application code.

In `._root.open_root_actor()`,
- read `TRACTOR_LOGLEVEL` early, overriding any caller-passed
  `loglevel` and stashing an `env_ll_report` to emit once the
  console log is set up.
- pull the `loglevel` fallback (`or _default_loglevel`) and
  `log.get_console_log()` init *up* so the env-var report
  routes through tractor's own logger.
- read `TRACTOR_SPAWN_METHOD`, overriding any caller-passed
  `start_method` and warn-logging when the env-var clobbers
  an explicit caller value.

Wire the same vars through `tests/devx/conftest.py::spawn`,
- request the `loglevel` fixture, set both `TRACTOR_LOGLEVEL`
  and `TRACTOR_SPAWN_METHOD` in `os.environ` before each
  `pexpect.spawn()` (inherited by the example subproc).
- expand `supported_spawners` to include
  `main_thread_forkserver` and `subint_forkserver` bc
  example scripts no longer need per-script CLI plumbing.
- pop both vars in fixture teardown so a leaked value can't
  re-route a later in-process tractor test's spawn-backend
  or loglevel.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-29 17:29:38 -04:00
Gud Boi 22cdf15b73 Flip back to default `pytest` capture for CI 2026-04-29 15:03:26 -04:00
13 changed files with 155 additions and 108 deletions

View File

@ -148,9 +148,13 @@ jobs:
- name: Run tests - name: Run tests
run: > run: >
uv run uv run
pytest tests/ -rsx pytest
tests/
-rsx
--spawn-backend=${{ matrix.spawn_backend }} --spawn-backend=${{ matrix.spawn_backend }}
--tpt-proto=${{ matrix.tpt_proto }} --tpt-proto=${{ matrix.tpt_proto }}
--capture=fd
# ^XXX^ can't work with --spawn-method=main_thread_forkserver
# XXX legacy NOTE XXX # XXX legacy NOTE XXX
# #

View File

@ -62,6 +62,7 @@ dev = [
{include-group = 'devx'}, {include-group = 'devx'},
{include-group = 'testing'}, {include-group = 'testing'},
{include-group = 'repl'}, {include-group = 'repl'},
{include-group = 'sync_pause'},
] ]
devx = [ devx = [
# `tractor.devx` tooling # `tractor.devx` tooling

View File

@ -4,6 +4,7 @@
''' '''
from __future__ import annotations from __future__ import annotations
import platform import platform
import os
import signal import signal
import time import time
from typing import ( from typing import (
@ -56,6 +57,7 @@ type PexpectSpawner = Callable[
@pytest.fixture @pytest.fixture
def spawn( def spawn(
start_method: str, start_method: str,
loglevel: str,
testdir: pytest.Pytester, testdir: pytest.Pytester,
reg_addr: tuple[str, int], reg_addr: tuple[str, int],
@ -67,11 +69,12 @@ def spawn(
''' '''
supported_spawners: set[str] = { supported_spawners: set[str] = {
'trio', 'trio',
# ?TODO, other spawners that will work? # `examples/debugging/<script>.py` picks up the spawn
# - [ ] need to pass `start_method={spawner}` to underlying # backend via the `TRACTOR_SPAWN_METHOD` env-var which
# `examples/debugging/<script>.py` somehow? # is honored inside `tractor._root.open_root_actor()`,
# 'main_thread_forkserver', # so no per-script edits are required.
# 'subint_forkserver', 'main_thread_forkserver',
'subint_forkserver',
} }
if start_method not in supported_spawners: if start_method not in supported_spawners:
pytest.skip( pytest.skip(
@ -88,12 +91,35 @@ def spawn(
https://docs.python.org/3/using/cmdline.html#using-on-controlling-color https://docs.python.org/3/using/cmdline.html#using-on-controlling-color
''' '''
import os
# disable colored tbs # disable colored tbs
os.environ['PYTHON_COLORS'] = '0' os.environ['PYTHON_COLORS'] = '0'
# disable all ANSI color output # disable all ANSI color output
# os.environ['NO_COLOR'] = '1' # os.environ['NO_COLOR'] = '1'
def set_spawn_method():
'''
Drive the actor-spawn backend inside the spawned
`examples/debugging/<script>.py` subproc via env-var
(consumed by `tractor._root.open_root_actor()`),
without requiring per-script CLI plumbing.
'''
os.environ['TRACTOR_SPAWN_METHOD'] = start_method
def set_loglevel():
'''
Forward the test-suite parametrized `loglevel` into the
spawned `examples/debugging/<script>.py` subproc via
env-var (consumed by `tractor._root.open_root_actor()`),
so console verbosity can be cranked or silenced from
the test harness without per-script edits.
'''
if loglevel:
os.environ['TRACTOR_LOGLEVEL'] = loglevel
else:
os.environ.pop('TRACTOR_LOGLEVEL', None)
spawned: PexpectSpawner|None = None spawned: PexpectSpawner|None = None
def _spawn( def _spawn(
@ -103,6 +129,8 @@ def spawn(
) -> pty_spawn.spawn: ) -> pty_spawn.spawn:
nonlocal spawned nonlocal spawned
unset_colors() unset_colors()
set_spawn_method()
set_loglevel()
spawned = testdir.spawn( spawned = testdir.spawn(
cmd=mk_cmd( cmd=mk_cmd(
cmd, cmd,
@ -146,6 +174,14 @@ def spawn(
if ptyproc.isalive(): if ptyproc.isalive():
ptyproc.kill(signal.SIGKILL) ptyproc.kill(signal.SIGKILL)
# Scope our env-var mutations to this single fixture invocation
# — both `TRACTOR_SPAWN_METHOD` and `TRACTOR_LOGLEVEL` are
# honored by `tractor._root.open_root_actor()` so leaking them
# past this test could inadvertently re-route a later in-process
# tractor test's spawn-backend / loglevel.
os.environ.pop('TRACTOR_SPAWN_METHOD', None)
os.environ.pop('TRACTOR_LOGLEVEL', None)
# TODO? ensure we've cleaned up any UDS-paths? # TODO? ensure we've cleaned up any UDS-paths?
# breakpoint() # breakpoint()

View File

@ -841,7 +841,7 @@ def test_multi_nested_subactors_error_through_nurseries(
) )
@pytest.mark.timeout(15) # @pytest.mark.timeout(15)
@has_nested_actors @has_nested_actors
def test_root_nursery_cancels_before_child_releases_tty_lock( def test_root_nursery_cancels_before_child_releases_tty_lock(
spawn, spawn,

View File

@ -535,13 +535,13 @@ async def kill_transport(
# At timeout the plugin hard-kills the pytest process — that's # At timeout the plugin hard-kills the pytest process — that's
# the intended behavior here; the alternative is an unattended # the intended behavior here; the alternative is an unattended
# suite run that never returns. # suite run that never returns.
@pytest.mark.timeout( # @pytest.mark.timeout(
30, # 30,
# NOTE should be a 2.1s happy path. # # NOTE should be a 2.1s happy path.
# XXX for `main_thread_forkserver` this is SUPER SENSITIVE # # XXX for `main_thread_forkserver` this is SUPER SENSITIVE
# so keep it higher to avoid flaky runs.. # # so keep it higher to avoid flaky runs..
method='thread', # method='thread',
) # )
@pytest.mark.skipon_spawn_backend( @pytest.mark.skipon_spawn_backend(
'subint', 'subint',
# 'main_thread_forkserver', # 'main_thread_forkserver',

View File

@ -182,7 +182,7 @@ async def run_fork_in_non_trio_thread(
# `subint_sigint_starvation_issue.md`. Each test also has an # `subint_sigint_starvation_issue.md`. Each test also has an
# inner `trio.fail_after()` so assertion failures fire fast # inner `trio.fail_after()` so assertion failures fire fast
# under normal conditions. # under normal conditions.
@pytest.mark.timeout(30, method='thread') # @pytest.mark.timeout(30, method='thread')
def test_fork_from_worker_thread_via_trio( def test_fork_from_worker_thread_via_trio(
) -> None: ) -> None:
''' '''

View File

@ -179,10 +179,10 @@ def test_subint_happy_teardown(
# `subint_sigint_starvation_issue.md` GIL-starvation flavor, # `subint_sigint_starvation_issue.md` GIL-starvation flavor,
# so `method='thread'` keeps us safe in case ordering or # so `method='thread'` keeps us safe in case ordering or
# load shifts the failure mode. # load shifts the failure mode.
@pytest.mark.timeout( # @pytest.mark.timeout(
3, # NOTE never passes pre-3.14+ subints support. # 3, # NOTE never passes pre-3.14+ subints support.
method='thread', # method='thread',
) # )
def test_subint_non_checkpointing_child( def test_subint_non_checkpointing_child(
reg_addr: tuple[str, int|str], reg_addr: tuple[str, int|str],
) -> None: ) -> None:

View File

@ -430,10 +430,10 @@ async def inf_streamer(
print('streamer exited .open_streamer() block') print('streamer exited .open_streamer() block')
@pytest.mark.timeout( # @pytest.mark.timeout(
6, # 6,
method='signal', # method='signal',
) # )
def test_local_task_fanout_from_stream( def test_local_task_fanout_from_stream(
reg_addr: tuple, reg_addr: tuple,
debug_mode: bool, debug_mode: bool,

View File

@ -458,10 +458,10 @@ async def spawn_and_error(
# `test_nested_multierrors`. See # `test_nested_multierrors`. See
# `ai/conc-anal/subint_forkserver_test_cancellation_leak_issue.md` # `ai/conc-anal/subint_forkserver_test_cancellation_leak_issue.md`
# / #449 for the post-mortem. # / #449 for the post-mortem.
@pytest.mark.timeout( # @pytest.mark.timeout(
10, # 10,
method='thread', # method='thread',
) # )
@tractor_test @tractor_test
async def test_nested_multierrors( async def test_nested_multierrors(
reg_addr: tuple, reg_addr: tuple,

View File

@ -241,6 +241,7 @@ async def open_root_actor(
f'_registry_addrs: {registry_addrs!r}\n' f'_registry_addrs: {registry_addrs!r}\n'
) )
# debug.mk_pdb().set_trace()
async with maybe_block_bp( async with maybe_block_bp(
debug_mode=debug_mode, debug_mode=debug_mode,
maybe_enable_greenback=maybe_enable_greenback, maybe_enable_greenback=maybe_enable_greenback,
@ -284,6 +285,75 @@ async def open_root_actor(
) )
enable_modules.extend(rpc_module_paths) enable_modules.extend(rpc_module_paths)
# `TRACTOR_LOGLEVEL` env-var wins over any caller-passed
# `loglevel` so devs/test-runs can crank (or silence)
# console verbosity without touching application code.
env_ll_report: str = ''
if env_ll := os.environ.get('TRACTOR_LOGLEVEL'):
loglevel = env_ll
env_ll_report: str = (
f'Detected env-var setting,\n'
f'TRACTOR_LOGLEVEL={env_ll!r}\n'
f'\n'
f'Setting console loglevel per,\n'
f'loglevel={loglevel!r}\n'
)
if (
loglevel
and
loglevel.upper() != env_ll.upper()
):
env_ll_report += (
f'\n'
f'NOTE env-var OVERRIDES caller-passed,\n'
f'loglevel={loglevel!r}\n'
)
loglevel: str = (
loglevel
or
log._default_loglevel
)
loglevel: str = loglevel.upper()
assert loglevel
_log = log.get_console_log(
level=loglevel,
name='tractor',
logger=logger,
)
assert _log
if env_ll_report:
_log.info(env_ll_report)
# `TRACTOR_SPAWN_METHOD` env-var wins over any caller-passed
# `start_method` so devs/test-runs can swap the actor spawn
# backend without touching application code (e.g. driving
# the `examples/debugging/<script>.py` suite under each
# backend from `tests/devx/conftest.py::spawn`).
if env_sm := os.environ.get('TRACTOR_SPAWN_METHOD'):
start_method: str = env_sm
env_sm_report: str = (
f'Detected env-var setting,\n'
f'TRACTOR_SPAWN_METHOD={env_sm!r}\n'
f'\n'
f'Setting spawn backend as,\n'
f'start_method={env_sm!r}\n'
)
if (
start_method
and
start_method != env_sm
):
_log.warning(
env_sm_report
+
f'NOTE env-var OVERRIDES caller-passed,\n'
f'`start_method={start_method!r}`\n'
)
else:
_log.info(env_sm_report)
if start_method is not None: if start_method is not None:
_spawn.try_set_start_method(start_method) _spawn.try_set_start_method(start_method)
@ -300,12 +370,6 @@ async def open_root_actor(
wrap_address(uw_addr) wrap_address(uw_addr)
for uw_addr in uw_reg_addrs for uw_addr in uw_reg_addrs
] ]
loglevel: str = (
loglevel
or
log._default_loglevel
)
loglevel: str = loglevel.upper()
# Debug-mode is currently only supported for backends whose # Debug-mode is currently only supported for backends whose
# subactor root runtime is trio-native (so `tractor.devx. # subactor root runtime is trio-native (so `tractor.devx.
@ -341,13 +405,6 @@ async def open_root_actor(
f'{_spawn._spawn_method!r}.' f'{_spawn._spawn_method!r}.'
) )
assert loglevel
_log = log.get_console_log(
level=loglevel,
name='tractor',
)
assert _log
# TODO: factor this into `.devx._stackscope`!! # TODO: factor this into `.devx._stackscope`!!
if ( if (
debug_mode debug_mode

View File

@ -346,7 +346,6 @@ from __future__ import annotations
import errno import errno
import os import os
import signal import signal
import sys
import threading import threading
from functools import partial from functools import partial
from typing import ( from typing import (
@ -370,7 +369,6 @@ from ._spawn import (
cancel_on_completion, cancel_on_completion,
soft_kill, soft_kill,
) )
from ._subint import _has_subints
if TYPE_CHECKING: if TYPE_CHECKING:
from tractor.discovery._addr import UnwrappedAddress from tractor.discovery._addr import UnwrappedAddress
@ -832,13 +830,6 @@ async def main_thread_forkserver_proc(
thread instead of `trio.lowlevel.open_process()`. thread instead of `trio.lowlevel.open_process()`.
''' '''
if not _has_subints:
raise RuntimeError(
f'The {"main_thread_forkserver"!r} spawn backend '
f'requires Python 3.14+.\n'
f'Current runtime: {sys.version}'
)
# Backend-scoped config pulled from `proc_kwargs`. Using # Backend-scoped config pulled from `proc_kwargs`. Using
# `proc_kwargs` (vs a first-class kwarg on this function) # `proc_kwargs` (vs a first-class kwarg on this function)
# matches how other backends expose per-spawn tuning # matches how other backends expose per-spawn tuning

View File

@ -135,13 +135,15 @@ def try_set_start_method(
case 'mp_spawn': case 'mp_spawn':
_ctx = mp.get_context('spawn') _ctx = mp.get_context('spawn')
case 'trio': case (
'trio'
| 'main_thread_forkserver'
):
_ctx = None _ctx = None
case ( case (
'subint' 'subint'
| 'subint_fork' | 'subint_fork'
| 'main_thread_forkserver'
| 'subint_forkserver' | 'subint_forkserver'
): ):
# All subint-family backends need no `mp.context`; # All subint-family backends need no `mp.context`;

68
uv.lock
View File

@ -682,6 +682,7 @@ dependencies = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "greenback", marker = "python_full_version < '3.14'" },
{ name = "pexpect" }, { name = "pexpect" },
{ name = "prompt-toolkit" }, { name = "prompt-toolkit" },
{ name = "psutil" }, { name = "psutil" },
@ -736,6 +737,7 @@ requires-dist = [
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "greenback", marker = "python_full_version == '3.13.*'", specifier = ">=1.2.1,<2" },
{ name = "pexpect", specifier = ">=4.9.0,<5" }, { name = "pexpect", specifier = ">=4.9.0,<5" },
{ name = "prompt-toolkit", specifier = ">=3.0.50" }, { name = "prompt-toolkit", specifier = ">=3.0.50" },
{ name = "psutil", specifier = ">=7.0.0" }, { name = "psutil", specifier = ">=7.0.0" },
@ -744,7 +746,7 @@ dev = [
{ name = "pytest-timeout", specifier = ">=2.3" }, { name = "pytest-timeout", specifier = ">=2.3" },
{ name = "stackscope", specifier = ">=0.2.2,<0.3" }, { name = "stackscope", specifier = ">=0.2.2,<0.3" },
{ name = "typing-extensions", specifier = ">=4.14.1" }, { name = "typing-extensions", specifier = ">=4.14.1" },
{ name = "xonsh", editable = "../xonsh" }, { name = "xonsh", specifier = ">=0.23.0" },
] ]
devx = [ devx = [
{ name = "stackscope", specifier = ">=0.2.2,<0.3" }, { name = "stackscope", specifier = ">=0.2.2,<0.3" },
@ -756,7 +758,7 @@ repl = [
{ name = "prompt-toolkit", specifier = ">=3.0.50" }, { name = "prompt-toolkit", specifier = ">=3.0.50" },
{ name = "psutil", specifier = ">=7.0.0" }, { name = "psutil", specifier = ">=7.0.0" },
{ name = "pyperclip", specifier = ">=1.9.0" }, { name = "pyperclip", specifier = ">=1.9.0" },
{ name = "xonsh", editable = "../xonsh" }, { name = "xonsh", specifier = ">=0.23.0" },
] ]
subints = [{ name = "msgspec", marker = "python_full_version >= '3.14'", specifier = ">=0.21.0" }] subints = [{ name = "msgspec", marker = "python_full_version >= '3.14'", specifier = ">=0.21.0" }]
sync-pause = [{ name = "greenback", marker = "python_full_version == '3.13.*'", specifier = ">=1.2.1,<2" }] sync-pause = [{ name = "greenback", marker = "python_full_version == '3.13.*'", specifier = ">=1.2.1,<2" }]
@ -870,61 +872,15 @@ wheels = [
[[package]] [[package]]
name = "xonsh" name = "xonsh"
source = { editable = "../xonsh" } version = "0.23.2"
source = { registry = "https://pypi.org/simple" }
[package.metadata] sdist = { url = "https://files.pythonhosted.org/packages/60/e5/2dfa99e21a8118bed0e73ed50e91962fdad01b900e23497064e8810b03b5/xonsh-0.23.2.tar.gz", hash = "sha256:633608c8292938af0f242f05326cc2912f25fa72bd808824ab0534a6df304402", size = 1030659, upload-time = "2026-04-26T19:28:40.744Z" }
requires-dist = [ wheels = [
{ name = "click", marker = "extra == 'full'" }, { url = "https://files.pythonhosted.org/packages/53/0d/bf7869dd57b40888ea1da8fc88f70d8e94ec2f8ee236ea4c22a757593235/xonsh-0.23.2-py311-none-any.whl", hash = "sha256:a38dd84e23e97fc42e0156c80024b3449474dfcbb6c3a344bd38c45a2b2de44d", size = 756215, upload-time = "2026-04-26T19:28:38.875Z" },
{ name = "coverage", marker = "extra == 'test'", specifier = ">=5.3.1" }, { url = "https://files.pythonhosted.org/packages/f7/9f/b1bb0c15bf2120469c94b062f4b854588370ab94c7a1679c84ff646bf50b/xonsh-0.23.2-py312-none-any.whl", hash = "sha256:190a348fa19774de8e697af5f44c9adb95aca687fa475b31dda23d1a3462a3c6", size = 756224, upload-time = "2026-04-26T19:28:39.17Z" },
{ name = "distro", marker = "sys_platform == 'linux' and extra == 'full'" }, { url = "https://files.pythonhosted.org/packages/83/23/8e037579ac86d8f266b4116338f902eab04175b88574a6438ee739dd3084/xonsh-0.23.2-py313-none-any.whl", hash = "sha256:4ebbf42a94f505d25694f154556ca0caa149a3f59870ec850bd13ad8df519dce", size = 756728, upload-time = "2026-04-26T19:28:39.493Z" },
{ name = "distro", marker = "extra == 'linux'" }, { url = "https://files.pythonhosted.org/packages/05/ec/090300d9c5f14f58b5a684302f43535457f733a62f11673aa3ac38460717/xonsh-0.23.2-py314-none-any.whl", hash = "sha256:5efcd0f6db8f9f1dace256de2c04c3c044f2d86b48434187c43a69d602283a9e", size = 756767, upload-time = "2026-04-26T19:28:37.218Z" },
{ name = "furo", marker = "extra == 'doc'" },
{ name = "gnureadline", marker = "sys_platform == 'darwin' and extra == 'full'" },
{ name = "gnureadline", marker = "extra == 'mac'" },
{ name = "matplotlib", marker = "extra == 'doc'" },
{ name = "myst-parser", marker = "extra == 'doc'" },
{ name = "numpydoc", marker = "extra == 'doc'" },
{ name = "pre-commit", marker = "extra == 'dev'" },
{ name = "prompt-toolkit", marker = "extra == 'bestshell'", specifier = ">=3.0.29" },
{ name = "prompt-toolkit", marker = "extra == 'ptk'", specifier = ">=3.0.29" },
{ name = "prompt-toolkit", marker = "extra == 'test'", specifier = ">=3.0.29" },
{ name = "psutil", marker = "extra == 'doc'" },
{ name = "pygments", marker = "extra == 'bestshell'", specifier = ">=2.2" },
{ name = "pygments", marker = "extra == 'pygments'", specifier = ">=2.2" },
{ name = "pygments", marker = "extra == 'test'", specifier = ">=2.2" },
{ name = "pyperclip", marker = "extra == 'ptk'" },
{ name = "pyte", marker = "extra == 'test'", specifier = ">=0.8.0" },
{ name = "pytest", marker = "extra == 'test'", specifier = ">=7" },
{ name = "pytest-cov", marker = "extra == 'test'" },
{ name = "pytest-mock", marker = "extra == 'test'" },
{ name = "pytest-rerunfailures", marker = "extra == 'test'" },
{ name = "pytest-subprocess", marker = "extra == 'test'" },
{ name = "pytest-timeout", marker = "extra == 'test'" },
{ name = "pyzmq", marker = "extra == 'doc'" },
{ name = "re-ver", marker = "extra == 'dev'" },
{ name = "requests", marker = "extra == 'test'" },
{ name = "restructuredtext-lint", marker = "extra == 'test'" },
{ name = "runthis-sphinxext", marker = "extra == 'doc'" },
{ name = "setproctitle", marker = "sys_platform == 'win32' and extra == 'full'" },
{ name = "setproctitle", marker = "extra == 'proctitle'" },
{ name = "sphinx", marker = "extra == 'doc'", specifier = ">=3.1" },
{ name = "sphinx-autobuild", marker = "extra == 'doc'" },
{ name = "sphinx-prompt", marker = "extra == 'doc'" },
{ name = "sphinx-reredirects", marker = "extra == 'doc'" },
{ name = "sphinx-sitemap", marker = "extra == 'doc'" },
{ name = "tomli", marker = "extra == 'dev'" },
{ name = "tornado", marker = "extra == 'doc'" },
{ name = "ujson", marker = "extra == 'full'" },
{ name = "virtualenv", marker = "extra == 'test'", specifier = ">=20.16.2" },
{ name = "xonsh", extras = ["bestshell"], marker = "extra == 'doc'" },
{ name = "xonsh", extras = ["bestshell"], marker = "extra == 'test'" },
{ name = "xonsh", extras = ["doc", "test"], marker = "extra == 'dev'" },
{ name = "xonsh", extras = ["ptk", "pygments"], marker = "extra == 'full'" },
] ]
provides-extras = ["ptk", "pygments", "mac", "linux", "proctitle", "full", "bestshell", "test", "dev", "doc"]
[package.metadata.requires-dev]
dev = [{ name = "xonsh", extras = ["dev"] }]
[[package]] [[package]]
name = "zipp" name = "zipp"