Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,57 @@ Creating a new release
10. **Publish to PyPI**

- Approve the ``Latest Release`` workflow under ``Actions`` to publish the package to PyPI.

Creating a pre-release
----------------------

Use pre-releases to publish alpha, beta, or release candidate versions. These follow
`PEP 440 <https://peps.python.org/pep-0440/>`_ pre-release format (e.g., ``1.10.0a1``, ``1.10.0b1``, ``1.10.0rc1``).

1. **Bump to a pre-release version**

.. code-block:: bash

make pre-release version=1.10.0a1 # First alpha
make pre-release version=1.10.0a2 # Second alpha
make pre-release version=1.10.0b1 # First beta
make pre-release version=1.10.0rc1 # First release candidate

2. **Commit and push**

.. code-block:: bash

git add -A && git commit -m "chore(release): bump to v1.10.0a1"
git push origin HEAD

3. **Create a GitHub pre-release**

.. code-block:: bash

gh release create v1.10.0a1 --prerelease --title "v1.10.0a1"

4. **PyPI behavior**

PyPI automatically marks PEP 440 pre-release versions:

- Users **won't** get pre-releases via ``pip install advanced-alchemy``
- Users can opt-in via ``pip install --pre advanced-alchemy``
- Or pin explicitly: ``pip install advanced-alchemy==1.10.0a1``

Graduating from pre-release to stable
++++++++++++++++++++++++++++++++++++++

From the last release candidate, bump the ``pre`` part to move past ``rc`` to ``stable``:

.. code-block:: bash

make release bump=pre # e.g. 1.10.0rc1 → 1.10.0

Or skip to the next stable version directly:

.. code-block:: bash

make release bump=patch # From any version → next patch
make release bump=minor # From any version → next minor

Then follow the standard `Creating a new release`_ steps above.
25 changes: 25 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,31 @@ release: ## Bump version and create re
@uv lock --upgrade-package advanced-alchemy
@echo "${OK} Release complete 🎉"

.PHONY: pre-release
pre-release: ## Start a pre-release: make pre-release version=1.10.0a1
@if [ -z "$(version)" ]; then \
echo "${ERROR} Usage: make pre-release version=X.Y.ZaN"; \
echo ""; \
echo "Pre-release workflow:"; \
echo " 1. Start alpha: make pre-release version=1.10.0a1"; \
echo " 2. Next alpha: make pre-release version=1.10.0a2"; \
echo " 3. Move to beta: make pre-release version=1.10.0b1"; \
echo " 4. Move to rc: make pre-release version=1.10.0rc1"; \
echo " 5. Final release: make release bump=pre (from rc) OR bump=patch/minor (from stable)"; \
exit 1; \
fi
@echo "${INFO} Preparing pre-release $(version)... 🧪"
@make clean
@make build
@uv run bump-my-version bump --new-version $(version) pre
@uv lock --upgrade-package advanced-alchemy
@echo "${OK} Pre-release $(version) complete 🧪"
@echo ""
@echo "${INFO} Next steps:"
@echo " 1. Push: git push origin HEAD"
@echo " 2. Create a GitHub pre-release: gh release create v$(version) --prerelease --title 'v$(version)'"
@echo " 3. This will publish to PyPI with pre-release tags"

# =============================================================================
# Cleaning and Maintenance
# =============================================================================
Expand Down
3 changes: 2 additions & 1 deletion docs/releases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Pre-release Versions
++++++++++++++++++++

Before a new major release, we will make ``alpha``, ``beta``, and release candidate (``rc``) releases, numbered as
``<major>.<minor>.<patch><release_type><number>``. For example, ``2.0.0alpha1``, ``2.0.0beta1``, ``2.0.0rc1``.
``<major>.<minor>.<patch><release_type><number>`` following `PEP 440 <https://peps.python.org/pep-0440/>`_.
For example, ``2.0.0a1``, ``2.0.0b1``, ``2.0.0rc1``.

- ``alpha``
Early developer preview. Features may not be complete and breaking changes can occur.
Expand Down
18 changes: 16 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -180,16 +180,30 @@ current_version = "1.9.0"
ignore_missing_files = false
ignore_missing_version = false
message = "chore(release): bump to v{new_version}"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
parse = """(?x)
(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)
((?P<pre>a|b|rc)(?P<pre_n>\\d+))?
"""
regex = false
replace = "{new_version}"
search = "{current_version}"
serialize = ["{major}.{minor}.{patch}"]
serialize = [
"{major}.{minor}.{patch}{pre}{pre_n}",
"{major}.{minor}.{patch}",
]
sign_tags = false
tag = false
tag_message = "chore(release): v{new_version}"
tag_name = "v{new_version}"

[tool.bumpversion.parts.pre]
optional_value = "stable"
first_value = "stable"
values = ["a", "b", "rc", "stable"]

[tool.bumpversion.parts.pre_n]
first_value = "1"

[[tool.bumpversion.files]]
filename = "pyproject.toml"
replace = 'version = "{new_version}"'
Expand Down
6 changes: 4 additions & 2 deletions tools/prepare_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,15 @@ async def get_release_info(self) -> ReleaseInfo:
)

async def create_draft_release(self, body: str, release_branch: str) -> str:
is_prerelease = bool(re.search(r"(a|b|rc)\d+$", self._new_release_version))
res = await self._api_client.post(
"/releases",
json={
"tag_name": self._new_release_tag,
"target_commitish": release_branch,
"name": self._new_release_tag,
"draft": True,
"prerelease": is_prerelease,
"body": body,
},
)
Expand Down Expand Up @@ -380,7 +382,7 @@ def update_pyproject_version(new_version: str) -> None:
# can't use tomli-w / tomllib for this as is messes up the formatting
pyproject = pathlib.Path("pyproject.toml")
content = pyproject.read_text()
content = re.sub(r'(\nversion ?= ?")\d+\.\d+\.\d+("\s*\n)', rf"\g<1>{new_version}\g<2>", content)
content = re.sub(r'(\nversion ?= ?")\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?("\s*\n)', rf"\g<1>{new_version}\g<2>", content)
pyproject.write_text(content)


Expand Down Expand Up @@ -414,7 +416,7 @@ def cli(
if base is None:
base = _get_latest_tag()

if not re.match(r"\d+\.\d+\.\d+", version):
if not re.match(r"\d+\.\d+\.\d+((a|b|rc)\d+)?$", version):
click.secho(f"Invalid version: {version!r}")
sys.exit(1)

Expand Down
Loading