From 78c2304cbec88a7b516d1aec09b17ef94581eb18 Mon Sep 17 00:00:00 2001 From: Karthic Raghupathi Date: Wed, 24 Jun 2026 14:32:00 -0400 Subject: [PATCH 1/3] Migrate packaging to pyproject.toml with an SPDX license (#54) - Add pyproject.toml: PEP 621 metadata, requires-python >=3.9, the test extra, project URLs, and a dynamic version read from pystrix.VERSION. - PEP 639 SPDX license: license = "LGPL-3.0-or-later" + license-files for COPYING and COPYING.LESSER. Drop the deprecated License :: classifier. - Pin setuptools>=77 in [build-system] (PEP 639 support; supplied by build isolation). - Move the ruff config from ruff.toml into [tool.ruff]. - Remove setup.py, build-release.py, and ruff.toml. Build with `python -m build`. - Update README and AGENTS.md references (and refresh stale AGENTS notes that predated the test suite and linter), and the changelog. Verified: `python -m build` produces sdist + wheel with the SPDX license and bundled license files; `pip install -e '.[test]'` works; ruff and the 37 tests pass. Closes #54 --- AGENTS.md | 9 ++++--- CHANGELOG.md | 9 +++---- MANIFEST.in | 3 +-- README.md | 2 +- build-release.py | 52 ---------------------------------------- pyproject.toml | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ ruff.toml | 20 ---------------- setup.py | 52 ---------------------------------------- 8 files changed, 73 insertions(+), 136 deletions(-) delete mode 100644 build-release.py create mode 100644 pyproject.toml delete mode 100644 ruff.toml delete mode 100644 setup.py diff --git a/AGENTS.md b/AGENTS.md index 54497d0..b507d04 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,8 +37,7 @@ pystrix/ └── core.py ~45 Action classes (Answer, StreamFile, SayNumber, ...) doc/ Sphinx/reStructuredText docs + runnable examples in doc/examples/ -setup.py Packaging; reads README.md for long_description -build-release.py Release helper +pyproject.toml Packaging (PEP 621) and ruff config; version read from pystrix.VERSION ``` ## Architecture notes @@ -88,8 +87,8 @@ Two bytes/string details at the I/O boundary are not Python 2 baggage and must s ## Working in this repo -- There is no test suite and no linter config. Verify changes against the runnable examples in `doc/examples/` and, where possible, a live Asterisk server. -- `VERSION` in `pystrix/__init__.py` is the single source of truth. `setup.py` imports it. Bump it for releases. -- `setup.py` reads `README.md`. Keep that filename in sync if the README is ever renamed again. +- Run `pytest` for the unit suite, and `ruff check .` / `ruff format --check .` for lint and format. CI enforces all of them across Python 3.9 through 3.13. There is no live-Asterisk integration test, so socket-level changes are still worth checking against a real server. +- `VERSION` in `pystrix/__init__.py` is the single source of truth. `pyproject.toml` reads it dynamically through `[tool.setuptools.dynamic]`. Bump it for releases. +- Packaging is `pyproject.toml` (PEP 621, setuptools backend). Build with `python -m build`. - Docs are Sphinx reStructuredText under `doc/`. Update the relevant `.rst` when adding public actions or events. - Keep inline docstrings complete and in reStructuredText. The project relies on them for its documentation. diff --git a/CHANGELOG.md b/CHANGELOG.md index f39365e..a5f0a93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,20 +12,21 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - A Contributing section and AGI, FastAGI, and AMI quick-start examples in the README. - `.readthedocs.yaml` and `doc/requirements.txt` for reproducible documentation builds. - A GitHub Actions CI workflow that runs the test suite with coverage across Python 3.9 through 3.13, plus a documentation build check. -- A `pytest` unit-test suite covering AMI message parsing and request building, AGI response parsing, and action and helper formatting, with `pytest` and `pytest-cov` in a `test` extra in `setup.py` (`pip install -e '.[test]'`). +- A `pytest` unit-test suite covering AMI message parsing and request building, AGI response parsing, and action and helper formatting, with `pytest` and `pytest-cov` in a `test` extra (`pip install -e '.[test]'`). - Coverage measurement through `pytest-cov`, reported in the CI logs. No coverage data leaves CI. - A CI status badge in the README. -- A curated `ruff` lint configuration (`ruff.toml`), a `.pre-commit-config.yaml`, and a CI lint job. +- A curated `ruff` configuration, a `.pre-commit-config.yaml`, and a CI lint job. - This changelog. ### Changed - Converted `README.rst` to `README.md` and corrected the version and install URL. -- Stated the license as the GNU LGPLv3 or later across the README and `setup.py`. pystrix is not dual-licensed. Both `COPYING` and `COPYING.LESSER` ship because the LGPL extends the GPL. -- Declared Python 3.9+ in `setup.py` through `python_requires` and trove classifiers (3.9 through 3.13), and dropped Python 2 and the end-of-life 3.x entries. +- Stated the license as the GNU LGPLv3 or later across the README and packaging metadata. pystrix is not dual-licensed. Both `COPYING` and `COPYING.LESSER` ship because the LGPL extends the GPL. +- Declared Python 3.9+ through `python_requires` and trove classifiers (3.9 through 3.13), and dropped Python 2 and the end-of-life 3.x entries. - Narrowed the "any platform" claim. The FastAGI server runs on Linux and macOS only, because it reads `SOMAXCONN` with `sysctl`. - Removed the `AUTHORS` file. Provenance now lives in the README, and the contributor list comes from git history and the GitHub contributors page. - Modernized `doc/conf.py` (`exclude_patterns`, Read the Docs theme with a fallback, raw-string regex). - Formatted the whole codebase with `ruff format` (line length 88) and enabled import sorting (ruff's `I` rule). Both are now enforced in pre-commit and CI. A `.git-blame-ignore-revs` file lets `git blame` skip the reformat commit. +- Migrated packaging from `setup.py` to `pyproject.toml` (PEP 621) with a PEP 639 SPDX license expression (`LGPL-3.0-or-later`). Moved the ruff config into `[tool.ruff]`, and removed `setup.py`, `build-release.py`, and `ruff.toml`. Build with `python -m build`; the version is read dynamically from `pystrix.VERSION`. ### Removed - The Python 2 compatibility shims: the `queue` and `socketserver` import fallbacks, the `basestring` branch in `generic_transforms`, and the explicit `(object)` base classes. The codebase is now Python 3 only. diff --git a/MANIFEST.in b/MANIFEST.in index f5ef662..fc49293 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1 @@ -include README.md -include COPYING \ No newline at end of file +include CHANGELOG.md diff --git a/README.md b/README.md index e0ec876..26ce47c 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ A few things to know before you send a change: - **Lint and format with ruff.** `ruff check .` and `ruff format --check .` must pass, and CI enforces both. Install the hooks to run them on each commit: `pip install pre-commit && pre-commit install`. - **Target Python 3.9+.** The codebase is Python 3 only; the old Python 2 compatibility shims have been removed, so don't reintroduce them. - **Build the docs** when you touch them: `pip install -r doc/requirements.txt`, then `cd doc && make html`. -- **Version bumps** go in `pystrix/__init__.py`. `setup.py` reads `VERSION` from there. +- **Version bumps** go in `pystrix/__init__.py`. `pyproject.toml` reads `VERSION` from there dynamically. - **Keep docstrings complete** and written in reStructuredText. The reference docs are generated from them. ## License diff --git a/build-release.py b/build-release.py deleted file mode 100644 index 6eaef15..0000000 --- a/build-release.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python -""" -Release-building script for pystrix. -""" - -import tarfile - -from pystrix import VERSION - - -def _filter(tarinfo, acceptable_extensions): - if tarinfo.name.startswith("."): # Ignore hidden files - return None - - if tarinfo.isdir(): # Directories are good - return tarinfo - - if tarinfo.name.endswith(acceptable_extensions): # It's a file we want - print("\tAdded " + tarinfo.name) - return tarinfo - - return None # DO NOT WANT - - -def python_filter(tarinfo): - return _filter(tarinfo, (".py",)) - - -def doc_filter(tarinfo): - return _filter(tarinfo, (".py", ".rst", "Makefile")) - - -if __name__ == "__main__": - base_name = "pystrix-" + VERSION - archive_name = base_name + ".tar.bz2" - print("Assembling " + archive_name) - f = tarfile.open(name=archive_name, mode="w:bz2") - f.add("COPYING", arcname="%s/COPYING" % base_name) - f.add("COPYING.LESSER", arcname="%s/COPYING.LESSER" % base_name) - print("\tAdded license files") - f.add("README.md", arcname="%s/README.md" % base_name) - f.add("CHANGELOG.md", arcname="%s/CHANGELOG.md" % base_name) - print("\tAdded README and changelog") - f.add("doc", arcname="%s/doc" % base_name, filter=doc_filter) - f.add( - "build-release.py", - arcname="%s/build-release.py" % base_name, - filter=python_filter, - ) - f.add("setup.py", arcname="%s/setup.py" % base_name, filter=python_filter) - f.add("pystrix", arcname="%s/pystrix" % base_name, filter=python_filter) - f.close() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..52eaafe --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,62 @@ +[build-system] +# setuptools >= 77 is required for the PEP 639 SPDX license expression below. +# Build isolation supplies it regardless of the locally installed version. +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[project] +name = "pystrix" +description = "Python bindings for Asterisk Manager Interface and Asterisk Gateway Interface" +readme = "README.md" +requires-python = ">=3.9" +license = "LGPL-3.0-or-later" +license-files = ["COPYING", "COPYING.LESSER"] +authors = [{ name = "Neil Tallim" }] +maintainers = [{ name = "Marta Solano", email = "marta.solano@ivrtechnology.com" }] +keywords = ["asterisk", "ami", "agi", "fastagi", "telephony"] +classifiers = [ + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Communications :: Telephony", +] +dynamic = ["version"] + +[project.optional-dependencies] +test = ["pytest", "pytest-cov"] + +[project.urls] +Homepage = "https://github.com/IVRTech/pystrix" +Documentation = "https://pystrix.readthedocs.io/" +Repository = "https://github.com/IVRTech/pystrix" +Changelog = "https://github.com/IVRTech/pystrix/blob/master/CHANGELOG.md" + +[tool.setuptools] +packages = ["pystrix", "pystrix.agi", "pystrix.ami"] + +[tool.setuptools.dynamic] +version = { attr = "pystrix.VERSION" } + +# Lint and format configuration (moved from ruff.toml). +[tool.ruff] +target-version = "py39" + +[tool.ruff.lint] +# pycodestyle errors, pyflakes, import sorting (isort), and invalid escape +# sequences. Whitespace, indentation, and line length are owned by ruff format. +select = ["E4", "E7", "E9", "F", "I", "W605"] + +[tool.ruff.lint.per-file-ignores] +# The __init__ modules deliberately re-export their public API. +"pystrix/__init__.py" = ["F401"] +"pystrix/agi/__init__.py" = ["F401"] +"pystrix/ami/__init__.py" = ["F401"] +# The AGI wrapper modules intentionally star-import the shared core. +"pystrix/agi/agi.py" = ["F403", "F405"] +"pystrix/agi/fastagi.py" = ["F403", "F405"] diff --git a/ruff.toml b/ruff.toml deleted file mode 100644 index d866ebd..0000000 --- a/ruff.toml +++ /dev/null @@ -1,20 +0,0 @@ -# Lint and format configuration for pystrix. -# -# Formatting is handled by `ruff format` at its default line length of 88. -# When the project moves to pyproject.toml, this config can move there. -target-version = "py39" - -[lint] -# pycodestyle errors, pyflakes, import sorting (isort), and invalid escape -# sequences. The whitespace, indentation, and line-length families are owned by -# `ruff format`. -select = ["E4", "E7", "E9", "F", "I", "W605"] - -[lint.per-file-ignores] -# The __init__ modules deliberately re-export their public API. -"pystrix/__init__.py" = ["F401"] -"pystrix/agi/__init__.py" = ["F401"] -"pystrix/ami/__init__.py" = ["F401"] -# The AGI wrapper modules intentionally star-import the shared core. -"pystrix/agi/agi.py" = ["F403", "F405"] -"pystrix/agi/fastagi.py" = ["F403", "F405"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 6dbfd47..0000000 --- a/setup.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python -""" -Deployment script for pystrix. -""" - -import os - -from setuptools import setup - -from pystrix import VERSION - -CLASSIFIERS = [ - "Intended Audience :: Developers", - "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Topic :: Communications :: Telephony", -] - -README = open(os.path.join(os.path.dirname(__file__), "README.md")).read() - -# allow setup.py to be run from any path -os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) - -setup( - author="Marta Solano", - author_email="marta.solano@ivrtechnology.com", - name="pystrix", - version=VERSION, - description="Python bindings for Asterisk Manager Interface and Asterisk Gateway Interface", - long_description=README, - long_description_content_type="text/markdown", - url="https://github.com/IVRTech/pystrix", - license="GNU Lesser General Public License v3 or later", - platforms=["OS Independent"], - classifiers=CLASSIFIERS, - python_requires=">=3.9", - extras_require={ - "test": ["pytest", "pytest-cov"], - }, - packages=[ - "pystrix", - "pystrix.agi", - "pystrix.ami", - ], -) From ddf36ef5f21c927d4739c03332de824125c9a655 Mon Sep 17 00:00:00 2001 From: Karthic Raghupathi Date: Wed, 24 Jun 2026 14:40:30 -0400 Subject: [PATCH 2/3] Set maintainer to IVR Technology Group (org), no email Per maintainer decision: the package metadata names the maintaining organization rather than enumerating individuals (who stay credited via the GitHub contributors graph and README). No emails, so no contributor PII is published. Author remains Neil Tallim. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 52eaafe..115d467 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ requires-python = ">=3.9" license = "LGPL-3.0-or-later" license-files = ["COPYING", "COPYING.LESSER"] authors = [{ name = "Neil Tallim" }] -maintainers = [{ name = "Marta Solano", email = "marta.solano@ivrtechnology.com" }] +maintainers = [{ name = "IVR Technology Group" }] keywords = ["asterisk", "ami", "agi", "fastagi", "telephony"] classifiers = [ "Intended Audience :: Developers", From 9faa660b3c21143f56d4de7302e1692985df0b95 Mon Sep 17 00:00:00 2001 From: Karthic Raghupathi Date: Wed, 24 Jun 2026 14:50:17 -0400 Subject: [PATCH 3/3] Address review panel: CI package job + drop stale changelog entry - Add a `package` CI job that runs `python -m build`, validates metadata with `twine check`, installs the wheel, and imports the package. The packaging migration's main regression surface was previously unguarded in CI (codex finding). - Remove the stale `build-release.py` Fixed changelog entry: it credited a fix to a script this branch deletes, contradicting the migration entry in the same Unreleased block (consistency finding). The dynamic-version reorder suggestion was dropped: codex verified the static AST attr resolution returns the version without importing the package, so no change is needed. --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ CHANGELOG.md | 1 - 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8db09ae..80f52a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,3 +61,24 @@ jobs: pip install -r doc/requirements.txt - name: Build docs with warnings as errors run: sphinx-build -W --keep-going -b html doc doc/_build/html + + package: + name: Package build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Build sdist and wheel + run: | + python -m pip install build + python -m build + - name: Validate metadata and install the wheel + run: | + python -m pip install twine + twine check dist/* + python -m pip install dist/*.whl + python -c "import pystrix; print('installed', pystrix.VERSION)" diff --git a/CHANGELOG.md b/CHANGELOG.md index a5f0a93..09a51aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,6 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Applied safe lint fixes surfaced by ruff: `not x is`/`not x in` rewritten to `x is not`/`x not in`, removed unused imports, and turned two invalid escape sequences (`\d`, `\*`) into raw strings. Intentional re-exports are marked with targeted ignores. - `_Request.build_request` now honors its documented ActionID precedence: an explicit argument wins, then a value already set on the request, then a generated one. Previously a pre-set ActionID was dropped when no argument was passed, and it overrode an explicit argument. The resolved ActionID is also coerced to a string so a pre-set non-string value matches Asterisk's responses (#43). - Replaced the removed `cgi.urlparse.parse_qs` in `pystrix/agi/fastagi.py` with `urllib.parse.parse_qs`. This fixes FastAGI query-string parsing on all Python 3 and restores Python 3.13 support (#36). -- `build-release.py` no longer fails on the missing `pystrix.spec`. The RPM packaging path was removed, and the release tarball now ships `README.md` and `CHANGELOG.md` so a build from the sdist can read the long description. - Corrected the `_Response.time` description in the AMI docs and fixed several typos. - Stopped tracking `doc/_build/` output and macOS `._` metadata files, and fixed the `.gitignore` rule that let them in.