{ "cells": [ { "cell_type": "markdown", "id": "62735494", "metadata": {}, "source": [ "# Make a Release" ] }, { "cell_type": "markdown", "id": "d7153c4a", "metadata": {}, "source": [ "Releases are a key step in the development of a project. A release tells to the world that a new version of your work is available.\n", "\n", "You cannot take a release back: if you made a mistake, you will have to make another release to patch your errors. It is therefore important to take your time when you craft a release." ] }, { "cell_type": "markdown", "id": "0203db95", "metadata": {}, "source": [ "## Bump dependencies in your `pyproject.toml` " ] }, { "cell_type": "markdown", "id": "dc11f759", "metadata": {}, "source": [ "Unless stated otherwise, Poetry uses [carets](https://python-poetry.org/docs/dependency-specification/#caret-requirements) to specify requirements. This basically claims that your package is compatible with new versions of your dependencies as long as these versions do not change too much.\n", "\n", "The caret system avoids the burden to constantly update your `pyproject.toml`. However, there is always a moment when your package become incompatible with the latest version of one dependency. Incompatibility can be semantic (e.g. the latest version is outside the caret range) or real (the latest version does not behave like you expect it to do).\n", "\n", "Being incompatible with latest versions will not stop anyone from installing your package in a dedicated environment (docker, `venv`, etc) where the version of each dependency is controlled by `poetry.lock`. However, it will be harder and harder for users to use it in their main Python environment. For example, imagine that the `numpy` requirement of your package is `^1.22.4` (e.g. `>=1.22.4` and `<1.23`) and that the latest Pandas requires `numpy>=1.23.2`. This means that everytime you install your package in its latest version you will have to downgrade Pandas, and conversely.\n", "\n", "This is why you should regularly bump the versions of our dependencies, and releases are a good opportunity to do that. Forcing users to upgrade to more recent (stable) versions of dependencies is always preferred to forcing downgrades." ] }, { "cell_type": "markdown", "id": "06b3ecb9", "metadata": {}, "source": [ "If you followed all the steps of the tutorial and installed the Poetry plugin `up`, just enter (inside your project, for example in a PyCharm terminal):\n", "\n", "```console\n", "$ poetry up\n", "```" ] }, { "cell_type": "markdown", "id": "ce97a497", "metadata": {}, "source": [ "### [optional] Manual upgrade" ] }, { "cell_type": "markdown", "id": "2794ad04", "metadata": {}, "source": [ "Optionally, you can look inside `pyproject.toml`. If you see non-caret requirements that you want to bump, you can try to force the update manually with:\n", "\n", "```console\n", "$ poetry add package-name@latest\n", "```\n", "\n", "Poetry will complain if there are semantic issues, possibly giving you suggestions to address them. " ] }, { "cell_type": "markdown", "id": "f2087a21", "metadata": {}, "source": [ "### Check you didn't break anything" ] }, { "cell_type": "markdown", "id": "c748cdb4", "metadata": {}, "source": [ "After `pyproject.toml` is updated, refresh `poetry.lock` with:\n", "\n", "```console\n", "$ poetry update\n", "```\n", "\n", "Run your tests both locally and remotely. If your package is broken:\n", "\n", "- Fix it if the error is obvious.\n", "- If not, live to fight another day:\n", " - Revert your git branch to the last commit before you did `poetry up`. PyCharm has a nice graphical display of git branches that should make it easy.\n", " - Refresh `poetry.lock` with a `poetry update`.\n", " - Check that your tests are back on track (locally and remotely).\n", " - After the current release is done, you should prepare a dedicated release to make your package compatible with latest versions. If possible, preserve backward-compatibility, but not at all cost." ] }, { "cell_type": "markdown", "id": "b5801530", "metadata": {}, "source": [ "## Make it easy to use" ] }, { "cell_type": "markdown", "id": "8090778c", "metadata": {}, "source": [ "- In the main `__init__.py`, import the classes that you believe users will use on a regular basis. `from my_package import MyClass` is easier to use than `from my_package.subpackage.my_class import MyClass`.\n", "- If you have some notebooks in your documentation (e.g. tutorials), read and run them one last time to check they work and they are relevant.\n", "- Check that the reference section of your documentation is consistent with your actual code, in particular, check that new classes/methods are documented (Sphinx will not complain if they are not).\n", "\n", "To reference a class:\n", "\n", "```rst\n", ".. autoclass:: my_first_ph3_package.MyClass3\n", " :members:\n", "```\n", "\n", "To reference a module (all the content of a Python file):\n", "\n", "```rst\n", ".. automodule:: my_first_ph3_package.cli\n", " :members:\n", "```\n" ] }, { "cell_type": "markdown", "id": "cb47bb1b", "metadata": {}, "source": [ "## Bulletproof your tests and documentation" ] }, { "cell_type": "markdown", "id": "bf50e849", "metadata": {}, "source": [ "- Be sure that your tests are running OK, both locally in PyCharm and remotely (GitHub actions).\n", "- Check your coverage (locally in `cov/index.html` and/or on [Codecov](https://app.codecov.io/gh/)) and add tests if necessary.\n", " - It is usually recommended to maintain at least a 70%-80% coverage, but some people see the coverage badge as a motivation to maintain 100%.\n", " - Coverage is here to give **you** some (relative) confidence that your code will do what you expect, and to ease pinpointing issues when/before they occur. It is not a rating.\n", " - In particular, don't abuse of `# pragma: no cover` (a magic comment to exclude parts of your code from the coverage statistics) to artificially reach a high coverage.\n", "- Generate the documentation (in PyCharm and/or with GitHub actions) in order to check that it is working.\n", "- If Sphinx issue some warnings, investigate them as it is usually a sign you did something wrong.\n", "- Take some time to browse the resulting documentation as if you were a regular user. Are you happy with it?" ] }, { "cell_type": "markdown", "id": "4e0c9019", "metadata": {}, "source": [ "## Finalize your release" ] }, { "cell_type": "markdown", "id": "55b001c5", "metadata": {}, "source": [ "- Bump the version of your project (unless it is your first release). In PyCharm's terminal:\n", " - `poetry version patch` (version x.y.z → x.y.(z+1)) when you made a backwards-compatible modification (such as a bug fix).\n", " - `poetry version minor` (version x.y.z → x.(y+1).0) when you added a functionality.\n", " - `poetry version major` (version x.y.z → (x+1).0.0) when you changed the API. Note: in versions 0.y.z, the API is not expected to be stable anyway.\n", "- Update the file `HISTORY.rst`:\n", " - Follow the pattern of previous entries:\n", " - Title enclosed in dashes lines (`------------`).\n", " - The two dashes lines should never be shorter than your title.\n", " - The title should have the version, date of release, and short description.\n", " - Enter release notes (changes) in short items (lines starting with `*`).\n", " - Stick to pure `.rst` syntax: never use Sphinx' specific directives such as ``:class:`MyClass` ``.\n", "- Commit/push.\n", "- If you were working on a secondary branch, do what you have to do: pull request to \"main\" or \"master\", etc.\n", "- Ensure that you are now in your default git branch (\"main\" or \"master\").\n", "- Wait for the last GitHub actions to finish." ] }, { "cell_type": "markdown", "id": "5c2c27f0", "metadata": {}, "source": [ "## Publish" ] }, { "cell_type": "markdown", "id": "d7f09005", "metadata": {}, "source": [ "On [GitHub's website](https://github.com/) go to your project:\n", "\n", "- \"Releases\" → \"Create a new release\".\n", "- \"Choose a tag\" → the version you publish prefixed with the letter \"v\" (e.g. `v0.1.0`) → \"Create new tag\"\n", "- Add a release title as in `HISTORY.rst`, e.g. `First release`.\n", "- Add release notes as in `HISTORY.rst`, e.g. `* First release on PyPI.`.\n", "- Optionally, check \"Set as a pre-release\" so the release will be labeled as non-production ready (recommended for early versions of your package).\n", "- Select \"Publish release\".\n", "\n", "After a few minutes, GitHub has finished the built and it is deployed on PyPI. If you want to check:\n", "- Search for your package name on PyPI and check that the version number is correct.\n", "- Check that the readme is correctly rendered on PyPI.\n", "- Note that the PyPI badge may take several more minutes before being updated.\n", "\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.4" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 5 }