diff --git a/docs/contributing.rst b/docs/contributing.rst index 43a8d2c22..74462ea49 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -27,6 +27,28 @@ Clone you github fork repo locally and install errbot in development mode from t From there, anytime you execute `errbot` it will run from the checked out version of Errbot with all your local changes taken into account. +Updating the dependency lockfile +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Errbot ships a pinned dependency lockfile (``pylock.toml``) in `PEP 751`_ format, +generated by uv_ from the top-level dependencies declared in ``pyproject.toml``. +The lockfile is the source of truth for reproducible installs in CI and for +downstream consumers that want exact versions. + +Regenerate the lockfile any time you change a dependency in ``pyproject.toml`` (or +to refresh the pinned versions). From the root of the repository:: + + # Install uv if you don't have it (https://docs.astral.sh/uv/) + pip install uv + + # Recompile the lockfile from pyproject.toml, pinned to the project's + # minimum supported Python version so the resolution covers 3.10+. + uv pip compile pyproject.toml -o pylock.toml \ + --format pylock.toml --python-version 3.10 + +Commit the resulting ``pylock.toml`` alongside your ``pyproject.toml`` change in +the same pull request so the two stay in sync. + Preparing your pull request ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -42,6 +64,56 @@ these guidelines as you open a pull request. * If you can, please add tests for your code. We know large parts of our codebase are missing tests, so we won't reject your code if it lacks tests, though. +Releasing Errbot +---------------- + +This section is for maintainers cutting a release. The flow assumes a CHANGES.rst +maintained by hand under a ``v9.9.9 (unreleased)`` heading on master. + +Cutting a release +~~~~~~~~~~~~~~~~~ + +1. **Open a release PR** that does two edits: + + - Bump ``errbot/version.py``:: + + VERSION = "X.Y.Z" + + - Rename the unreleased heading in ``CHANGES.rst`` from ``v9.9.9 (unreleased)`` + to ``vX.Y.Z (YYYY-MM-DD)`` and double-check the entries beneath it. + +2. **Let CI gate the change.** The test ``tests/release_metadata_test.py`` skips on the + ``9.9.9`` sentinel but, once ``version.py`` is bumped, asserts that ``CHANGES.rst`` + contains a section heading for the new version. A release PR that forgets the heading + rename will fail this test before it can merge. + +3. **Merge the release PR**, then run the release tooling from a clean checkout:: + + ./tools/releases.sh + + The script reruns the same pre-release gate, builds the sdist/wheel with + ``python -m build``, and calls ``twine`` to publish to PyPI. + +4. **Tag the commit** and push the tag:: + + git tag vX.Y.Z + git push origin vX.Y.Z + +5. **Return master to dev mode** in a follow-up PR: + + - Reset ``errbot/version.py`` to ``VERSION = "9.9.9"``. + - Add a fresh ``v9.9.9 (unreleased)`` heading at the top of ``CHANGES.rst``. + +After this, the gate test goes back to skipping on master and the next release +cycle starts. + +Refreshing the lockfile for a release +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If dependency pins have changed since the previous release, regenerate +``pylock.toml`` (see `Updating the dependency lockfile`_) and include the result +in the release PR. + Contributing documentation & making changes to the website ---------------------------------------------------------- @@ -94,3 +166,5 @@ to ask your question on Stack Overflow, `tagged errbot .. _repos.py: https://github.com/errbotio/errbot/blob/master/errbot/repos.py .. _`issue tracker`: https://github.com/errbotio/errbot/issues/ .. _Gitter: https://gitter.im/errbotio/errbot +.. _uv: https://docs.astral.sh/uv/ +.. _`PEP 751`: https://peps.python.org/pep-0751/ diff --git a/pylock.toml b/pylock.toml new file mode 100644 index 000000000..01859dff9 --- /dev/null +++ b/pylock.toml @@ -0,0 +1,170 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml -o pylock.toml --format pylock.toml --python-version 3.10 +lock-version = "1.0" +created-by = "uv" +requires-python = ">=3.10" + +[[packages]] +name = "ansi" +version = "0.3.7" +sdist = { url = "https://files.pythonhosted.org/packages/ed/58/9444d8e465858ae9cb2096aa48236ab5760ea9dbaac58e609f3b461776de/ansi-0.3.7.tar.gz", upload-time = 2024-01-22T00:52:48Z, size = 7926, hashes = { sha256 = "7e59108922259e03c54e4d93fc611bba0e756513086849708b86b6c80f8d4cd4" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/0e/44/8244b092c39aa39cb61afcb5d64f4a2a22bf9ab291d5f689a9406d158d04/ansi-0.3.7-py3-none-any.whl", upload-time = 2024-01-22T00:52:46Z, size = 9000, hashes = { sha256 = "bdd9e3c2dc3e4c8df8c2b745ca6f07f715aa4edee5ed4a5bcb29065da06a3f71" } }] + +[[packages]] +name = "blinker" +version = "1.9.0" +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", upload-time = 2024-11-08T17:25:47Z, size = 22460, hashes = { sha256 = "b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", upload-time = 2024-11-08T17:25:46Z, size = 8458, hashes = { sha256 = "ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc" } }] + +[[packages]] +name = "certifi" +version = "2026.4.22" +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", upload-time = 2026-04-22T11:26:11Z, size = 137077, hashes = { sha256 = "8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", upload-time = 2026-04-22T11:26:09Z, size = 135707, hashes = { sha256 = "3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a" } }] + +[[packages]] +name = "cffi" +version = "2.0.0" +marker = "python_full_version >= '3.9' and platform_python_implementation != 'PyPy'" +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", upload-time = 2025-09-08T23:24:04Z, size = 523588, hashes = { sha256 = "44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-09-08T23:22:10Z, size = 180504, hashes = { sha256 = "f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49" } }] + +[[packages]] +name = "charset-normalizer" +version = "3.4.7" +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", upload-time = 2026-04-02T09:28:39Z, size = 144271, hashes = { sha256 = "ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", upload-time = 2026-04-02T09:25:40Z, size = 315182, hashes = { sha256 = "cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d" } }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", upload-time = 2026-04-02T09:28:37Z, size = 61958, hashes = { sha256 = "3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d" } }, +] + +[[packages]] +name = "click" +version = "8.1.8" +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", upload-time = 2024-12-21T18:38:44Z, size = 226593, hashes = { sha256 = "ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", upload-time = 2024-12-21T18:38:41Z, size = 98188, hashes = { sha256 = "63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2" } }] + +[[packages]] +name = "colorlog" +version = "6.10.1" +sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", upload-time = 2025-10-16T16:14:11Z, size = 17162, hashes = { sha256 = "eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", upload-time = 2025-10-16T16:14:10Z, size = 11743, hashes = { sha256 = "2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c" } }] + +[[packages]] +name = "cryptography" +version = "47.0.0" +sdist = { url = "https://files.pythonhosted.org/packages/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", upload-time = 2026-04-24T19:54:57Z, size = 830863, hashes = { sha256 = "9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/e0/34/a4fae8ae7c3bc227460c9ae43f56abf1b911da0ec29e0ebac53bb0a4b6b7/cryptography-47.0.0-cp38-abi3-macosx_10_9_universal2.whl", upload-time = 2026-04-24T19:54:06Z, size = 7904072, hashes = { sha256 = "14432c8a9bcb37009784f9594a62fae211a2ae9543e96c92b2a8e4c3cd5cd0c4" } }] + +[[packages]] +name = "daemonize" +version = "2.5.0" +marker = "sys_platform != 'win32'" +sdist = { url = "https://files.pythonhosted.org/packages/8c/20/96f7dbc23812cfe4cf479c87af3e4305d0d115fd1fffec32ddeee7b9c82b/daemonize-2.5.0.tar.gz", upload-time = 2018-12-12T19:47:39Z, size = 8759, hashes = { sha256 = "dd026e4ff8d22cb016ed2130bc738b7d4b1da597ef93c074d2adb9e4dea08bc3" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/45/ad/1b20db02287afd40d3130a218ac5ce2f7d2ab581cfda29bada5e1c4bee17/daemonize-2.5.0-py2.py3-none-any.whl", upload-time = 2018-12-12T19:47:37Z, size = 5231, hashes = { sha256 = "9b6b91311a9d934ff3f5f766666635ca280d3de8e7137e4cd7d3f052543b989f" } }] + +[[packages]] +name = "deepmerge" +version = "2.0" +sdist = { url = "https://files.pythonhosted.org/packages/a8/3a/b0ba594708f1ad0bc735884b3ad854d3ca3bdc1d741e56e40bbda6263499/deepmerge-2.0.tar.gz", upload-time = 2024-08-30T05:31:50Z, size = 19890, hashes = { sha256 = "5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", upload-time = 2024-08-30T05:31:48Z, size = 13475, hashes = { sha256 = "6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00" } }] + +[[packages]] +name = "dulwich" +version = "1.2.1" +sdist = { url = "https://files.pythonhosted.org/packages/72/0f/46df53e30b03cc8fee9d1bbd7ca624b4d1b579ce2e4efeaa1cb712d119b0/dulwich-1.2.1.tar.gz", upload-time = 2026-04-29T15:12:19Z, size = 1223320, hashes = { sha256 = "ba43bfb3a7cad40d9607170561e8c3be42e7083b4b57af89a5f54e01577ff791" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/4c/4a505c6d0597666e5842d73c4d6b62c4325d47742fc6b881027070394366/dulwich-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2026-04-29T15:11:09Z, size = 1320113, hashes = { sha256 = "e88fd960a9327d87556a3bad76ad84b6346d3a07409fe8f081877a775977c86b" } }, + { url = "https://files.pythonhosted.org/packages/08/55/f5470846eb8bdbf204f1c1b47c3d25f542fe4a0134f027c672498fb6e0d3/dulwich-1.2.1-py3-none-any.whl", upload-time = 2026-04-29T15:12:18Z, size = 674611, hashes = { sha256 = "1961e0b6c0b1f2920f4ab05821652d8eb12f19ddb5a4c167c385902391c08dd3" } }, +] + +[[packages]] +name = "flask" +version = "3.1.3" +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", upload-time = 2026-02-19T05:00:57Z, size = 759004, hashes = { sha256 = "0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", upload-time = 2026-02-19T05:00:56Z, size = 103424, hashes = { sha256 = "f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c" } }] + +[[packages]] +name = "idna" +version = "3.13" +sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", upload-time = 2026-04-22T16:42:42Z, size = 194210, hashes = { sha256 = "585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", upload-time = 2026-04-22T16:42:40Z, size = 68629, hashes = { sha256 = "892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3" } }] + +[[packages]] +name = "itsdangerous" +version = "2.2.0" +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", upload-time = 2024-04-16T21:28:15Z, size = 54410, hashes = { sha256 = "e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", upload-time = 2024-04-16T21:28:14Z, size = 16234, hashes = { sha256 = "c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef" } }] + +[[packages]] +name = "jinja2" +version = "3.1.6" +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", upload-time = 2025-03-05T20:05:02Z, size = 245115, hashes = { sha256 = "0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", upload-time = 2025-03-05T20:05:00Z, size = 134899, hashes = { sha256 = "85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" } }] + +[[packages]] +name = "markdown" +version = "3.10.2" +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", upload-time = 2026-02-09T14:57:26Z, size = 368805, hashes = { sha256 = "994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", upload-time = 2026-02-09T14:57:25Z, size = 108180, hashes = { sha256 = "e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36" } }] + +[[packages]] +name = "markupsafe" +version = "3.0.3" +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", upload-time = 2025-09-27T18:37:40Z, size = 80313, hashes = { sha256 = "722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-09-27T18:36:07Z, size = 12057, hashes = { sha256 = "e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419" } }] + +[[packages]] +name = "pycparser" +version = "2.23" +marker = "python_full_version >= '3.9' and implementation_name != 'PyPy' and platform_python_implementation != 'PyPy'" +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", upload-time = 2025-09-09T13:23:47Z, size = 173734, hashes = { sha256 = "78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", upload-time = 2025-09-09T13:23:46Z, size = 118140, hashes = { sha256 = "e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934" } }] + +[[packages]] +name = "pygments" +version = "2.20.0" +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", upload-time = 2026-03-29T13:29:33Z, size = 4955991, hashes = { sha256 = "6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", upload-time = 2026-03-29T13:29:30Z, size = 1231151, hashes = { sha256 = "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176" } }] + +[[packages]] +name = "pygments-markdown-lexer" +version = "0.1.0.dev39" +sdist = { url = "https://files.pythonhosted.org/packages/c3/12/674cdee66635d638cedb2c5d9c85ce507b7b2f91bdba29e482f1b1160ff6/pygments-markdown-lexer-0.1.0.dev39.zip", upload-time = 2015-07-06T11:08:10Z, size = 28039, hashes = { sha256 = "4c128c26450b5886521c674d759f95fc3768b8955a7d9c81866ee0213c2febdf" } } + +[[packages]] +name = "pyopenssl" +version = "26.1.0" +sdist = { url = "https://files.pythonhosted.org/packages/8c/a8/26d36401e3ab8eed9030ad33f381da7856fcfad5691780fccd1b019718fc/pyopenssl-26.1.0.tar.gz", upload-time = 2026-04-24T20:23:48Z, size = 186181, hashes = { sha256 = "737f0a2275c5bc54f3b02137687e1a765931fb3949b9a92a825e4d33b9eec08b" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/a8/41/52f3a3e812b816a91e89aa504199d8bf989a1f873192b10762be66cf2009/pyopenssl-26.1.0-py3-none-any.whl", upload-time = 2026-04-24T20:23:46Z, size = 58109, hashes = { sha256 = "115563879b2c8ccb207975705d3e491434d8c9d7c79667c902ecbf5f3bbd2ece" } }] + +[[packages]] +name = "requests" +version = "2.32.5" +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", upload-time = 2025-08-18T20:46:02Z, size = 134517, hashes = { sha256 = "dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", upload-time = 2025-08-18T20:46:00Z, size = 64738, hashes = { sha256 = "2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6" } }] + +[[packages]] +name = "setuptools" +version = "82.0.1" +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", upload-time = 2026-03-09T12:47:17Z, size = 1152316, hashes = { sha256 = "7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", upload-time = 2026-03-09T12:47:15Z, size = 1006223, hashes = { sha256 = "a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb" } }] + +[[packages]] +name = "typing-extensions" +version = "4.15.0" +marker = "python_full_version < '3.13'" +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", upload-time = 2025-08-25T13:49:26Z, size = 109391, hashes = { sha256 = "0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", upload-time = 2025-08-25T13:49:24Z, size = 44614, hashes = { sha256 = "f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" } }] + +[[packages]] +name = "urllib3" +version = "2.6.3" +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", upload-time = 2026-01-07T16:24:43Z, size = 435556, hashes = { sha256 = "1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", upload-time = 2026-01-07T16:24:42Z, size = 131584, hashes = { sha256 = "bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4" } }] + +[[packages]] +name = "werkzeug" +version = "3.1.8" +sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", upload-time = 2026-04-02T18:49:14Z, size = 875852, hashes = { sha256 = "9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", upload-time = 2026-04-02T18:49:12Z, size = 226459, hashes = { sha256 = "63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50" } }] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..99fdc834a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,86 @@ +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[project] +name = "errbot" +dynamic = ["version", "readme"] +description = "Errbot is a chatbot designed to be simple to extend with plugins written in Python." +authors = [{name = "errbot.io", email = "info@errbot.io"}] +license = "GPL-3.0-or-later" +license-files = ["COPYING"] +requires-python = ">=3.10" +keywords = ["xmpp", "irc", "slack", "hipchat", "gitter", "tox", "chatbot", "bot", "plugin", "chatops"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Topic :: Communications :: Chat", + "Topic :: Communications :: Chat :: Internet Relay Chat", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +dependencies = [ + "setuptools>=78.1.1", + "flask==3.1.3", + "requests==2.32.5", + "jinja2==3.1.6", + "pyOpenSSL==26.1.0", + "colorlog==6.10.1", + "markdown==3.10.2", + "ansi==0.3.7", + "Pygments==2.20.0", + "pygments-markdown-lexer==0.1.0.dev39", + "dulwich==1.2.1", + "deepmerge==2.0", + 'daemonize==2.5.0; sys_platform != "win32"', +] + +[project.optional-dependencies] +slack = ["errbot-backend-slackv3==0.3.2"] +discord = ["err-backend-discord==3.0.1"] +mattermost = ["err-backend-mattermost==3.0.0"] +IRC = ["irc==20.5.0"] +telegram = ["python-telegram-bot==13.15"] +XMPP = [ + "slixmpp==1.12.0; python_version < '3.11'", + "slixmpp==1.15.0; python_version >= '3.11'", + "pyasn1==0.6.3", + "pyasn1-modules==0.4.2", +] +test = ["pytest", "requests"] + +[project.urls] +Homepage = "http://errbot.io/" + +[project.scripts] +errbot = "errbot.cli:main" + +[tool.setuptools.dynamic] +version = {attr = "errbot.version.VERSION"} +readme = {file = ["README.rst", "CHANGES.rst"], content-type = "text/x-rst"} + +[tool.setuptools.packages.find] +include = ["errbot", "errbot.*"] + +[tool.setuptools.package-data] +errbot = [ + "backends/*.plug", + "backends/*.html", + "backends/styles/*.css", + "backends/images/*.svg", + "core_plugins/*.plug", + "core_plugins/*.md", + "core_plugins/templates/*.md", + "storage/*.plug", + "templates/initdir/example.py", + "templates/initdir/example.plug", + "templates/initdir/config.py.tmpl", + "templates/*.md", + "templates/new_plugin.py.tmpl", +] + +[tool.ruff.lint.per-file-ignores] +"tests/borken_plugin/broken.py" = ["F401"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 15fa4ccb8..000000000 --- a/setup.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env python - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import os -import sys - -from setuptools import find_packages, setup - -py_version = sys.version_info[:2] - -if py_version < (3, 10): - raise RuntimeError("Errbot requires Python 3.10 or later") - -VERSION_FILE = os.path.join("errbot", "version.py") - -deps = [ - "setuptools>=78.1.1", - "flask==3.1.3", - "requests==2.32.5", - "jinja2==3.1.6", - "pyOpenSSL==26.1.0", - "colorlog==6.10.1", - "markdown==3.10.2", - "ansi==0.3.7", - "Pygments==2.20.0", - "pygments-markdown-lexer==0.1.0.dev39", # syntax coloring to debug md - "dulwich==1.2.1", # python implementation of git - "deepmerge==2.0", -] - -src_root = os.curdir - - -def read_version(): - """ - Read directly the errbot/version.py and gives the version without loading Errbot. - :return: errbot.version.VERSION - """ - - variables = {} - with open(VERSION_FILE) as f: - exec(compile(f.read(), "version.py", "exec"), variables) - return variables["VERSION"] - - -def read(fname, encoding="ascii"): - return open( - os.path.join(os.path.dirname(__file__), fname), "r", encoding=encoding - ).read() - - -if __name__ == "__main__": - VERSION = read_version() - - args = set(sys.argv) - - changes = read("CHANGES.rst", "utf8") - - if changes.find(VERSION) == -1: - raise Exception("You forgot to put a release note in CHANGES.rst ?!") - - if args & {"bdist", "bdist_dumb", "bdist_rpm", "bdist_wininst", "bdist_msi"}: - raise Exception("err doesn't support binary distributions") - - packages = find_packages(src_root, include=["errbot", "errbot.*"]) - - setup( - name="errbot", - version=VERSION, - packages=packages, - entry_points={ - "console_scripts": [ - "errbot = errbot.cli:main", - ] - }, - install_requires=deps, - tests_require=["nose", "requests"], - package_data={ - "errbot": [ - "backends/*.plug", - "backends/*.html", - "backends/styles/*.css", - "backends/images/*.svg", - "core_plugins/*.plug", - "core_plugins/*.md", - "core_plugins/templates/*.md", - "storage/*.plug", - "templates/initdir/example.py", - "templates/initdir/example.plug", - "templates/initdir/config.py.tmpl", - "templates/*.md", - "templates/new_plugin.py.tmpl", - ], - }, - extras_require={ - "slack": [ - "errbot-backend-slackv3==0.3.2", - ], - "discord": [ - # held at 3.0.1: 4.0 is a major bump on external backend; compat unverified - "err-backend-discord==3.0.1", - ], - "mattermost": [ - "err-backend-mattermost==3.0.0", - ], - "IRC": [ - "irc==20.5.0", - ], - "telegram": [ - # held at 13.15: v20+ is fully async; backend would need a rewrite - "python-telegram-bot==13.15", - ], - "XMPP:python_version < '3.11'": [ - "slixmpp==1.12.0", - "pyasn1==0.6.3", - "pyasn1-modules==0.4.2", - ], - "XMPP:python_version >= '3.11'": [ - "slixmpp==1.15.0", - "pyasn1==0.6.3", - "pyasn1-modules==0.4.2", - ], - ':sys_platform!="win32"': ["daemonize==2.5.0"], - }, - author="errbot.io", - author_email="info@errbot.io", - description="Errbot is a chatbot designed to be simple to extend with plugins written in Python.", - long_description_content_type="text/x-rst", - long_description="".join([read("README.rst"), "\n\n", changes]), - license="GPL", - keywords="xmpp irc slack hipchat gitter tox chatbot bot plugin chatops", - url="http://errbot.io/", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Topic :: Communications :: Chat", - "Topic :: Communications :: Chat :: Internet Relay Chat", - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - ], - src_root=src_root, - platforms="any", - ) diff --git a/tests/release_metadata_test.py b/tests/release_metadata_test.py new file mode 100644 index 000000000..3e27bf080 --- /dev/null +++ b/tests/release_metadata_test.py @@ -0,0 +1,38 @@ +"""Release-time gate. + +When ``errbot/version.py`` has been bumped past the dev sentinel ``9.9.9``, +``CHANGES.rst`` must contain a section heading for that version. On master +the sentinel keeps this test skipped; it only fires once a release is being +prepared (or a release branch is checked out), at which point it forces the +maintainer to update CHANGES.rst before the tag goes out. + +This replaces the install-time check that lived in setup.py. +""" +import re +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parent.parent +DEV_SENTINEL = "9.9.9" + + +def _read_version() -> str: + """Read VERSION from errbot/version.py without importing the package.""" + ns: dict = {} + exec((REPO_ROOT / "errbot" / "version.py").read_text(), ns) + return ns["VERSION"] + + +def test_changes_rst_has_section_for_current_version(): + version = _read_version() + if version == DEV_SENTINEL: + pytest.skip(f"dev sentinel {DEV_SENTINEL} — no CHANGES.rst entry expected") + + changes = (REPO_ROOT / "CHANGES.rst").read_text() + pattern = rf"^v?{re.escape(version)}\b" + assert re.search(pattern, changes, re.MULTILINE), ( + f"CHANGES.rst is missing a section heading for version {version}. " + f"Rename the 'v{DEV_SENTINEL} (unreleased)' heading to the new " + "version, or add a new release section, before tagging." + ) diff --git a/tools/README.md b/tools/README.md index bc201fb15..02a82f810 100644 --- a/tools/README.md +++ b/tools/README.md @@ -18,12 +18,12 @@ These are support tools for the project - creates a temporary directory, clones the repository, builds the python package, and prepares multi-arch docker images. - **Requirements:** - `git` - - `pipenv` + - `uv` - `python 3.12` - `podman` (for docker image builds) - **Execution (macOS):** 1. **Pre-requisite:** Open `tools/releases.sh` and update the `RELEASE`, `BRANCH`, and `PYTHON_VERSION` variables to match the target release. - 2. Ensure you have the requirements installed (e.g., `brew install pipenv podman`). + 2. Ensure you have the requirements installed (e.g., `brew install uv podman`). 3. Make the script executable: `chmod +x tools/releases.sh` 4. Run the script from the project root: `./tools/releases.sh` 5. Follow the manual steps printed at the end of the script to complete the publication. diff --git a/tools/releases.sh b/tools/releases.sh index cbe653964..774e20759 100755 --- a/tools/releases.sh +++ b/tools/releases.sh @@ -36,13 +36,14 @@ pushd errbot git checkout ${BRANCH} header "pypi build" -pipenv --python ${PYTHON_VERSION} -pipenv run pip3 install pytest twine build +uv venv --python ${PYTHON_VERSION} +source .venv/bin/activate +uv pip install pytest twine build #header "pre-release gate (version <-> CHANGES.rst)" -#pipenv run python3 -m pytest tests/release_metadata_test.py -v +#python3 -m pytest tests/release_metadata_test.py -v -pipenv run python3 -m build +python3 -m build header "Building multi-arch docker images..." podman rmi -f errbotio/errbot:test 2>/dev/null || true @@ -50,10 +51,10 @@ podman manifest rm errbotio/errbot:test 2>/dev/null || true podman build --platform linux/amd64,linux/arm64 --manifest errbotio/errbot:test -f Dockerfile . header "Checking and uploading Python package..." -pipenv run twine check dist/* +twine check dist/* header "Manual: publish pypi and docker" -echo pipenv run twine upload dist/* +echo twine upload dist/* echo podman build --platform linux/amd64,linux/arm64 --manifest errbotio/errbot:${RELEASE} -f Dockerfile . echo podman manifest push errbotio/errbot:${RELEASE} docker://docker.io/errbotio/errbot:${RELEASE} echo git tag v${RELEASE} diff --git a/tox.ini b/tox.ini index a4e0e7fe4..d17e4032f 100644 --- a/tox.ini +++ b/tox.ini @@ -17,9 +17,10 @@ commands = [testenv:dist-check] deps = + build twine commands = - python setup.py sdist + python -m build --sdist twine check {toxinidir}/dist/* [testenv:security]