[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

# ------ build-system ------

[project]
name = "tractor"
version = "0.1.0a6dev0"
description = 'structured concurrent `trio`-"actors"'
authors = [{ name = "Tyler Goodlet", email = "goodboy_foss@protonmail.com" }]
requires-python = ">=3.13, <3.15"
readme = "docs/README.rst"
license = "AGPL-3.0-or-later"
keywords = [
  "trio",
  "async",
  "concurrency",
  "structured concurrency",
  "actor model",
  "distributed",
  "multiprocessing",
]
classifiers = [
  "Development Status :: 3 - Alpha",
  "Operating System :: POSIX :: Linux",
  "Operating System :: MacOS",
  "Framework :: Trio",
  "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
  "Programming Language :: Python :: Implementation :: CPython",
  "Programming Language :: Python :: 3 :: Only",
  "Programming Language :: Python :: 3.13",
  "Programming Language :: Python :: 3.14",
  "Topic :: System :: Distributed Computing",
]
dependencies = [
  # trio runtime and friends
  # (poetry) proper range specs,
  # https://packaging.python.org/en/latest/discussions/install-requires-vs-requirements/#id5
  # TODO, for 3.13 we must go go `0.27` which means we have to
  # disable strict egs or port to handling them internally!
  "trio>0.27",
  "tricycle>=0.4.1,<0.5",
  "wrapt>=1.16.0,<2",
  "colorlog>=6.8.2,<7",
  # built-in multi-actor `pdb` REPL
  "pdbp>=1.8.2,<2", # windows only (from `pdbp`)
  # typed IPC msging
  "msgspec>=0.20.0",
  "bidict>=0.23.1",
  # unreleased `/wg/` codec from py-multiaddr#108
  "multiaddr @ git+https://github.com/multiformats/py-multiaddr.git@f86519daaa21699023d0037c58cdff600313dd09",
  # encode/decode `wg` pubkeys carried by multiaddrs
  "py-multibase>=2.0.0,<3",
  "platformdirs>=4.4.0",
  # per-actor `argv[0]` proc-title for OS-level diag tools
  # (`ps`, `top`, `psutil`-backed tooling like `acli.pytree`).
  # Optional at runtime — guarded by `try/except ImportError` in
  # `tractor.devx._proctitle` — but listed here so default
  # installs benefit from it. See tracking issue for follow-ups
  # (e.g. richer formats, per-backend overrides).
  "setproctitle>=1.3,<2",
]

# ------ project ------

[dependency-groups]
dev = [
  {include-group = 'devx'},
  {include-group = 'testing'},
  {include-group = 'repl'},
  {include-group = 'sync_pause'},
]
devx = [
  # `tractor.devx` tooling
  "stackscope>=0.2.2,<0.3",
  # ^ requires this?
  "typing-extensions>=4.14.1",
  # {include-group = 'sync_pause'},  # XXX, no 3.14 yet!
]
sync_pause = [
  "greenback>=1.2.1,<2",  # TODO? 3.14 greenlet on nix?
]
testing = [
  # test suite
  # TODO: maybe some of these layout choices?
  # https://docs.pytest.org/en/8.0.x/explanation/goodpractices.html#choosing-a-test-layout-import-rules
  # bumped 8.3.5 → 9.0.3 per upstream security advisory + our
  # local-only reliance on the post-9.0 capture-machinery shape
  # (the `sys.__stderr__`-bypass print in
  # `tractor._testing.trace._do_capture_snapshot` works on 8.x
  # too, but standardizing on 9.x here ensures `--show-capture`
  # interactions stay predictable across dev installs).
  "pytest>=9.0.3",  # CVE-2025-71176 (insecure tmpdir) patched in 9.0.3
  "pexpect>=4.9.0,<5",
  # per-test wall-clock bound (used via
  # `@pytest.mark.timeout(..., method='thread')` on the
  # known-hanging `subint`-backend audit tests; see
  # `ai/conc-anal/subint_*_issue.md`).
  "pytest-timeout>=2.3",
  # used by `tractor._testing._reap` for the
  # `tractor-reap` zombie-subactor + leaked-shm
  # cleanup utility (xplatform `Process.memory_maps`,
  # `Process.open_files`).
  "psutil>=7.0.0",
  # rerun timing-sensitive macOS CI nodes without hiding a
  # deterministic failure after the final attempt.
  "pytest-rerunfailures>=16.6,<17",
]
repl = [
  "pyperclip>=1.9.0",
  "prompt-toolkit>=3.0.50",
  "xonsh>=0.23.8",
  "psutil>=7.0.0",
]
lint = [
  "ruff>=0.9.6"
]
# XXX, used for linux-only hi perf eventfd+shm channels
# now mostly moved over to `hotbaud`.
eventfd = [
  "cffi>=1.17.1",
]
subints = [
  "msgspec>=0.21.0",
]
# docs generation; build locally via,
#   uv run --group docs make -C docs html
# diagrams re-render when a `d2` bin is found, see
# `docs/_ext/d2diagrams.py` (eg. via `nix run
# nixpkgs#d2` and the `D2_BIN` env var).
docs = [
  "sphinx>=9.1,<10",
  "pydata-sphinx-theme>=0.18,<0.19",
  "sphinx-design>=0.7,<0.8",
  "sphinx-copybutton>=0.5.2,<0.6",
  "sphinxext-opengraph>=0.13,<0.14",
  "sphinx-togglebutton>=0.4.5,<0.5",
]
# ------ dependency-groups ------

[tool.uv.dependency-groups]
# for subints, we require 3.14+ due to 2 issues,
# - hanging behaviour for various multi-task teardown cases (see
#   "Availability" section in the `tractor.spawn._subints` doc string).
# - `msgspec` support which is oustanding per PEP 684 upstream tracker:
#   https://github.com/jcrist/msgspec/issues/563
#
# https://docs.astral.sh/uv/concepts/projects/dependencies/#group-requires-python
subints = {requires-python = ">=3.14"}
eventfd = {requires-python = ">=3.13, <3.14"}
sync_pause = {requires-python = ">=3.13, <3.14"}

[tool.uv.sources]
# XXX NOTE, only for @goodboy's hacking on `pprint(sort_dicts=False)`
# for the `pp` alias..
# ------ gh upstream ------
# xonsh = { git = 'https://github.com/anki-code/xonsh.git', branch = 'prompt_next_suggestion' }
# ^ https://github.com/xonsh/xonsh/pull/6048
# xonsh = { git = 'https://github.com/xonsh/xonsh.git', branch = 'main' }
# xonsh = { path = "../xonsh", editable = true }

# [tool.uv.sources.pdbp]
# XXX, in case we need to tmp patch again.
# git = "https://github.com/goodboy/pdbp.git"
# branch ="repair_stack_trace_frame_indexing"
# path = "../pdbp"
# editable = true

# ------ tool.uv.sources ------
# TODO, distributed (multi-host) extensions
# linux kernel networking
# 'pyroute2

# ------ tool.uv.sources ------

[tool.uv]
# XXX NOTE, prefer the sys python bc apparently the distis from
# `astral` are built in a way that breaks `pdbp`+`tabcompleter`'s
# likely due to linking against `libedit` over `readline`..
# |_https://docs.astral.sh/uv/concepts/python-versions/#managed-python-distributions
# |_https://gregoryszorc.com/docs/python-build-standalone/main/quirks.html#use-of-libedit-on-linux
#
# https://docs.astral.sh/uv/reference/settings/#python-preference
python-preference = 'system'

# ------ tool.uv ------

[tool.hatch.metadata]
allow-direct-references = true

[tool.hatch.build.targets.sdist]
include = ["tractor"]

[tool.hatch.build.targets.wheel]
include = ["tractor"]

# ------ tool.hatch ------

[tool.towncrier]
package = "tractor"
filename = "NEWS.rst"
directory = "nooz/"
version = "0.1.0a6"
title_format = "tractor {version} ({project_date})"
template = "nooz/_template.rst"
all_bullets = true

[[tool.towncrier.type]]
  directory = "feature"
  name = "Features"
  showcontent = true

[[tool.towncrier.type]]
  directory = "bugfix"
  name = "Bug Fixes"
  showcontent = true

[[tool.towncrier.type]]
  directory = "doc"
  name = "Improved Documentation"
  showcontent = true

[[tool.towncrier.type]]
  directory = "trivial"
  name = "Trivial/Internal Changes"
  showcontent = true

# ------ tool.towncrier ------

[tool.pytest.ini_options]
minversion = '6.0'
# NOTE: `pytest-timeout`'s global per-test cap is intentionally
# NOT set — both of its enforcement methods break trio's
# runtime under our fork-based spawn backends:
#
# - `method='signal'` (the default; SIGALRM) raises `Failed`
#   synchronously from the signal handler in trio's main
#   thread, which leaves `GLOBAL_RUN_CONTEXT` half-installed
#   ("Trio guest run got abandoned"). EVERY subsequent
#   `trio.run()` in the same pytest session then bails with
#   `RuntimeError: Attempted to call run() from inside a
#   run()` — full-session poison: a single 200s hang
#   cascades into 30+ false-positive failures across
#   downstream test files.
#
# - `method='thread'` calls `_thread.interrupt_main()` which
#   can let the resulting `KeyboardInterrupt` escape trio's
#   `KIManager` under fork-cascade teardown races, killing
#   the whole pytest session.
#
# For tests that legitimately need a wall-clock cap, use
# `with trio.fail_after(N):` INSIDE the test — trio's own
# Cancelled machinery handles the timeout cleanly through
# the actor nursery without disturbing global state. See
# `tests/test_advanced_streaming.py::test_dynamic_pub_sub`'s
# module-level NOTE for the canonical pattern.
#
# CI environments should rely on job-level wall-clock
# timeouts (e.g. GitHub Actions `timeout-minutes`) for an
# escape hatch on genuinely-stuck suites.
# https://docs.pytest.org/en/stable/reference/reference.html#configuration-options
testpaths = [
  'tests'
]
addopts = [
  # TODO: figure out why this isn't working..
  '--rootdir=./tests',
  '--import-mode=importlib',
  # don't show frickin captured logs AGAIN in the report..
  '--show-capture=no',

  # load builtin plugin since we need a boostrapping hook,
  # `pytest_load_initial_conftests()` for `--capture=` per:
  # https://docs.pytest.org/en/stable/reference/reference.html#bootstrapping-hooks
  '-p tractor._testing.pytest',

  # disable `xonsh` plugin
  # https://docs.pytest.org/en/stable/how-to/plugins.html#disabling-plugins-from-autoloading
  # https://docs.pytest.org/en/stable/how-to/plugins.html#deactivating-unregistering-a-plugin-by-name
  '-p no:xonsh',

  # XXX default on non-forking spawners
  '--capture=fd',
  # '--capture=sys',
  # ^XXX NOTE^ ALWAYS SET THIS for `*_forkserver` spawner
  # backends! see details @
  # `tractor._testing.pytest.pytest_load_initial_conftests()`

]
log_cli = false
# TODO: maybe some of these layout choices?
# https://docs.pytest.org/en/8.0.x/explanation/goodpractices.html#choosing-a-test-layout-import-rules
# pythonpath = "src"

# https://docs.pytest.org/en/stable/reference/reference.html#confval-console_output_style
console_output_style = 'progress'

# `pexpect` (used only by the `devx` debugger REPL tests) allocates
# its child pty via stdlib `pty.py:os.forkpty()`, which CPython
# 3.12+ flags as unsafe in a multi-threaded process. This is NOT
# ours to fix at source — we never call `forkpty()`; pexpect does.
# UPSTREAM to actually resolve: pexpect would need to avoid
# `forkpty()` under multithreading (or perform the pty spawn before
# any thread starts); until then this single third-party warning is
# the one we filter rather than fix.
filterwarnings = [
  'ignore:This process \(pid=\d+\) is multi-threaded, use of forkpty\(\):DeprecationWarning',

  # `tests/test_legacy_one_way_streaming.py` exists *specifically*
  # to exercise the DEPRECATED `@tractor.stream` async-gen API, so
  # its decoration-time `DeprecationWarning` is expected — and since
  # it fires at import (module-level decorators) it can't be caught
  # by a per-test mark, hence this test-wide ini filter. Real
  # library users still see the warning; drop this once the legacy
  # `@tractor.stream` / `Portal.open_stream_from()` API is removed.
  'ignore:`@tractor.stream decorated funcs:DeprecationWarning',
]
# ------ tool.pytest ------
