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/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..09a51aa 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. @@ -35,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. 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..115d467 --- /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 = "IVR Technology Group" }] +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", - ], -)