PYTHON-SEMANTIC-RELEASE(1)

๐Ÿ“ฆ
version 10.6.1 ยท 5 versions
, in ubuntu stonking

๐ŸŒˆ NAME ๐ŸŒˆ

okay bestie, say hi to python-semantic-release ๐Ÿ’… โ€” aka the star of this here manual page, PYTHON-SEMANTIC-RELEASE(1), from the python-semantic-release manual. she's a lot, but she's worth it, i promise.

Python Semantic Release (we call her PSR around here ๐Ÿซถ) is an automated release mechanism that figures out your releases for you, based on SemVer and Commit Message Conventions, for all your Git projects. no cap.

so like, what's the whole point?? the vibe is: she reads your latest commit messages, parses them, and detects what the next version of your project should be. if those commit messages are describing changes that would need a major, minor, or patch version bump, PSR bumps the version number accordingly, automatically. iconic behaviour, honestly. but that's not even all of it โ€” she doesn't just stop there, she'll help you automate the whole release process. she'll update your project code and your distribution artifact, upload that artifact, and post changelogs over to a remotely hosted Version Control System (VCS). โœจ

she's built to run inside a CI/CD pipeline service, which is her natural habitat, but she can also be run locally if that's your thing. ๐Ÿ’–

fun origin story for you ๐ŸŽ€: this project was originally inspired by the semantic-release project for JavaScript (https://github.com/semantic-release/semantic-release) by Stephan Bรถnnemann. but! the codebases have significantly deviated since then, because PSR has been driving towards the goal of providing flexible changelogs and simple initial setup. glow-up fr fr. ๐Ÿ”ฅ


๐Ÿ› ๏ธ INSTALLATION ๐Ÿ› ๏ธ

get her installed, bestie:

python3 -m pip install python-semantic-release
semantic-release --help

Python Semantic Release is also available from conda-forge (https://anaconda.org/conda-forge/python-semantic-release) or as a GitHub Action (see #gh-actions). โœจ

and go read more about the setup and configuration in the Getting Started Guide. ๐Ÿฆ‹


๐Ÿ“š DOCUMENTATION CONTENTS ๐Ÿ“š

๐Ÿ“ CHANGELOG ๐Ÿ“

v10.6.1 (2026-07-06)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

โš™๏ธ Build System

v10.6.0 (2026-07-04)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v10.5.3 (2025-12-14)

๐Ÿชฒ Bug Fixes

v10.5.2 (2025-11-10)

๐Ÿชฒ Bug Fixes

v10.5.1 (2025-11-10)

๐Ÿชฒ Bug Fixes

v10.5.0 (2025-11-09)

โœจ Features


๐Ÿชฒ Bug Fixes


๐Ÿ“– Documentation


โš™๏ธ Build System


๐Ÿ’ก Additional Release Information

  • cmd-version โ€” okay so if you were previously handling the unshallowing of a repository clone in your CI/CD pipelines, you may now remove that step from your workflow. PSR will now detect a shallow repository and unshallow it before evaluating the commit history. that's it, that's the whole vibe, no cap ๐Ÿ™ˆ

v10.4.1 (2025-09-13)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v10.4.0 (2025-09-08)

โœจ Features

๐Ÿ“– Documentation

๐Ÿ’ก Additional Release Information

  • config โ€” This release introduces a new built-in parser type that can be utilized for monorepo projects. The type value is conventional-monorepo and when specified it will apply the conventional commit parser to a monorepo environment. This parser has specialized options to help handle monorepo projects as well. For more information, please refer to the Monorepo Docs </configuration/configuration-guides/monorepos.html>. go read it, it's worth it ๐ŸŒˆ

v10.3.2 (2025-09-06)

๐Ÿชฒ Bug Fixes

โšก Performance Improvements

๐Ÿ“– Documentation

๐Ÿ’ก Additional Release Information

  • cmd-version โ€” Unfortunately, PSR introduced a bug in 10.3.0 when attempting to provide more CI outputs for GitHub Actions. It required our GitHub client interface to be loaded and even if it was not using GitHub CI to be run. This caused errors in Gitea and likely GitLab/Bitbucket environments. This change prevents that from happening but if any users pipelines were intentionally presenting the environment variable GITHUB_OUTPUT to enable action output to enable passing along internal outputs of PSR then their hack will no longer work after this change.
so yeah, that hack is gone bestie, no more sneaky GITHUB_OUTPUT tricks ๐Ÿ˜ญ

v10.3.1 (2025-08-06)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v10.3.0 (2025-08-04)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v10.2.0 (2025-06-29)

โœจ Features

๐Ÿ“– Documentation


v10.1.0 (2025-06-12)

โœจ Features

๐Ÿ“– Documentation

โš™๏ธ Build System


v10.0.2 (2025-05-26)

๐Ÿชฒ Bug Fixes


v10.0.1 (2025-05-25)

๐Ÿชฒ Bug Fixes


v10.0.0 (2025-05-25)

โœจ Features

๐Ÿชฒ Bug Fixes


๐Ÿ“– Documentation ๐ŸŽ€


โš™๏ธ Build System ๐Ÿ› ๏ธ


โ™ป๏ธ Refactoring ๐Ÿงน


๐Ÿ’ฅ Breaking Changes ๐Ÿ’ฅ

See also: ๐Ÿ’œ For a summarized walkthrough, check out our v10 migration guide ../upgrading/10-upgrade.html as well.
  • changelog-md โ€” bestie, the default Markdown changelog template and the release notes template are not printing out the entire commit message contents anymore. It's giving โœจ*subject line only*โœจ now. Why? Because literally everyone was highkey begging for better formatted changelogs and asking for subject line only, so here we are. Originally, the whole decision to not hide commit subjects that were included in the commit body via the git merge --squash command existed because PSR did not have another alternative. At this point though, all the built-in parsers can parse squashed commits and separate them out into their own entry on the changelog. So the default template no longer needs to write out the full commit body. See the commit parser options if you want to enable/disable parsing squash commits. ๐Ÿ‘‘
  • changelog-rst โ€” same energy for the default ReStructured changelog template: it will no longer print out the entire commit message contents, instead it will only print the commit subject line. This comes to meet the high demand of better formatted changelogs and requests for subject line only. Originally, it was a decision to not hide commit subjects that were included in the commit body via the git merge --squash command and PSR did not have another alternative. At this point, all the built-in parsers have the ability to parse squashed commits and separate them out into their own entry on the changelog. Therefore, the default template no longer needs to write out the full commit body. See the commit parser options if you want to enable/disable parsing squash commits. ๐Ÿฆ‹
  • config โ€” this release switches the allow_zero_version default to false. The whole point is to encourage less 0.x releases as the default, and instead let the experienced developer choose when 0.x is actually appropriate. There are way too many projects in the ecosystems that never leave 0.x and that is problematic for the industry tools that help auto-update based on SemVer. We should strive for publishing usable tools and maintaining good forethought for when compatibility must break. If your configuration already sets the allow_zero_version value, this change will have no effect on your project. If you want to use 0.x versions from the start, then change allow_zero_version to true in your configuration.
  • config โ€” this release switches the changelog.default_templates.mask_initial_release default to true. This change is intended to toggle better recommended outputs of the default changelog. Conceptually, the very first release is hard to describe โ€” one can only provide new features as nothing exists yet for the end user. No changelog should be written as there is no start point to compare the "changes" to. The recommendation instead is to only list a simple message as Initial Release. This is now the default for PSR when providing the very first release (no pre-existing tags) in the changelog and release notes. If your configuration already sets the changelog.default_templates.mask_initial_release value, then this change will have no effect on your project. If you do NOT want to mask the first release information, then set changelog.default_templates.mask_initial_release to false in your configuration.
  • config โ€” this release switches the changelog.mode default to update. In this mode, if a changelog exists, PSR will update the changelog IF AND ONLY IF the configured insertion flag exists in the changelog. The Changelog output will remain unchanged if no insertion flag exists. The insertion flag may be configured with the changelog.insertion_flag setting. When upgrading to v10, you must add the insertion flag manually โ€” or you can just delete the changelog file and run PSR's changelog generation and it will rebuild the changelog (similar to init mode) but it will add the insertion flag. If your configuration already sets the changelog.mode value, then this change will have no effect on your project. If you would rather the changelog be generated from scratch every release, than set the changelog.mode value to init in your configuration. ๐Ÿซถ
  • github-action โ€” oof, okay, important one: the root_options action input parameter has been removed, because it created a command injection vulnerability for arbitrary code to execute within the container context of the GitHub action if a command injection code was provided as part of the root_options parameter string. ๐Ÿ˜ญ To eliminate the vulnerability, each relevant option that can be provided to semantic-release has been individually added as its own parameter and will be processed individually to prevent command injection. Please review our Github Actions Configuration page to review the newly available configuration options that replace the root_options parameter: https://github.com/python-semantic-release/python-semantic-release/blob/v10.0.0/docs/configuration/automatic-releases/github-actions.rst
  • parser-conventional โ€” any breaking change footer messages that the conventional commit parser detects will now be removed from the commit.descriptions[] list but maintained in and only in the commit.breaking_descriptions[] list. Previously, the descriptions included all text from the commit message but that was redundant as the default changelog now handles breaking change footers in its own section.
  • parser-conventional, parser-emoji, parser-scipy โ€” any issue resolution footers that the parser detects will now be removed from the commit.descriptions[] list. Previously, the descriptions included all text from the commit message, but now that the parser pulls out the issue numbers the numbers will be included in the commit.linked_issues tuple for user extraction in any changelog generation. ๐ŸŽ€
  • parser-conventional, parser-emoji, parser-scipy โ€” ok so when the commit parser spots any release notice footer messages, those are now out of commit.descriptions[] entirely, and instead they're kept in commit.notices[] and only in commit.notices[]. Why the glow-up? Because the descriptions used to include literally all the text from the commit message and it was giving redundant โœ‹ since the default changelog handles release notice footers in its own lil section now. ๐Ÿ’…
  • parser-conventional, parser-emoji, parser-scipy โ€” so on some common VCSs like GitHub, a pull request or merge request number reference usually gets tacked onto the end of the subject line inside parentheses. PSR now looks for that reference and extracts it into the commit.linked_merge_request and commit.linked_pull_request attributes of a commit object. And since it's pulled out individually now, it's just cleaner to drop it from the first line of the commit.descriptions list (that's the subject line, bestie) so changelog macros don't have to replace the text โ€” they only append a PR/MR link to the end of the line. The reference keeps its PR/MR prefix indicator (# or !), obviously. ๐ŸŽ€
  • parser-conventional, parser-emoji, parser-scipy โ€” commit_parser_options.ignore_merge_commits is now set to true by default, no cap. The ignore-squash-commits feature dropped in v9.18.0 and at the time it was set to false to prevent unexpected results on a non-breaking update. What does ignoring merge commits do? It prevents extra unnecessary processing on a commit message that probably isn't gonna match a commit message syntax anyway. Most merge commits are syntactically pre-defined by Git or a Remote Version Control System (ex. GitHub, etc.) and do not follow a commit convention โ€” nor should they, fr fr. The bigger issue with merge commits is that they're ultimately a full copy of all the changes that were previously created and committed. The merge commit itself ensures the previous commit tree is maintained in history, so the commit message always exists. But if merge commits get parsed, it generally creates duplicate messages that end up in your changelog, which is less than desired in most cases, lowkey tragic. ๐Ÿ™ˆ So: if you've previously used the changelog.exclude_commit_patterns functionality to ignore merge commit messages, then you want this setting set to true to improve parsing speed. You can also now remove the merge commit exclude pattern from the list as well, to improve parsing speed even more. And if this functionality is not desired, you'll need to update your configuration to change the new setting to false. ๐Ÿ‘‘
  • parser-conventional, parser-emoji, parser-scipy โ€” commit_parser_options.parse_squash_commits is now set to true by default too! The parse-squash-commits feature landed in v9.17.0 and was originally set to false to prevent unexpected results on a non-breaking update. It attempts to find additional commits of the same commit type within the body of a single commit message. When squash commits are found, Python Semantic Release will separate out each commit into its own artificial commit object and parse them individually. Heads up though โ€” this potentially can change the resulting version bump if a larger bump was detected within the squashed components. It also allows for the changelog and release notes to separately order and display each commit as originally written. If that's not what you want, you'll need to update your configuration to change the new setting to false. โญ

v9.21.2 (2026-07-04) ๐Ÿฆ‹

๐Ÿชฒ Bug Fixes

  • hvcs: Mask credentials in git URL debug logs, closes #1426 (PR#1450, da0cb2b)

๐Ÿ“– Documentation

  • github-actions: Fix recommended upstream detection script's upstream name parsing (911f62a)

โš™๏ธ Build System

  • Mark for Python 3.14 compatibility (86c0439)
  • deps: Bump tomlkit dependency from ~=0.11.0 to ~=0.13.0 (5395d06)
  • deps: Prevent update to click@8.2.0 (128f06a)

v9.21.1 (2025-05-05) ๐Ÿงš

๐Ÿชฒ Bug Fixes

  • changelog-filters: Fixes url resolution when prefix & path share letters, closes #1204 (PR#1239, f61f8a3)

๐Ÿ“– Documentation

  • github-actions: Expound on monorepo example to include publishing actions (PR#1229, 550e85f)

โš™๏ธ Build System

  • deps: Bump rich dependency from 13.0 to 14.0 (PR#1224, 691536e)
  • deps: Expand python-gitlab dependency to include v5.0.0 (PR#1228, a0cd1be)

v9.21.0 (2025-02-23) ๐Ÿ’œ

โœจ Features

  • Add package name variant, python-semantic-release, project script, closes #1195 (PR#1199, 1ac97bc)

๐Ÿ“– Documentation

  • github-actions: Update example workflow to handle rapid merges (PR#1200, 1a4116a)

v9.20.0 (2025-02-17) ๐Ÿ“

โœจ Features

  • cmd-version: Enable stamping of tag formatted versions into files, closes #846 (PR#1190, 8906d8e)
  • cmd-version: Extend version_variables to stamp versions with @ symbol separator, closes #1156 (PR#1185, 23f69b6)

๐Ÿ“– Documentation

  • configuration: Add usage information for tag format version stamping (PR#1190, 8906d8e)
  • configuration: Clarify version_variables config description & @ separator usage (PR#1185, 23f69b6)

โš™๏ธ Build System

  • deps: Add deprecated~=1.2 for deprecation notices & sphinx documentation (PR#1190, 8906d8e)

v9.19.1 (2025-02-11) ๐ŸŒˆ

๐Ÿชฒ Bug Fixes

  • changelog: Standardize heading format for across all version sections (PR#1182, 81f9e80)
  • changelog-md: Standardize heading format for extra release information (PR#1182, 81f9e80)
  • changelog-rst: Standardize heading format for extra release information (PR#1182, 81f9e80)
  • config: Handle invalid commit_parser type gracefully (PR#1180, 903c8ba)
  • release-notes: Standardize heading format for extra release information (PR#1182, 81f9e80)

๐Ÿ“– Documentation


๐ŸŒˆ v9.19.0 (2025-02-10)

โœจ Features

๐Ÿ“– Documentation

๐Ÿ’ก Additional Release Information

  • parser-conventional: heads up!! The 'angular' commit parser has been renamed to 'conventional', because that's what it literally evolved into to match the official conventional-commits standard. Please update your configs so commit_parser says 'conventional' where you used to write 'angular'. And fr fr: the 'angular' type will be removed in v11 ๐Ÿซฃ

๐Ÿฆ‹ v9.18.1 (2025-02-08)

๐Ÿชฒ Bug Fixes


๐ŸŽ€ v9.18.0 (2025-02-06)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v9.17.0 (2025-01-26)

โœจ Features

๐Ÿชฒ Bug Fixes

โšก Performance Improvements

๐Ÿ“– Documentation


v9.16.1 (2025-01-12)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v9.16.0 (2025-01-12)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v9.15.2 (2024-12-16)

๐Ÿชฒ Bug Fixes

โšก Performance Improvements


v9.15.1 (2024-12-03)

๐Ÿชฒ Bug Fixes

v9.15.0 (2024-12-02)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v9.14.0 (2024-11-11)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v9.13.0 (2024-11-10)

โœจ Features

๐Ÿชฒ Bug Fixes

โšก Performance Improvements

๐Ÿ“– Documentation


v9.12.2 (2024-11-07)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v9.12.1 (2024-11-06)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v9.12.0 (2024-10-18)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

So for the changelog part I'm putting each release's full URLs in a lil "receipts" list at the end of that version, so the bullets stay readable but not a single character goes missing, bestie ๐Ÿ’–

v9.11.1 (2024-10-15)

๐Ÿชฒ Bug Fixes

  • changelog โ€” custom templates used to have a whole meltdown when the components they were pulling in lived inside hidden folders ๐Ÿ™ˆ that error era is OVER (like, no cap), it's fixed now (PR#1060, a7614b0)

๐Ÿ”— the receipts


v9.11.0 (2024-10-12)

โœจ Features

  • changelog โ€” new filter called convert_md_to_rst has entered the changelog environment chat ๐ŸŽ€ it converts yer markdown to reStructuredText, slay (PR#1055, c2e8831)
  • changelog โ€” there's now a default changelog in re-structured text format, and this one closes #399 like it was born to do it ๐Ÿ‘‘ (PR#1055, c2e8831)
  • changelog โ€” brand new default changelog template, written in reStructuredText format, fresh out the box โœจ (PR#1055, c2e8831)
  • config โ€” the default changelog.insertion_flag now gets enabled based on whatever output format you're using ๐Ÿฆ‹ it just knows (PR#1055, c2e8831)
  • config โ€” name your target changelog filename and it'll trigger RST output format all by itself, which also closes #399 ๐Ÿ”ฅ (PR#1055, c2e8831)

๐Ÿชฒ Bug Fixes

  • changelog โ€” spacing for the default markdown template during updates is fixed, because wonky spacing is giving chaos and we don't do that here ๐Ÿ’… (PR#1055, c2e8831)

๐Ÿ“– Documentation

  • changelog โ€” clarified the whole convert_md_to_rst filter situation that got added to the template environment ๐Ÿ“ (PR#1055, c2e8831)
  • changelog โ€” more detail added about the configuration options for default changelog creation, so you actually know what you're doing ๐Ÿซถ (PR#1055, c2e8831)
  • configuration โ€” updated changelog_file with a deprecation notice about setting relocation, so heads up bestie, peep that โš ๏ธ (PR#1055, c2e8831)
  • configuration โ€” the output_format description got updated to cover the whole reStructuredText support era ๐ŸŒˆ (PR#1055, c2e8831)
  • configuration โ€” details about insertion_flag's dynamic defaults with rst are now documented, because dynamic defaults deserve lore ๐Ÿ“– (PR#1055, c2e8831)

๐Ÿ”— the receipts


v9.10.1 (2024-10-10)

๐Ÿชฒ Bug Fixes

  • config โ€” branch match regex errors now get handled gracefully instead of just detonating ๐Ÿ’€ the vibes are civilised now (PR#1054, 4d12251)

๐Ÿ”— the receipts


v9.10.0 (2024-10-08)

โœจ Features

  • changelog โ€” changelog_insertion_flag got added to the changelog template context ๐ŸŽ€ go forth and insert (PR#1045, c18c245)
  • changelog โ€” changelog_mode got added to the changelog template context, main character of the context fr fr (PR#1045, c18c245)
  • changelog โ€” prev_changelog_file got added to the changelog template context, so past-you can talk to present-you ๐Ÿฆ‹ (PR#1045, c18c245)
  • changelog โ€” a read_file function got added to the changelog template context ๐Ÿ“‚ reading files inside templates, iconic behaviour (PR#1045, c18c245)
  • changelog โ€” shorthand ctx variable got added to the changelog template env, because nobody has time to type the whole word โœจ (PR#1045, c18c245)
  • changelog โ€” the changelog template got modified so it can actually support changelog updates, and this one closes #858 ๐Ÿ‘‘ (PR#1045, c18c245)
  • config โ€” new config option changelog.default_templates.output_format has entered the building ๐Ÿ”ฅ (PR#1045, c18c245)
  • config โ€” changelog.insertion_flag is now a configuration option, yours to set ๐Ÿ’… (PR#1045, c18c245)
  • config โ€” changelog.mode is now a configuration option too, obsessed ๐Ÿฅฐ (PR#1045, c18c245)
  • github-actions โ€” added an action build directive to toggle the --skip-build option, so you can skip the build like it's a monday ๐Ÿ’€ (PR#1044, 26597e2)

๐Ÿชฒ Bug Fixes

  • changelog โ€” angular heading names got adjusted for readability, because nobody was reading those ๐Ÿ“– (PR#1045, c18c245)
  • changelog โ€” changelog templates can now handle complex directory includes without crumbling ๐ŸŒธ (PR#1045, c18c245)
  • changelog โ€” user templates only render when the actual files exist, no ghost rendering here ๐Ÿ‘ปโžก๏ธ๐Ÿšซ (PR#1045, c18c245)
  • config โ€” jinja will no longer autoescape markdown content by default, so your markdown stays markdown and not a pile of entities ๐Ÿ˜ญ (PR#1045, c18c245)

๐Ÿ“– Documentation

  • changelog-templates โ€” improved the detail and described the new changelog.mode="update" situation, so it's giving "actually documented" ๐ŸŽ€ (PR#1045, c18c245)
  • commands โ€” updated the definition of the version commands' --skip-build option, clarity is the vibe โœจ (PR#1044, 26597e2)
  • configuration โ€” added the changelog.mode and changelog.insertion_flag config definitions ๐Ÿ“ (PR#1045, c18c245)
  • configuration โ€” defined the new changelog.default_templates.output_format option, brand new and already famous ๐ŸŒŸ (PR#1045, c18c245)
  • configuration โ€” marked the version of configuration setting introduction, timestamping the lore โณ (PR#1045, c18c245)
  • configuration โ€” standardized every true/false to lowercase to keep that toml-compatibility, slay ๐Ÿ’… (PR#1045, c18c245)
  • configuration โ€” updated the changelog.environment.autoescape default to false so the docs actually match the code, no more catfishing ๐ŸŸ (PR#1045, c18c245)

๐Ÿ”— the receipts


๐ŸŽ€ v9.9.0 (2024-09-28)

โœจ Features

๐Ÿ“– Documentation


๐ŸŽ€ v9.8.9 (2024-09-27)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


๐ŸŽ€ v9.8.8 (2024-09-01)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


๐ŸŽ€ v9.8.7 (2024-08-20)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


๐ŸŽ€ v9.8.6 (2024-07-20)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


๐ŸŽ€ v9.8.5 (2024-07-06)

๐Ÿชฒ Bug Fixes

โšก Performance Improvements

v9.8.4 (2024-07-04)

๐Ÿชฒ Bug Fixes

v9.8.3 (2024-06-18)

๐Ÿชฒ Bug Fixes

v9.8.2 (2024-06-17)

๐Ÿชฒ Bug Fixes

v9.8.1 (2024-06-05)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v9.8.0 (2024-05-27)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v9.7.3 (2024-05-15)

๐Ÿชฒ Bug Fixes

v9.7.2 (2024-05-13)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v9.7.1 (2024-05-07)

๐Ÿชฒ Bug Fixes

v9.7.0 (2024-05-06)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation โœจ

โš™๏ธ Build System ๐Ÿ”ง


v9.6.0 (2024-04-29) ๐ŸŽ€

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation โœจ


v9.5.0 (2024-04-23) ๐Ÿฆ‹

โœจ Features


v9.4.2 (2024-04-14) ๐Ÿ“

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation โœจ

โš™๏ธ Build System ๐Ÿ”ง


v9.4.1 (2024-04-06) ๐ŸŽ€

๐Ÿชฒ Bug Fixes


v9.4.0 (2024-03-31) ๐ŸŒˆ

โœจ Features

v9.3.1 (2024-03-24) โœจ

๐Ÿชฒ Bug Fixes

โšก Performance Improvements


v9.3.0 (2024-03-21) โœจ

โœจ Features


v9.2.2 (2024-03-19) โœจ

๐Ÿชฒ Bug Fixes


v9.2.1 (2024-03-19) โœจ

๐Ÿชฒ Bug Fixes


v9.2.0 (2024-03-18) โœจ

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

โš™๏ธ Build System


v9.1.1 (2024-02-25) โœจ

๐Ÿชฒ Bug Fixes


v9.1.0 (2024-02-14) โœจ

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

โš™๏ธ Build System


v9.0.3 (2024-02-08) โœจ

๐Ÿชฒ Bug Fixes

โšก Performance Improvements


v9.0.2 (2024-02-08) โœจ

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v9.0.1 (2024-02-06) โœจ

๐Ÿชฒ Bug Fixes


v9.0.0 (2024-02-06) โœจ

โ™ป๏ธ Refactoring

๐Ÿ’ฅ BREAKING CHANGES

okay bestie listen up, this one is serious.
  • Removed Python 3.7 specific control flows and made more modern implementations the default control flow without a bypass or workaround. Will break on Python 3.7 now. If you require Python 3.7, you should lock your major version at v8. Since we only have enough manpower to maintain the latest major release, unfortunately there will not be any more updates to v8.
  • We decided to remove support for Python 3.7 because it has been officially deprecated by the Python Foundation over a year ago and our codebase is starting to have limitations and custom implementations just to maintain support for 3.7.

v8.7.2 (2024-01-03) โœจ

๐Ÿชฒ Bug Fixes


v8.7.1 (2024-01-03) โœจ

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v8.7.0 (2023-12-22) โœจ

โœจ Features


v8.6.0 (2023-12-22) โœจ

โœจ Features

๐Ÿ“– Documentation


v8.5.2 (2023-12-19) โœจ

๐Ÿชฒ Bug Fixes


v8.5.1 (2023-12-12) โœจ

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v8.5.0 (2023-12-07) โœจ

โœจ Features


v8.4.0 (2023-12-07)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v8.3.0 (2023-10-23)

โœจ Features


v8.2.0 (2023-10-23)

โœจ Features

๐Ÿ“– Documentation


v8.1.2 (2023-10-13)

๐Ÿชฒ Bug Fixes


v8.1.1 (2023-09-19)

๐Ÿชฒ Bug Fixes


v8.1.0 (2023-09-19)

โœจ Features

๐Ÿ“– Documentation


v8.0.8 (2023-08-26)

๐Ÿชฒ Bug Fixes


v8.0.7 (2023-08-16)

๐Ÿชฒ Bug Fixes


v8.0.6 (2023-08-13)

๐Ÿชฒ Bug Fixes


v8.0.5 (2023-08-10)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v8.0.4 (2023-07-26)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v8.0.3 (2023-07-21)

๐Ÿชฒ Bug Fixes


v8.0.2 (2023-07-18)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v8.0.1 (2023-07-17)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v8.0.0 (2023-07-16)

โœจ Features


๐Ÿชฒ Bug Fixes


๐Ÿ“– Documentation


โ™ป๏ธ Refactoring


๐Ÿ’ฅ BREAKING CHANGES


v7.34.6 (2023-06-17)

๐Ÿชฒ Bug Fixes


v7.34.5 (2023-06-17)

๐Ÿชฒ Bug Fixes


v7.34.4 (2023-06-15)

๐Ÿชฒ Bug Fixes


v7.34.3 (2023-06-01)

๐Ÿชฒ Bug Fixes


v7.34.2 (2023-05-29)

๐Ÿชฒ Bug Fixes


v7.34.1 (2023-05-28)

๐Ÿชฒ Bug Fixes


v7.34.0 (2023-05-28)

โœจ Features

๐ŸŽ€ v7.33.5 (2023-05-19)

๐Ÿชฒ Bug Fixes (we fixed things, bestie!)

๐Ÿ“– Documentation


๐ŸŒธ v7.33.4 (2023-05-14)

๐Ÿชฒ Bug Fixes


โœจ v7.33.3 (2023-04-24)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


๐Ÿ’œ v7.33.2 (2023-02-17)

๐Ÿชฒ Bug Fixes


๐Ÿฆ‹ v7.33.1 (2023-02-01)

๐Ÿชฒ Bug Fixes


๐Ÿ‘‘ v7.33.0 (2023-01-15)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


๐Ÿ“ v7.32.2 (2022-10-22)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


๐ŸŒˆ v7.32.1 (2022-10-07)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


๐ŸŒŸ v7.32.0 (2022-09-25)

โœจ Features

๐Ÿ“– Documentation


๐Ÿ’– v7.31.4 (2022-08-23)

๐Ÿชฒ Bug Fixes


๐Ÿงš v7.31.3 (2022-08-22)

๐Ÿชฒ Bug Fixes


โญ v7.31.2 (2022-07-29)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


๐Ÿซถ v7.31.1 (2022-07-29)

๐Ÿชฒ Bug Fixes


๐Ÿ”ฅ v7.31.0 (2022-07-29)

โœจ Features

๐Ÿชฒ Bug Fixes


๐ŸŽ€ v7.30.2 (2022-07-26)

๐Ÿชฒ Bug Fixes


๐Ÿ’… v7.30.1 (2022-07-25)

๐Ÿชฒ Bug Fixes


๐ŸŒธ v7.30.0 (2022-07-25)

โœจ Features

๐Ÿชฒ Bug Fixes

okay bestie, the changelog saga keeps rolling and she is NOT slowing down ๐Ÿ’… every date, every PR, every single commit hash is riding with us โ€” receipts stay receipts:

๐Ÿฆ‹ v7.29.7 (2022-07-24)

๐Ÿชฒ Bug Fixes

๐ŸŒธ v7.29.6 (2022-07-15)

๐Ÿชฒ Bug Fixes

๐Ÿ’– v7.29.5 (2022-07-14)

๐Ÿชฒ Bug Fixes

โœจ v7.29.4 (2022-06-29)

๐Ÿชฒ Bug Fixes

๐ŸŽ€ v7.29.3 (2022-06-26)

๐Ÿชฒ Bug Fixes

๐Ÿซถ v7.29.2 (2022-06-20)

๐Ÿชฒ Bug Fixes

๐ŸŒˆ v7.29.1 (2022-06-01)

๐Ÿชฒ Bug Fixes

๐Ÿ‘‘ v7.29.0 (2022-05-27)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

๐Ÿ“ v7.28.1 (2022-04-14)

๐Ÿชฒ Bug Fixes

๐Ÿ’œ v7.28.0 (2022-04-11)

โœจ Features

๐Ÿงš v7.27.1 (2022-04-03)

๐Ÿชฒ Bug Fixes

๐ŸŒŸ v7.27.0 (2022-03-15)

โœจ Features

๐Ÿ”ฅ v7.26.0 (2022-03-07)

โœจ Features

๐Ÿ“– Documentation

๐Ÿฆ„ v7.25.2 (2022-02-24)

๐Ÿชฒ Bug Fixes

โญ v7.25.1 (2022-02-23)

๐Ÿชฒ Bug Fixes

๐ŸŽ‰ v7.25.0 (2022-02-17)

โœจ Features

๐Ÿ“– Documentation

๐Ÿ’ซ v7.24.0 (2022-01-24)

โœจ Features

๐ŸŒ™ v7.23.0 (2021-11-30)

โœจ Features

๐Ÿชฉ v7.22.0 (2021-11-21)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ’ v7.21.0 (2021-11-21)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿงธ v7.20.0 (2021-11-21)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

๐Ÿฆ‹ v7.19.2 (2021-09-04)

๐Ÿชฒ Bug Fixes

v7.19.1 (2021-08-17) ๐Ÿ’–

๐Ÿชฒ Bug Fixes


v7.19.0 (2021-08-16) โœจ

โœจ Features

๐Ÿ“– Documentation


v7.18.0 (2021-08-09) ๐ŸŒˆ

โœจ Features

๐Ÿ“– Documentation


v7.17.0 (2021-08-07) ๐Ÿฆ‹

โœจ Features


v7.16.4 (2021-08-03) ๐Ÿ›

๐Ÿชฒ Bug Fixes


v7.16.3 (2021-07-29) ๐Ÿ’€

๐Ÿชฒ Bug Fixes


v7.16.2 (2021-06-25) ๐ŸŒธ

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v7.16.1 (2021-06-08) ๐Ÿ’…

๐Ÿชฒ Bug Fixes


v7.16.0 (2021-06-08) โœจ

โœจ Features


v7.15.6 (2021-06-08) ๐Ÿ

๐Ÿชฒ Bug Fixes


v7.15.5 (2021-05-26) ๐Ÿ”ง

๐Ÿชฒ Bug Fixes


v7.15.4 (2021-04-29) ๐Ÿ˜ญ

๐Ÿชฒ Bug Fixes


v7.15.3 (2021-04-03) ๐ŸŒˆ

๐Ÿชฒ Bug Fixes


v7.15.2 (2021-04-03) ๐Ÿ’–

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v7.15.1 (2021-03-26) ๐Ÿ›

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v7.15.0 (2021-02-18) โœจ

โœจ Features

๐Ÿ“– Documentation


v7.14.0 (2021-02-11) ๐Ÿš€

โœจ Features

๐Ÿ“– Documentation


v7.13.2 (2021-01-29) ๐Ÿ’€

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v7.13.1 (2021-01-26) ๐Ÿ›

๐Ÿชฒ Bug Fixes


v7.13.0 (2021-01-26) โœจ

โœจ Features


v7.12.0 (2021-01-25) ๐Ÿ”ฅ

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v7.11.0 (2021-01-08)

โœจ Features

๐Ÿชฒ Bug Fixes

โš™๏ธ Build System


v7.10.0 (2021-01-08)

โœจ Features

๐Ÿ“– Documentation


v7.9.0 (2020-12-21)

โœจ Features

๐Ÿชฒ Bug Fixes


v7.8.2 (2020-12-19)

โœจ Features

๐Ÿชฒ Bug Fixes


v7.8.1 (2020-12-18)

๐Ÿชฒ Bug Fixes


v7.8.0 (2020-12-18)

โœจ Features

๐Ÿชฒ Bug Fixes


v7.7.0 (2020-12-12)

โœจ Features


v7.6.0 (2020-12-06)

โœจ Features

๐Ÿ“– Documentation


v7.5.0 (2020-12-04)

โœจ Features


v7.4.1 (2020-12-04)

๐Ÿชฒ Bug Fixes


v7.4.0 (2020-11-24)

โœจ Features

๐Ÿ“– Documentation


v7.3.0 (2020-09-28)

โœจ Features

๐Ÿ“– Documentation


v7.2.5 (2020-09-16)

๐Ÿชฒ Bug Fixes


v7.2.4 (2020-09-14)

๐Ÿชฒ Bug Fixes


v7.2.3 (2020-09-12)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v7.2.2 (2020-07-26)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v7.2.1 (2020-06-29)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v7.2.0 (2020-06-15)

โœจ Features


v7.1.1 (2020-05-28)

๐Ÿชฒ Bug Fixes


v7.1.0 (2020-05-24)

โœจ Features


v7.0.0 (2020-05-22)

โœจ Features

๐Ÿ“– Documentation

๐Ÿ’ฅ BREAKING CHANGES

  • changelog: The compare_url option has been removed in favor of using changelog_components. This functionality is now available as the semantic_release.changelog.compare_url component. so if you were vibing with compare_url, bestie, it's a glow-up not a glow-down โ€” head over to the new component, okay? ๐Ÿ’…

v6.4.1 (2020-05-15)

๐Ÿชฒ Bug Fixes


v6.4.0 (2020-05-15)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

โ™ป๏ธ Refactoring


v6.3.1 (2020-05-11)

๐Ÿชฒ Bug Fixes


v6.3.0 (2020-05-09)

โœจ Features

๐Ÿ“– Documentation


v6.2.0 (2020-05-02)

โœจ Features

๐Ÿ“– Documentation


v6.1.0 (2020-04-26)

โœจ Features

๐Ÿ“– Documentation


v6.0.1 (2020-04-15)

๐Ÿชฒ Bug Fixes


v6.0.0 (2020-04-15)

๐Ÿ“– Documentation

โ™ป๏ธ Refactoring

๐Ÿ’ฅ BREAKING CHANGES

  • debug: debug="*" no longer has an effect, instead use --verbosity DEBUG. so likeโ€ฆ that whole debug="*" thing? gone. retired. it's giving legacy. use --verbosity DEBUG now bestie ๐Ÿ™ˆ

v5.2.0 (2020-04-09)

โœจ Features

๐Ÿ“– Documentation


v5.1.0 (2020-04-04)

โœจ Features

๐Ÿ“– Documentation


v5.0.3 (2020-03-26)

๐Ÿชฒ Bug Fixes


v5.0.2 (2020-03-22)

๐Ÿชฒ Bug Fixes


v5.0.1 (2020-03-22)

๐Ÿชฒ Bug Fixes


v5.0.0 (2020-03-22)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

๐Ÿ’ฅ BREAKING CHANGES

  • build: Previously the build_commands configuration variable set the types of bundles sent to python setup.py. It has been replaced by the configuration variable build_command which takes the full command e.g. python setup.py sdist or poetry build. so heads up: build_commands is OUT, build_command (singular, bestie) is IN, and it wants the whole command now, not just the bundle types. it's giving glow-up ๐Ÿ’…

v4.11.0 (2020-03-22)

โœจ Features

๐Ÿ“– Documentation

v4.10.0 (2020-03-03)

โœจ Features


v4.9.0 (2020-03-02)

โœจ Features

๐Ÿชฒ Bug Fixes


v4.8.0 (2020-02-28)

โœจ Features


v4.7.1 (2020-02-28)

๐Ÿชฒ Bug Fixes


v4.7.0 (2020-02-28)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v4.6.0 (2020-02-19)

โœจ Features

๐Ÿชฒ Bug Fixes


v4.5.1 (2020-02-16)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v4.5.0 (2020-02-08)

โœจ Features

๐Ÿชฒ Bug Fixes


v4.4.1 (2020-01-18)

๐Ÿชฒ Bug Fixes


v4.4.0 (2020-01-17)

โœจ Features

๐Ÿชฒ Bug Fixes


v4.3.4 (2019-12-17)

๐Ÿชฒ Bug Fixes


v4.3.3 (2019-11-06)

๐Ÿชฒ Bug Fixes


v4.3.2 (2019-10-05)

๐Ÿชฒ Bug Fixes


v4.3.1 (2019-09-29)

๐Ÿชฒ Bug Fixes


v4.3.0 (2019-09-06)

โœจ Features

๐Ÿชฒ Bug Fixes


v4.2.0 (2019-08-05)

โœจ Features

๐Ÿชฒ Bug Fixes


v4.1.2 (2019-08-04)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v4.1.1 (2019-02-15)

๐Ÿ“– Documentation

v4.1.0 (2019-01-31)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v4.0.1 (2019-01-12)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v4.0.0 (2018-11-22)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

โ™ป๏ธ Refactoring

๐Ÿ’ฅ BREAKING CHANGES

  • If you rely on the commit message to be the version number only, this will break your code
  • This will only work with python 3 after this commit.
Okay bestie, that second one is literally a whole era change. Python 2 said bye. ๐Ÿ’€

v3.11.2 (2018-06-10)

๐Ÿชฒ Bug Fixes

v3.11.1 (2018-06-06)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v3.11.0 (2018-04-12)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation


v3.10.3 (2018-01-29)

๐Ÿชฒ Bug Fixes

okay bestie we're deep in the lore now, this is the "wait why is it exploding when there's no git repo" era ๐Ÿ’€ and look, they closed #74 twice โ€” iconique behaviour, honestly.

v3.10.2 (2017-08-03)

๐Ÿชฒ Bug Fixes

v3.10.1 (2017-07-22)

๐Ÿชฒ Bug Fixes

v3.10.0 (2017-05-05)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v3.9.0 (2016-07-03)

โœจ Features

๐Ÿชฒ Bug Fixes

v3.8.1 (2016-04-17)

๐Ÿชฒ Bug Fixes

v3.8.0 (2016-03-21)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v3.7.2 (2016-03-19)

๐Ÿชฒ Bug Fixes

v3.7.1 (2016-03-15)

๐Ÿ“– Documentation

v3.7.0 (2016-01-10)

โœจ Features

v3.6.1 (2016-01-10)

๐Ÿชฒ Bug Fixes

v3.6.0 (2015-12-28)

โœจ Features

๐Ÿ“– Documentation

v3.5.0 (2015-12-22)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v3.4.0 (2015-12-22)

โœจ Features

v3.3.3 (2015-12-22)

๐Ÿชฒ Bug Fixes

v3.3.2 (2015-12-21)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

v3.3.1 (2015-12-21)

๐Ÿชฒ Bug Fixes

v3.3.0 (2015-12-20)

โœจ Features

๐Ÿชฒ Bug Fixes


ngl this little scroll through 2015โ€“2018 has everything โ€” badges getting fixed like five separate times, pandoc just vibing in travis settings, and a commit that literally moves code around "a bit" to make flake8 happy ๐Ÿซถ we are SO not done tho, the history keeps going ๐Ÿ‘‘

โ€ฆaaand finishing that version off, bestie ๐Ÿ’…


๐Ÿ› v3.2.1 (2015-12-20)

๐Ÿชฒ Bug Fixes

โœจ v3.2.0 (2015-12-20)

โœจ Features

๐Ÿชฒ Bug Fixes

โœจ v3.1.0 (2015-08-31)

โœจ Features

โœจ v3.0.0 (2015-08-25)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ’œ v2.1.4 (2015-08-24)

๐Ÿชฒ Bug Fixes

๐Ÿ’œ v2.1.3 (2015-08-22)

๐Ÿชฒ Bug Fixes

๐Ÿ“– Documentation

๐Ÿ’œ v2.1.2 (2015-08-20)

๐Ÿชฒ Bug Fixes

๐Ÿ’œ v2.1.1 (2015-08-20)

๐Ÿชฒ Bug Fixes

๐Ÿ’œ v2.1.0 (2015-08-20)

โœจ Features

๐Ÿชฒ Bug Fixes

๐ŸŒˆ v2.0.0 (2015-08-19)

โœจ Features

๐Ÿชฒ Bug Fixes

๐Ÿ’ฅ BREAKING CHANGES

  • history: the default parser is now angular. Thus, the default behavior of the commit log evaluator will change. From now on it will use the angular commit message spec to determine the new version.
Yup, that's a big one bestie โ€” the whole vibe of how your next version gets figured out changed here. ๐Ÿ˜ญ

๐ŸŒˆ v1.0.0 (2015-08-04)

๐Ÿ’ฅ Breaking

๐Ÿ“– Documentation

๐ŸŒธ v0.9.1 (2015-08-04)

๐Ÿชฒ Bug Fixes

๐ŸŒธ v0.9.0 (2015-08-03)

โœจ Features

๐ŸŒธ v0.8.0 (2015-08-03)

โœจ Features

๐Ÿฆ‹ v0.7.0 (2015-08-02)

โœจ Features

๐Ÿ“– Documentation

๐Ÿฆ‹ v0.6.0 (2015-08-02)

โœจ Features

๐Ÿฆ‹ v0.5.4 (2015-07-29)

๐Ÿชฒ Bug Fixes

๐Ÿฆ‹ v0.5.3 (2015-07-28)

โš™๏ธ Build System

๐Ÿฆ‹ v0.5.2 (2015-07-28)

๐Ÿชฒ Bug Fixes

๐Ÿฆ‹ v0.5.1 (2015-07-28)

๐Ÿชฒ Bug Fixes

๐Ÿฆ‹ v0.5.0 (2015-07-28)

โœจ Features

๐Ÿฆ‹ v0.4.0 (2015-07-28)

โœจ Features

v0.3.2 (2015-07-28)

  • No change. literally nothing. a whole release of vibes only ๐Ÿ’…

v0.3.1 (2015-07-28)

๐Ÿชฒ Bug Fixes

v0.3.0 (2015-07-27)

โœจ Features

๐Ÿชฒ Bug Fixes

v0.2.0 (2015-07-27)

โœจ Features

โš™๏ธ Build System

v0.1.1 (2015-07-27)

๐Ÿชฒ Bug Fixes

v0.1.0 (2015-07-27)

  • Initial Release ๐ŸŽ€ (the origin story, the birth, the iconic first breath)

Concepts

okay bestie, this section is the whole โœจloreโœจ โ€” it covers the core concepts of Python Semantic Release and how it actually works. Understanding these concepts will help you effectively use Python Semantic Release in your projects. read it, love it, internalize it ๐Ÿง ๐Ÿ’–

Getting Started

If you haven't done so already, install Python Semantic Release locally following the <u>installation instructions</u> (#installation). don't skip this, fr fr ๐Ÿซถ

If you are using a CI/CD service, you may not have to add Python Semantic Release to your project's dependencies permanently, but for the duration of this guide for the initial setup, you will need to have it installed locally. like, locally-local. on your machine. right now. ๐Ÿ˜Œ

Configuring PSR

Python Semantic Release ships with a reasonable default configuration but some aspects MUST be customized to your project. To view the default configuration, run the following command:

semantic-release generate-config

The output of the above command is the default configuration in TOML format without any modifications. If this is fine for your project, then you do not need to configure anything else. slay, no notes ๐Ÿ’…

PSR accepts overrides to the default configuration keys individually. If you don't define the key-value pair in your configuration file, the default value will be used. so like, you only write what you wanna change. main character minimalism โœจ

By default, Python Semantic Release will look for configuration overrides in pyproject.toml under the TOML table [tool.semantic_release]. You may specify a different file using the -c/--config option, for example:

# In TOML format with top level table [semantic_release]
semantic-release -c releaserc.toml

# In JSON format with top level object key {"semantic_release": {}}
semantic-release -c releaserc.json

The easiest way to get started is to output the default configuration to a file, delete keys you do not need to override, and then edit the remaining keys to suit your project. chef's kiss, a whole workflow ๐Ÿ“

To set up in pyproject.toml, run the following command:

# In file redirect in bash
semantic-release generate-config --pyproject >> pyproject.toml

# Open your editor to edit the configuration
vim pyproject.toml

See also:

  • cmd-generate-config
  • Configuration (#configuration)

Configuring the Version Stamp Feature

One of the best features of Python Semantic Release is the ability to automatically stamp the new version number into your project files, so you don't have to manually update the version upon each release. The version that is stamped is automatically determined by Python Semantic Release from your commit messages which compliments automated versioning seamlessly. no cap, it just knows ๐Ÿ”ฎ

The most crucial version stamp is the one in your project metadata, which is used by the Python Package Index (PyPI) and other package managers to identify the version of your package. ๐Ÿ‘‘

For Python projects, this is typically the version field in your pyproject.toml file. First, set up your project metadata with the base version value. If you are starting with a brand new project, set project.version = "0.0.0". If you are working on an existing project, set it to the last version number you released. Do not include any v prefix. ๐Ÿšซ

Important: The version number must be a valid SemVer version, which means it should follow the format MAJOR.MINOR.PATCH (e.g., 1.0.0). Python Semantic Release does NOT support Canonical version values defined in the PEP 440 (https://peps.python.org/pep-0440/) specification at this time. See Issue #455 (https://github.com/python-semantic-release/python-semantic-release/issues/455) for more details. Note that you can still define a SemVer version in the project.version field, and when your build is generated, the build tool will automatically generate a PEP 440 compliant version as long as you do NOT use a non-pep440 compliant pre-release token.

Your project metadata might look like this in pyproject.toml:

[project]
name = "my-package"
version = "0.0.0"  # Set this to the last released version or "0.0.0" for new projects
description = "A sample Python package"

To configure PSR to automatically update this version number, you need to specify the file and value to update in your configuration. Since pyproject.toml uses TOML format, you will add the replacement specification to the tool.semantic_release.version_toml list. Update the following configuration in your pyproject.toml file to include the version variable location:

[tool.semantic_release]
version_toml = ["pyproject.toml:project.version"]

# Alternatively, if you are using poetry's 'version' key, then you would use:
version_toml = ["pyproject.toml:tool.poetry.version"]

If you have other TOML files where you want to stamp the version, you can add them to the version_toml list as well. In the above example, there is an implicit assumption that you only want the version as the raw number format. If you want to specify the full tag value (e.g. v-prefixed version), then include :tf for "tag format" at the end of the version variable specification. โœจ cute, right?

For non-TOML formatted files (such as JSON or YAML files), you can use the version_variables (#config-version-variables) configuration key instead. This feature uses an advanced Regular Expression to find and replace the version variable in the specified files. regex girlies rise up ๐Ÿงš

For Python files, its much more effective to use importlib instead which will allow you to dynamically import the version from your package metadata and not require your project to commit the version number bump to the repository. For example, in your package's base __init__.py:

# my_package/__init__.py
from importlib.metadata import version as get_version

__version__ = get_version(__package__)
# Note: __package__ must match your 'project.name' as defined in pyproject.toml

See also:

  • Configuration specification of version_toml (#config-version-toml)
  • Configuration specification of version_variables (#config-version-variables)

Using PSR to Build your Project

PSR provides a convenient way to build your project artifacts as part of the versioning process now that you have stamped the version into your project files. To enable this, you will need to specify the build command in your configuration. This command will be executed after the next version has been determined, and stamped into your files but before a release tag has been created. order matters babes, keep up ๐Ÿ˜ค

To set up the build command, add the following to your pyproject.toml file:

[tool.semantic_release]
build_command = "python -m build --sdist --wheel ."

See also:

  • build_command (#config-build-command) โ€” Configuration specification for the build command.
  • build_command_env (#config-build-command-env) โ€” Configuration specification for the build command environment variables.

Choosing a Commit Message Parser

PSR uses commit messages to determine the type of version bump that should be applied to your project. PSR supports multiple commit message parsing styles, allowing you to choose the one that best fits your project's needs. Choose one of the supported commit parsers defined in Commit Parsing (#commit-parsing), or provide your own and configure it in your pyproject.toml file. pick your fighter bestie ๐ŸฅŠ

Each commit parser has its own default configuration options so if you want to customize the parser behavior, you will need to specify the parser options you want to override.

[tool.semantic_release]
commit_parser = "conventional"

[tool.semantic_release.commit_parser_options]
minor_tags = ["feat"]
patch_tags = ["fix", "perf"]
parse_squash_commits = true
ignore_merge_commits = true

Choosing your Changelog

Prior to creating a release, PSR will generate a changelog from the commit messages of your project. The changelog is extremely customizable from the format to the content of each section. PSR ships with a default changelog template that will be used if you do not provide custom templates. The default should be sufficient for most projects and has its own set of configuration options. glow-up included, no extra charge ๐ŸŒˆ

For basic customization, you can choose either an traditional Markdown formatted changelog (default) or if you want to integrate with a Sphinx Documentation project, you can use the reStructuredText (RST) format. You can also choose the file name and location of where to write the default changelog.

To set your changelog location and changelog format, add the following to your pyproject.toml file:

[tool.semantic_release.changelog.default_templates]
changelog_file = "docs/source/CHANGELOG.rst"
output_format = "rst" # or "md" for Markdown format

Secondly, the more important aspect of configuring your changelog is to define Commit Exclusion Patterns or patterns that will be used to filter out commits from the changelog. PSR does NOT (yet) come with a built-in set of exclusion patterns, so you will need to define them yourself. These commit patterns should be in line with your project's commit parser configuration. like, keep it consistent, ok? ๐Ÿ’…

To set commit exclusion patterns for a conventional commits parsers, add the following to your pyproject.toml file:

[tool.semantic_release.changelog]
# Recommended patterns for conventional commits parser that is scope aware
exclude_commit_patterns = [
    '''chore(?:\([^)]*?\))?: .+''',
    '''ci(?:\([^)]*?\))?: .+''',
    '''refactor(?:\([^)]*?\))?: .+''',
    '''style(?:\([^)]*?\))?: .+''',
    '''test(?:\([^)]*?\))?: .+''',
    '''build\((?!deps\): .+)''',
    '''Initial [Cc]ommit.*''',
]

See also:

  • Changelog <#config-changelog> โ€” customize your changelog โœจ
  • changelog.mode <#config-changelog-mode> โ€” pick your changelog mode ('update' or 'init') ๐Ÿ’ซ
  • Migrating an Existing Changelog <#changelog-templates-migrating-existing-changelog> ๐Ÿฆ‹

๐ŸŒˆ Defining your Release Branches

okay so this is the fun part. PSR has a genuinely powerful feature where you can manage release types across multiple branches โ€” which means you can set your project up so different branches do different jobs, like pre-release branches, beta branches, and your stable releases. main character energy for your whole branching strategy, honestly ๐Ÿ‘‘

Note: most projects that do NOT publish pre-releases are gonna be totally fine with PSR's built-in default. don't overthink it bestie ๐Ÿ’…

Wanna define an alpha pre-release branch for when you're working on a fix or a new feature? drop this into your pyproject.toml:

[tool.semantic_release.branches.alpha]
# Matches branches with the prefixes 'feat/', 'fix/', or 'perf/'.
match = "^(feat|fix|perf)/.+"
prerelease = true
prerelease_token = "alpha"

so any time you run semantic-release version on a branch with the prefix feat/, fix/, or perf/, PSR will figure out if a version bump is needed, and if it is, the resulting version is gonna be a pre-release version with the alpha token. like, for example:

  • branch main, version bump Patch, current version 1.0.0 โ†’ next version 1.0.1
  • branch fix/bug-1, version bump Patch, current version 1.0.0 โ†’ next version 1.0.1-alpha.1

see? it just knows. โœจ

See also:

  • Multibranch Releases <#multibranch-releases> โ€” learn all about multi-branch releases and how to configure them ๐Ÿซถ

๐Ÿš€ Configuring VCS Releases

You can set Python Semantic Release up to create Releases in your remote version control system, so you can publish assets and release notes for your project. cute, right? ๐Ÿ’–

To do that, you're gonna need to put an authentication token in the right environment variable so Python Semantic Release can authenticate with the remote VCS to push tags, create releases, or upload files. no token, no vibe check passed.


๐Ÿ‘‘ GitHub (GH_TOKEN)

For local publishing to GitHub, you should use a personal access token and store it in your environment variables. Specify the name of the environment variable in your remote.token setting <#config-remote-token>. The default is GH_TOKEN.

To generate a token go to https://github.com/settings/tokens and click on "Generate new token".

For Personal Access Token (classic), you will need the repo scope to write (ie. push) to the repository.

For fine-grained Personal Access Tokens, you will need the contents permission:

https://docs.github.com/en/rest/authentication/permissions-required-for-fine-grained-personal-access-tokens#repository-permissions-for-contents


๐Ÿฆ‹ GitLab (GITLAB_TOKEN)

A personal access token from GitLab. This is used for authenticating when pushing tags, publishing releases etc. This token should be stored in the GITLAB_TOKEN environment variable.


๐ŸŒธ Gitea (GITEA_TOKEN)

A personal access token from Gitea. This token should be stored in the GITEA_TOKEN environment variable.


๐Ÿ“ Bitbucket (BITBUCKET_TOKEN)

ngl, Bitbucket does not support uploading releases, but it can still benefit from automated tags and changelogs ๐Ÿ™ˆ. The user has three options to push changes to the repository:

  1. Use SSH keys. ๐Ÿ”‘
  2. Use an App Secret โ€” https://support.atlassian.com/bitbucket-cloud/docs/push-back-to-your-repository/#App-secret โ€” store the secret in the BITBUCKET_TOKEN environment variable and the username in BITBUCKET_USER.
  3. Use an Access Token for the repository โ€” https://support.atlassian.com/bitbucket-cloud/docs/repository-access-tokens โ€” and store it in the BITBUCKET_TOKEN environment variable.

See also:

  • Changelog <#config-changelog> โ€” customize your project's changelog โœจ
  • Custom Release Notes <#changelog-templates-custom-release-notes> โ€” customize the published release notes ๐Ÿ’ซ
  • version --vcs-release/--no-vcs-release โ€” enable/disable VCS release creation ๐ŸŽ€

๐Ÿงช Testing your Configuration

okay bestie, it's time to test your configuration! periodt ๐Ÿ’…

# 1. Run the command in no-operation mode to see what would happen
semantic-release -v --noop version

# 2. If the output looks reasonable, try to run the command without any history changes
#    '-vv' will give you verbose debug output, which is useful for troubleshooting
#    commit parsing issues.
semantic-release -vv version --no-commit --no-tag

# 3. Evaluate your repository to see the changes that were made but not committed
#    - Check the version number in your pyproject.toml
#    - Check the distribution files from the build command
#    - Check the changelog file for the new release notes

# 4. If everything looks good, make sure to commit/save your configuration changes
git add pyproject.toml
git commit -m "chore(config): configure Python Semantic Release"

# 5. Now, try to run the release command with your history changes but without pushing
semantic-release -v version --no-push --no-vcs-release

# 6. Check the result on your local repository
git status
git log --graph --decorate --all

# 7a. If you are happy with the release history and resulting commit & tag,
#     reverse your changes before trying the full release command.
git tag -d v0.0.1  # replace with the actual version you released
git reset --hard HEAD~1

# 7b. [Optional] Once you have configured a remote VCS token, try
#     running the full release command to update the remote repository.
semantic-release version --push --vcs-release
#    This is optional as you may not want a personal access token set up or make
#    make the release permanent yet.

See also:

  • cmd-version
  • Increasing Verbosity <#troubleshooting-verbosity> ๐Ÿ”

โšก Configuring CI/CD

PSR is literally meant to help you release at speed, so go check out our CI/CD Configuration guides under the Automated Releases <#automatic> section. ship it ๐Ÿšข


๐Ÿ” Commit Parsing

One of the core components of Python Semantic Release (PSR) is the commit parser. The commit parser is responsible for parsing a Project's Git Repository commit history to extract insights about project changes and make decisions based on this insight. it's basically the brain of the whole operation ๐Ÿง 

The primary decision that PSR makes based on the commit history is whether or not to release a new version of the project, and if so, what version number to release. This decision is made based on the commit message descriptions of the change impact introduced by the commit. The change impact describes the impact to the end consumers of the project. Depending on the type of change, the version number will be incremented according to the Semantic Versioning specification (semver) at https://semver.org/. It is the commit parser's job to extract the change impact from the commit message to determine the severity of the changes and then subsequently determine the semver level that the version should be bumped to for the next release. no cap ๐Ÿ“ˆ

The commit parser is also responsible for interpreting other aspects of the commit message which can be used to generate a helpful and detailed changelog. This includes extracting the type of change, the scope of the change, any breaking change descriptions, any linked pull/merge request numbers, and any linked issue numbers.

PSR provides several built-in commit parsers to handle a variety of different commit message styles. And if the built-in parsers do not meet your needs, you can write your own custom parser to handle your specific commit message style. build your own, king ๐Ÿ’…

Warning: PSR's built-in commit parsers are designed to be flexible enough to provide a convenient way to generate the most effective changelogs we can, which means some features are added beyond the scope of the original commit message style guidelines. Other tools may not follow the same conventions as PSR's guideline extensions, so if you plan to use any similar programs in tandem with PSR, you should be aware of the differences in feature support and fall back to the official format guidelines if necessary. โš ๏ธ not me warning you twice but like, be careful bestie

๐ŸŽ€ Built-in Commit Parsers

The following parsers are built in to Python Semantic Release:

  • ConventionalCommitParser
  • ConventionalCommitMonorepoParser (available in v10.4.0+) โœจ
  • AngularCommitParser (deprecated in v9.19.0) ๐Ÿ’€
  • EmojiCommitParser
  • ScipyCommitParser
  • TagCommitParser (deprecated in v9.12.0) ๐Ÿ’€

๐ŸŒŸ Conventional Commits Parser

Introduced in v9.19.0

A parser that is designed to parse commits formatted according to the Conventional Commits Specification at https://www.conventionalcommits.org/en/v1.0.0. The parser is implemented with the following logic in relation to PSR's core features:

  • Version Bump Determination</p> โ€” so like, this parser peeks at the commit type sitting in the subject line of the commit (that's the first line of a commit message, bestie, the headline of the whole thing ๐Ÿ’…). That type gets matched against the config mapping to figure out the level bump for that specific commit. If the commit type isn't in the config mapping, no cap, the commit is considered a non-parsable commit and it gets returned as a ParseError object and ultimately a commit of type "unknown" ๐Ÿ˜ญ. The config mapping holds lists of commit types that correspond to the level bump for each commit type. Some commit types are still valid but just do not trigger a level bump โ€” think "chore" or "docs" โ€” they're just vibing, living their life. You can also configure the default level bump commit_parser_options.default_level_bump (see #config-commit-parser-options) if you want. And to trigger a major release, the commit message body must contain a paragraph that begins with BREAKING CHANGE:. That overrides the level bump determined by the commit type, periodt. ๐Ÿ‘‘
  • Changelog Generation</p> โ€” PSR groups commits in the changelog by the commit type used in the commit message. The commit type shorthand gets converted to a more human-friendly section heading and then used as the version section title of the changelog and release notes. Under that section title, the parsed commit descriptions are listed out in full โ€” all of them, no skimming โœจ. If the commit includes an optional scope, that scope gets prefixed onto the first line of the commit description. If a commit has any breaking change prefixed paragraphs in the commit message body, those paragraphs are separated out into a "Breaking Changes" section in the changelog (the Breaking Changes section is available from the default changelog in v9.15.0 ๐ŸŽ€). Each breaking change paragraph is listed in a bulleted list format across the entire version. A single commit is allowed to have more than one breaking change prefixed paragraph (as opposed to the Conventional Commits Specification https://www.conventionalcommits.org/en/v1.0.0). Commits with an optional scope and a breaking change will have the scope prefixed onto the breaking change paragraph. Parsing errors return a ParseError object and ultimately a commit of type "unknown". Unknown commits are consolidated into an "Unknown" section in the changelog by the default template. And to remove unwanted commits from the changelog that normally get placed in the "unknown" section, consider using the configuration option changelog.exclude_commit_patterns (see #config-changelog-exclude-commit-patterns) to ignore those commit styles ๐Ÿซถ.
  • Pull/Merge Request Identifier Detection</p> โ€” this parser implements PSR's Common Linked Merge Request Detection to identify and extract pull/merge request numbers. The parser returns a string value if a pull/merge request number is found in the commit message. If no pull/merge request number is found, the parser returns an empty string. Simple as that ๐ŸŒธ.
  • Linked Issue Identifier Detection</p> โ€” this parser implements PSR's Common Issue Identifier Detection to identify and extract issue numbers. The parser returns a tuple of issue numbers as strings if any are found in the commit message. If no issue numbers are found, the parser returns an empty tuple. Iconic behaviour honestly ๐Ÿฆ‹.
  • Squash Commit Evaluation</p> โ€” this parser implements PSR's Common Squash Commit Evaluation to identify and extract each commit message as a separate commit message within a single squashed commit. You can toggle this feature on/off via the commit_parser_options setting (see #config-commit-parser-options) ๐Ÿ”ฅ.
  • Release Notice Footer Detection</p> โ€” this parser implements PSR's Common Release Notice Footer Detection, which is a custom extension to the traditional Conventional Commits Specification https://www.conventionalcommits.org/en/v1.0.0 so you can use the NOTICE keyword as a git footer to document additional release information that is not considered a breaking change. Cute, informative, not dramatic โœจ.

Limitations: ๐Ÿ’€

If no commit parser options are provided via the configuration, the parser will use PSR's built-in defaults (see #semantic_release.commit_parser.conventional.options.ConventionalCommitParserOptions) ๐ŸŽ€.


Conventional Commits Monorepo Parser ๐Ÿข๐Ÿ’–

Introduced in v10.4.0

Important: In order for this parser to be effective, please review the section titled Releasing Packages from a Monorepo (see #monorepos) for details on file structure, configurations, and release actions. Bestie, actually read it, I'm begging ๐Ÿฅบ

This parser is an extension of the Conventional Commits Parser, designed specifically for monorepo environments. A monorepo environment is defined as a single source control repository that contains multiple packages, each of which can be released independently and may have different version numbers. Yes, multiple packages, multiple versions, one repo, one girlboss ๐Ÿ’….

This parser introduces two new configuration options that determine which packages are affected by a commit. These options control whether a commit is considered for version determination, changelog generation, and other release actions for the relevant packages. The 2 new configuration options are path_filters (see #semantic_release.commit_parser.conventional.options_monorepo.ConventionalCommitMonorepoParserOptions.path_filters) and scope_prefix (see #semantic_release.commit_parser.conventional.options_monorepo.ConventionalCommitMonorepoParserOptions.scope_prefix).

Features: ๐ŸŒˆ

  • Package Specific Commit Filtering โ€” for monorepo support, this parser uses 2 filtering rules to determine if a commit should be considered for a specific package. The first rule is based on file paths that are changed in the commit, and the second rule is based on the optional scope prefix defined in the commit message. If either rule matches, then the commit is considered relevant to that package and will be used in version determination, changelog generation, etc, for that package. If neither rule matches, then the commit is ignored for that package. File path filtering rules are applied first and are the primary way to determine package relevance. The path_filters option (see #semantic_release.commit_parser.conventional.options_monorepo.ConventionalCommitMonorepoParserOptions.path_filters) allows for specifying a list of file path patterns and will also support negated patterns to ignore specific paths that otherwise would be selected from the file glob pattern. Negated patterns are defined by prefixing the pattern with an exclamation point (!). File path filtering is quite effective by itself, but to handle the edge cases, the parser has the scope_prefix configuration option (see #semantic_release.commit_parser.conventional.options_monorepo.ConventionalCommitMonorepoParserOptions.scope_prefix) to allow the developer to specifically define when the commit is relevant to the package. In monorepo setups, there are often shared files between packages (generally at the root project level) that are modified occasionally but not always relevant to the package being released. Since you do not want to define this path in the package configuration as it may not be relevant to the release, then this parser will look for a match with the scope prefix. The scope prefix is a regular expression that is used to match the text inside the scope field of a Conventional Commit. The scope prefix is optional and is used only if file path filtering does not match. Commits that have matching files in the commit will be considered relevant to the package regardless if a scope prefix exists or if it matches ๐Ÿ”ฅ.
  • Version Bump Determination โ€” once package-specific commit filtering is applied, the relevant commits are passed to the Conventional Commits Parser for evaluation and then used for version bump determination. See Conventional Commits Parser for details ๐Ÿ‘€.
  • Changelog Generation โ€” once package-specific commit filtering is applied, the relevant commits are passed to the Conventional Commits Parser for evaluation and then used for changelog generation. See Conventional Commits Parser for details โœจ.
  • Pull/Merge Request Identifier Detection โ€” once package-specific commit filtering is applied, the relevant commits are passed to the Conventional Commits Parser for pull/merge request identifier detection. See Common Linked Merge Request Detection for details ๐ŸŽ€.
  • Linked Issue Identifier Detection โ€” once package-specific commit filtering is applied, the relevant commits are passed to the Conventional Commits Parser for linked issue identifier detection. See Common Issue Identifier Detection for details ๐Ÿซถ.
  • Squash Commit Evaluation โ€” squashed commits are separated out into individual commits with the same set of changed files BEFORE the package-specific commit filtering is applied. Each pseudo-commit is then subjected to the same filtering rules as regular commits. See Common Squash Commit Evaluation for details ๐Ÿ’–.
  • Release Notice Footer Detection โ€” once package-specific commit filtering is applied, the relevant commits are passed to the Conventional Commits Parser for release notice footer detection. See Common Release Notice Footer Detection for details โญ.

Limitations: ๐Ÿ™ˆ

  • revert commit type is NOT supported, see Conventional Commits Parser's limitations for details. (Yeah, still no revert, we been knew ๐Ÿ’€)

If no commit parser options are provided via the configuration, the parser will use PSR's built-in defaults (see #semantic_release.commit_parser.conventional.options_monorepo.ConventionalCommitMonorepoParserOptions) ๐Ÿ’œ.


Angular Commit Parser ๐Ÿ˜ญ

Warning: This parser was deprecated in v9.19.0. It will be removed in a future release. This parser is being replaced by the Conventional Commits Parser. No cap, she's retiring, go say hi to the Conventional Commits Parser instead ๐Ÿ‘‘

A parser that is designed to parse commits formatted according to the Angular Commit Style Guidelines https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits. The parser is implemented with the following logic in relation to how PSR's core features:

  • Version Bump Determination ๐ŸŒˆ โ€” okay so this parser pulls the commit type out of the subject line of the commit (that's the very first line of a commit message, bestie). That type gets matched against the configuration mapping to figure out the level bump for that specific commit. If the commit type isn't found in the configuration mapping, it's a non-parsable commit โ€” it comes back as a ParseError object and, ultimately, a commit of type "unknown" ๐Ÿ’€. The configuration mapping holds lists of commit types, each one corresponding to the level bump for that commit type. And lowkey, some commit types are still valid but do not trigger a level bump at all โ€” think "chore" or "docs". No bump, no drama ๐Ÿ“. You can also configure the default level bump with commit_parser_options.default_level_bump (#config-commit-parser-options) if you want. And to trigger a major release, the commit message body has to contain a paragraph that begins with BREAKING CHANGE:. That overrides the level bump determined by the commit type. periodt ๐Ÿ’…
  • Changelog Generation โœจ โ€” PSR groups commits in the changelog by the commit type used in the commit message. That commit type shorthand gets converted into a way more human-friendly section heading, and that becomes the version section title of the changelog and release notes. Under the section title, the parsed commit descriptions are listed out in full, no skimping ๐Ÿ‘‘. If the commit includes an optional scope, that scope is prefixed on to the first line of the commit description. And if a commit has any breaking-change-prefixed paragraphs in the commit message body, those paragraphs get separated out into a "Breaking Changes" section in the changelog (the Breaking Changes section is available from the default changelog in v9.15.0). Each breaking change paragraph is listed in a bulleted list format across the entire version. A single commit is allowed to have more than one breaking change prefixed paragraph (as opposed to the Angular Commit Style Guidelines at https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits). Commits with an optional scope and a breaking change get the scope prefixed on to the breaking change paragraph too. Parsing errors return a ParseError object and, ultimately, a commit of type "unknown". Unknown commits get consolidated into an "Unknown" section in the changelog by the default template. Wanna yeet the unwanted commits that normally land in the "unknown" section? Peep the configuration option changelog.exclude_commit_patterns (#config-changelog-exclude-commit-patterns) to ignore those commit styles ๐Ÿงš
  • Pull/Merge Request Identifier Detection ๐Ÿ”ฅ โ€” this parser implements PSR's Common Linked Merge Request Detection to identify and extract pull/merge request numbers. The parser returns a string value if a pull/merge request number is found in the commit message. If no pull/merge request number is found, the parser returns an empty string. Feature available in v9.13.0+ โญ
  • Linked Issue Identifier Detection ๐Ÿ’œ โ€” this parser implements PSR's Common Issue Identifier Detection to identify and extract issue numbers. The parser returns a tuple of issue numbers as strings if any are found in the commit message. If no issue numbers are found, the parser returns an empty tuple. Feature available in v9.15.0+ ๐ŸŽ€
  • Squash Commit Evaluation ๐Ÿฆ‹ โ€” this parser implements PSR's Common Squash Commit Evaluation to identify and extract each commit message as a separate commit message within a single squashed commit. You can toggle this feature on/off via the commit_parser_options (#config-commit-parser-options) setting. Feature available in v9.17.0+ ๐ŸŒธ
  • Release Notice Footer Detection ๐ŸŒˆ โ€” this parser implements PSR's Common Release Notice Footer Detection, which is a custom extension to the traditional Angular Commit Style Guidelines at https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits to use the NOTICE keyword as a git footer to document additional release information that is not considered a breaking change. Feature available in v9.18.0+ ๐Ÿ’–

Limitations: ๐Ÿซ 

If no commit parser options are provided via the configuration, the parser will use PSR's built-in defaults (#semantic_release.commit_parser.angular.AngularParserOptions) โœ…


Emoji Commit Parser ๐Ÿ˜ญ๐Ÿ‘‘

A parser that's designed to parse commits formatted to the Gitmoji Specification at https://gitmoji.dev/specification, with a few extra features that the spec doesn't cover but that give you similar functionality to what you'd expect from a Semantic Release tool. As the Gitmoji Specification at https://gitmoji.dev/specification describes, the emojis can be specified in either the unicode format or the shortcode text format. Go read the Gitmoji Specification at https://gitmoji.dev/specification for the pros and cons of which format to use, but regardless: your configuration options must match the format actually used in your commit messages. iykyk ๐Ÿ™ˆ

The parser is implemented with the following logic in relation to how PSR's core features work:

  • Version Bump Determination โ€” this parser only looks for emojis in the subject line of the commit (the first line of a commit message). If more than one emoji is found, the emoji configured with the highest priority is the one selected for the change impact for that specific commit. The highest-priority emoji is the one configured in the major configuration option, followed by minor, and patch, in descending priority order. If no emoji is found in the subject line, the commit is classified as other and defaults to the level bump defined by the configuration option commit_parser_options.default_level_bump (#config-commit-parser-options) ๐Ÿ“
  • Changelog Generation โ€” PSR groups commits in the changelog by the emoji type used in the commit message. The emoji becomes the version section title, and the commit descriptions are listed under that section. No emojis are removed from the commit message, so every single one of them will be listed in the changelog and release notes ๐Ÿ‘‘. When more than one emoji is found in the subject line of a commit, the highest-priority emoji is the one that influences the grouping of the commit in the changelog. Commits containing no emojis or non-configured emojis get consolidated into an "Other" section. Wanna clean out the unwanted commits that would normally land in the "other" section? Consider the configuration option changelog.exclude_commit_patterns (#config-changelog-exclude-commit-patterns) to ignore those commit styles ๐Ÿ’…
  • Pull/Merge Request Identifier Detection โ€” this parser implements PSR's Common Linked Merge Request Detection to identify and extract pull/merge request numbers. The parser returns a string value if a pull/merge request number is found in the commit message. If no pull/merge request number is found, the parser returns an empty string. Feature available in v9.13.0+ โญ
  • Linked Issue Identifier Detection โ€” [Disabled by default] this parser implements PSR's Common Issue Identifier Detection to identify and extract issue numbers. The parser returns a tuple of issue numbers as strings if any are found in the commit message. If no issue numbers are found, the parser returns an empty tuple. This feature is disabled by default since it is not a part of the Gitmoji Specification at https://gitmoji.dev/specification, but you can enable it by setting the configuration option commit_parser_options.parse_linked_issues to true. Feature available in v9.15.0+ ๐ŸŽ€
  • Squash Commit Evaluation โ€” this parser implements PSR's Common Squash Commit Evaluation to identify and extract each commit message as a separate commit message within a single squashed commit. You can toggle this feature on/off via the commit_parser_options (#config-commit-parser-options) setting. Feature available in v9.17.0+ ๐Ÿฆ‹
  • Release Notice Footer Detection โ€” this parser implements PSR's Common Release Notice Footer Detection, which is a custom extension that uses the NOTICE keyword as a git footer to document additional release information that is not considered a breaking change. Feature available in v9.18.0+ ๐ŸŒˆ

If no commit parser options are provided via the configuration, the parser will use PSR's built-in defaults (#semantic_release.commit_parser.emoji.EmojiParserOptions) โœ…


Scipy Commit Parser ๐Ÿงช

A parser designed to parse commits formatted according to the Scipy Commit Style Guidelines at https://scipy.github.io/devdocs/dev/contributor/development_workflow.html#writing-the-commit-message. Basically it's a variation of the Angular Commit Style Guidelines at https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits but with all different commit types. And because that variance is so small, this parser just extends our Angular Commit Parser with pre-defined scipy commit types in the default Scipy Parser Options, and every other feature is inherited ๐Ÿ’œ

Limitations: ๐Ÿซ 

If no commit parser options are provided via the configuration, the parser will use PSR's built-in defaults (#semantic_release.commit_parser.scipy.ScipyParserOptions) โœ…


Tag Commit Parser ๐Ÿท๏ธ

Warning: โš ๏ธ This parser was deprecated in v9.12.0. It will be removed in a future release.

The original parser from v1.0.0 of Python Semantic Release. Similar to the emoji parser above, but with fewer features ๐Ÿฅฒ

If no commit parser options are provided via the configuration, the parser will use PSR's built-in defaults (#semantic_release.commit_parser.tag.TagParserOptions) โœ…


Common Linked Merge Request Detection ๐Ÿ”—

Introduced in v9.13.0 โœจ

All of the PSR built-in parsers implement common pull/merge request identifier detection logic to extract pull/merge request numbers from the commit message, no matter the VCS platform. The parsers evaluate the subject line for a parenthesis-enclosed number at the end of the line. PSR's parsers will return a string value if a pull/merge request number is found in the commit message. If no pull/merge request number is found, the parsers will return an empty string ๐Ÿ‘‘

Examples: ๐ŸŒŸ

Alllll of the following will hand you back a MR number of x123, where x is the character prefix ๐Ÿ’…

  1. BitBucket: Merged in feat/my-awesome-feature (pull request #123)
  2. GitHub: feat: add new feature (#123)
  3. GitLab: fix: resolve an issue (!123)

๐Ÿ’– Common Issue Identifier Detection ๐Ÿ’–

Introduced in v9.15.0

okay so every single one of the PSR built-in parsers has this common issue identifier detection logic baked in, and it's giving very much GitHub / GitLab / BitBucket energy โ€” same vibe as those platforms, basically. What the parsers do is go looking for the usual issue closure text prefixes in the Git Trailer format (https://git-scm.com/docs/git-interpret-trailers) inside the commit message, and then they identify + extract the issue numbers. The detection logic is not strict to any one specific issue tracker because PSR is trying to be flexible about finding issue numbers โ€” but bestie, being flexible comes at a price: you must use that Git Trailer format with a colon (:) as the token separator. That's the deal, no cap.

PSR attempts to support all the variants of issue closure text prefixes, but heads up: not all of them will work for your VCS. Here's what PSR supports โ€” case-insensitive, plus their conjugations (plural, present, & past tense):

  • close (closes, closing, closed)
  • fix (fixes, fixing, fixed)
  • resolve (resolves, resolving, resolved)
  • implement (implements, implementing, implemented)

PSR also lets you be flexible about spotting more than one issue number without needing extra git trailers (though yes, PSR does support multiple git trailers too, she's versatile like that ๐Ÿ’). PSR supports various list formats so you can identify multiple issues in one list. Fair warning though: this format will not necessarily work on your VCS. Currently PSR supports these list formats:

  • comma-separated (ex. Closes: #123, #456, #789)
  • space-separated (ex. resolve: #123 #456 #789)
  • semicolon-separated (ex. Fixes: #123; #456; #789)
  • slash-separated (ex. close: #123/#456/#789)
  • ampersand-separated (ex. Implement: #123 & #789)
  • and-separated (ex. Resolve: #123 and #456 and #789)
  • mixed (ex. Closed: #123, #456, and #789 or Fixes: #123, #456 & #789)

Every single example up there uses the most common issue number prefix (#), but PSR is flexible enough to support other prefixes that VCS platforms or issue trackers use, like JIRA (ex. ABC-###).

If any issue numbers are found in the commit message, the parsers return a tuple of issue numbers as strings. Why strings? So that any issue number prefix characters get preserved (ex. #123 or ABC-123). And if no issue numbers are found at all? The parsers return an empty tuple. Simple as. โœจ

References:


*Introduced in v9.18.0**

All the PSR built-in parsers implement common release notice footer detection logic, and its whole job is to find and extract a NOTICE git trailer โ€” aka the place a developer documents any extra release info they want the software consumer to know about. The idea literally extends from the concept of the BREAKING CHANGE: git trailer, which documents breaking change descriptions. But the NOTICE trailer is meant for info that's below the threshold of a breaking change while still being important enough that the software consumer should be aware of it. Common uses? Dropping deprecation warnings, or giving more detailed change usage info for that release. ๐Ÿซถ

Parsers will collapse single newlines after the NOTICE trailer into a single line paragraph. And a commit can have more than one NOTICE trailer in a single commit message โ€” she can multitask. Each ParsedCommit (#semantic_release.commit_parser.token.ParsedCommit) will have a release_notices attribute that is a tuple of string paragraphs, one per release notice.

In the default changelog and release notes template, these release notices get formatted into their very own section called Additional Release Information. Each one includes any commit scope defined, and each release notice appears in alphabetical order. Organized queen. ๐Ÿ‘‘


๐ŸŒธ Common Squash Commit Evaluation ๐ŸŒธ

Introduced in v9.17.0

Every PSR built-in parser implements common squash commit evaluation logic, which identifies and extracts the individual commit messages out of a single squashed commit. The parsers look for common squash commit delimiters plus multiple matches of the commit message format, so they can find each individual commit message that got squashed. What you get back is a list containing each commit message as a separate commit object. Squashed commits get evaluated individually for both the level bump and changelog generation. And if no squash commits are found? You get a list with the single commit object in it.

Currently PSR has been tested against GitHub, BitBucket, and official git squash merge commit messages. GitLab doesn't have a default template for squash commit messages, but you can customize it per project or server. So if you're on GitLab, bestie, you need to make sure your squash commit message format is similar to the example below. ๐Ÿ™ˆ

Example:

The following example will extract three separate commit messages from a single GitHub formatted squash commit message of conventional commit style:

feat(config): add new config option (#123)

* refactor(config): change the implementation of config loading

* docs(configuration): defined new config option for the project

When parsed with the default conventional-commit parser with squash commits toggled on, the version bump will be determined by the highest level bump of the three commits (in this case, a minor bump because of the feature commit) and the release notes would look similar to the following:

## Features

- **config**: add new config option (#123)

## Documentation

- **configuration**: defined new config option for the project (#123)

## Refactoring

- **config**: change the implementation of config loading (#123)

Merge request numbers and commit hash values will be the same across all extracted commits. Also, any exclude_commit_patterns (#config-changelog-exclude-commit-patterns) get applied individually to each extracted commit โ€” so if you have an exclusion match for ignoring refactor commits, that second commit in the example above would be excluded from the changelog. Bye bye refactor commit ๐Ÿ‘‹

Important: When squash commit evaluation is enabled, if you squashed a higher level bump commit into the body of a lower level bump commit, the higher level bump commit will be evaluated as the level bump for the entire squashed commit. This includes breaking change descriptions.

๐Ÿ’œ Customization ๐Ÿ’œ

Each of the built-in parsers can be customized by providing overrides in the commit_parser_options (#config-commit-parser-options) setting of the configuration file. Use this to toggle parsing features on and off, or to add, modify, or remove the commit types that determine the level bump for a commit. Go review the API documentation for your specific parser's options class to see what changes to the default behavior you're allowed to make. Customize away, it's your world โœจ


๐Ÿฆ‹ Custom Parsers ๐Ÿฆ‹

Custom parsers can be written to handle commit message styles that the built-in parsers don't cover โ€” or that option customization of the built-in parsers can't reach. Sometimes you just gotta write your own, and that's valid.

Python Semantic Release provides several building blocks to help you write your parser. To maintain compatibility with how Python Semantic Release will invoke your parser, you should use the appropriate object as described below โ€” or create your own object as a subclass of the original that maintains the same interface. Type parameters are defined where appropriate to assist with static type-checking. ๐Ÿ”ฅ

The commit_parser (#config-commit-parser) option, if it's set to a string that does not match one of Python Semantic Release's built-in commit parsers, will be used to attempt to dynamically import a custom commit parser class.

In order to use your custom parser, you must provide how to import the module and class via the configuration option. There are two ways to provide the import string:

  1. File Path & Class: The format is "path/to/module_file.py:ClassName". This is the easiest way to provide a custom parser. This method allows you to store your custom parser directly in the repository with no additional installation steps. PSR will locate the file, load the module, and instantiate the class. Relative paths are recommended and it should be provided relative to the current working directory. This import variant is available in v9.16.0 and later.
  2. Module Path & Class: The format is "package.module_name:ClassName". This method allows you to store your custom parser in a package that is installed in the same environment as PSR. This method is useful if you want to share your custom parser across multiple repositories. To share it across multiple repositories you'll generally need to publish the parser as its own separate package, and then pip install it into the current virtual environment. You can also keep it in the same repository as your project as long as it's in the current directory of the virtual environment and is locatable by the Python import system. You may need to set the PYTHONPATH environment variable if you have a more complex directory structure. This import variant is available in v8.0.0 and later.

To test that your custom parser is importable, you can run the following command in the directory where PSR will be executed:

python -c "from package.module_name import ClassName"
Note: Remember this is basic python import rules, so the package name is optional and generally packages are defined by a directory with __init__.py files.

Tokens

The tokens baked into Python Semantic Release's commit parsing mechanism are inspired by two very iconic sources: the error-handling mechanism in Rust's error handling (https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html) and its implementation in black (https://github.com/psf/black/blob/main/src/black/rusty.py). It is documented that catching exceptions in Python is slower (https://docs.python.org/3/faq/design.html#how-fast-are-exceptions) than the equivalent guard implemented using if/else checking when exceptions are actually caught, so although try/except blocks are cheap if no exception is raised, commit parsers should always return an object such as ParseError <#semantic_release.commit_parser.token.ParseError> instead of raising an error immediately. Why? Because otherwise you'd be catching a potentially large number of parsing errors as the commit history of a repository is being parsed. Not the vibe. Python Semantic Release does not raise an exception if a commit cannot be parsed. ๐Ÿ’…

Python Semantic Release uses ParsedCommit <#semantic_release.commit_parser.token.ParsedCommit> as the return type of a successful parse operation, and ParseError <#semantic_release.commit_parser.token.ParseError> as the return type from an unsuccessful parse of a commit. Bestie, you should review the API documentation linked to understand the fields available on each of these objects. ๐Ÿ‘‘

It is important to note, the ParseError <#semantic_release.commit_parser.token.ParseError> implements an additional method, raise_error. This method raises a CommitParseError <#semantic_release.errors.CommitParseError> with the message contained in the error field, as a convenience.

In Python Semantic Release, the type semantic_release.commit_parser.token.ParseResult is defined as ParseResultType[ParsedCommit, ParseError], as a convenient shorthand. ๐Ÿซถ

ParseResultType is a generic type, which is the Union of its two type parameters. One of the types in this union should be the type returned on a successful parse of the commit, while the other should be the type returned on an unsuccessful parse of the commit.

A custom parser result type, therefore, could be implemented as follows:

  • MyParsedCommit subclasses ParsedCommit <#semantic_release.commit_parser.token.ParsedCommit>
  • MyParseError subclasses ParseError <#semantic_release.commit_parser.token.ParseError>
  • MyParseResult = ParseResultType[MyParsedCommit, MyParseError]

Internally, Python Semantic Release uses isinstance() to determine if the result of parsing a commit was a success or not, so you should check that your custom result and error types return True from isinstance(<object>, ParsedCommit) and isinstance(<object>, ParseError) respectively. Literally the vibe check of the whole parsing pipeline. โœจ

While it's not advisable to remove any of the fields that are available in the built-in token types, currently only the bump field of the successful result type is used to determine how the version should be incremented as part of this release. However, it's perfectly possible to add additional fields to your tokens which can be populated by your parser; these fields will then be available on each commit in your changelog template <#changelog-templates>, so you can make additional information available. Extra data? We love to see it. ๐ŸŽ€

Parser Options

When writing your own parser, you should accompany the parser with an "options" class which accepts the appropriate keyword arguments. This class' __init__ method should store the values that are needed for parsing appropriately. Python Semantic Release will pass any configuration options from the configuration file's commit_parser_options <#config-commit-parser-options> into your custom parser options class. To ensure that the configuration options are passed correctly, the options class should inherit from the ParserOptions class.

The "options" class is used to validate the options which are configured in the repository, and to provide default values for these options where appropriate. So it's giving validator and fallback bestie at the same time. ๐Ÿฆ‹

Commit Parsers

The commit parsers that are built into Python Semantic Release implement an instance method called parse, which takes a single parameter commit of type git.objects.commit.Commit <gitpython-commit-object>, and returns the type ParseResultType.

To be compatible with Python Semantic Release, a commit parser must subclass CommitParser. A subclass must implement the following:

  • A class-level attribute parser_options, which must be set to ParserOptions or a subclass of this.
  • An __init__ method which takes a single parameter, options, that should be of the same type as the class' parser_options attribute.
  • A method, parse, which takes a single parameter commit that is of type git.objects.commit.Commit <gitpython-commit-object>, and returns ParseResult, or a subclass of this.

By default, the constructor for CommitParser will set the options parameter on the options attribute of the parser, so there is no need to override this in order to access self.options during the parse method. However, if you have any parsing logic that needs to be done only once, it may be a good idea to perform this logic during parser instantiation rather than inside the parse method. The parse method will be called once per commit in the repository's history during parsing, so the effect of slow parsing logic within the parse method will be magnified significantly for projects with sizeable Git histories. No cap โ€” one slow line in there and it's over for your big-repo besties. ๐Ÿ’€

Commit Parsers have two type parameters, "TokenType" and "OptionsType". The first is the type which is returned by the parse method, and the second is the type of the "options" class for this parser.

Therefore, a custom commit parser could be implemented via:

class MyParserOptions(semantic_release.ParserOptions):
    def __init__(self, message_prefix: str) -> None:
        self.prefix = message_prefix * 2

class MyCommitParser(
    semantic_release.CommitParser[semantic_release.ParseResult, MyParserOptions]
):
    def parse(self, commit: git.objects.commit.Commit) -> semantic_release.ParseResult:
        ...

Version Change Reports

When using the cmd-version and cmd-changelog commands, Python Semantic Release (PSR) will generate a changelog and release notes for your project automatically in the default configuration. The changelog is rendered using the Jinja (https://jinja.palletsprojects.com/en/3.1.x/) template engine, and in the default configuration, PSR will use a built-in template file to render the changelog at the file location defined by the changelog_file <#config-changelog-changelog-file> setting. Automatic glow-up, zero effort. ๐ŸŒˆ

Through the use of the templating engine & the template_dir <#config-changelog-template-dir> configuration setting, you can customize the appearance of your changelog and release notes content. You may also generate a set of files using your custom template directory and the templates will be rendered relative to the root of your repository.

Because PSR uses a third-party library, Jinja (https://jinja.palletsprojects.com/en/3.1.x/), as its template engine, we do not include all the syntax within our documentation but rather you should refer to the Template Designer Documentation (https://jinja.palletsprojects.com/en/3.1.x/templates/) for guidance on how to customize the appearance of your release files. If you would like to customize the template environment itself, then certain options are available to you via changelog environment configuration <#config-changelog-environment>.

If you do not want to use the changelog generation features, you can disable changelog generation entirely during the cmd-version command by providing the --no-changelog command-line option.

Using the Default Changelog

If you don't provide any custom templates in the changelog.template_dir <#config-changelog-template-dir>, the default changelog templates will be used to render the changelog. Defaults ate, honestly. ๐Ÿ’–

PSR provides two default changelog output formats:

  1. Markdown (.md), default
  2. reStructuredText (.rst), available since v9.11.0

Both formats are kept in sync with one another to display the equivalent information in the respective format. The default changelog template is located in the data/templates/ directory within the PSR package. The templates are written in modular style (ie. multiple files) and during the render process are ultimately combined together to render the final changelog output. The rendering start point is the CHANGELOG.{FORMAT_EXT}.j2 underneath the respective format directory.

PSR provides a few configuration options to customize the default changelog output and can be found under the changelog.default_templates <#config-changelog-default-templates> section as well as some common configuration options under the changelog <#config-changelog> section.

To toggle the output format, you only need to set the changelog.default_templates.changelog_file <#config-changelog-default-templates-changelog-file> file name to include the desired file extension (.md or .rst). If you would like a different extension for the resulting changelog file, but would like to still have control over the template format, you can set the changelog.default_templates.output_format <#config-changelog-default-templates-output-format> configuration setting to the desired format.

A common and highly-recommended configuration option is the changelog.exclude_commit_patterns <#config-changelog-exclude-commit-patterns> setting which allows the user to define regular expressions that will exclude commits from the changelog output. This is useful to filter out change messages that are not relevant to your external consumers (ex. ci and test in the conventional commit standard) and only include the important changes that impact the consumer of your software. Keep the feed clean, bestie. ๐Ÿงš

Another important configuration option is the changelog.mode <#config-changelog-mode> setting which determines the behavior of the changelog generation. There are 2 modes that available that described in detail below.

  1. Initialization Mode when mode = "init".
  2. Update Mode when mode = "update".

Initialization Mode

When using the initialization mode, the changelog file will be created from scratch using the entire git history and overwrite any existing changelog file. This is the default behavior introduced in v8.0.0. This is useful when you are trying to convert over to Python Semantic Release for the first time or when you want to automatically update the entire format of your changelog file. Fresh start energy. ๐ŸŒธ

Warning: If you have an existing changelog in the location you have configured with the changelog.changelog_file <#config-changelog-changelog-file> setting, PSR will overwrite the contents of this file on each release. Please make sure to refer to Migrating an Existing Changelog.

โš ๏ธ Like, actually read that warning. It will not ask twice and it will not feel bad about it. ๐Ÿ™ˆ

๐Ÿซถ Update Mode ๐Ÿซถ

Note: introduced in v9.10.0, bestie. It's giving fresh off the press. ๐Ÿ’…

So when you're in update mode, the only thing that gets added to your existing changelog file is the change info from the last release โ€” and it gets prepended into the file that's defined by changelog.changelog_file. That's it. That's the whole vibe.

This lowkey mimics the behavior that was used in versions prior to v8.0.0 โ€” back before the conversion to a templating engine โ€” except now it uses Jinja to accomplish the update. โœจ

This mode is best suited for managing changes over the lifetime of your project when you may have a need to make manual changes or adjustments to the changelog and it's not easily recreated with a template. Like, if you and your changelog have a whole relationship going on, this is the mode for you. ๐Ÿฅฐ


โœจ How It Works โœจ

In order to insert the new release information into an existing changelog file, your changelog file must have an insertion flag to indicate where the new release information should be inserted. Think of it as a little bookmark that says "new stuff goes here, queen." ๐Ÿ”–

The default template will read in your existing changelog file, split the content based on the insertion flag, and then recombine the content (including the insertion flag โ€” she stays) with the new release information added after the insertion flag.

The insertion flag is customizable through the changelog.insertion_flag setting. Generally, your insertion flag should be unique text to your changelog file to avoid any unexpected behavior. See the examples below. ๐Ÿ‘€

In the case where the insertion flag is NOT found in the existing changelog file, the changelog file will be re-written without any changes. (Not me quietly doing nothingโ€ฆ ๐Ÿ’€ it literally just rewrites itself identical.)

If there is no existing changelog file found, then the changelog file will be initialized from scratch as if the mode was set to init, except the changelog.insertion_flag will be included into the newly created changelog file. ๐ŸŽ€

Tip: we have accomplished changelog updating through the use of the Jinja templating and additional context filters and context variables. This is notable because in the case that you want to customize your changelog template, you now can use the same logic to enable changelog updates of your custom template! Slay.

See also:

  • Migrating an Existing Changelog.

๐ŸŒธ Example ๐ŸŒธ

Given your existing changelog looks like the following with a changelog.insertion_flag set to <!-- version list -->, when you run the cmd-version command, the new release information will be inserted after the insertion flag.

Before

# CHANGELOG

<!-- version list -->

## 1.0.0

- Initial Release

After

# CHANGELOG

<!-- version list -->

## v1.1.0

### Feature

- feat: added a new feature

### Fix

- fix: resolved divide by zero error

## 1.0.0

- Initial Release

๐Ÿงš Configuration Examples ๐Ÿงš

  1. Goal: Configure an updating reStructuredText changelog with a custom insertion flag within pyproject.toml.
[tool.semantic_release.changelog]
mode = "update"
insertion_flag = "..\n    All versions below are listed in reverse chronological order"

[tool.semantic_release.changelog.default_templates]
changelog_file = "CHANGELOG.rst"
output_format = "rst"  # optional because of the file extension
  1. Goal: Configure an updating Markdown changelog with custom file name and default insertion flag within a separate config file releaserc.json.
{
  "semantic_release": {
    "changelog": {
      "mode": "update",
      "default_templates": {
        "changelog_file": "docs/HISTORY",
        "output_format": "md"
      }
    }
  }
}
  1. Goal: Configure an initializing reStructuredText changelog with filtered conventional commits patterns and merge commits within a custom config file releaserc.toml.
[semantic_release.changelog]
mode = "init"
default_templates = { changelog_file = "docs/CHANGELOG.rst" }
exclude_commit_patterns = [
  '''chore(?:\([^)]*?\))?: .+''',
  '''ci(?:\([^)]*?\))?: .+''',
  '''refactor(?:\([^)]*?\))?: .+''',
  '''style(?:\([^)]*?\))?: .+''',
  '''test(?:\([^)]*?\))?: .+''',
  '''build\((?!deps\): .+)''',
  '''Merged? .*''',
]

If identified or supported by the parser, the default changelog templates will include a separate section of breaking changes and additional release information. Refer to the commit parsing section to see how to write commit messages that will be properly parsed and displayed in these sections. Write your commits right and they'll pop off in the changelog, fr fr. ๐Ÿ“


๐Ÿ‘‘ Using the Default Release Notes ๐Ÿ‘‘

PSR has the capability to generate release notes as part of the publishing of a new version, similar to the changelog. The release notes are generated using a Jinja template and posted to your remote version control server (VCS) such as GitHub, GitLab, etc. during the cmd-version command. PSR provides a default built-in template out-of-the-box for generating release notes. No setup, just vibes. ๐Ÿฆ‹

The difference between the changelog and release notes is that the release notes only contain the changes for the current release. Due to the modularity of the PSR templates, the format is similar to an individual version of the default changelog but may include other version specific information.

At this time, the default template for version release notes is only available in Markdown format for all VCS types. That's the whole menu, bestie. ๐Ÿ’…

If you want to review what the default release notes look like you can use the following command to print the release notes to the console (remove any configuration for defining a custom template directory):

# Create a current tag
git tag v1.0.0
semantic-release --noop changelog --post-to-release-tag v1.0.0

The default template provided by PSR will respect the mask_initial_release setting and will also add a comparison link to the previous release if one exists without customization. Iconic behavior, honestly. โœจ

As of v9.18.0, the default release notes will also include a statement to declare which license the project was released under. PSR determines which license to declare based on the value of project.license-expression in the pyproject.toml file as defined in the PEP 639 specification. Legal girlies rise. โš–๏ธ๐Ÿ’–

See also:

  • To personalize your release notes, see the Custom Release Notes section.

๐Ÿ’œ Custom Changelogs ๐Ÿ’œ

If you would like to customize the appearance of your changelog, you can create your own custom templates and configure PSR to render your templates instead during the cmd-version and cmd-changelog commands. Main character template energy. ๐ŸŽ€

To use a custom template, you need to create a directory within your repository and set the template_dir setting to the name of this directory. The default name is "templates".

Templates are identified by giving a .j2 extension to the template file. Any such templates have the .j2 extension removed from the target file. Therefore, to render an output file foo.csv, you should create a template called foo.csv.j2 within your template directory. Cute, right? ๐Ÿ“

If you have additional files that you would like to render alongside your changelog, you can place these files within the template directory. A file within your template directory which does not end in .j2 will not be treated as a template; it will be copied to its target location without being rendered by the template engine. (It's just along for the ride. ๐Ÿ›๏ธ)

Tip: hidden files within the template directory (i.e. filenames that begin with a period ".") are excluded from the rendering process. Hidden folders within the template directory are also excluded, along with all files and folders contained within them. This is useful for defining macros or other template components that should not be rendered individually.
Tip: when initially starting out at customizing your own changelog templates, you should reference the default template embedded within PSR. The template directory is located at data/templates/ within the PSR package. Within our templates directory we separate out each type of commit parser (e.g. conventional) and the content format type (e.g. markdown). You can copy this directory to your repository's templates directory and then customize the templates to your liking. Steal her look, respectfully. ๐Ÿ’…

๐ŸŒˆ Directory Structure ๐ŸŒˆ

When the templates are rendered, files within the templates directory tree are output to the location within your repository that has the same relative path to the root of your project as the relative path of the template within the templates directory.

๐Ÿ’– Example ๐Ÿ’–

An example project has the following structure:

example-project/
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ example_project/
โ”‚       โ””โ”€โ”€ __init__.py
โ””โ”€โ”€ ch-templates/
    โ”œโ”€โ”€ CHANGELOG.md.j2
    โ”œโ”€โ”€ .components/
    โ”‚   โ””โ”€โ”€ authors.md.j2
    โ”œโ”€โ”€ .macros.j2
    โ”œโ”€โ”€ src/
    โ”‚   โ””โ”€โ”€ example_project/
    โ”‚       โ””โ”€โ”€ data/
    โ”‚           โ””โ”€โ”€ data.json.j2
    โ””โ”€โ”€ static/
        โ””โ”€โ”€ config.cfg

And a custom templates folder configured via the following snippet in pyproject.toml:

[tool.semantic_release.changelog]
template_dir = "ch-templates"

After running a release with Python Semantic Release, the directory structure of the project will now look like this (excluding the template directory):

example-project/
โ”œโ”€โ”€ CHANGELOG.md
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ example_project/
โ”‚       โ”œโ”€โ”€ data/
โ”‚       โ”‚   โ””โ”€โ”€ data.json
โ”‚       โ””โ”€โ”€ __init__.py
โ””โ”€โ”€ static/
    โ””โ”€โ”€ config.cfg

Importantly, note the following:

  • There is no top-level .macros file created, because hidden files are excluded from the rendering process.
  • There is no top-level .components directory created, because hidden folders and all files and folders contained within it are excluded from the rendering process.
  • The .components/authors.md.j2 file is not rendered directly, however, it is used as a component to the CHANGELOG.md.j2 via an include statement in the changelog template.
  • To render data files into the src/ folder, the path to which the template should be rendered has to be created within the ch-templates directory.
  • the ch-templates/static folder gets created at the top-level of your project, and the file ch-templates/static/config.cfg is copied โ€” not rendered โ€” into the new top-level static folder ๐Ÿซถ

You may lowkey want to lean into this behavior to modularize your changelog template, to define macros in a separate file, or to reference static data you'd rather not duplicate between your template environment and the rest of your project. very main character of you honestly ๐Ÿ’…


๐ŸŒธ Changelog Template Context ๐ŸŒธ

While it's rendering a directory tree, Python Semantic Release hands your templating environment all the info about the project's history so you can use it to generate the changelog and whatever other documents you're dreaming up โœจ

The important project info shows up in the templating environment through the global variable context, or ctx for short. Inside the template environment, that context object has these attributes:

  • changelog_insertion_flag (str): the insertion flag that decides where the new release info should get inserted into the changelog file. This value is passed straight through from changelog.insertion_flag (<#config-changelog-insertion-flag>).

Introduced in v9.10.0.

Example Usage:

{%  set changelog_parts = prev_changelog_contents.split(
        ctx.changelog_insertion_flag, maxsplit=1
    )
%}
  • changelog_mode (Literal["init", "update"]): the mode of changelog generation currently in use. you can use this to pick different rendering logic. The value is passed straight through from the changelog.mode (<#config-changelog-mode>) configuration setting.

Introduced in v9.10.0.

Example Usage:

{%    if ctx.changelog_mode == "init"
%}{%    include ".changelog_init.md.j2"
%}{#
#}{%  elif ctx.changelog_mode == "update"
%}{%    include ".changelog_update.md.j2"
%}{#
#}{%  endif
%}
  • history (ReleaseHistory): the ReleaseHistory (<#semantic_release.changelog.release_history.ReleaseHistory>) instance for the project โ€” peek the Release History section for the deets ๐Ÿ‘€

Example Usage:

{%    set unreleased_commits = ctx.history.unreleased | dictsort
%}{%  for release in context.history.released.values()
%}{%    include ".versioned_changes.md.j2"
#}{%  endfor
%}
  • hvcs_type (str): the name of the VCS server type that's currently configured. Use it to figure out which filters are available, or to branch your rendering logic.

Introduced in v9.6.0.

Example Usage:

{%    if ctx.hvcs_type == "github"
%}{{   "29" | pull_request_url
}}{#
#}{%  elif ctx.hvcs_type == "gitlab"
%}{{    "29" | merge_request_url
}}{#
#}{%  endif
%}
  • mask_initial_release (bool): a boolean that says whether the initial release should be masked with a generic message. Passed straight through from the changelog.default_templates.mask_initial_release (<#config-changelog-default-templates-mask-initial-release>) configuration setting.

Introduced in v9.14.0.

Example Usage:

#}{%  if releases | length == 1 and ctx.mask_initial_release
%}{#    # On a first release, generate a generic message
#}{%    include ".components/first_release.md.j2"
%}{%  else
%}{#    # Not the first release
#}{%    include ".components/versioned_changes.md.j2"
%}{%  endif
%}
  • repo_name (str): the name of the current repository, parsed from the Git url.

Example Usage:

{{ ctx.repo_name }}
example_repo
  • repo_owner (str): the owner of the current repository, parsed from the Git url.

Example Usage:

{{ ctx.repo_owner }}
example_org
  • prev_changelog_file (str): the path to the previous changelog file that should get updated with the new release info. Passed straight through from changelog.changelog_file (<#config-changelog-changelog-file>).

Introduced in v9.10.0.

Example Usage:

{% set prev_changelog_contents = prev_changelog_file | read_file | safe %}

๐Ÿ’– Release History ๐Ÿ’–

A ReleaseHistory (<#semantic_release.changelog.release_history.ReleaseHistory>) object has two attributes: released and unreleased. that's it, that's the whole vibe ๐ŸŽ€

The unreleased attribute is of type Dict[str, List[ParseResult]]. Every commit in the current branch's commit history since the last release on this branch gets grouped by the type attribute of the ParsedCommit (<#semantic_release.commit_parser.token.ParsedCommit>) that the commit parser returned โ€” or, if the parser returned a ParseError (<#semantic_release.commit_parser.token.ParseError>), the result is grouped under the "unknown" key ๐Ÿ‘ป

Because of that: every single element of ReleaseHistory.unreleased["unknown"] is a ParseError (<#semantic_release.commit_parser.token.ParseError>), and every element of every other value in ReleaseHistory.unreleased is of type ParsedCommit (<#semantic_release.commit_parser.token.ParsedCommit>). no cap.

Typically the commit types are gonna be "feature", "fix", "breaking", though the specific types are decided by the parser. For example, the EmojiCommitParser (<#semantic_release.commit_parser.emoji.EmojiCommitParser>) uses a textual representation of the emoji that matches the most significant change introduced in a commit (like ":boom:") as the different commit types. As a template author you're totally free to customize how these get presented in the rendered template โœจ

Note: if you're using a custom commit parser following the guide at Custom Parsers (<#commit-parser-custom-parser>), your custom implementations of ParseResult, ParseError (<#semantic_release.commit_parser.token.ParseError>) and ParsedCommit (<#semantic_release.commit_parser.token.ParsedCommit>) will be used in place of the built-in types. ๐Ÿซก

The released attribute is of type Dict[Version, Release]. The keys of this dictionary correspond to each version released within this branch's history, and they're of type Version (<#semantic_release.version.version.Version>). You can use the as_tag() method to render these as the Git tag they correspond to inside your template ๐Ÿ’ซ

A Release (<#semantic_release.changelog.release_history.Release>) object has an elements attribute, which has the same structure as the unreleased attribute of a ReleaseHistory (<#semantic_release.changelog.release_history.ReleaseHistory>); that is, elements is of type Dict[str, List[ParseResult]], where every element of elements["unknown"] is a ParseError (<#semantic_release.commit_parser.token.ParseError>), and elements of every other value correspond to the type attribute of the ParsedCommit (<#semantic_release.commit_parser.token.ParsedCommit>) returned by the commit parser.

The commits represented inside each ReleaseHistory.released[version].elements grouping are the commits that were made between version and the release corresponding to the previous version. So like, given two releases Version(1, 0, 0) and Version(1, 1, 0), ReleaseHistory.released[Version(1, 0, 0)].elements contains only commits made after the release of Version(1, 0, 0) up to and including the release of Version(1, 1, 0) ๐Ÿฆ‹

To keep a consistent order of subsections in the changelog headed by the commit type, it's recommended to use Jinja's dictsort (<https://jinja.palletsprojects.com/en/3.1.x/templates/#jinja-filters.dictsort>) filter.

Every Release (<#semantic_release.changelog.release_history.Release>) object also has these attributes:

  • tagger: git.Actor: the tagger who tagged the release.
  • committer: git.Actor: the committer who made the release commit.
  • tagged_date: datetime: the date and time the release was tagged at.

See also:


๐Ÿงš Changelog Template Filters ๐Ÿงš

On top of the context variables, PSR seeds the template environment with a set of custom functions (usually called filters in Jinja (<https://jinja.palletsprojects.com/en/3.1.x/>) terminology) for you to use inside the template. A filter's first argument is always piped (|) into the function, while any extra arguments get passed in parentheses like normal function calls ๐Ÿ’…

The filters provided vary based on the VCS you've configured and what features are available:

  • autofit_text_width (Callable[[textStr, maxWidthInt, indent_sizeInt], textStr]): given a text string, fit the text to the max width you provide. This filter is the moment when you wanna wrap text to a specific width. It'll try to break the text at word boundaries and will indent the text by however much you put in the indent_size parameter ๐Ÿ˜Œ

Introduced in v9.12.0.

Example Usage:

{{ "This is a long string that needs to be wrapped to a specific width" | autofit_text_width(40, 4) }}

Markdown Output:

This is a long string that needs to be
    wrapped to a specific width
  • convert_md_to_rst (Callable[[MdStr], RstStr]): given a markdown string, convert it to reStructuredText format. This filter is perfect when you're building a reStructuredText changelog but your commit messages are in markdown format. It's used by the default RST changelog template. It's a lil limited in its ability to convert all markdown to reStructuredText, but it handles most common cases (bold, italics, inline-raw, etc.) within commit messages ๐Ÿ™ˆ

Introduced in v9.11.0.

Example Usage:

{{  "\n* %s (`%s`_)\n" | format(
      commit.message.rstrip() | convert_md_to_rst,
      commit.short_hash,
    )
}}
  • create_pypi_url(package_name: str, version: str = ""): given a package name and an optional version, return a URL to the PyPI page for that package. If you pass a version, the URL points to that specific version page. If you don't, the URL points to the package page ๐Ÿ“

Introduced in v9.18.0.

Example Usage:

{{ "example-package" | create_pypi_url }}
{{ "example-package" | create_pypi_url("1.0.0") }}

Markdown Output:

https://pypi.org/project/example-package
https://pypi.org/project/example-package/1.0.0
  • create_release_url (Callable[[TagStr], UrlStr]): given a tag, return a URL to the release page on the remote vcs. This filter is useful when you want to link to the release page on the remote vcs.

Introduced in v9.18.0. ๐ŸŽ€

Example Usage:

{{ "v1.0.0" | create_release_url }}

Markdown Output:

https://example.com/example/repo/releases/tag/v1.0.0
  • create_server_url ( Callable[[PathStr, AuthStr | None, QueryStr | None, FragmentStr | None], UrlStr] ) โ€” bestie, you hand it a path and it slaps the configured vcs server host and url scheme right on the front. And if you want extra, you can also hand it an auth string, a query string, or a url fragment and it'll normalize those into the url it gives back. The parameter order goes exactly like we just listed, in that order, respectively. ๐Ÿ’…

Introduced in v9.6.0.

Example Usage:

{{ "example/repo.git" | create_server_url }}
{{ "example/repo" | create_server_url(None, "results=1", "section-header") }}

Markdown Output:

https://example.com/example/repo.git
https://example.com/example/repo?results=1#section-header
  • create_repo_url ( Callable[[RepoPathStr, QueryStr | None, FragmentStr | None], UrlStr] ) โ€” give it a repo path and it prepends the configured vcs server host and the repo namespace. Want more? You can optionally pass an extra query string and/or a url fragment and it'll put those in the url too. Same deal with the parameter order โ€” as described above, respectively. โœจ It's giving create_server_url's cooler sibling, lowkey, because it pulls in the repo namespace and owner for you automatically. ๐Ÿซถ

Introduced in v9.6.0.

Example Usage:

{{ "releases/tags/v1.0.0" | create_repo_url }}
{{ "issues" | create_repo_url("q=is%3Aissue+is%3Aclosed") }}

Markdown Output:

https://example.com/example/repo/releases/tags/v1.0.0
https://example.com/example/repo/issues?q=is%3Aissue+is%3Aclosed
  • commit_hash_url ( Callable[[hashStr], UrlStr] ) โ€” you pass in a commit hash, it hands you back a URL to that commit on the remote. No cap, that's the whole thing. ๐Ÿ“

Introduced in v8.0.0.

Example Usage:

{{ commit.hexsha | commit_hash_url }}

Markdown Output:

https://example.com/example/repo/commit/a1b2c3d435657f5d339ba10c7b1ed81b460af51d
  • compare_url ( Callable[[StartRefStr, StopRefStr], UrlStr] ) โ€” give it a starting git reference and an ending git reference and it builds a comparison url between those two references, one you can literally just open on the remote. ๐ŸŒˆ

Introduced in v9.6.0.

Example Usage:

{{ "v1.0.0" | compare_url("v1.1.0") }}

Markdown Output:

https://example.com/example/repo/compare/v1.0.0...v1.1.0
  • issue_url ( Callable[[IssueNumStr | IssueNumInt], UrlStr] ) โ€” hand it an issue number and it returns a URL to that issue on the remote vcs. Heads up though: in v9.12.2 this filter got updated so it can handle a string with leading prefix symbols (ex. #32) and it will strip that prefix off before generating the URL. Considerate queen behavior honestly. ๐Ÿ‘‘

Introduced in v9.6.0, Modified in v9.12.2.

Example Usage:

{# Add Links to issues annotated in the commit message
 # NOTE: commit.linked_issues is only available in v9.15.0 or greater
 #
#}{% for issue_ref in commit.linked_issues
%}{{     "- [%s](%s)" | format(issue_ref, issue_ref | issue_url)
}}{% endfor
%}

Markdown Output:

- [#32](https://example.com/example/repo/issues/32)
  • merge_request_url ( Callable[[MergeReqStr | MergeReqInt], UrlStr] ) โ€” give it a merge request number and boom, URL to that merge request on the remote. This one is an alias to the pull_request_url, but it's only available for the VCS that actually uses the merge request terminology, iykyk. And in v9.12.2 this filter got updated to handle strings with leading prefix symbols (ex. #29) โ€” it strips the prefix before generating the URL. โœจ

Introduced in v9.6.0, Modified in v9.12.2.

Example Usage:

{{
    "[%s](%s)" | format(
      commit.linked_merge_request,
      commit.linked_merge_request | merge_request_url
    )
}}
{# commit.linked_merge_request is only available in v9.13.0 or greater #}

Markdown Output:

[#29](https://example.com/example/repo/-/merge_requests/29)
  • pull_request_url ( Callable[[PullReqStr | PullReqInt], UrlStr] ) โ€” pass it a pull request number and it returns a URL to that pull request on the remote. For remote vcs' that use merge request terminology, this filter is literally an alias to the merge_request_url filter function. And yes, in v9.12.2 it got updated to handle strings with leading prefix symbols (ex. #29) โ€” prefix gets stripped before the URL is generated. ๐Ÿ’–

Introduced in v9.6.0, Modified in v9.12.2.

Example Usage:

{# Create a link to the merge request associated with the commit
 # NOTE: commit.linked_merge_request is only available in v9.13.0 or greater
#}{{
    "[%s](%s)" | format(
      commit.linked_merge_request,
      commit.linked_merge_request | pull_request_url
    )
}}

Markdown Output:

[#29](https://example.com/example/repo/pull/29)
  • format_w_official_vcs_name ( Callable[[str], str] ) โ€” bestie, you give it a format string and it inserts the official VCS type name into that string and returns it. This filter is so handy when you wanna show off the proper name of the VCS type in a changelog or release notes. It supports three different replace formats: %s, {}, and {vcs_name}. Periodt. ๐Ÿ’…

Introduced in v9.18.0.

Example Usage:

{{ "%s Releases" | format_w_official_vcs_name }}
{{ "{} Releases" | format_w_official_vcs_name }}
{{ "{vcs_name} Releases" | format_w_official_vcs_name }}

Markdown Output:

GitHub Releases
GitHub Releases
GitHub Releases
  • read_file ( Callable[[str], str] ) โ€” hand it a file path, it reads the file and gives you back the contents as a string. This function was added for a very specific reason: to enable the changelog update feature, where it loads the existing changelog file into the templating environment so it can be updated. ๐ŸŽ€

Introduced in v9.10.0.

Example Usage:

{% set prev_changelog_contents = prev_changelog_file | read_file | safe %}
  • sort_numerically ( Callable[[Iterable[str], bool], list[str]] ) โ€” give it a sequence of strings that maybe have some non-number characters as a prefix or suffix, and it sorts them as if they were purely numbers, lowest to highest. Obsessed, honestly. ๐Ÿ”ฅ This filter is the moment when you wanna sort issue numbers or other strings with a numeric component that you cannot just cast to a number directly to sort them. And if you want them in reverse order? Just pass a boolean value of True as the second argument. Easy. ๐Ÿฆ‹

Introduced in v9.16.0.

Example Usage:

{{ ["#222", "#1023", "#444"] | sort_numerically }}
{{ ["#222", "#1023", "#444"] | sort_numerically(True) }}

Markdown Output:

['#222', '#444', '#1023']
['#1023', '#444', '#222']

okay so not every filter slays on every platform ๐Ÿ’… the availability of the documented filters is literally right here, bestie โ€” check your hosting provider's column before you get attached:

  • autofit_text_width โ€” bitbucket โœ…, gitea โœ…, github โœ…, gitlab โœ…
  • convert_md_to_rst โ€” bitbucket โœ…, gitea โœ…, github โœ…, gitlab โœ…
  • create_pypi_url โ€” bitbucket โœ…, gitea โœ…, github โœ…, gitlab โœ…
  • create_server_url โ€” bitbucket โœ…, gitea โœ…, github โœ…, gitlab โœ…
  • create_release_url โ€” bitbucket โŒ, gitea โœ…, github โœ…, gitlab โœ…
  • create_repo_url โ€” bitbucket โœ…, gitea โœ…, github โœ…, gitlab โœ…
  • commit_hash_url โ€” bitbucket โœ…, gitea โœ…, github โœ…, gitlab โœ…
  • compare_url โ€” bitbucket โœ…, gitea โŒ, github โœ…, gitlab โœ…
  • format_w_official_vcs_name โ€” bitbucket โœ…, gitea โœ…, github โœ…, gitlab โœ…
  • issue_url โ€” bitbucket โŒ, gitea โœ…, github โœ…, gitlab โœ…
  • merge_request_url โ€” bitbucket โŒ, gitea โŒ, github โŒ, gitlab โœ…
  • pull_request_url โ€” bitbucket โœ…, gitea โœ…, github โœ…, gitlab โœ…
  • read_file โ€” bitbucket โœ…, gitea โœ…, github โœ…, gitlab โœ…
  • sort_numerically โ€” bitbucket โœ…, gitea โœ…, github โœ…, gitlab โœ…

See also:


Example ๐Ÿฆ‹

okay this next bit is genuinely iconic โ€” here's a lil template that shows you how to render a changelog in Markdown using the PSR template context, no cap.

Configuration: pyproject.toml

[tool.semantic_release.changelog]
template_dir = "templates"

Template: templates/CHANGELOG.md.j2

# CHANGELOG

{%    for version, release in ctx.history.released.items()
%}{{
        "## %s (%s)" | format(version.as_tag(), release.tagged_date.strftime("%Y-%m-%d"))

}}{%    for type_, commits in release["elements"] if type_ != "unknown" | dictsort
%}{{
          "### %s" | format(type_ | title)

}}{%      for commit in commits
%}{{
            "* %s ([`%s`](%s))" | format(
              commit.descriptions[0] | capitalize,
              commit.hexsha[:7],
              commit.hexsha | commit_hash_url,
            )

}}{%      endfor
%}{%    endfor
%}{%  endfor
%}

Result: CHANGELOG.md

# CHANGELOG

## v1.1.0 (2022-01-01)

### Feature

* Added a new feature ([`a1b2c3d`](https://github.com/example/repo/commit/a1b2c3d))

## v1.0.0 (2021-12-31)

### Fix

* Resolved divide by zero error ([`e4f5g6h`](https://github.com/example/repo/commit/e4f5g6h))

lowkey important to note: this template pulls the project history straight out of the context variable, and it uses the commit_hash_url filter to cook up a URL pointing at the remote VCS for every single commit. Both of those? Injected into the template environment by PSR itself. ๐Ÿซถ


Custom Release Notes ๐ŸŒธ

so if you wanna make your release notes look however you want, drop a hidden file called .release_notes.md.j2 at the root of your changelog.template_dir. PSR will sniff it out automatically and use it to render the release notes during the cmd-version and cmd-changelog commands. automatically detected = main character behaviour fr.

The rendering mechanism for release notes is basically the same as the one for the changelog โ€” there are minor differences in what context is available to the release notes template, but the template directory structure and the modularity are all maintained. โœจ

Tip: when you're first starting out customizing your own release notes template, you should go look at the default template embedded inside PSR. It lives in the directory data/templates/<parser>/md within the PSR package. study the blueprint, bestie ๐Ÿ’…

Release Notes Context ๐ŸŽ€

All of the changelog's template context is exposed to the <https://jinja.palletsprojects.com/en/3.1.x/> Jinja template when it's rendering the release notes.

AND ALSO โ€” these two globals are available to the template as well:

  • release (Release <#semantic_release.changelog.release_history .Release>): holds metadata about the content of the release, exactly as parsed from the commit logs.
    • Introduced in v8.0.0.
  • version (Version <#semantic_release.version.version.Version>): holds metadata about the software version that's about to be released, plus its git tag.
    • Introduced in v8.0.0.

Example โญ

here's a template you can use to render release notes โ€” it's giving very much GitHub's automatically generated release notes <https://docs.github.com/en/repositories/releasing-projects-on-github/ automatically-generated-release-notes> energy:

Configuration: pyproject.toml

[tool.semantic_release.changelog]
template_dir = "templates"

Template: templates/.release_notes.md.j2

## What's Changed
{%    for type_, commits in release["elements"] | dictsort
%}{%-   if type_ != "unknown"
%}{{
          "### %s" | format(type_ | title)

}}{%      for commit in commits
%}{{
            "* %s by %s in [`%s`](%s)" | format(
              commit.descriptions[0] | capitalize,
              commit.commit.author.name,
              commit.hexsha[:7],
              commit.hexsha | commit_hash_url,
            )

}}{%-     endfor
%}{%    endif
%}{%  endfor
%}

Result: <https://github.com/example/repo/releases/tag/v1.1.0>

## What's Changed

### Feature

* Added a new feature by John Doe in [`a1b2c3d`](https://github.com/example/repo/commit/a1b2c3d)

Migrating an Existing Changelog ๐Ÿ’œ

v9.10.0 or greater

migrating an existing changelog is simple with Python Semantic Release, bestie! to preserve your existing changelog, just follow these steps:

  1. Set the changelog.mode to "update" in your configuration file. This makes sure only the new release info gets added to your existing changelog file. โœจ
  2. Set the changelog.insertion_flag to a unique string. You can use the default value, or pick a string that doesn't already show up in your existing changelog file. This flag is how PSR figures out where the new release info should be inserted into your existing changelog.
  3. Add the insertion flag to your changelog file. It must match the value you set in step 2. The insertion flag goes in the location above where you want the new release information to be inserted. ๐Ÿ“
Note: if you're trying to convert an existing changelog over to a new format, you'll have to do most of that conversion manually (or rebuild via init and modify) and make absolutely sure you include your insertion flag into the format of the new changelog. no shortcuts on this one ๐Ÿ˜ญ

Prior to v9.10.0

if you've got an existing changelog you want to preserve and you're on an older version, you'll need to add the contents of the changelog file to your changelog template โ€” either directly, or via Jinja's include <https://jinja .palletsprojects.com/en/3.1.x/templates/#include> tag.

and if you only want the history from your next release onwards rendered into the changelog on top of the existing changelog, you can add an if statement <https://jinja.palletsprojects.com/en/3.1.x/ templates/#if> based on the versions in the keys of context.released. ๐Ÿฆ‹

๐Ÿ› ๏ธ Upgrading Templates

okay so PSR is a living project bestie, it keeps growing, and the templating engine keeps getting new features and glow-ups โœจ If you made your own custom templates, you might need to give them a little update so they can actually enjoy those new features. Below is the rundown on how to upgrade your templates and unlock the new stuff. ๐Ÿ”“


๐Ÿ”„ Incrementally Updating Changelog Template

Note: this whole section only matters if you're upgrading from a version of PSR greater than v8.0.0 and prior to v9.10.0 and you made your own custom templates. iykyk ๐Ÿซถ

So if you already made your own custom templates and you want in on the new updating-changelog feature, you're gonna need to tweak your existing templates a lil bit. No biggie.

These steps are just suggestions to help you upgrade, but honestly? The main character move is to go review the embedded default templates in the PSR package for the full example. You can find the default templates in the data/templates/ directory (https://github.com/python-semantic-release/python-semantic-release/tree/master/src/semantic_release/data/templates). ๐Ÿ’…

  1. Add a conditional to check the changelog_mode. This lets you figure out whether you should render the entire changelog or just the new release info. See data/templates/*/md/CHANGELOG.md.j2 for reference.
  2. Use the new read_file filter to read in the existing changelog file ctx.prev_changelog_file. This lets you include the existing changelog content in your new changelog file. See data/templates/*/md/.components/changelog_update.md.j2 for reference.
  3. Split the changelog content based on the insertion flag. This lets you insert the new release information after the insertion flag (ctx.changelog_insertion_flag). See data/templates/*/md/.components/changelog_update.md.j2 for reference.
  4. Print the leading content before the insertion flag. This keeps any content that should come before the new release info. See data/templates/*/md/.components/changelog_update.md.j2 for reference.
  5. Print your insertion flag. This one is imperative, bestie ๐Ÿšจ โ€” it's what makes sure the resulting changelog can be updated again in the future. See data/templates/*/md/.components/changelog_update.md.j2 for reference.
  6. Print the new release information. Be sure to consider both unreleased and released commits here, because of the cmd-changelog command that can be run at any time. See data/templates/*/md/.components/changelog_update.md.j2 for reference.
  7. Print the trailing content after the insertion flag. This keeps any content that should come after the new release info. See data/templates/*/md/.components/changelog_update.md.j2 for reference.
Tip: modularity of your templates is key to handling both modes of changelog generation. Go reference the default templates for examples on how both modes are handled and how they defensively handle a whole bunch of breaking scenarios. ๐ŸŒˆ
Tip: if you're having trouble upgrading your templates, just post a question on the PSR GitHub <https://github.com/python-semantic-release/python-semantic-release/issues> โ€” the community got you ๐Ÿซถ

๐ŸŒฟ Multibranch Releases

Python Semantic Release supports releases from multiple branches within your Git repository. You can elect to have a branch or set of branches create releases or prereleases (https://semver.org/#spec-item-9) โ€” your call, bestie. There are no restrictions enforced on how you set up your releases, but be aware: if you create new releases from multiple branches, or prereleases from multiple independent branches using the same prerelease token, there's a chance Python Semantic Release will calculate the next version to be the same on more than one branch โ€” which leads to an error that a Git tag already exists. ๐Ÿ˜ญ

Note: a "prerelease token" is the string used to suffix onto the 3-digit form of a full semantic version. For example, in the version 1.2.3-beta.1, the prerelease token is "beta". Typical strings used for pre-release tokens include "alpha", "beta", "dev" and "rc". These tend to indicate a level of maturity of the software associated with the version, but the specific meaning of each string is up to the project to decide. Generally, it's good practice to maintain a single branch from which full releases are made, and one branch at a time for each type of prerelease (alpha, beta, rc, etc).

If you absolutely require tagging and (pre-)releases to take place from multiple branches where there's a risk that tags could conflict between branches, you can use the --build-metadata command line argument to attach additional information (such as the branch name) to the tag, so it's uniquely distinguished from any other tags that might be calculated against other branches. Such a situation may occur in the following scenario:

                       O ----------- O      <---- feature-1
                      /          "feat: abc"
                     /
         O -------- O --------------- O    <---- main
      v1.0.0     v1.1.0
                      \
                       O ----------- O    <---- feature-2
                                 "feat: 123"

Suppose that Python Semantic Release has been configured to use the same prerelease token "alpha" for all feature-* branches, and the default tag format "v{version}". In this case, running a pre-release from branch feature-1 will recognize that since the last release, 1.1.0, a feature has been introduced and therefore the next tag to be applied to feature-1 will be v1.2.0-alpha.1.

However, suppose we then try to run a release against feature-2. This will also recognize that a feature has been introduced against the last released version of v1.1.0 and therefore will try to create the tag v1.2.0-alpha.1, leading to an error as this tag was already created against feature-1. ๐Ÿ’€

To get around this issue, you can pass the branch name as part of the build metadata:

semantic-release version --build-metadata $(git branch --show-current)

This would lead to the tag v1.2.0-alpha.1+feature-1 and v1.2.0-alpha.1+feature-2 being applied to branches feature-1 and feature-2, respectively. Note that "build metadata MUST be ignored" (https://semver.org/#spec-item-10) per the semver specification when comparing two versions, so these two prereleases would be considered equivalent semantic versions โ€” but when merged to the branch configured to produce full releases (main), if released separately the changes from each branch would be released in two versions that would be considered different according to the semver specification. ๐Ÿฆ‹

Note: if you have tags in your Git repository that are not valid semantic versions (which have then been formatted into your tag_format), these tags will be ignored for the purposes of calculating the next version.

โš™๏ธ Configuring Multibranch Releases

Within your configuration file, you can create one or more groups of branches ("release groups") that produce a certain type of release. Options are configured at the group level, and the group to use is chosen based on the current branch name against which Python Semantic Release is running. ๐ŸŽ€

Each release group is configured as a nested mapping under the tool.semantic_release.branches key in pyproject.toml, or the equivalent structure in other formats. The mapping requires a single key that is used as a name for the release group โ€” which can help to identify it in log messages but has no effect on the behavior of the release. For example, Python Semantic Release has only one release group by default, with the name main. ๐Ÿ‘‘

Inside each release group, these key-value pairs can be set:

  • match โ€” required: Yes, default: N/A. It's a Python regular expression (the re module, docs at <https://docs.python.org/3/library/re.html>) that gets matched against the name of your active branch. If the branch name matches the regex you gave, that release group gets picked to supply all the other configuration settings available. ๐Ÿ’…
  • prerelease โ€” required: No, default: false. Whether or not branches in this release group should make a prerelease instead of a full release.
  • prerelease_token โ€” required: No, default: rc. If you're creating a prerelease, this is the string used as the prerelease token in any new versions created against this branch.
โš ๏ธ WARNING โš ๏ธ If two release groups have overlapping match patterns โ€” meaning the name of a branch could theoretically match both patterns โ€” then the release group defined first in your config file is the one that gets used. Bestie, this is not a drill. Because of that, it's recommended you put release groups with more specific match patterns higher up in your config file than the ones with patterns that would match a broader range of branch names. Order matters, iykyk. ๐Ÿซถ

For example, suppose a project currently on version 1.22.4 is working on a new major version. The project wants to create a branch called 2.x.x against which they will develop the new major version, and they would like to create "release candidate" ("rc") prereleases from this branch. There are also a number of new features to integrate, and the project has agreed that all such branches should be named according to the convention next-{developer initials}-{issue number}, leading to branches named similarly to next-bc-prj-123. The project would like to release with tags that include some way to identify the branch and date on which the release was made from the tag.

This project would be able to leverage the following configuration to achieve the above requirements from their release configuration:

[tool.semantic_release.branches.main]
match = "(main|master)"
prerelease = false

[tool.semantic_release.branches."2.x.x"]
match = "2.x.x"
prerelease = true
prerelease_token = "rc"

[tool.semantic_release.branches."2.x.x New Features"]
match = "next-\\w+-prj-\\d+"
prerelease = true
prerelease_token = "alpha"

In a CI pipeline, the following command would allow attaching the date and branch name to the versions that are produced (note this example uses the UNIX date command):

semantic-release version \
   --build-metadata "$(git branch --show-current).$(date +%Y%m%d)"

This would lead to versions such as 1.1.1+main.20221127 or 2.0.0-rc.4+2.x.x.20221201. โœจ Look at those tags, they're gorgeous.

Note: Remember that is always possible to override the release rules configured by using the cmd-version-option-force-level and cmd-version-option-as-prerelease flags. You always get a say, bestie. ๐Ÿ’–

๐Ÿ’€ Strict Mode

Strict Mode is enabled by use of the strict parameter to the main command for Python Semantic Release. Strict Mode alters the behavior of Python Semantic Release when certain conditions are encountered that prevent Python Semantic Release from performing an action. Typically, this will result in a warning becoming an error, or a different exit code (0 vs non-zero) being produced when Python Semantic Release exits early.

For example:

#!/usr/bin/bash

set -euo pipefail

git checkout $NOT_A_RELEASE_BRANCH

pip install \
   black \
   isort \
   twine \
   pytest \
   python-semantic-release

isort .  # sort imports
black .  # format the code
pytest   # test the code
semantic-release --strict version  # ERROR - not a release branch
twine upload dist/*  # publish the code

Using Strict Mode with the --strict flag ensures this simple pipeline will fail while running semantic-release, as the non-zero exit code will cause it to stop when combined with the -e option.

Without Strict Mode, the semantic-release command will exit with code 0, causing the above pipeline to continue. ๐Ÿ˜ญ (the pipeline continues, the vibes do not)

The specific effects of enabling Strict Mode are detailed below.


๐Ÿšซ Non-Release Branches

When running in Strict Mode, invoking Python Semantic Release on a non-Release branch will cause an error with a non-zero exit code. This means that you can prevent an automated script from running further against branches you do not want to release from, for example in multibranch CI pipelines.

Running without Strict Mode will allow subsequent steps in the pipeline to also execute, but be aware that certain actions that Python Semantic Release may perform for you will likely not have been carried out, such as writing to files or creating a git commit in your repository. ๐Ÿ‘€

See also:

  • Multibranch Releases <#multibranch-releases>

๐Ÿ™… Version Already Released/No Release To Be Made

When Strict Mode is not enabled and Python Semantic Release identifies that no release needs to be made, it will exit with code 0. You can cause Python Semantic Release to raise an error if no release needs to be made by enabling Strict Mode.


๐ŸŽ€ Configuration

Python Semantic Release is highly configurable, allowing you to tailor it to your project's needs. It supports various runtime environments and can be integrated with different CI/CD services.

  1. Check out our set of configuration guides <#config-guides> to help walk you through the set up of common project customizations.
  2. Dive in deep and explore the full set of possible customization options <#config>.
  3. Go Automatic and Configure your CI/CD services <#automatic> to use Python Semantic Release.

๐Ÿ“š Configuration Guides

This section provides detailed guides on how to configure PSR for various use cases and integrations. It is recommended to complete the Getting Started Guide <#getting-started-guide> first before diving into these more specific configurations. Fr fr, do the basics first, it makes everything else hit different. ๐Ÿ’œ


๐Ÿข Releasing Packages from a Monorepo

A monorepo (mono-repository) is a software development strategy where code for multiple projects is stored in a single source control system. This approach streamlines and consolidates configuration, but introduces complexities when using automated tools like Python Semantic Release (PSR).

Previously, PSR offered limited compatibility with monorepos. As of v10.4.0, PSR introduces the Conventional Commits Monorepo Parser <#commit-parser-builtin-conventional-monorepo>, designed specifically for monorepo environments. To fully leverage this new parser, you must configure your monorepo as described below. โœจ


๐Ÿ› ๏ธ Configuring PSR

๐Ÿ“ Example: Simple

Directory Structure: PSR does not yet support a single, workspace-level configuration definition. This means each package in the monorepo requires its own PSR configuration file. A compatible and common monorepo file structure looks like:

project/
โ”œโ”€โ”€ .git/
โ”œโ”€โ”€ .venv/
โ”œโ”€โ”€ packages/
โ”‚   โ”œโ”€โ”€ pkg1/
โ”‚   โ”‚   โ”œโ”€โ”€ docs/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ source/
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ conf.py
โ”‚   โ”‚   โ”‚       โ””โ”€โ”€ index.rst
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ”œโ”€โ”€ src/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ pkg1/
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ __main__.py
โ”‚   โ”‚   โ”‚       โ””โ”€โ”€ py.typed
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ”œโ”€โ”€ CHANGELOG.md
โ”‚   โ”‚   โ”œโ”€โ”€ README.md
โ”‚   โ”‚   โ””โ”€โ”€ pyproject.toml             <-- PSR Configuration for Package 1
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ pkg2/
โ”‚       โ”œโ”€โ”€ docs/
โ”‚       โ”‚   โ””โ”€โ”€ source/
โ”‚       โ”‚       โ”œโ”€โ”€ conf.py
โ”‚       โ”‚       โ””โ”€โ”€ index.rst
โ”‚       โ”œโ”€โ”€ src/
โ”‚       โ”‚   โ””โ”€โ”€ pkg2/
โ”‚       โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚       โ”‚       โ”œโ”€โ”€ __main__.py
โ”‚       โ”‚       โ””โ”€โ”€ py.typed
โ”‚       โ”‚
โ”‚       โ”œโ”€โ”€ CHANGELOG.md
โ”‚       โ”œโ”€โ”€ README.md
โ”‚       โ””โ”€โ”€ pyproject.toml             <-- PSR Configuration for Package 2
โ”‚
โ”œโ”€โ”€ .gitignore
โ””โ”€โ”€ README.md

Okay so this is the most bare-bones monorepo setup ever โ€” every package is fully self-contained with its own config files, its own docs, its own CHANGELOG files, like each one said "I'm my own girl ๐Ÿ’…". To release a package you just cd into the package directory and run PSR's cmd-version. PSR will automatically read that package's pyproject.toml, hunting for the [tool.semantic_release] section to figure out the package's versioning and release config, and then it walks up the file tree to find the Git repository. ๐Ÿฆ‹

And because there's no workspace-level config at all, you have to duplicate any shared PSR config into every single package's config file. ๐Ÿ˜ญ Yes, bestie, copy-paste core. Then you customize each one per package so PSR knows how to tell the commits apart.

With the file structure above, here's an example config file for each package:

# FILE: pkg1/pyproject.toml
[project]
name = "pkg1"
version = "1.0.0"

[tool.semantic_release]
commit_parser = "conventional-monorepo"
commit_message = """\
chore(release): pkg1@{version}`

Automatically generated by python-semantic-release
"""
tag_format = "pkg1-v{version}"
version_toml = ["pyproject.toml:project.version"]

[tool.semantic_release.commit_parser_options]
path_filters = ["."]
scope_prefix = "pkg1-"

# FILE: pkg2/pyproject.toml
[project]
name = "pkg2"
version = "1.0.0"

[tool.semantic_release]
commit_parser = "conventional-monorepo"
commit_message = """\
chore(release): pkg2@{version}`

Automatically generated by python-semantic-release
"""
tag_format = "pkg2-v{version}"
version_toml = ["pyproject.toml:project.version"]

[tool.semantic_release.commit_parser_options]
path_filters = ["."]
scope_prefix = "pkg2-"

These are the bare minimum config options each package needs. Notice how tag_format <#config-tag-format> is doing the heavy lifting to keep tags from different packages from colliding โ€” iconic. The commit parser options are specific to the new Conventional Commits Monorepo Parser <#commit-parser-builtin-conventional-monorepo> and they're huge for figuring out which commits actually belong to which package. And since you're expected to cd into each package before releasing, the file paths in each config file should be relative to the package directory, not the repo root. ๐Ÿ‘‘

Each package also defines a slightly different commit_message <#config-commit-message> so the package name shows up in the message. Which helps you tell which release number got bumped in the commit history. Thank you for your service, commit message. ๐Ÿซถ


Release Steps

Say you've got this Git history in a monorepo using a GitHub Flow branching strategy (no CI/CD in the mix): [image]

To release both packages by hand, run:

cd packages/pkg1
semantic-release version
# 1.0.1 (tag: pkg1-v1.0.1)

cd ../pkg2
semantic-release version
# 1.1.0 (tag: pkg2-v1.1.0)

After releasing both packages, the Git history ends up looking like this: [image]

See also:

  • GitHub Actions with Monorepos <#gh-actions-monorepo>

Considerations

  1. Custom Changelogs: managing changelogs can get messy depending on where you want the changelog files to live. In this simple example, the changelog sits inside each package directory, and the changelog template has zero package-specific formatting or naming convention. You can use one shared template directory at the root of the project and point each package at it. ๐Ÿ’…
# FILE: pkg1/pyproject.toml
[tool.semantic_release]
template_dir = "../../config/release-templates"

# FILE: pkg2/pyproject.toml
[tool.semantic_release]
template_dir = "../../config/release-templates"
project/
โ”œโ”€โ”€ .git/
โ”œโ”€โ”€ config/
โ”‚   โ””โ”€โ”€ release-templates/
โ”‚       โ”œโ”€โ”€ CHANGELOG.md.j2
โ”‚       โ””โ”€โ”€ .release_notes.md.j2
โ”œโ”€โ”€ packages/
โ”‚   โ”œโ”€โ”€ pkg1/
โ”‚   โ”‚   โ”œโ”€โ”€ CHANGELOG.md
โ”‚   โ”‚   โ””โ”€โ”€ pyproject.toml
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ pkg2/
โ”‚       โ”œโ”€โ”€ CHANGELOG.md
โ”‚       โ””โ”€โ”€ pyproject.toml
โ”‚
โ”œโ”€โ”€ .gitignore
โ””โ”€โ”€ README.md

See also:

  • For situations with more complex documentation needs, see our Advanced Example.
  1. Package Prereleases: prereleases are totally possible, but please use package-prefixed branch names so packages don't collide with each other. ๐Ÿšง For example, to turn on alpha prereleases for new features in both packages, use this config:
# FILE: pkg1/pyproject.toml
[tool.semantic_release.branches.alpha-release]
match = "^pkg1/feat/.+"                       # <-- note pkg1 prefix
prerelease = true
prerelease_token = "alpha"

# FILE: pkg2/pyproject.toml
[tool.semantic_release.branches.alpha-release]
match = "^pkg2/feat/.+"                       # <-- note pkg2 prefix
prerelease = true
prerelease_token = "alpha"

----

Example: Advanced

If you want to consolidate documentation into one single top-level directory, the setup gets more complex. In this example there's a common documentation folder at the top level, and each package gets its own subfolder inside that documentation folder. Group project energy. ๐ŸŒˆ

And because of naming conventions, PSR cannot pull this off automatically with its default changelog templates. So for this scenario you have to copy the internal PSR templates into a custom directory (even if you don't change them at all ๐Ÿ˜ญ) and add custom scripting to prep each release.

The directory structure looks like this:

project/
โ”œโ”€โ”€ .git/
โ”œโ”€โ”€ docs/
โ”‚   โ”œโ”€โ”€ source/
โ”‚   โ”‚   โ”œโ”€โ”€ pkg1/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ changelog.md
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ README.md
โ”‚   โ”‚   โ”œโ”€โ”€ pkg2/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ changelog.md
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ README.md
โ”‚   โ”‚   โ””โ”€โ”€ index.rst
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ templates/
โ”‚       โ”œโ”€โ”€ .base_changelog_template/
โ”‚       โ”‚   โ”œโ”€โ”€ components/
โ”‚       โ”‚   โ”‚   โ”œโ”€โ”€ changelog_header.md.j2
โ”‚       โ”‚   โ”‚   โ”œโ”€โ”€ changelog_init.md.j2
โ”‚       โ”‚   โ”‚   โ”œโ”€โ”€ changelog_update.md.j2
โ”‚       โ”‚   โ”‚   โ”œโ”€โ”€ changes.md.j2
โ”‚       โ”‚   โ”‚   โ”œโ”€โ”€ first_release.md.j2
โ”‚       โ”‚   โ”‚   โ”œโ”€โ”€ macros.md.j2
โ”‚       โ”‚   โ”‚   โ”œโ”€โ”€ unreleased_changes.md.j2
โ”‚       โ”‚   โ”‚   โ””โ”€โ”€ versioned_changes.md.j2
โ”‚       โ”‚   โ””โ”€โ”€ changelog.md.j2
โ”‚       โ”œโ”€โ”€ .gitignore
โ”‚       โ””โ”€โ”€ .release_notes.md.j2
โ”‚
โ”œโ”€โ”€ packages/
โ”‚   โ”œโ”€โ”€ pkg1/
โ”‚   โ”‚   โ”œโ”€โ”€ src/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ pkg1/
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚   โ”‚   โ”‚       โ””โ”€โ”€ __main__.py
โ”‚   โ”‚   โ””โ”€โ”€ pyproject.toml
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ pkg2/
โ”‚       โ”œโ”€โ”€ src/
โ”‚       โ”‚   โ””โ”€โ”€ pkg2/
โ”‚       โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚       โ”‚       โ””โ”€โ”€ __main__.py
โ”‚       โ””โ”€โ”€ pyproject.toml
โ”‚
โ””โ”€โ”€ scripts/
    โ”œโ”€โ”€ release-pkg1.sh
    โ””โ”€โ”€ release-pkg2.sh

Each package should point at the docs/templates/ directory so it uses the common release notes template. And here's the cute part: PSR ignores hidden files and directories when it's searching for template files to create, which means you can hide your shared templates in that directory for use in your release setup script. โœจ Sneaky. Clever. Love it.

Here's our config file for package 1 (package 2 is defined in basically the same way):

# FILE: pkg1/pyproject.toml
[project]
name = "pkg1"
version = "1.0.0"

[tool.semantic_release]
commit_parser = "conventional-monorepo"
commit_message = """\
chore(release): Release `pkg1@{version}`

Automatically generated by python-semantic-release
"""
tag_format = "pkg1-v{version}"
version_toml = ["pyproject.toml:project.version"]

[tool.semantic_release.commit_parser_options]
path_filters = [
    ".",
    "../../../docs/source/pkg1/**",
]
scope_prefix = "pkg1-"

[tool.semantic_release.changelog]
template_dir = "../../../docs/templates"
mode = "update"
exclude_commit_patterns = [
    '''^chore(?:\([^)]*?\))?: .+''',
    '''^ci(?:\([^)]*?\))?: .+''',
    '''^refactor(?:\([^)]*?\))?: .+''',
    '''^style(?:\([^)]*?\))?: .+''',
    '''^test(?:\([^)]*?\))?: .+''',
    '''^Initial [Cc]ommit''',
]

[tool.semantic_release.changelog.default_templates]
# To enable update mode: this value must set here because the default is not the
# same as the default in the other package & must be the final destination filename
# for the changelog relative to this file
changelog_file = "../../../docs/source/pkg1/changelog.md"
Note: in this config we added path filters for extra documentation files that belong to the package, so the changelog picks up documentation changes too. ๐ŸŽ€

Next up: you're gonna want a release script that sets up your common changelog templates in the right directory format, so PSR actually creates the files where they belong. ๐Ÿ’… Per the Custom Changelogs reference (the template rendering bit), you have to define the folder structure from the root of the project inside the templates directory โ€” that's how PSR knows how to lay the files down across the repo. The script wipes any old templates, makes the directories it needs on the fly, and copies the shared templates into a package-named directory. Then bestie, you're ready to run PSR for a pkg1 release. ๐ŸŽ€

#!/bin/bash
# FILE: scripts/release-pkg1.sh

set -euo pipefail

PROJECT_ROOT="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")")"
VIRTUAL_ENV="$PROJECT_ROOT/.venv"

PACKAGE_NAME="pkg1"

cd "$PROJECT_ROOT" || exit 1

# Setup documentation template
pushd "docs/templates" >/dev/null || exit 1

rm -rf docs/
mkdir -p "docs/source/"
cp -r .base_changelog_template/ "docs/source/$PACKAGE_NAME"

popd >/dev/null || exit 1

# Release the package
pushd "packages/$PACKAGE_NAME" >/dev/null || exit 1

printf '%s\n' "Releasing $PACKAGE_NAME..."
"$VIRTUAL_ENV/bin/semantic-release" -v version --no-push

popd >/dev/null || exit 1

And that's literally it! This whole example is showing you how to set up a monorepo with shared changelog templates and a consolidated documentation folder for multiple packages. Iconic. ๐Ÿ‘‘

See also:


๐Ÿฆ‹ Ultraviolet (uv) Integration ๐Ÿฆ‹

uv <https://docs.astral.sh/uv/> is an extremely fast Python package and project manager โ€” it's giving modern alternative to pip <https://pip.pypa.io/en/stable/> and venv <https://docs.python.org/3/library/venv.html>. It solves a bunch of the classic Python package management headaches, no cap, but it also brings a few little quirks you gotta keep in mind when you're rolling with Python Semantic Release.

Important: Prerequisite โ€” make sure you've run through the Getting Started Guide <#getting-started-guide> before you go any further with this guide. fr fr.

๐Ÿ” Updating the uv.lock ๐Ÿ”

One of the best features of uv is that it auto-generates a lock file (uv.lock) holding the exact versions of every dependency in your project. That lock file gets generated when you run uv install, and it's what keeps your CI workflows repeatable and your dev environments consistent. ๐Ÿซถ

So here's the tea: when you create a new release with Python Semantic Release, PSR updates the version in your project's definition file (e.g., pyproject.toml) to show the new version. Sadly, that action makes uv fail on the next run, because the lock file is now out of sync with your project's definition file. ๐Ÿ˜ญ There are two ways to fix this, depending on your vibe:

  1. Add a step to your build command: modify your semantic_release.build_command <#config-build-command> so it includes the command that updates the lock file and stages it for commit. This is commonly used with the GitHub Action <#gh-actions-psr> and other CI/CD tools when you're building the artifact at the time of release.
[tool.semantic_release]
build_command = """
    uv lock --upgrade-package "$PACKAGE_NAME"
    git add uv.lock
    uv build
"""

The intent of the lock upgrade-package call is ONLY to update the version of your project within the lock file after PSR has updated the version in your project's definition file (e.g., pyproject.toml). When you're running PSR, you've already tested the project as-is, and you do not want to actually update the dependencies just because some new one became available. ๐Ÿ™ˆ

For ease of use, PSR provides the $PACKAGE_NAME environment variable โ€” it contains the name of your package from the project's definition file (pyproject.toml:project.name).

If you're using the PSR GitHub Action <#gh-actions-psr>, you'll need to add an installation command for uv to the build_command <#config-build-command>, because the action runs in a Docker environment that does not include uv by default. The best way to make sure the correct version of uv is installed is to define the version of uv in an optional dependency list (e.g. build). That also helps other automated tools like Dependabot or Renovate keep your uv version up to date. โœจ

[project.optional-dependencies]
build = ["uv ~= 0.7.12"]

[tool.semantic_release]
build_command = """
    python -m pip install -e '.[build]'
    uv lock --upgrade-package "$PACKAGE_NAME"
    git add uv.lock
    uv build
"""
  1. Stamp the code first & then separately run release: if you'd rather not modify your build command, then you'll need to run the uv lock --upgrade-package <your-package-name> command before you actually create the release. Basically you're running PSR twice: (1) once to update the version in the project's definition file, and (2) a second time to generate the release.

The intent of the uv lock --upgrade-package <your-package-name> command is ONLY to update the version of your project within the lock file after PSR has updated the version in your project's definition file (e.g., pyproject.toml). When you're running PSR, you've already tested the project as-is, and you do not want to actually update the dependencies just because a new one became available.

# 1. PSR stamps version into files (nothing else)
# don't build the changelog (especially in update mode)
semantic-release -v version --skip-build --no-commit --no-tag --no-changelog

# 2. run UV lock as pyproject.toml is updated with the next version
uv lock --upgrade-package <your-package-name>

# 3. stage the lock file to ensure it is included in the PSR commit
git add uv.lock

# 4. run PSR fully to create release
semantic-release -v version

๐ŸŒˆ Advanced Example ๐ŸŒˆ

Obviously you can mix and match these 2 approaches however you need, bestie. If PSR's pipeline was using uv, we'd end up with a mixture of the 2 approaches, because we run the build in a separate job from the release. In our case, PSR would also need to carry the lock file along the pipeline as a workflow artifact, so that the release job can commit it. That advanced workflow would look like this:

# File: .tool-versions
uv 0.7.12

# File: .python-version
3.11.11

# File: pyproject.toml
[project.optional-dependencies]
build = ["python-semantic-release ~= 10.0"]
test = ["pytest ~= 8.0"]

[tool.semantic_release]
build_command = """
  uv lock --upgrade-package "$PACKAGE_NAME"
  uv build
"""

# File: .github/workflows/release.yml
on:
  push:
    branches:
      - main

jobs:

  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    env:
      dist_artifacts_name: dist
      dist_artifacts_dir: dist
      lock_file_artifact: uv.lock
    steps:
      - name: Setup | Checkout Repository at workflow sha
        uses: actions/checkout@COMMIT_HASH  # vX.X.X
        with:
          ref: ${{ github.sha }}

      - name: Setup | Force correct release branch on workflow sha
        run: git checkout -B ${{ github.ref_name }}

      - name: Setup | Install uv
        uses: asdf-vm/actions/install@COMMIT_HASH  # v4.X.X

      - name: Setup | Install Python & Project dependencies
        run: uv sync --extra build

      - name: Build | Build next version artifacts
        id: version
        env:
          GH_TOKEN: "none"
        run: uv run semantic-release -v version --no-changelog --no-commit --no-tag

      - name: Upload | Distribution Artifacts
        if: ${{ steps.version.outputs.released == 'true' }}
        uses: actions/upload-artifact@COMMIT_HASH  # v4.X.X
        with:
          name: ${{ env.dist_artifacts_name }}
          path: ${{ format('{0}/**', env.dist_artifacts_dir) }}
          if-no-files-found: error
          retention-days: 2

      - name: Upload | Lock File Artifact
        if: ${{ steps.version.outputs.released == 'true' }}
        uses: actions/upload-artifact@COMMIT_HASH  # v4.X.X
        with:
          name: ${{ env.lock_file_artifact }}
          path: ${{ env.lock_file_artifact }}
          if-no-files-found: error
          retention-days: 2

    outputs:
      new-release-detected: ${{ steps.version.outputs.released }}
      new-release-version: ${{ steps.version.outputs.version }}
      new-release-tag: ${{ steps.version.outputs.tag }}
      new-release-is-prerelease: ${{ steps.version.outputs.is_prerelease }}
      distribution-artifacts: ${{ env.dist_artifacts_name }}
      lock-file-artifact: ${{ env.lock_file_artifact }}

  test-e2e:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Setup | Checkout Repository
        uses: actions/checkout@COMMIT_HASH  # vX.X.X
        with:
          ref: ${{ github.sha }}
          fetch-depth: 1

      - name: Setup | Download Distribution Artifacts
        uses: actions/download-artifact@COMMIT_HASH  # v4.X.X
        if: ${{ needs.build.outputs.new-release-detected == 'true' }}
        id: artifact-download
        with:
          name: ${{ needs.build.outputs.distribution-artifacts }}
          path: ./dist

      - name: Setup | Install uv
        uses: asdf-vm/actions/install@COMMIT_HASH  # v4.X.X

      - name: Setup | Install Python & Project dependencies
        run: uv sync --extra test

      - name: Setup | Install distribution artifact
        if: ${{ steps.artifact-download.outcome == 'success' }}
        run: |
          uv pip uninstall my-package
          uv pip install dist/python_semantic_release-*.whl

      - name: Test | Run pytest
        run: uv run pytest -vv tests/e2e

  release:
    runs-on: ubuntu-latest
    needs:
      - build
      - test-e2e

    if: ${{ needs.build.outputs.new-release-detected == 'true' }}
              concurrency:
                group: ${{ github.workflow }}-release-${{ github.ref_name }}
                cancel-in-progress: false

              permissions:
                contents: write

              steps:
                - name: Setup | Checkout Repository on Release Branch
                  uses: actions/checkout@COMMIT_HASH  # vX.X.X
                  with:
                    ref: ${{ github.ref_name }}

                - name: Setup | Force release branch to be at workflow sha
                  run: git reset --hard ${{ github.sha }}

                - name: Setup | Install uv
                  uses: asdf-vm/actions/install@COMMIT_HASH  # v4.X.X

                - name: Setup | Install Python & Project dependencies
                  run: uv sync --extra build

                - name: Setup | Download Build Artifacts
                  uses: actions/download-artifact@COMMIT_HASH  # v4.X.X
                  id: artifact-download
                  with:
                    name: ${{ needs.build.outputs.distribution-artifacts }}
                    path: dist

                - name: Setup | Download Lock File Artifact
                  uses: actions/download-artifact@COMMIT_HASH  # v4.X.X
                  with:
                    name: ${{ needs.build.outputs.lock-file-artifact }}

                - name: Setup | Stage Lock File for Version Commit
                  run: git add uv.lock

                - name: Release | Create Release
                  id: release
                  shell: bash
                  env:
                    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
                  run: |
                    uv run semantic-release -v --strict version --skip-build
                    uv run semantic-release publish

              outputs:
                released: ${{ steps.release.outputs.released }}
                new-release-version: ${{ steps.release.outputs.version }}
                new-release-tag: ${{ steps.release.outputs.tag }}

            deploy:
              name: Deploy
              runs-on: ubuntu-latest
              if: ${{ needs.release.outputs.released == 'true' && github.repository == 'python-semantic-release/my-package' }}
              needs:
                - build
                - release

              environment:
                name: pypi
                url: https://pypi.org/project/my-package/

              permissions:
                id-token: write

              steps:
                - name: Setup | Download Build Artifacts
                  uses: actions/download-artifact@COMMIT_HASH  # v4.X.X
                  id: artifact-download
                  with:
                    name: ${{ needs.build.outputs.distribution-artifacts }}
                    path: dist

                - name: Publish package distributions to PyPI
                  uses: pypa/gh-action-pypi-publish@COMMIT_HASH  # v1.X.X
                  with:
                    packages-dir: dist
                    print-hash: true
                    verbose: true

Whew ok that was a whole YAML situation but we got through it ๐Ÿ’ช note that git reset --hard ${{ github.sha }} is doing the absolute most to force the release branch onto the workflow sha, and yes, uv sync --extra build is how we get the build extras in there. Obsessed with a clean release pipeline fr fr.


๐Ÿ’– Configuration ๐Ÿ’–

So PSR reads its config from a file, and you point at that file with the --config option on cmd-main. Right now it speaks TOML or JSON โ€” pick your fighter โ€” and it will try to auto-detect and parse whichever one you threw at it. Versatile queen behavior โœจ

Now, the details of where it lives in those files matter, so listen up bestie:

  • JSON config? Python Semantic Release goes looking for its settings under a top-level semantic_release key.
  • TOML config? It checks under the table [tool.semantic_release] first (that's the whole convention where Python tools put their config under the top-level tool table in their pyproject.toml, iykyk), and then falls back to [semantic_release] โ€” which is honestly nicer if you're using some file that isn't the default pyproject.toml.

Every example on this page is written in TOML, but there's zero restriction on using JSON instead. And if you wanna flip any example below into its JSON twin, these commands do it for you (in Bash):

export TEXT="<the TOML to convert>"

cat <<EOF | python3
import tomlkit, json
print(json.dumps(tomlkit.loads('''$TEXT'''), indent=4))
EOF

Slay, no cap. ๐ŸŽ€

๐ŸŒธ A note on null ๐ŸŒธ

Okay so TOML literally does not have a "null" or "nil" value, and it's not planned as a language feature either โ€” the relevant GitHub issue is at https://github.com/toml-lang/toml/issues/30. So here's the tea: in Python Semantic Release, options that default to None get inferred from the relevant configuration settings just... not being present at all in your config. Because of that limitation, you currently can't explicitly set those settings as "null" in a TOML-format config. Technically you can do it in a JSON-format config, but honestly? Keep it consistent and just omit those settings. Periodt. ๐Ÿ’…

๐Ÿฆ‹ Environment Variables ๐Ÿฆ‹

Some settings are simply better pulled straight out of environment variables instead of sitting in plaintext in your config file. Python Semantic Release can be told to go look up an environment variable's value for a given setting โ€” but heads up, this feature is not available for all settings. To use it, you have to say in your config file the name of the environment variable you want used.

The classic, most common use case is passing auth tokens. You do NOT want to hard code your authentication token into your config file โ€” that's a security risk, fr fr. A plaintext token in your config could be exposed to literally anyone with access to your repo, and even long after it's deleted if that token ended up in your git history. ๐Ÿ˜ญ So instead: define the name of the environment variable holding your remote.token, like GH_TOKEN, in your config file, and Python Semantic Release does the rest. Like this:

[semantic_release.remote.token]
env = "GH_TOKEN"

And given basic TOML syntax compatibility, that's equivalent to:

[semantic_release.remote]
token = { env = "GH_TOKEN" }

The general format for saying "yo, source this from an environment variable" is:

[semantic_release.variable]
env = "ENV_VAR"
default_env = "FALLBACK_ENV_VAR"
default = "default value"

In that structure:

  • env โ€” the environment variable that Python Semantic Release will search for.
  • default_env โ€” a fallback environment variable to read in case the one specified by env isn't set. This is optional โ€” if you don't specify it, no fallback is used.
  • default โ€” a default value to use in case the environment variable specified by env isn't set. This is optional too โ€” if default isn't specified, then the environment variable specified by env is considered required.

๐Ÿ‘‘ semantic_release settings ๐Ÿ‘‘

The next sections lay out all the definitions and descriptions of every supported configuration setting. If there are type mis-matches, PSR will throw validation errors upon load. And if a setting isn't provided, PSR fills in the default value for you. Easy.

Python Semantic Release expects a root level key to kick off the configuration definition, so make sure you're using the right root key for the config format you've got.

Note: if you're using pyproject.toml, this heading should include the tool prefix as specified within PEP 517, which gives you [tool.semantic_release].
Note: if you're using a releaserc.toml, use [semantic_release] as the root key.
Note: if you're using a releaserc.json, semantic_release must be the root key in the top level dictionary.

๐Ÿ’ซ add_partial_tags ๐Ÿ’ซ

Type: bool

Tells PSR whether partial version tags should be handled when creating a new version. Set it to true and a major tag and a major.minor tag get created or updated, using the format specified in tag_format. And if the version has build metadata, a major.minor.patch tag also gets created or updated.

Partial version tags are disabled for pre-release versions. Just so you know ๐Ÿซถ

Example

[semantic_release]
tag_format = "v{version}"
add_partial_tags = true

This config, with the next version of 1.2.3, results in:

git log --decorate --oneline --graph --all
# * 4d4cb0a (tag: v1.2.3, tag: v1.2, tag: v1, origin/main, main) 1.2.3
# * 3a2b1c0 fix: some bug
# * 2b1c0a9 (tag: v1.2.2) 1.2.2
# ...

And if build-metadata is used, the next version of 1.2.3+20251109 results in:

git log --decorate --oneline --graph --all
# * 4d4cb0a (tag: v1.2.3+20251109, tag: v1.2.3, tag: v1.2, tag: v1, origin/main, main) 1.2.3+20251109
# * 3a2b1c0 chore: add partial tags to PSR configuration
# * 2b1c0a9 (tag: v1.2.3+20251031) 1.2.3+20251031
# ...

Default: false


โœจ allow_zero_version โœจ

Introduced in v9.2.0

Type: bool

This flag controls whether Python Semantic Release will use version numbers aligning with the 0.x.x pattern.

If it's true and you're starting at 0.0.0, a minor bump sets the next version to 0.1.0, whereas a patch bump sets the next version to 0.0.1. A breaking change (i.e. a major bump) sets the next version to 1.0.0 โ€” unless major_on_zero is set to false.

If it's false, Python Semantic Release considers the first possible version to be 1.0.0, regardless of whether it's a patch, minor, or major change level. And bonus: when allow_zero_version is set to false, the major_on_zero setting is ignored entirely.

Default changed to false in v10.0.0

Default: false


๐Ÿ“ assets ๐Ÿ“

Type: list[str]

One or more paths to additional assets that should be committed to the remote repository, on top of any files modified by writing the new version. Extra goodies, basically.

Default: []


๐ŸŒˆ branches ๐ŸŒˆ

This setting gets discussed in way more detail over at Multibranch Releases <#multibranch-releases>. Go read that when you're ready bestie.

Default:

[semantic_release.branches.main]
match = "(main|master)"
prerelease_token = "rc"
prerelease = false

๐Ÿงš build_command ๐Ÿงš

Type: Optional[str]

The command used to build the current project during cmd-version.

Python Semantic Release runs the build command in the OS default shell with a subset of environment variables. PSR hands you the variable NEW_VERSION in the environment, set to the value of the next determined version. The following table summarizes all the environment variables that are passed on to the build_command runtime if they exist in the parent process โ€”

If you wanna slide extra environment variables into your build command, bestie, go peek at build_command_env. But here's the tea on what already gets passed along for you โ€” nobody's making you set these by hand, okay? ๐Ÿ’…

Here's the whole roster, no gatekeeping:

  • CI โ€” passes through as true if it already exists in the process env; if it doesn't, it just stays unset. No inventing stuff fr.
  • BITBUCKET_CI โ€” true when Bitbucket CI variables exist in the env, unset otherwise. Bitbucket girlies, this one's for you ๐Ÿซถ
  • GITHUB_ACTIONS โ€” pass-through true if it exists in the process env, unset otherwise. GitHub Actions stays winning โœจ
  • GITEA_ACTIONS โ€” pass-through true if it exists in the process env, unset otherwise.
  • GITLAB_CI โ€” pass-through true if it exists in the process env, unset otherwise.
  • HOME โ€” passes through the HOME of the parent process. Home sweet home ๐Ÿ 
  • NEW_VERSION โ€” the semantically determined next version (ex. 1.2.3). This is the moment. The main character. ๐Ÿ‘‘
  • PATH โ€” passes through the PATH of the parent process, so your command can actually find its little binaries.
  • PACKAGE_NAME โ€” your project name as defined in pyproject.toml:project.name. Yes, pulled straight from there, no cap.
  • PSR_DOCKER_GITHUB_ACTION โ€” pass-through true if it exists in the process env, unset otherwise. Docker + GitHub Action combo meal ๐Ÿ“
  • VIRTUAL_ENV โ€” passes through VIRTUAL_ENV if it exists in the process env, unset otherwise. Your lil venv gets to tag along ๐Ÿ’œ
Note the pattern, bestie: a bunch of these are pass-through โ€” meaning if the process env already has them, they come through as true, and if it doesn't, they're just unset. The tool is not out here making stuff up for you. It only reports what's actually there ๐Ÿ’€

In addition, on windows systems these environment variables get passed too, bestie ๐Ÿ’–:

  • ALLUSERSAPPDATA โ€” Pass-through ALLUSERAPPDATA if exists in process env, unset otherwise
  • ALLUSERSPROFILE โ€” Pass-through ALLUSERSPPPROFILE if exists in process env, unset otherwise
  • APPDATA โ€” Pass-through APPDATA if exists in process env, unset otherwise
  • COMMONPROGRAMFILES โ€” Pass-through COMMONPROGRAMFILES if exists in process env, unset otherwise
  • COMMONPROGRAMFILES(X86) โ€” Pass-through COMMONPROGRAMFILES(X86) if exists in process env, unset otherwise
  • DEFAULTUSERPROFILE โ€” Pass-through DEFAULTUSERPROFILE if exists in process env, unset otherwise
  • HOMEPATH โ€” Pass-through HOMEPATH if exists in process env, unset otherwise
  • PATHEXT โ€” Pass-through PATHEXT if exists in process env, unset otherwise
  • PROFILESFOLDER โ€” Pass-through PROFILESFOLDER if exists in process env, unset otherwise
  • PROGRAMFILES โ€” Pass-through PROGRAMFILES if exists in process env, unset otherwise
  • PROGRAMFILES(X86) โ€” Pass-through PROGRAMFILES(X86) if exists in process env, unset otherwise
  • SYSTEM โ€” Pass-through SYSTEM if exists in process env, unset otherwise
  • SYSTEM16 โ€” Pass-through SYSTEM16 if exists in process env, unset otherwise
  • SYSTEM32 โ€” Pass-through SYSTEM32 if exists in process env, unset otherwise
  • SYSTEMDRIVE โ€” Pass-through SYSTEMDRIVE if exists in process env, unset otherwise
  • SYSTEMROOT โ€” Pass-through SYSTEMROOT if exists in process env, unset otherwise
  • TEMP โ€” Pass-through TEMP if exists in process env, unset otherwise
  • TMP โ€” Pass-through TMP if exists in process env, unset otherwise
  • USERPROFILE โ€” Pass-through USERPROFILE if exists in process env, unset otherwise
  • USERSID โ€” Pass-through USERSID if exists in process env, unset otherwise
  • WINDIR โ€” Pass-through WINDIR if exists in process env, unset otherwise
okay real talk ๐Ÿซถ โ€” the spelling differences between the variable name and the name it's passing through (ALLUSERSAPPDATA vs ALLUSERAPPDATA, ALLUSERSPROFILE vs ALLUSERSPPPROFILE) are literally in the original, triple-P and all. I am not fixing them, I am not merging them, I'm just the messenger ๐Ÿ’€ don't shoot.

Default: None (not specified)


๐ŸŒธ build_command_env ๐ŸŒธ

Introduced in v9.7.2 โœจ

Type: Optional[list[str]]

List of environment variables to include or pass-through on to the build command that executes during cmd-version.

This config option lets you extend the list of environment variables from the table above in build_command. The input is a list of strings where each individual string handles a single variable definition. There are two formats accepted, and they're laid out right here:

  • VAR_NAME โ€” Detects value from the PSR process environment, and passes value to build_command process
  • VAR_NAME=value โ€” Sets variable name to value inside of build_command process
Note: Although variable name capitalization is not required, it is recommended as to be in-line with the POSIX-compliant recommendation for shell variable names. ๐Ÿ’…

Default: None (not specified)


๐Ÿ’– changelog ๐Ÿ’–

okay bestie, this section is literally the control room for how your changelog gets generated. all the config options that shape it live riiiight here ๐ŸŽ€

๐Ÿ“ Note: here's where this little home lives in your config file, depending on which flavour you're using: - pyproject.toml: [tool.semantic_release.changelog] - releaserc.toml: [semantic_release.changelog] - releaserc.json: { "semantic_release": { "changelog": {} } }

๐Ÿซถ changelog_file

โš ๏ธ Warning: Deprecated as of v9.11.0 ๐Ÿ’€ this setting got moved to changelog.default_templates.changelog_file because that's just a more logical place to group it. It's gonna get yeeted in a future major release, so like... migrate, bestie.

Type: str

This is the name of the changelog file that gets created. PSR creates it, or overwrites it if it was already chilling there, using the rendered default template that ships with Python Semantic Release. ๐ŸŽ€

Depending on the file extension you give it, the changelog renders in whatever format that extension points to. As of v9.11.0, PSR gives you a default changelog template in both Markdown (.md) and reStructuredText (.rst) formats. If the extension is not recognized, the changelog renders in Markdown format instead โ€” unless the output_format setting is set, in which case that wins. โœจ

If you're out here using the template_dir setting to supply your own custom templates, then this setting doesn't get used at all. Go check template_dir for the deets.

Default: "CHANGELOG.md"


๐Ÿงš default_templates

๐Ÿ“ Note: this little section holds the options that customize or modify the default changelog templates that come bundled with PSR. - pyproject.toml: [tool.semantic_release.changelog.default_templates] - releaserc.toml: [semantic_release.changelog.default_templates] - releaserc.json: { "semantic_release": { "changelog": { "default_templates": {} } } }

โœจ changelog_file

Introduced in v9.11.0.

Type: str

This is the name of the changelog file that will be created. It gets created, or overwritten if it already existed, with the rendered default template that's included with Python Semantic Release. ๐Ÿ’…

Depending on the file extension of this setting, the changelog renders in the format that extension designates. PSR, as of v9.11.0, ships a default changelog template in both Markdown (.md) and reStructuredText (.rst) formats. If the file extension isn't recognized, the changelog renders in Markdown format instead โ€” unless the output_format setting is set, which takes over.

If you're using the template_dir setting to provide customized templates, this setting isn't used. See template_dir for more info.

Default: "CHANGELOG.md"


๐ŸŒธ mask_initial_release

Introduced in v9.14.0

Type: bool

This option toggles how the changelog and release note templates behave, specifically masking the release details for the very first release only. When it's set to true, the first version's release notes get masked with a generic message instead of the usual commit details. When it's set to false, the release notes generate like normal. ๐Ÿ™ˆ

Why tho? It's about clarity for your audience, bestie. Conceptually it does NOT make sense to have a list of changes (aka a Changelog) for the first release, because literally nothing has been published yet โ€” so in the eyes of your consumers, what change is there to even document? ๐Ÿค”

The message details live in the first_release.md.j2 and first_release.rst.j2 templates inside the default changelog template directory. ๐Ÿ‘‘

Default changed to true in v10.0.0.

Default: true

See also:

  • Using the Default Changelog (#changelog-templates-default-changelog)

โญ output_format

Introduced in v9.10.0

Type: Literal["md", "rst"]

This one tells the default changelog template which output format to use when rendering the changelog. PSR supports both Markdown (md) and reStructuredText (rst). ๐ŸŒˆ

This setting takes precedence over the file extension of the changelog_file setting. If you omit it, the file extension of changelog_file decides the output format for you. And if that extension isn't recognized? Output format defaults to Markdown. periodt.

Default: "md"

See also:

  • changelog_file

๐Ÿฆ‹ environment

๐Ÿ“ Note: this section holds the options that customize the template environment used to render templates like the changelog. Most of these get passed straight into the jinja2.Environment constructor (https://jinja.palletsprojects.com/en/3.1.x/api/#jinja2.Environment), and the full documentation for those parameters lives over there. - pyproject.toml: [tool.semantic_release.changelog.environment] - releaserc.toml: [semantic_release.changelog.environment] - releaserc.json: { "semantic_release": { "changelog": { "environment": {} } } }

๐ŸŽ€ autoescape

Type: Union[str, bool]

If this setting is a string, it should be given in module:attr form ๐Ÿ’… Python Semantic Release will attempt to dynamically import that string, and it should represent a path to a suitable callable that satisfies the following:

As of Jinja 2.4 this can also be a callable that is passed the template name and has to return true or false depending on whether autoescape should be enabled by default.

The result of that dynamic import gets passed directly into the jinja2.Environment constructor.

If this setting is a boolean instead, it gets passed directly into the jinja2.Environment constructor. ๐Ÿซถ

Default: false


๐ŸŒธ block_start_string

Type: str

Passed directly into the jinja2.Environment constructor. no notes, it just vibes.

Default: "{%"


๐ŸŒธ block_end_string

Type: str

Passed directly into the jinja2.Environment constructor.

Default: "%}"


๐ŸŒธ comment_start_string

Type: str

Passed directly into the jinja2.Environment constructor.

Default: {#


๐ŸŒธ comment_end_string

Type: str

Passed directly into the jinja2.Environment constructor.

Default: "#}"


๐ŸŒธ extensions

Type: list[str]

Passed directly into the jinja2.Environment constructor.

Default: []


๐ŸŒธ keep_trailing_newline

Type: bool

Passed directly into the jinja2.Environment constructor.

Default: false


๐ŸŒธ line_comment_prefix

Type: Optional[str]

Passed directly into the jinja2.Environment constructor.

Default: None (not specified)


๐ŸŒธ line_statement_prefix

Type: Optional[str]

Passed directly into the jinja2.Environment constructor.

Default: None (not specified)


๐ŸŒธ lstrip_blocks

Type: bool

Passed directly into the jinja2.Environment constructor.

Default: false


๐ŸŒธ newline_sequence

Type: Literal["\n", "\r", "\r\n"]

Passed directly into the jinja2.Environment constructor.

Default: "\n"


๐ŸŒธ trim_blocks

Type: bool

Passed directly into the jinja2.Environment constructor.

Default: false


๐ŸŒธ variable_start_string

Type: str

Passed directly into the jinja2.Environment constructor.

Default: "{{"


๐ŸŒธ variable_end_string

Type: str

Passed directly into the jinja2.Environment constructor.

Default: "}}"


๐Ÿ”ฅ exclude_commit_patterns

Type: list[str]

Any patterns you drop in here get excluded from the commits that are available to your changelog ๐Ÿ‘‘ This lets you, for example, filter out automated commits if you want them gone. Python Semantic Release also removes its own commits from the Changelog through this very mechanism โ€” so if you change the automated commit message that PSR uses when it makes commits, you might wanna add the old commit message pattern here too.

The patterns in this list are treated as regular expressions. no cap.

Default: []


๐Ÿ’œ mode

Introduced in v9.10.0. Default changed to update in v10.0.0.

Type: Literal["init", "update"]

This setting is a flag that ultimately gets passed into the changelog context environment. It sets the value of context.changelog_mode to a string value of either init or update. โœจ

When you use it with the provided changelog template, it decides how the changelog gets written. When mode is init, the changelog file gets written from scratch, overwriting any existing changelog file ๐Ÿ˜ญ This is the v8 and v9 default behavior.

When mode is update, the changelog file looks for the insertion_flag value inside the changelog file (the one defined by changelog_file) and inserts the new version info at that location. ๐ŸŽ€

If you're using a custom template directory, the context.changelog_mode value will still exist in the changelog context โ€” but it's up to your implementation to decide if and/or how to use it.

Default: update

See also:

  • Using the Default Changelog (#changelog-templates-default-changelog)

๐Ÿ“ insertion_flag

Introduced in v9.10.0

Type: str

A string that PSR uses to figure out where the shiny new version should get yeeted into your changelog file (the one you named in changelog_file) โ€” and this only matters when the changelog mode is set to update.

If you change this value in your config, bestie, you have to go manually update any changelog file you've already saved so it matches the new insertion flag. That's only if you're on update mode, though. In init mode the changelog file just gets overwritten like normal, no drama. ๐Ÿซถ

Heads up: in v9.11.0 the insertion_flag default got way more dynamic, because an reStructuredText template showed up. The default now depends on your output_format setting. The default flag values are:

  • Markdown (md) โ†’ <!-- version list -->
  • reStructuredText โ†’ literally this, in all its chaotic glory:
..
    version list

(i.e. .., then a newline, then four spaces, then version list ๐Ÿ’…)

Default: various, see above


๐ŸŒธ template_dir ๐ŸŒธ

Type: str

When files exist inside the directory you point at here, they get used as templates for the whole changelog rendering process. And get this โ€” regardless of whether the directory contains a changelog file, the directory you provide will be rendered and the files placed relative to the root of the project directory.

When this directory exists and it isn't empty, no default changelog template or release notes template will be used. If the directory is empty, the default changelog template steps in instead.

This option gets discussed in more detail over at Version Change Reports. ๐Ÿ“

Default: "templates"


๐Ÿ’œ commit_author ๐Ÿ’œ

Type: str

The author used in commits, formatted as name <email>.

Note: if you're using the built-in GitHub Action, the default value is github-actions <actions@github.com>. You can change that with the git_committer_name and git_committer_email inputs.

See also:

  • GitHub Actions

Default: semantic-release <semantic-release>


โœจ commit_message โœจ

Type: str

The commit message to use when making release commits. It can use {version} as a format key โ€” if you do, the version being released gets formatted right into the message. Cute, right?

If at some point in your project's lifetime you change this, you might wanna consider adding the old message pattern(s) to exclude_commit_patterns.

Default: "{version}\n\nAutomatically generated by python-semantic-release"


๐ŸŽ€ commit_parser ๐ŸŽ€

Type: str

Tells Python Semantic Release which commit parser to use for parsing the commits inside the Git repository.

Built-in parsers:

  • angular โ€” AngularCommitParser (deprecated in v9.19.0 โš ๏ธ)
  • conventional โ€” ConventionalCommitParser (available in v9.19.0+)
  • conventional-monorepo โ€” ConventionalCommitMonorepoParser (available in v10.4.0+)
  • emoji โ€” EmojiCommitParser
  • scipy โ€” ScipyCommitParser
  • tag โ€” TagCommitParser (deprecated in v9.12.0 โš ๏ธ)

You can set any of the built-in parsers by their keyword, or you can bring your own commit parser using either the path/to/module_file.py:Class form or the module:Class form. Main character energy, honestly. ๐Ÿ‘‘

For more info check out Commit Parsing.

Default: "conventional"


๐Ÿฆ‹ commit_parser_options ๐Ÿฆ‹

Type: dict[str, Any]

This whole set of options gets passed directly to the commit parser class you named in the commit parser configuration option. Straight through, no filter.

For more information (including the defaults), see Customization.

Default: ParserOptions { ... }, where the ... depends on commit_parser.


๐Ÿ”ฅ logging_use_named_masks ๐Ÿ”ฅ

Type: bool

Do you want secrets found in logging messages replaced with named masks that tell you which secret got swapped out? Or do you just want a generic string doing the masking? This flag decides.

Default: false


๐Ÿ’– major_on_zero ๐Ÿ’–

Type: bool

This flag controls whether PSR bumps the major version on a breaking change when the version looks like 0.y.z.

It defaults to true, which means breaking changes will bump that 0 major version up to 1.0.0 like you'd normally expect.

If you set it to false, major (breaking) releases will bump the minor digit of the version while the major version is still 0, instead of the major digit. That lets you keep shipping breaking changes while the major version stays at 0.

From the Semantic Versioning Specification:

Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.

When you're finally ready to release a stable version, set major_on_zero to true and run Python Semantic Release again. That'll push the major version up to 1.0.0. ๐ŸŽ‰

When allow_zero_version is set to false, this setting just gets ignored entirely.

Default: true


๐ŸŒˆ no_git_verify ๐ŸŒˆ

Introduced in v9.8.0

Type: bool

This flag gets passed along to git when a git commit happens during cmd-version.

When true, it bypasses any git hooks set for the repository when Python Semantic Release makes a version commit. When false, the commit happens as normal. This option does absolutely nothing when there are no git hooks configured, and also nothing when the --no-commit option is passed.

Default: false


๐Ÿ’Œ publish ๐Ÿ’Œ

This section defines configuration options that modify cmd-publish.

Note: pyproject.toml: [tool.semantic_release.publish] releaserc.toml: [semantic_release.publish] releaserc.json: { "semantic_release": { "publish": {} } }

๐Ÿ“ dist_glob_patterns ๐Ÿ“

Type: list[str]

Uploads any files matching any of these globs to your VCS release. Every item in the list should be a string holding a Unix-style glob pattern.

Default: ["dist/*"]


โญ upload_to_vcs_release โญ

Type: bool

If set to true, uploads any artifacts matched by dist_glob_patterns to the release created in the remote VCS corresponding to the latest tag. Artifacts only get uploaded if release artifact uploads are actually supported by that VCS type. (Yes, this is conditional. Read the fine print, bestie. ๐Ÿ™ˆ)

Default: true


๐Ÿงš remote ๐Ÿงš

The remote configuration is a group of settings that configure PSR's integration with remote version control systems.

Note: pyproject.toml: [tool.semantic_release.remote] releaserc.toml: [semantic_release.remote] releaserc.json: { "semantic_release": { "remote": {} } }

๐ŸŒธ api_domain ๐ŸŒธ

Type: Optional[str | Dict['env', str]]

The hosting domain for your remote HVCS's API, in case it's different from the domain. Generally you'd use this to point at a separate subdomain used for API calls instead of the primary domain (ex. api.github.com).

Most on-premise HVCS installations will NOT use this setting! Whether or not this value gets used depends on the HVCS configured (and your server administration) in the remote.type setting, and it's used in tandem with the remote.domain setting.

When you're using a custom remote.domain together with an HVCS remote.type that's configured with a separate domain or sub-domain for API requests, this value configures the location of API requests that PSR sends out.

Most on-premise or self-hosted HVCS environments use a path prefix to handle inbound API requests, which means this value will be ignored.

PSR already knows the expected api domains for known cloud services and their associated api domains, so you don't need to explicitly define this value for services like bitbucket.org and github.com.

Including the protocol schemes, like https://, for the API domain is optional. Secure HTTPS connections are assumed unless remote.insecure is true.

Default: None


๐Ÿ’œ domain ๐Ÿ’œ

Type: Optional[str | Dict['env', str]]

The host domain for your HVCS server. This setting exists to support on-premise installations of HVCS providers with custom domain hosts.

If you're using the official domain of the associated remote.type, you don't need this value. PSR will use the default domain value for that remote.type when it's not specified. For example, when remote.type="github" is specified, the default domain of github.com gets used.

Including the protocol schemes, like https://, for the domain value is optional. Secure HTTPS connections are assumed unless remote.insecure is true.

This setting also supports reading from an environment variable, for ease-of-use in CI pipelines. See Environment Variable for more info. Depending on the remote.type, the default environment variable for the default domain's CI pipeline environment gets checked automatically, so you don't need this value in default environments. For example, when remote.type="gitlab" is specified, PSR will look at the CI_SERVER_URL environment variable when remote.domain isn't specified.

Default: None

See also:

  • remote.api_domain

๐ŸŽ€ ignore_token_for_push ๐ŸŽ€

Type: bool

If set to true, it ignores the authentication token when pushing changes to the remote. This is ideal, for example, if you already have SSH keys set up that you can use for pushing. ๐Ÿ”‘

Default: false


๐Ÿ‘‘ insecure ๐Ÿ‘‘

Introduced in v9.4.2

Type: bool

insecure is used to allow non-secure HTTP connections to your HVCS server.

If set to true, any domain value you pass will assume http:// if it isn't specified, and it'll allow it. When set to false (whether implicitly or explicitly), it forces https:// communications. No chill, all encryption. ๐Ÿ”’

When you hand over a custom domain or api_domain as a config value, this flag is the one calling the shots on the protocol scheme those connections use. If there's no protocol scheme baked into the field value, this insecure option decides whether the connection goes out over HTTP or HTTPS. And if the field value does spell out a scheme? Then it has to match this setting, or PSR throws an error. like, no discussion. ๐Ÿ˜ค

The whole point of this flag is to stop typos in your domain and api_domain values from lowkey sneaking an insecure connection in without you noticing โ€” while still letting you toggle the protection scheme off whenever you actually want to.

Default: false


name

Type: str

The name of the remote you're pushing to, used with git push -u $name <branch_name> โœจ

Default: "origin"


token

Type: Optional[str | Dict['env', str]]

The environment variable you pull the authentication token for your remote VCS from. The classic faves are "GH_TOKEN", "GITLAB_TOKEN" and "GITEA_TOKEN", but bestie you can absolutely roll your own custom env var if that's your vibe. ๐Ÿ’…

Note: by default this is a mandatory environment variable, and it has to be set before you use anything that needs auth against your remote VCS. If you're leaning on this token to get push access to the repo, it's gotta be set before you even think about pushing. If your push access comes through SSH keys instead, then you do not need to set this environment variable just to push the version increment, changelog and modified source code assets to the remote using cmd-version. That said, you will need to disable release creation using the cmd-version-option-vcs-release option (plus some other options) if you want to run Python Semantic Release without configuring the environment variable for your remote VCS auth token. no free lunch, sorry ๐Ÿ“

What this setting defaults to depends entirely on what you set as remote.type. Peep the deets below to see what the default token value is for each remote type:

  • "github" โ†’ { env = "GH_TOKEN" }
  • "gitlab" โ†’ { env = "GITLAB_TOKEN" }
  • "gitea" โ†’ { env = "GITEA_TOKEN" }
  • "bitbucket" โ†’ { env = "BITBUCKET_TOKEN" }

Default: { env = "<envvar name>" }, where <envvar name> depends on remote.type as laid out above.


type

Type: Literal["bitbucket", "gitea", "github", "gitlab"]

Which remote VCS you're on. Right now Python Semantic Release holds it down for "github", "gitlab", "gitea" and "bitbucket". Not every bit of functionality is available on every remote type, but bestie we are so open to pull requests that help make this better! ๐Ÿซถ

Default: "github"


url

Type: Optional[str | Dict['env', str]]

An override setting for specifying the remote upstream location of git push.

Not commonly used! This exists for when you want to override the derived upstream location because the place you wanna push to is different from the place you cloned the repository from.

This setting overrides the upstream location url that would normally be derived from the remote.name location of your git repository.

Default: None


repo_dir

Type: str

Tells PSR where the Git repository directory lives. It's used to figure out the repository location for all sorts of operations, like creating tags and commits. PSR will try to auto-detect the repository location by walking up the parent directories of the current working directory, but it'll emit a warning if the repo is found higher up than this value you defined.

And if there's no git repository from this location or any of its parent directories? PSR raises an error and exits. bye ๐Ÿ’€

Default: "."


tag_format

Type: str

Sets the format for the Git tag that gets slapped onto the repo during a release invoked via cmd-version. The string is used as a template for the tag name, and it must include the {version} format key. no cap.

This format also gets used to parse tags already chillin' in the repository into semantic versions. Which means: if the tag format changes at some point in your repo's history, historic versions that no longer match this pattern will not be considered as versions. ๐ŸŒธ

The format keys, bestie:

  • {version} โ€” Mandatory: Yes โ€” Contents: the new semantic version number, for example 1.2.3, or 2.1.0-alpha.1+build.1234

Tags that don't match this format will not be treated as versions of your project. periodt.

This is critical for Monorepo projects, where the tag format is literally what defines which package a version tag belongs to. Generally, each package's tag format in the monorepo will include the package name as the prefix of the tag format. So if a package is named pkg1, the tag format would be pkg1-v{version}, and in the other package pkg2 the tag format would be pkg2-v{version}. That's how PSR works out which tags to use when determining the version for each package. iconic behaviour, honestly. ๐Ÿ‘‘

Default: "v{version}"


version_toml

Type: list[str]

This config option is giving cousin-of-version_variables energy โ€” except it runs a TOML parser over the data structure before inserting the version. Which means you get to use dot-notation to point at the version via the TOML file's logical structure, and that's way more accurate than a pattern replace. slay. โœจ

version_toml is commonly used to bump the version number in the project definition file, pyproject.toml, like in the example below.

As of v9.20.0, the version_toml option accepts a colon-separated definition with either 2 or 3 parts. The 2-part definition includes the file path and the version parameter (in dot-notation). And newly with v9.20.0, it also accepts an optional 3rd part so you can configure the format type.

Available Format Types

  • nf: Number format (ex. 1.2.3)
  • tf: Tag Format (ex. v1.2.3)

If you don't specify the format type, it defaults to the number format.

Example

[semantic_release]
version_toml = [
    # "file:variable:[format_type]"
    "pyproject.toml:tool.poetry.version",  # Implied Default: Number format
    "definition.toml:project.version:nf",  # Number format
    "definition.toml:project.release:tf",  # Tag format
]

This configuration will result in the following changes:

diff a/pyproject.toml b/pyproject.toml

  [tool.poetry]
- version = "0.1.0"
+ version = "0.2.0"

diff a/definition.toml b/definition.toml

  [project]
  name = "example"

- version = "0.1.0"
+ version = "0.1.0"

- release = "v0.1.0"
+ release = "v0.2.0"

Default: []


version_variables

Type: list[str]

The version_variables configuration option is a list of string definitions that says where the version number should be updated in the repository when a new version gets released. ๐Ÿ’–

As of v9.20.0, the version_variables option accepts a colon-separated definition with either 2 or 3 parts. The 2-part definition includes the file path and the variable name. And newly with v9.20.0, it also accepts an optional 3rd part so you can configure the format type.

As of v10.6.0, the version_variables option also supports replacing an entire file by using an asterisk (*) as the pattern/variable name. Super handy for files that contain nothing but a version number, like those VERSION files. ๐ŸŽ€

Available Format Types

  • nf: Number format (ex. 1.2.3)
  • tf: Tag Format (ex. v1.2.3)

If you don't specify the format type, it defaults to the number format.

Prior to v9.20.0, PSR only supports entries with the first 2 parts, because the tag format type wasn't available yet and it would only replace numeric version numbers.

Example

[semantic_release]
tag_format = "v{version}"
version_variables = [
    # "file:variable:format_type"
    "src/semantic_release/__init__.py:__version__",  # Implied Default: Number format
    "docs/conf.py:version:nf",                       # Number format for sphinx docs
    "kustomization.yml:newTag:tf",                   # Tag format
    # File replacement (entire file content is replaced with version)
    "VERSION:*:nf",                                  # Replace entire file with number format
    "RELEASE:*:tf",                                  # Replace entire file with tag format
]

First up, the __version__ variable in src/semantic_release/__init__.py gets updated with the next version using the SemVer number format (https://semver.org/).

diff a/src/semantic_release/__init__.py b/src/semantic_release/__init__.py

- __version__ = "0.1.0"
+ __version__ = "0.2.0"

Then the version variable in docs/conf.py gets swapped for the next version, using the SemVer number format, because we slapped an explicit nf on it ๐Ÿ’…

diff a/docs/conf.py b/docs/conf.py

- version = "0.1.0"
+ version = "0.2.0"

Then the newTag variable in kustomization.yml gets updated to the next version too, but this time using the configured tag_format, because the definition had tf in it ๐ŸŽ€

diff a/kustomization.yml b/kustomization.yml

  images:
    - name: repo/image
-     newTag: v0.1.0
+     newTag: v0.2.0

Next up, the entire contents of the VERSION file get replaced with the next version in SemVer number format โ€” that's because of the * pattern plus the nf format type โœจ

diff a/VERSION b/VERSION

- 0.1.0
+ 0.2.0

And finally, the whole of the RELEASE file gets replaced with the next version using the configured tag_format โ€” because of the * pattern and the tf format type ๐Ÿฆ‹

diff a/RELEASE b/RELEASE

- v0.1.0
+ v0.2.0

How It works

Every version variable gets transformed into either a Regular Expression (when it's pattern-based replacement) or a file replacement operation (when the pattern is *) ๐Ÿงš

Pattern-Based Replacement

When you name an actual variable (so, not *), the replacement algorithm is ONLY a pattern match and replace. Periodt. It will NOT evaluate the code, and PSR will not understand any internal object structures โ€” meaning file:object.version will not work, bestie ๐Ÿ˜ญ

The regular expression generated from the version_variables definition will:

  1. Look for the specified variable name in the file. The variable name can be wrapped in single (') or double (") quotation marks โ€” but they have to match each other.
  2. The variable name you defined via variable and the version must be separated by an operand symbol (=, :, :=, or @). Whitespace around the symbol is optional. As of v10.0.0, a double-equals (==) operator is also supported as a valid operand symbol. As of v10.5.0, PSR can omit all operands entirely as long as there's at least one whitespace character between the variable name and the version ๐Ÿ‘‘
  3. The value of the variable has to match a SemVer regular expression, and it can be wrapped in single (') or double (") quotation marks โ€” but again, they must match. However, the quotes around the value don't have to match the quotes around the variable name. Mix and match, slay ๐Ÿ’…
  4. If the format type is set to tf, then the variable value must have the matching prefix and suffix of the tag_format setting wrapped around the SemVer version number.

Given how pattern-matchy this whole feature is, the Regular Expression can support most file formats โ€” because variable declarations across programming languages all kinda look the same. PSR specifically supports Python, YAML, and JSON, since those have been the most commonly requested formats. This configuration option will also work regardless of file extension, because it's just looking for a matching pattern string ๐Ÿ”

Note: This will also work for TOML, but we recommend using version_toml for TOML files, because that one actually interprets the TOML file and replaces the version number before writing the file back to disk. No cap ๐Ÿซถ

File Replacement

When the pattern/variable name is specified as an asterisk (*), the entire file content gets replaced with the version string. This is the move for files that contain nothing but a version number โ€” like VERSION files, or any similar single-line version storage file ๐ŸŒธ

The file replacement operation:

  1. Reads the current file content if it exists (any whitespace is stripped)
  2. Sets or replaces the entire file content with the new version string
  3. Writes the new version back to the file (with only a single trailing newline)

The format type (nf or tf) decides whether the version gets written as a plain number (like 1.2.3) or with the tag_format prefix/suffix (like v1.2.3) โœจ

Examples of Pattern-Based Replacement

Here's a comprehensive list (though not every variation) of examples where the versions below will be matched and replaced by the new version ๐Ÿ’–

# Common variable declaration formats
version='1.2.3'
version = "1.2.3"
release = "v1.2.3"     # if tag_format is set

# YAML
version: 1.2.3

# JSON
"version": "1.2.3"

# NPM & GitHub Actions YAML
version@1.2.3
version@v1.2.3        # if tag_format is set

# Walrus Operator
version := "1.2.3"

# Excessive whitespace
version        =    '1.2.3'

# Mixed Quotes
"version" = '1.2.3'

# Custom Tag Format with tag_format set (monorepos)
__release__ = "module-v1.2.3"

# requirements.txt
my-package == 1.2.3

# C-Macro style (no operand only whitespace required)
#define VERSION "1.2.3"
Important: The Regular Expression expects a version value to exist in the file that's getting replaced. It cannot be an empty string, and it can't be a non-semver compliant string either. If this is your very first time using PSR, we recommend you set the version to 0.0.0 ๐Ÿ’€ This may become more flexible in the future, once issue #941 (https://github.com/python-semantic-release/python-semantic-release/issues/941) gets resolved ๐Ÿ™ˆ
Warning: If the file you're replacing (say, a JSON file) has two of the same variable name in it, this pattern match cannot tell them apart โ€” and it will replace both of them. That's a limitation of the pattern matching, not a bug, bestie ๐Ÿ˜ญ

Default: []


โœจ Automated Releases โœจ

The whole key point of using this package is to automate your releases and stop stressing over version numbers, fr fr. There are different approaches to automatic releases and publishing with the help of this package โ€” you'll find them below. Using a CI is the recommended approach ๐Ÿ‘‘

๐ŸŽ€ Guides ๐ŸŽ€

Travis CI

This guide expects that you've already activated the repository on Travis CI. If that's not the case, please refer to the Travis documentation (https://docs.travis-ci.com/) on how to do that ๐Ÿซถ

1. Add python-semantic-release settings

See Configuration for details on how to configure Python Semantic Release. Make sure that at minimum you have set version_variables before continuing โญ

2. Add environment variables

You'll need to set up an environment variable in Travis. An easy way to do that is to go to the settings page for your package and add it there. Make sure the secret toggle is set correctly ๐Ÿ”

You need to set the GH_TOKEN environment variable with a personal access token for Github. It'll need either repo or public_repo scope, depending on whether the repository is private or public.

More info on how to set environment variables lives in the Travis documentation on environment variables (https://docs.travis-ci.com/user/environment-variables/#Defining-Variables-in-Repository-Settings) ๐ŸŒธ

3. Add travis configuration

The following should be added to your .travis.yml file:

after_success:
- git config --global user.name "semantic-release (via TravisCI)"
- git config --global user.email "semantic-release@travis"
- pip install python-semantic-release
- semantic-release version && semantic-release publish

The first line tells Travis that we want to run the listed tasks after a successful build. The two first lines in after_success configure git so that python-semantic-release can commit on Travis. The third installs the latest version of python-semantic-release. And the last one runs the publish command, which will publish a new version if the changes indicate that one is due ๐Ÿ’…

4. Push some changes

You are now ready to release automatically on Travis CI on every single change to your master branch.

Happy coding! ๐ŸŽ‰

GitHub Actions

There are two official GitHub Actions for Python Semantic Release:

python-semantic-release/python-semantic-release@TAG

This is the main action that runs the version CLI command. It's used to (1) determine the next version number, (2) stamp the version number, (3) run the build command, (4) build the changelog, (5) commit the changes, (6) tag the commit, (7) publish the commit & tag, and lastly (8) create a GitHub release. For more info, review the version command documentation and see below for the Action configuration options ๐Ÿ‘‘

python-semantic-release/publish-action@TAG

This action is used to execute the publish CLI command. It's used to upload files โ€” like distribution artifacts and other assets โ€” to a GitHub release ๐ŸŽ€

Included in this documentation are some recommended examples below, in case you wanna get started quickly. These examples are not exhaustive, and you'll need to adjust them for your specific project needs โ€” especially if you're using a monorepo ๐ŸŒˆ

  • GitHub Actions Example Workflows
  • GitHub Actions with Monorepos
Note: These GitHub Actions are only simplified wrappers around the python-semantic-release CLI. Ultimately they download and install the published package from PyPI, so if you find yourself trying to do something more advanced or less common, you may need to install and use the CLI directly ๐Ÿซถ

Python Semantic Release Action

The official Python Semantic Release GitHub Action (https://github.com/marketplace/actions/python-semantic-release) is a GitHub Docker Action (https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-docker-container-action), which means that at the beginning of the job it builds a Docker image containing the Python Semantic Release package and its dependencies. Then it runs the job step inside that Docker Container. This is done to make sure the environment is consistent across all GitHub Runners, regardless of platform. With that choice comes some limitations, though: non-configurable options like a pre-defined python version, a lack of installed build tools, and an inability to utilize caching ๐Ÿ’€

The primary benefit of using the GitHub Action is that it's easy to set up and use for most projects. We handle a lot of the git configuration under the hood, so you don't have to do it yourself. And there are plenty of customization options available, detailed individually below โœจ

Most importantly: your project's configuration file will be used as normal, because your project gets mounted into the container for the action to use ๐Ÿ’–

โœจ Inputs โœจ

okay so GitHub Action inputs?? they're how you do select configuration and hand the action all the info it needs to actually run ๐Ÿซถ You pass them in with the with keyword inside your workflow file. Lowkey a LOT of them straight-up mirror the command line options from the version command. This section goes through every single supported input and what it's for, no skips ๐Ÿ’…


๐Ÿ› ๏ธ build

Type: Literal["true", "false"]

Override whether the action should execute the build command or not. This one is equivalent to adding the command line switch --skip-build (when it's false) to the version command. If you set it to true, no command line switch gets passed and the default behavior of version is used. slay.

Required: false

Note: If it's not set, or it's set to true, the default behavior is defined by the version command and any user configurations <#config-root>.

See also:

  • cmd-version-option-skip_build option for the version command.

๐Ÿ› ๏ธ build_metadata

Type: string

Explicitly set the build metadata of the version. This is equivalent to running the command:

semantic-release version --build-metadata <metadata>

Required: false

See also:

  • cmd-version-option-build-metadata option for the version command

๐Ÿ› ๏ธ changelog

Type: Literal["true", "false"]

Override whether the action should generate a changelog or not. This option is equivalent to adding either --changelog (on true) or --no-changelog (on false) to the version command.

Required: false

Note: If it's not set, the default behavior is defined by the version command and any user configurations <#config-root>.

See also:

  • cmd-version-option-changelog options for the version command

๐Ÿ› ๏ธ commit

Type: Literal["true", "false"]

Override whether the action should commit any changes to the local repository. And "changes" here means the version stamps, the changelog, and any other files that get modified and added to the index during the build command ๐Ÿ‘€ This option is equivalent to adding either --commit (on true) or --no-commit (on false) to the version command.

Required: false

Note: If it's not set, the default behavior is defined by the version command and any user configurations <#config-root>.

See also:

  • cmd-version-option-commit options for the version command

๐Ÿ› ๏ธ config_file

Path to a custom semantic-release configuration file. By default, an empty string will look for the pyproject.toml file in the current directory. This is the same as passing the -c or --config parameter to semantic-release.

Required: false

Default: ""

See also:

  • cmd-main-option-config for the semantic-release command

๐Ÿ› ๏ธ directory

If the project is not at the root of the repository (like in monorepos, iykyk ๐Ÿ˜ญ), you can specify a sub-directory to change into before running semantic-release.

Required: false

Default: .


๐Ÿ› ๏ธ force

Type: Literal["prerelease", "patch", "minor", "major"]

Force the next version to be a specific bump type. This is equivalent to running the command:

semantic-release version --<type>

# Ex: force a patch level version bump
semantic-release version --patch

Required: false

See also:

  • cmd-version-option-force-level options for the version command

๐Ÿ› ๏ธ git_committer_email

The email of the account used to commit. If you customize it, it must be associated with the provided token. periodt.

Required: false


๐Ÿ› ๏ธ git_committer_name

The name of the account used to commit. If you customize it, it must be associated with the provided token. same energy as above fr fr.

Required: false


๐Ÿ› ๏ธ github_token

The GitHub Token is essential for access to your GitHub repository so it can allow the push of commits & tags AND create a release. And it's not just "provide the token and vibe" โ€” you also need to make sure that token has the correct permissions. ๐Ÿ’€

The token should have the following permissions: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idpermissions

  • id-token: write
  • contents: write

Required: true


๐Ÿ› ๏ธ no_operation_mode

If set to true, the github action will pass the --noop parameter to semantic-release. This will cause semantic-release to run in "no operation" mode.

This is useful for testing the action without making any permanent changes to the repository. perfect for a little test run, no commitment ๐Ÿงš

Required: false

Default: false

See also:

  • cmd-main-option-noop option for the semantic-release command

๐Ÿ› ๏ธ prerelease

Force the version to be a prerelease version when set to true. This is equivalent to running the command:

semantic-release version --as-prerelease

Required: false

Note: If it's not set, the default behavior is defined by the version command and any user configurations <#config-root>.

See also:

  • cmd-version-option-as-prerelease option for the version command

๐Ÿ› ๏ธ prerelease_token

Override any prerelease token in the configuration file with this value, if it is a pre-release. This will override the matching release branch configuration's prerelease_token value. And heads up ๐Ÿ‘‘ if you always want it to be a prerelease then you must also set the prerelease input to true.

This option is equivalent to running the command:

semantic-release version --prerelease-token <token>

Required: false

Note: If it's not set, the default behavior is defined by the version command and any user configurations <#config-root>.

See also:

  • cmd-version-option-prerelease-token option for the version command

๐Ÿ› ๏ธ push

Type: Literal["true", "false"]

Override whether the action should push any commits or tags from the local repository to the remote repository. This option is equivalent to adding either --push (on true) or --no-push (on false) to the version command.

Required: false

Note: If it's not set, the default behavior is defined by the version command and any user configurations <#config-root>.

See also:

  • cmd-version-option-push options for the version command

๐Ÿ› ๏ธ root_options

Important: This option has been removed in v10.0.0 and newer because of a command injection vulnerability. Please update to v10.0.0 as soon as possible. See Upgrading to v10 <#upgrade-v10-root-options> for more information. bestie please upgrade, this one is genuinely not a drill ๐Ÿ™ˆ

Additional options for the main semantic-release command, which will come before the version subcommand.

Example

- uses: python-semantic-release/python-semantic-release@v9
  with:
    root_options: "-vv --noop"

This configuration would cause the command to be semantic-release -vv --noop version, which would run the version command verbosely but in no-operation mode.

Required: false

Default: -v

See also:

  • Options for the semantic-release command

๐Ÿ› ๏ธ ssh_public_signing_key

The public key associated with the private key used in signing a commit and tag. ๐Ÿ”‘

Required: false


๐Ÿ› ๏ธ ssh_private_signing_key

The private key used to sign a commit and tag. ๐Ÿคซ keep it secret, keep it safe.

Required: false


๐Ÿ› ๏ธ strict

If set to true, the github action will pass the --strict parameter to semantic-release.

See also:

  • cmd-main-option-strict option for the semantic-release command

๐Ÿ› ๏ธ tag

Type: Literal["true", "false"]

Override whether the action should create a version tag in the local repository. This option is equivalent to adding either --tag (on true) or --no-tag (on false) to the version command.

Required: false

Note: If it's not set, the default behavior is defined by the version command and any user configurations <#config-root>.

See also:

  • cmd-version-option-tag options for the version command

๐Ÿ› ๏ธ vcs_release

Type: Literal["true", "false"]

Override whether the action should create a release on the VCS. This option is equivalent to adding either --vcs-release (on true) or --no-vcs-release (on false) to the version command.

Required: false

Note: If it's not set, the default behavior is defined by the version command and any user configurations <#config-root>.

See also:

  • cmd-version-option-vcs-release options for the version command

๐Ÿ› ๏ธ verbosity

Set the verbosity level of the output as the number of -v's to pass to semantic-release. 0 is no extra output, 1 is info level output, 2 is debug output, and 3 is a silly amount of debug output. (three v's is giving unhinged and I respect it ๐Ÿ’€โœจ)

Required: false

Default: "1"

See also:

  • cmd-main-option-verbosity for the semantic-release command

โœจ Outputs โœจ

The Python Semantic Release Action also provides outputs that can be used in subsequent steps of the workflow. These outputs are used to provide information about the release and any actions that were taken. ๐Ÿซถ


๐Ÿ“ค commit_sha

Type: string

The commit SHA of the release if a release was made, otherwise an empty string.

Example upon release: d4c3b2a1e0f9c8b7a6e5d4c3b2a1e0f9c8b7a6e5 Example when no release was made: ""


๐Ÿ“ค is_prerelease

Type: Literal["true", "false"]

A boolean value indicating whether the released version is a prerelease.


Type: string

The URL link to the release if a release was made, otherwise an empty string.

Example upon release: https://github.com/user/repo/releases/tag/v1.2.3 Example when no release was made: ""


๐Ÿ“ค previous_version

Type: string

The previous version before the release, if a release was or will be made. If no release is detected, this will be the current version or an empty string if no previous version exists. ๐ŸŒˆ

๐Ÿ’– released ๐Ÿ’–

Type: Literal["true", "false"]

A boolean value that tells you whether a release actually happened. "true" = we cooked, "false" = nothing to see here bestie. ๐Ÿซถ


โœจ release_notes โœจ

Type: string

The release notes that the release generated โ€” if there are any. And if no release was made at all? Then this is just an empty string, no cap. ๐Ÿ˜ญ


๐ŸŽ€ version ๐ŸŽ€

Type: string

The shiny new SemVer version string, but only if a release was made. If not, you get the current version instead.

Example: 1.2.3


๐Ÿฆ‹ tag ๐Ÿฆ‹

Type: string

The Git tag that goes with the version output โ€” except it's in whatever tag format your config tells it to use. iykyk ๐Ÿ’…

Example: v1.2.3


๐Ÿณ Python Semantic Release Publish Action ๐Ÿณ

The official Python Semantic Release Publish Action (https://github.com/marketplace/actions/python-semantic-release-publish) is a GitHub Docker Action (https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-docker-container-action), which basically means that at the very beginning of the job it builds a Docker image stuffed with the Python Semantic Release package and all its dependencies. Then it runs the job step inside that Docker Container. Why? To make sure the environment is consistent across all GitHub Runners no matter the platform. ๐Ÿ’…

But babes, that choice comes with some limitations โ€” things you just can't configure, like a pre-defined python version, no extra 3rd party tools, and an inability to use caching. ๐Ÿซ 

The main character benefit here is that it's genuinely easy to set up and use for most projects. Some extra configuration is handled under the hood so you don't have to deal with it yourself. That said, a few customization options are provided, and they're detailed one by one below. ๐Ÿ‘‘

Most importantly: your project's configuration file gets used like normal, because your project gets mounted into the container for the action to use. ๐Ÿซถ

If the action is giving you issues, please open an issue on the python-semantic-release/publish-action repository: https://github.com/python-semantic-release/publish-action/issues

See also:


๐Ÿ“ฅ Inputs ๐Ÿ“ฅ

GitHub Action inputs are used for select configuration and they carry the info needed to actually run the action. You pass them in with the with keyword in the workflow file. A lot of these inputs mirror the command line options available in the publish command, and the rest are specific to tweaking the action environment. This section walks through every supported input and what it's for. โœจ


๐ŸŒธ config_file ๐ŸŒธ

Path to a custom semantic-release configuration file. By default, an empty string will go looking for the pyproject.toml file in the current directory. This is the exact same thing as passing the -c or --config parameter to semantic-release.

Required: false

Default: ""

See also:

  • cmd-main-option-config for the semantic-release command

๐Ÿ’œ directory ๐Ÿ’œ

If your project isn't sitting at the root of the repo (hi monorepos ๐Ÿ‘‹), you can name a sub-directory to change into before running semantic-release.

Required: false

Default: .


๐Ÿ” github_token ๐Ÿ”

The GitHub Token is essential โ€” it's what gives you access to your GitHub repository so assets can be published to a release. And bestie, you don't just hand over the token, you also have to make sure it has the right permissions. ๐Ÿ˜ค

The token should have the following permissions (https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idpermissions):

  • contents: write: Required for modifying a GitHub Release

Required: true


๐Ÿ™ˆ no_operation_mode ๐Ÿ™ˆ

If you set this to true, the github action will pass the --noop parameter to semantic-release. That makes semantic-release run in "no operation" mode.

Super useful for testing the action without actually publishing anything. ๐ŸŒˆ

Required: false

Default: false

See also:

  • cmd-main-option-noop option for the semantic-release command

๐Ÿ’€ root_options ๐Ÿ’€

Important: This option has been removed in v10.0.0 and newer because of a command injection vulnerability. Please update to v10.0.0 as soon as possible. See Upgrading to v10 (#upgrade-v10-root-options) for more information.

Additional options for the main semantic-release command, which will come before the publish subcommand.

Example

- uses: python-semantic-release/publish-action@v9
  with:
    root_options: "-vv --noop"

This configuration would cause the command to be semantic-release -vv --noop publish, which would run the publish command verbosely but in no-operation mode.

Required: false

Default: -v

See also:

  • Options for the semantic-release command

๐ŸŽ€ tag ๐ŸŽ€

Type: string

The tag corresponding to the GitHub Release that the artifacts should be published to. This option is equivalent to running the command:

semantic-release publish --tag <tag>

Python Semantic Release will automatically work out the latest release if no --tag option is provided. ๐Ÿ‘‘

Required: false

See also:

  • cmd-publish-option-tag option for the publish command

๐Ÿ”Š verbosity ๐Ÿ”Š

Sets the verbosity level of the output, as the number of -v's to pass to semantic-release. 0 is no extra output, 1 is info level output, 2 is debug output, and 3 is a silly amount of debug output. ๐Ÿ“

Required: false

Default: "1"

See also:

  • cmd-main-option-verbosity for the semantic-release command

๐Ÿ“ค Outputs ๐Ÿ“ค

There are no outputs provided by the Python Semantic Release Publish Action at this time. ๐Ÿ˜ญ

Note: If you would like outputs to be provided by this action, please open an issue on the python-semantic-release/publish-action repository: https://github.com/python-semantic-release/publish-action/issues

๐Ÿ‘‘ Examples ๐Ÿ‘‘

๐ŸŒˆ Common Workflow Example ๐ŸŒˆ

Here's a simple, common workflow example that uses both the Python Semantic Release Action and the Python Semantic Release Publish Action. This workflow runs on every push to the main branch and will create a new release once a version determination succeeds. If a version is released, the workflow then publishes the package to PyPI and uploads the package to the GitHub Release Assets as well. Ate. ๐Ÿ”ฅ

name: Continuous Delivery

on:
  push:
    branches:
      - main

# default: least privileged permissions across all jobs
permissions:
  contents: read

jobs:
  release:
    runs-on: ubuntu-latest
    concurrency:
      group: ${{ github.workflow }}-release-${{ github.ref_name }}
      cancel-in-progress: false

    permissions:
      contents: write

    steps:
      # Note: We checkout the repository at the branch that triggered the workflow.
      # Python Semantic Release will automatically convert shallow clones to full clones
      # if needed to ensure proper history evaluation. However, we forcefully reset the
      # branch to the workflow sha because it is possible that the branch was updated
      # while the workflow was running, which prevents accidentally releasing un-evaluated
      # changes.
      - name: Setup | Checkout Repository on Release Branch
        uses: actions/checkout@COMMIT_HASH  # v6
        with:
          ref: ${{ github.ref_name }}

      - name: Setup | Force release branch to be at workflow sha
        run: |
          git reset --hard ${{ github.sha }}

      - name: Action | Semantic Version Release
        id: release
        # Adjust tag with desired version if applicable.
        uses: python-semantic-release/python-semantic-release@COMMIT_HASH  # v10.6.1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          git_committer_name: "github-actions"
          git_committer_email: "actions@users.noreply.github.com"

      - name: Publish | Upload to GitHub Release Assets
        uses: python-semantic-release/publish-action@COMMIT_HASH  # v10.6.1
        if: steps.release.outputs.released == 'true'
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          tag: ${{ steps.release.outputs.tag }}

      - name: Upload | Distribution Artifacts
        uses: actions/upload-artifact@COMMIT_HASH  # v4.X.X
        with:
          name: distribution-artifacts
          path: dist
          if-no-files-found: error

    outputs:
      released: ${{ steps.release.outputs.released || 'false' }}

  deploy:
    # 1. Separate out the deploy step from the publish step to run each step at
    #    the least amount of token privilege
    # 2. Also, deployments can fail, and its better to have a separate job if you need to retry
    #    and it won't require reversing the release.
    runs-on: ubuntu-latest
    needs: release
    if: ${{ needs.release.outputs.released == 'true' }}

    permissions:
      contents: read
      id-token: write

    steps:
      - name: Setup | Download Build Artifacts
        uses: actions/download-artifact@COMMIT_HASH  # v4.X.X
        id: artifact-download
        with:
          name: distribution-artifacts
          path: dist

      # ------------------------------------------------------------------- #
      # Python Semantic Release is not responsible for publishing your      #
      # python artifacts to PyPI. Use the official PyPA publish action      #
      # instead. The following steps are an example but is not guaranteed   #
      # to work as the action is not maintained by the                      #
      # python-semantic-release team.                                       #
      # ------------------------------------------------------------------- #
                # see https://docs.pypi.org/trusted-publishers/
                - name: Publish package distributions to PyPI
                  uses: pypa/gh-action-pypi-publish@COMMIT_HASH  # vX.X.X
                  with:
                    packages-dir: dist
                    print-hash: true
                    verbose: true

Important: bestie the concurrency directive <https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idconcurrency> is sitting on the job specifically so you never get race conditions โ€” like, if multiple pushes to main land in a short period of time, you do NOT want two release jobs fighting. ๐Ÿ’…

Warning: okay so the GITHUB_TOKEN secret is auto-configured by GitHub, and it comes with the same permissions role as the user who triggered the workflow run. That's a whole problem if your default branch is protected to specific users. ๐Ÿซ  The workaround: store an administrator's Personal Access Token as a separate secret and use that instead of GITHUB_TOKEN. But heads up โ€” you'll also need to pass the new token to actions/checkout (as the token input) so you can actually get push access. ๐Ÿ”‘
Note: as of v10.5.0, Python Semantic Release automatically detects shallow clones and converts them to full clones whenever it needs to. So like, you can still use fetch-depth: 0 with actions/checkout to fetch the full history upfront, but it's lowkey not required anymore. If you're rolling with the default shallow clone, PSR will auto-fetch the full history before it evaluates commits. Using an older version of PSR? Then you WILL have to unshallow the repo yourself before use. no cap.
Note: as of v10.5.0, the verify upstream step is no longer required because it got integrated into PSR directly. ๐ŸŽ€ Older version of PSR? Go review the older documentation for that step. See Issue #1201 <https://github.com/python-semantic-release/python-semantic-release/issues/1201> for more details.

๐ŸŽ€ Version Overrides Example ๐ŸŽ€

So when you want to hand multiple command line options to the version command, you pass them through the with directive in the workflow file. In this example we're forcing a patch version bump, not producing a changelog, and providing some specialized build metadata. As a regular CLI command, that's giving:

semantic-release version --patch --no-changelog --build-metadata abc123

The equivalent GitHub Action configuration would be:

# snippet

- name: Action | Semantic Version Release
  # Adjust tag with desired version if applicable.
  uses: python-semantic-release/python-semantic-release@v10.5.3
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    force: patch
    changelog: false
    build_metadata: abc123

See also:


๐Ÿฆ‹ Actions with Monorepos ๐Ÿฆ‹

Okay so real talk: python-semantic-release does NOT have full monorepo support. But if you've got multiple projects sitting inside a single repository (or your project just isn't at the root of the repo), you can pass the directory input to the action and it'll change directory before semantic-release execution. ๐Ÿ 

For multiple packages, you'd need to run the action multiple times to release each project. The example below shows how to release two projects in a monorepo.

Remember bestie: for each release of each submodule you'll then need to handle publishing each package separately as well. That all depends on the result of your build commands. In the example below, we assume a simple build module command to build an sdist and wheel artifacts into the submodule's dist directory. ๐Ÿ“ฆ

The directory input directive is also available for the Python Semantic Release Publish Action. โœจ

jobs:

  release:

    env:
      SUBMODULE_1_DIR: project1
      SUBMODULE_2_DIR: project2

    steps:

      # ------------------------------------------------------------------- #
      # Note the use of different IDs to distinguish which submodule was    #
      # identified to be released. The subsequent actions then reference    #
      # their specific release ID to determine if a release occurred.       #
      # ------------------------------------------------------------------- #

      - name: Release submodule 1
        id: release-submod-1
        uses: python-semantic-release/python-semantic-release@v10.5.3
        with:
          directory: ${{ env.SUBMODULE_1_DIR }}
          github_token: ${{ secrets.GITHUB_TOKEN }}

      - name: Release submodule 2
        id: release-submod-2
        uses: python-semantic-release/python-semantic-release@v10.5.3
        with:
          directory: ${{ env.SUBMODULE_2_DIR }}
          github_token: ${{ secrets.GITHUB_TOKEN }}

      # ------------------------------------------------------------------- #
      # For each submodule, you will have to publish the package separately #
      # and only attempt to publish if the release for that submodule was   #
      # deemed a release (and the release was successful).                  #
      # ------------------------------------------------------------------- #

      - name: Publish | Upload package 1 to GitHub Release Assets
        uses: python-semantic-release/publish-action@v10.5.3
        if: steps.release-submod-1.outputs.released == 'true'
        with:
          directory: ${{ env.SUBMODULE_1_DIR }}
          github_token: ${{ secrets.GITHUB_TOKEN }}
          tag: ${{ steps.release-submod-1.outputs.tag }}

      - name: Publish | Upload package 2 to GitHub Release Assets
        uses: python-semantic-release/publish-action@v10.5.3
        if: steps.release-submod-2.outputs.released == 'true'
        with:
          directory: ${{ env.SUBMODULE_2_DIR }}
          github_token: ${{ secrets.GITHUB_TOKEN }}
          tag: ${{ steps.release-submod-2.outputs.tag }}

      # ------------------------------------------------------------------- #
      # Python Semantic Release is not responsible for publishing your      #
      # python artifacts to PyPI. Use the official PyPA publish action      #
      # instead. The following steps are an example but is not guaranteed   #
      # to work as the action is not maintained by the                      #
      # python-semantic-release team.                                       #
      # ------------------------------------------------------------------- #

      - name: Publish | Upload package 1 to PyPI
        uses: pypa/gh-action-pypi-publish@COMMIT_HASH  # vX.X.X
        if: steps.release-submod-1.outputs.released == 'true'
        with:
          packages-dir: ${{ format('{}/dist', env.SUBMODULE_1_DIR) }}

      - name: Publish | Upload package 2 to PyPI
        uses: pypa/gh-action-pypi-publish@COMMIT_HASH  # vX.X.X
        if: steps.release-submod-2.outputs.released == 'true'
        with:
          packages-dir: ${{ format('{}/dist', env.SUBMODULE_2_DIR) }}

๐ŸŒˆ Cron Job Publishing ๐ŸŒˆ

This one is for you if, for some reason, you cannot publish from your CI, or you would like releases to drop at a certain interval. โฐ But before you start, answer this: are you sure you do not want a CI to release for you? (high version numbers are not a bad thing, bestie.) ๐Ÿ’–

The guide below is for setting up scheduled publishing on a server. It requires that the user that runs the cronjob has push access to the repository and upload access to an artifact repository. ๐Ÿ”

  1. Create a virtualenv:
virtualenv semantic_release -p `which python3`
  1. Install python-semantic-release:
pip install python-semantic-release
  1. Clone the repositories you want to have scheduled publishing.
  2. Put the following in publish:
VENV=semantic_release/bin

$VENV/pip install -U pip python-semantic-release > /dev/null

publish() {
  cd $1
  git stash -u # ensures that there is no untracked files in the directory
  git fetch && git reset --hard origin/master
  $VENV/semantic-release version && $VENV/semantic-release publish
  cd ..
}

publish <package1>
publish <package2>
  1. Add cronjob:
/bin/bash -c "cd <path> && source semantic_release/bin/activate && ./publish 2>&1 >> releases.log"

โฌ†๏ธ Upgrading PSR โฌ†๏ธ

Upgrading PSR is a process that may involve several steps, depending on the version you're upgrading from and to. This section is your little guide for going from older versions of PSR to the latest. ๐Ÿš€

Important: if you're upgrading across more than one major version, you should incrementally upgrade through each major version and its configuration update guide to keep the transition smooth. no skipping, bestie. ๐Ÿ˜ญ For example, if you're upgrading from v7 to v10, you should first upgrade to v8 and then to v9, and then lastly to v10 while following the upgrade guide for each version. At every single step you should confirm execution works as expected before proceeding to the next version. periodt.

๐ŸŒธ Upgrading to v10 ๐ŸŒธ

The upgrade to v10 is primarily motivated by a command injection security vulnerability found in the GitHub Actions configuration interpreter (see details below). ๐Ÿ’€ We also bundled a number of other changes, including new default configuration values and, most importantly, a return to 1-line commit subjects in the default changelog format. iconic.

For more specific change details for v10, please refer to the v10.0.0 (2025-05-25) <#changelog-v10-0-0> section of the CHANGELOG <#changelog>. ๐ŸŽ€

๐Ÿ”’ Security Fix: Command Injection Vulnerability (GitHub Actions)

okay bestie, this one is serious, sit down for it. ๐Ÿ’€

In the previous versions of the GitHub Actions configuration, we used a single root_options parameter to pass any options you wanted to pass to the semantic-release main command. That parameter was interpreted as a string and passed directly to the command line, which made it vulnerable to command injection attacks. An attacker could exploit this by crafting a malicious string as the root_options <#gh-actions-psr-inputs-root-options> input, and then it would literally be executed as part of the command line โ€” potentially letting them run arbitrary commands inside the GitHub Actions Docker container. ๐Ÿ˜ญ

ngl the blast radius is limited though: the ability to exploit this vulnerability is limited to people who can modify the GitHub Actions workflow file, which is typically only the repository maintainers โ€” unless you are pointing at an organizational workflow file or another third-party workflow file. ๐Ÿ™ˆ

How we fixed it: to mitigate this vulnerability, we removed the root_options parameter completely and replaced it with individual boolean flag inputs, which are then used to select the proper cli parameters for the semantic-release command. โœจ

Also, you can protect yourself by limiting the access to secrets in your GitHub Actions workflows and the permissions of the GitHub Actions CI TOKEN. ๐Ÿซถ

This vulnerability existed in both the python-semantic-release/python-semantic-release <#gh-actions-psr> and python-semantic-release/publish-action <#gh-actions-publish> actions.

For the main python-semantic-release/python-semantic-release <#gh-actions-psr> action, the following inputs are now available (in place of the old root_options parameter): config_file <#gh-actions-psr-inputs-config-file>, no_operation_mode <#gh-actions-psr-inputs-noop>, strict <#gh-actions-psr-inputs-strict>, and verbosity <#gh-actions-psr-inputs-verbosity>.

Example migration ๐Ÿ’…

If you previously had the following in your GitHub Actions workflow file:

- uses: python-semantic-release/python-semantic-release@v9
  with:
    root_options: "-vv --strict"

It would be updated to:

- uses: python-semantic-release/python-semantic-release@v10
  with:
    strict: true
    verbosity: 2

For the python-semantic-release/publish-action <#gh-actions-publish> action, the following inputs are now available (in place of the old root_options parameter): config_file <#gh-actions-publish-inputs-config-file>, no_operation_mode <#gh-actions-publish-inputs-noop>, and verbosity <#gh-actions-publish-inputs-verbosity>.

Example migration ๐ŸŽ€

If you previously had the following in your GitHub Actions workflow file:

- uses: python-semantic-release/publish-action@v9
  with:
    root_options: "-v -c /path/to/releaserc.yaml"

It would be updated to:

- uses: python-semantic-release/publish-action@v10
  with:
    config_file: /path/to/releaserc.yaml
    verbosity: 1

๐Ÿ“ Changelog Format: 1-Line Commit Subjects

In v10, the default changelog format has been changed to use 1-line commit subjects instead of including the full commit message. โœจ Why? Because this change was made to improve the readability of the changelog โ€” many commit messages are long and contain unnecessary details for the changelog. lowkey so real.

Important: if you use a squash commit merge strategy, it is recommended that you use the default parse_squash_commits commit parser option to ensure that all the squashed commits are parsed for version bumping and changelog generation. This is the default behavior in v10 across all supported commit parsers. If you are upgrading, you likely will need to manually set this option in your configuration file to ensure that the changelog is generated correctly.
Important: if you do not enable parse_squash_commits, then version will only be determined by the commit subject line and the changelog will only include the commit subject line as well. ๐Ÿซ 

๐ŸŽญ Changelog Format: Mask Initial Release

In v10, the default behavior for the changelog generation has been changed to mask the initial release in the changelog. What that means bestie: the first release will not contain a break down of the different types of changes (e.g., features, fixes, etc.), but instead it will just simply state that this is the initial release. ๐Ÿ’–


๐Ÿ” Changelog Format: Commit Parsing

We have made some minor changes to the commit parsing logic in v10 to separate out components of the commit message more clearly. You will find that the ParsedCommit <#semantic_release.commit_parser.token.ParsedCommit> object's descriptions list will no longer contain any Breaking Change footers, Release Notice footers, PR/MR references, or Issue Closure footers. Those were all previously extracted and placed into their own attributes, but were still included in the descriptions list. In v10, the descriptions list will only contain the actual commit subject line and any additional commit body text that is not part of the pre-defined footers. ๐Ÿ“‹

If you were relying on the descriptions list to contain these footers, you will need to update your code and changelog templates to reference the specific attributes you want to use. ๐Ÿ”ง


โš™๏ธ Default Configuration Changes

The following table summarizes the changes to the default configuration values in v10:

  1. allow_zero_version <#config-allow-zero-version> โ€” Previous Default Value: true โ†’ New Default Value: false
  2. changelog.mode <#config-changelog-mode> โ€” Previous Default Value: init โ†’ New Default Value: update
  3. changelog.default_templates.mask_initial_release <#config-changelog-default-templates-mask-initial-release> โ€” Previous Default Value: false โ†’ New Default Value: true
  4. commit_parser_options.parse_squash_commits <#config-commit-parser-options> โ€” Previous Default Value: false โ†’ New Default Value: true
  5. commit_parser_options.ignore_merge_commits <#config-commit-parser-options> โ€” Previous Default Value: false โ†’ New Default Value: true

๐Ÿ—‘๏ธ Deprecations & Removals

No additional deprecations were made in v10, but the following are staged for removal in v11:

๐Ÿšซ Deprecated Features & Functions ๐Ÿ’€

okay bestie, here's the receipts on everything that's on its way out. read the ๐Ÿ“‰ Deprecated column (when it got marked) v. the ๐Ÿชฆ Planned Removal column (when it's actually leaving) โ€” iykyk, don't get caught slipping:

  • # 1 โ€” Component: GitHub Actions root_options <#gh-actions-psr-inputs-root-options> ยท Deprecated: v10.0.0 ยท Planned Removal: v10.0.0 ยท Notes: Replaced with individual boolean flag inputs. See above for details.
  • # 2 โ€” Component: Angular Commit Parser <#commit-parser-builtin-angular> ยท Deprecated: v9.19.0 ยท Planned Removal: v11.0.0 ยท Notes: Replaced by the Conventional Commit Parser <#commit-parser-builtin-conventional>.
  • # 3 โ€” Component: Tag Commit Parser <#commit-parser-builtin-tag> ยท Deprecated: v9.12.0 ยท Planned Removal: v11.0.0 ยท Notes: Replaced by the Emoji Commit Parser <#commit-parser-builtin-emoji>.
๐Ÿ“Œ Note: for the most up-to-date info on the next version's deprecations and removals, go peep issue #1066 <https://github.com/python-semantic-release/python-semantic-release/issues/1066>. that's the source of truth, fr fr.

๐Ÿ†™ Upgrading to v9 โœจ

bestie you are in luck โ€” the upgrade to v9 is literally a simple one. no drama, no fifteen-step migration arc. ๐Ÿซถ

the one breaking change for this version is the removal of support for Python 3.7, because it's officially passed End-Of-Life (EOL). so if you're out here on Python 3.7, you gotta upgrade to at least Python 3.8 to use v9. and heads up โ€” this is permanent, no take-backs: all future versions of python-semantic-release will require Python 3.8 or later. periodt. ๐Ÿ’…


๐Ÿ†™ Upgrading to v8 โœจ

so Python Semantic Release v8.0.0 introduced a whole bunch of breaking changes ๐Ÿ˜ญ. the internals got changed significantly โ€” all so the project could better support highly-requested features and streamline maintenance. glow-up, basically.

which means: certain things got removed, certain things got reimplemented differently, and certain things now behave differently than in earlier versions of Python Semantic Release. this page is your guide so you can pip install python-semantic-release>=8.0.0 with fewer surprises. we got you. ๐ŸŽ€

๐Ÿ™ Python Semantic Release GitHub Action ๐Ÿ’–

GitHub Action no longer publishes artifacts to PyPI or GitHub Releases

Python Semantic Release no longer uploads distributions to PyPI โ€” see Repurposing of version and publish commands below. and if you were using Python Semantic Release to publish release notes and artifacts to GitHub releases, there's a shiny new GitHub Action for that: upload-to-gh-release <https://github.com/python-semantic-release/upload-to-gh-release>, and it'll do the job for you. โœจ

what that means: the two workflows below do the exact same actions, and if you're using the first one, you'll need to modify your workflow to include the steps from the second one.

this workflow is written to use Python Semantic Release v7.33.5:

---
name: Semantic Release

on:
  push:
    branches:
      - main

jobs:
  release:
    runs-on: ubuntu-latest
    concurrency: release

    steps:
    - uses: actions/checkout@COMMIT_HASH  # v3.0.0
      with:
        fetch-depth: 0

    # This action uses Python Semantic Release v7
    - name: Python Semantic Release
      uses: python-semantic-release/python-semantic-release@323ebf700ac0878aedfa899bcb0492f6d579986c  # v7.33.5
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        repository_username: __token__
        repository_password: ${{ secrets.PYPI_TOKEN }}

and this following workflow achieves the same result using Python Semantic Release v8, the upload-to-gh-release <https://github.com/python-semantic-release/upload-to-gh-release> GitHub Action, and the pypa/gh-action-pypi-publish <https://github.com/pypa/gh-action-pypi-publish> GitHub Action:

---
name: Semantic Release

on:
  push:
    branches:
      - main

jobs:
  release:
    runs-on: ubuntu-latest
    concurrency: release
    permissions:
      id-token: write

    steps:
    - uses: actions/checkout@COMMIT_HASH  # v3.0.0
      with:
        fetch-depth: 0

    # This action uses Python Semantic Release v8
    - name: Python Semantic Release
      id: release
      uses: python-semantic-release/python-semantic-release@COMMIT_HASH  # v8.0.0
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}

    - name: Publish package distributions to PyPI
      uses: pypa/gh-action-pypi-publish@COMMIT_HASH  # v1.0.0
      # NOTE: DO NOT wrap the conditional in ${{ }} as it will always evaluate to true.
      # See https://github.com/actions/runner/issues/1173
      if: steps.release.outputs.released == 'true'

    - name: Publish package distributions to GitHub Releases
      uses: python-semantic-release/upload-to-gh-release@COMMIT_HASH  # v8.0.0
      if: steps.release.outputs.released == 'true'
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}

Removal of pypi_token, repository_username and repository_password inputs

since the library no longer supports publishing to PyPI, the pypi_token, repository_username and repository_password inputs of the GitHub action have all been removed. ๐Ÿ™ˆ for how to publish to PyPI, peep the section just above โ€” you do it with the official GitHub Action from the Python Packaging Authority (PyPA).

Rename additional_options to root_options

because the purposes of the cmd-version and cmd-publish commands have changed, the GitHub action now runs both commands in sequence. so for that reason, and because the CLI usage has changed, additional_options has been renamed to root_options โ€” which just reflects the fact that the options belong to the main cmd-main command group. honest naming, we love to see it. ๐Ÿ’…

โŒจ๏ธ Commands

Repurposing of version and publish commands

Python Semantic Release's main character purpose is enabling automation of correct semantic versioning for software projects. and over the years, that automation got extended into other stuff: building/publishing the project and its artifacts to artefact repositories, creating releases in remote version control systems, and writing changelogs. busy queen. ๐Ÿ‘‘

but in Python Semantic Release <8.0.0, the publish command was a one-stop-shop that did every single piece of automation provided. that's changed now, bestie:

  • the version command now handles determining the next version, applying the changes to the project metadata according to the configuration, writing a changelog, and committing/pushing changes to the remote Git repository. it also handles creating a release in the remote VCS. it does not publish software artifacts to remote repositories such as PyPI โ€” and the rationale is simply that under the hood, Python Semantic Release used twine <https://twine.readthedocs.io/en/stable> to upload artifacts to package indexes such as PyPI, and it's recommended to use twine directly via the command-line.
  • the publish command now handles publishing software artifacts to releases in the remote version control system.

straight from the twine documentation <https://twine.readthedocs.io/en/stable/contributing.html#architectural-overview>:

Twine is a command-line tool for interacting with PyPI securely over HTTPS.

and as a result, Python Semantic Release no longer depends on twine internals. slay. โœจ

so to get a similar flow of logic like

  1. Determine the next version
  2. Write this version to the configured metadata locations
  3. Write the changelog
  4. Push the changes to the metadata and changelog to the remote repository
  5. Create a release in the remote version control system
  6. Build a wheel
  7. Publish the wheel to PyPI
  8. Publish the distribution artifacts to the release in the remote VCS

you should run:

semantic-release version
twine upload dist/*  # or whichever path your distributions are placed in
semantic-release publish

with steps 1-6 being handled by the cmd-version command, step 7 left to the developer to handle, and lastly step 8 handled by the cmd-publish command. ๐Ÿงš

Removal of -D/--define command-line option

it is no longer possible to override arbitrary configuration values using the -D/--define option. ๐Ÿ˜ญ instead, you should provide the appropriate values via a configuration file using cmd-main-option-config, or via the available command-line options.

This is the part that makes everything simpler, and bestie, that matters โ€” it means the command-line option parsing gets way less error-prone, and lowkey that area used to cause actual real-world issues (like #600 over at https://github.com/python-semantic-release/python-semantic-release/issues/600) whenever people tried to do overrides straight on the command line. ๐Ÿ˜ญ See, some of the config values Python Semantic Release expects are full-on complex data types โ€” lists, nested structures, the whole situation โ€” and hand-typing those as plain command-line options is tedious and, ngl, extremely easy to get wrong. ๐Ÿ™ˆ


๐Ÿšฎ Removal of CI verifications

Before v8, Python Semantic Release would do some prerequisite verification of environment variables before making any version changes via the publish command. But it's just not feasible for Python Semantic Release to fully verify every possible CI environment, and honestly these checks only fired if certain environment variables were set โ€” they would never fail locally. ๐Ÿ™ƒ

Those checks used to raise :py:class:semantic_release.CiVerificationError, and they were literally the only place that custom exception was used. So yeah โ€” that exception got removed from Python Semantic Release in v8 too. ๐Ÿ—‘๏ธ

If you were relying on that functionality, it's recommended that you add the following shell commands before invoking semantic-release, so you can verify your environment yourself:

Note: In the following, $RELEASE_BRANCH refers to the git branch against which you run your releases using Python Semantic Release. You will need to ensure it is set properly (e.g. via export RELEASE_BRANCH=main and/or replace the variable with the branch name you want to verify the CI environment for.

๐Ÿš‚ Travis

Condition: environment variable TRAVIS=true

Replacement:

if ! [[
      $TRAVIS_BRANCH == $RELEASE_BRANCH  && \
      $TRAVIS_PULL_REQUEST == 'false'
    ]]; then
  exit 1
fi

๐Ÿšฆ Semaphore

Condition: environment variable SEMAPHORE=true

Replacement:

if ! [[
        $BRANCH_NAME == $RELEASE_BRANCH && \
        $SEMAPHORE_THREAD_RESULT != 'failed' && \
        -n $PULL_REQUEST_NUMBER
    ]]; then
  exit 1
fi

๐Ÿฃ Frigg

Condition: environment variable FRIGG=true

Replacement:

if ! [[
      $FRIGG_BUILD_BRANCH == $RELEASE_BRANCH && \
      -n $FRIGG_PULL_REQUEST
    ]]; then
  exit 1
fi

โญ• Circle CI

Condition: environment variable CIRCLECI=true

Replacement:

if ! [[
      $CIRCLE_BRANCH == $RELEASE_BRANCH && \
      -n $CI_PULL_REQUEST
    ]]; then
  exit 1
fi

๐ŸฆŠ GitLab CI

Condition: environment variable GITLAB_CI=true

Replacement:

if ! [[ $CI_COMMIT_REF_NAME == $RELEASE_BRANCH ]]; then
  exit 1
fi

Condition: environment variable BITBUCKET_BUILD_NUMBER is set

Replacement:

if ! [[
      $BITBUCKET_BRANCH == $RELEASE_BRANCH && \
      -n $BITBUCKET_PR_ID
    ]]; then
  exit 1
fi

๐Ÿชฃ Jenkins

Condition: environment variable JENKINS_URL is set

Replacement:

if [[ -z $BRANCH_NAME ]]; then
  BRANCH_NAME=$BRANCH_NAME
elif [[ -z $GIT_BRANCH ]]; then
  BRANCH_NAME=$GIT_BRANCH
fi

if ! [[
      $BRANCH_NAME == $RELEASE_BRANCH && \
      -n $CHANGE_ID
    ]]; then
  exit 1
fi

๐Ÿงฑ Removal of Build Status Checking

Prior to v8, Python Semantic Release had a configuration option called check_build_status, which would try to block a release from happening if it could tell that the matching build pipeline was failing. โœ‹ For basically the same reasons that motivated the removal of CI Checks, this feature is gone too, bestie. ๐Ÿ’€

If you're using this feature in Python Semantic Release v7, these bash commands will replace the functionality, and you can drop them into your pipeline. You'll need jq and curl installed to run them; they're easy to install through your system's package manager โ€” for example on Ubuntu:

sudo apt update && sudo apt upgrade
sudo apt install -y curl jq

On Windows, you can refer to the installation guide for jq at https://jqlang.github.io/jq/download/, and if curl isn't already installed, you can grab it from the curl website at https://curl.se/


๐Ÿ™ GitHub

export RESP="$(
  curl \
     -H "Authorization: token $GITHUB_TOKEN" \
     -fSsL https://$GITHUB_API_DOMAIN/repos/$REPO_OWNER/$REPO_NAME/commits/$(git rev-parse HEAD)/status || exit 1
)"

if [ $(jq -r '.state' <<< "$RESP") != "success" ]; then
   echo "Build status is not success" >&2
   exit 1
fi

Note that $GITHUB_API_DOMAIN is typically api.github.com unless you are using GitHub Enterprise with a custom domain name. ๐ŸŒ


๐Ÿต Gitea

export RESP="$(
  curl \
     -H "Authorization: token $GITEA_TOKEN" \
     -fSsL https://$GITEA_DOMAIN/repos/$REPO_OWNER/$REPO_NAME/statuses/$(git rev-parse HEAD) || exit 1
)"

if [ $(jq -r '.state' <<< "$RESP") != "success" ]; then
   echo "Build status is not success" >&2
   exit 1
fi

๐ŸฆŠ Gitlab

export RESP="$(
  curl \
     -H "Authorization: token $GITLAB_TOKEN" \
     -fSsL https://$GITLAB_DOMAIN/api/v4/projects/$PROJECT_ID/repository/commits/$(git rev-parse HEAD)/statuses
)"

for line in $(jq -r '.[] | [.name, .status, .allow_failure] | join("|")' <<<"$RESP"); do
  IFS="|" read -r job_name job_status allow_failure <<<"$line"

  if [ "$job_status" == "pending" ]; then
     echo "job $job_name is pending" >&2
     exit 1
  elif [ "$job_status" == "failed" ] && [ ! "$allow_failure" == "true" ]; then
     echo "job $job_name failed" >&2
     exit 1
  fi
done

๐ŸŒฟ Multibranch releases

Prior to v8, Python Semantic Release would do a git checkout to hop over to your configured release branch and figure out whether a release needed to be made. In v8? Nope, changed. ๐Ÿซถ You now have to manually check out the branch you want to release against, and if you want to create releases against that branch, you also need to make sure it belongs to a release group (<#multibranch-releases-configuring>).


๐Ÿ“œ changelog command

A brand new option has appeared: cmd-changelog-option-post-to-release-tag. โœจ If you skip this argument on the command line, the changelog rendering process โ€” described in more detail at Custom Changelogs (<#changelog-templates-template-rendering>) โ€” will still be triggered, but the new changelog won't get posted to any release. If you do use this new command-line option, it should be set to a tag within the remote that has a corresponding release. For example, to update the changelog and post it to the release corresponding to the tag v1.1.4, you'd run:

semantic-release changelog --post-to-release-tag v1.1.4

๐ŸŽจ Changelog customization

A bunch of options related to customizing the changelog have been removed. Why? Because Python Semantic Release now supports authoring a completely custom Jinja template (Jinja lives at https://jinja.palletsprojects.com/en/3.1.x/) that holds the contents of your changelog. ๐ŸŽ€ Historically, the number of options added to Python Semantic Release just to allow this customization grew like crazy; now it uses templates instead, which fully opens up customizing how your changelog looks. ๐Ÿ’…


โš™๏ธ Configuration

The configuration structure has been completely reworked, so please read Configuration (<#configuration>) carefully while you're upgrading to v8+. ๐Ÿ‘‘ That said, a few common pitfalls and potential sources of confusion are summarized right here.


๐Ÿšซ setup.cfg is no longer supported

Python Semantic Release no longer supports configuration via setup.cfg. Reason being: the Python ecosystem is centering around pyproject.toml as the universal tool and project configuration file, and TOML allows expressions via configuration โ€” like the mechanism for declaring configuration via environment variables โ€” which would introduce so much more complexity to support in the otherwise equivalent ini-format configuration. ๐Ÿ˜ตโ€๐Ÿ’ซ

You can use cmd-generate-config to generate new-format configuration that can be added to pyproject.toml, and then adjust the default settings to your needs.

Warning: If you don't already have a pyproject.toml configuration file, pip can change its behavior once you add one, as a result of PEP-517 (https://peps.python.org/pep-0517/#evolutionary-notes). If you find that this breaks your packaging, you can add your Python Semantic Release configuration to a separate file such as semantic-release.toml, and use the --config option to reference this alternative configuration file. More detail about this issue can be found in this pip issue at https://github.com/pypa/pip/issues/8437#issuecomment-805313362.

๐Ÿงฉ Commit parser options

Options such as major_emoji, parser_angular_patch_types or parser_angular_default_level_bump have been removed. ๐Ÿ—‘๏ธ Instead, they've been replaced with a single set of recognized commit parser options: allowed_tags, major_tags, minor_tags, and patch_tags โ€” though how you interpret those is up to whichever specific parsers are in use. You can read more detail about using commit parser options in commit_parser_options (<#config-commit-parser-options>), and if you need to parse multiple commit styles for a single project, it's recommended that you create a parser following Custom Parsers (<#commit-parser-custom-parser>) that's tailored to your project's specific needs. ๐Ÿ› ๏ธ


๐Ÿท๏ธ version_variable

This option has been renamed to version_variables (<#config-version-variables>) because it refers to a list of variables which can be updated. ๐Ÿ“


๐Ÿ” version_pattern

This option has been removed. It's recommended to use an alternative tool to perform substitution using arbitrary regular expressions, such as sed. You can always use Python Semantic Release to identify the next version to be created for a project and store it in an environment variable like so:

export VERSION=$(semantic-release version --print)

๐Ÿ… version_toml

This option will no longer accept a string or comma-separated string of version locations to be updated in TOML files. Instead, you must supply a List[str]. ๐Ÿซก For existing configurations using a single location in this option, you can simply wrap the value in []:

# Python Semantic Release v7 configuration
[tool.semantic_release]
version_toml = "pyproject.toml:tool.poetry.version"

# Python Semantic Release v8 configuration
[tool.semantic_release]
version_toml = ["pyproject.toml:tool.poetry.version"]

๐Ÿ’– tag_format

okay so this one does literally the same thing it did back in Python Semantic Release before v8 โ€” same vibes, same effect. BUT โœจ plot twist โœจ: Python Semantic Release will now verify that it has a {version} format key and will straight up raise an error if it doesn't. so like, don't forget your {version} bestie, it's not optional anymore ๐Ÿ˜ค

๐Ÿ’– upload_to_release

this one got a glow-up and a new name, she's now called upload_to_vcs_release (see #config-publish-upload-to-vcs-release). same girl, new label ๐Ÿ’…

๐Ÿงš Custom Commit Parsers

so back in the day, a custom commit parser had to tick all these boxes:

  • it had to be import-able from the virtual environment where the semantic-release is run ๐Ÿ
  • it had to be a function that takes the commit message as its only argument and returns a semantic_release.history.parser_helpers.ParsedCommit if the commit parses successfully, or raises a semantic_release.UnknownCommitMessageStyleError if parsing is unsuccessful ๐Ÿšจ

and like โ€” you can still implement custom commit parsers, that dream is not dead ๐Ÿซถ but the interface for doing it has been modified, with stronger support for Python type annotations and broader input handed to the parser, so you can capture way more info from each commit โ€” like the commit's date and author, if that's your thing. a full guide to implementing a custom commit parser lives at Custom Parsers (see #commit-parser-custom-parser). go off, king ๐Ÿ‘‘

๐Ÿ”ง Troubleshooting

  • check your configuration file for Configuration (see #configuration) ๐Ÿ“
  • check your Git tags match your tag_format (see #config-tag-format); tags using other formats are ignored during calculation of the next version. so if your tags look weird and nothing's happening, that's lowkey why bestie ๐Ÿ˜ญ

๐Ÿ”Š Increasing Verbosity

if you're having trouble with Python Semantic Release, or you just wanna see extra info about the actions it's taking, you can use the top-level cmd-main-option-verbosity option โœจ. you can supply it multiple times to crank up the logging verbosity of the cmd-main command or any of its subcommands while they execute. supply it as many times as you like โ€” but supplying more than twice has no effect, so don't waste the keystrokes bestie ๐Ÿ’€

  • supply cmd-main-option-verbosity once for INFO output
  • supply it twice for DEBUG

for example:

semantic-release -vv version --print
Note: the cmd-main-option-verbosity option must be supplied to the top-level semantic-release command, before the name of any sub-command. not after. before. iykyk ๐Ÿซถ
Warning: the volume of logs when using DEBUG verbosity may be significantly increased, compared to INFO or the default WARNING, and as a result executing commands with semantic-release may be significantly slower than when using DEBUG. so like... buckle up ๐Ÿข๐Ÿ’€
Note: the provided GitHub action sets the verbosity level to INFO by default. cute of her tbh ๐ŸŽ€

๐Ÿ“ฆ src

๐Ÿ semantic_release package

Python Semantic Release

semantic_release.setup_hook(argv: list[str]) -> None

a hook to be used in setup.py to enable python setup.py publish ๐Ÿ”ฅ

Parameters

  • argv โ€” sys.argv

๐Ÿ“‚ Subpackages

๐Ÿ“‚ semantic_release.changelog package

๐Ÿ“‚ Submodules

๐Ÿ“‚ semantic_release.changelog.context module

class semantic_release.changelog.context.ChangelogContext(repo_name: 'str', repo_owner: 'str', hvcs_type: 'str', history: 'ReleaseHistory', changelog_mode: "Literal['update', 'init']", prev_changelog_file: 'str', changelog_insertion_flag: 'str', mask_initial_release: 'bool', filters: 'tuple[Callable[..., Any], ...]' = ())

Bases: object

  • bind_to_environment(env: Environment) -> Environment
  • changelog_insertion_flag: str
  • changelog_mode: Literal['update', 'init']
  • filters: tuple[Callable[..., Any], ...] = ()
  • history: ReleaseHistory (see #semantic_release.changelog.release_history.ReleaseHistory)
  • hvcs_type: str
  • mask_initial_release: bool
  • prev_changelog_file: str
  • repo_name: str
  • repo_owner: str

class semantic_release.changelog.context.ChangelogMode(*values)

Bases: Enum

  • INIT = 'init'
  • UPDATE = 'update'

class semantic_release.changelog.context.ReleaseNotesContext(repo_name: 'str', repo_owner: 'str', hvcs_type: 'str', version: 'Version', release: 'Release', mask_initial_release: 'bool', license_name: 'str', filters: 'tuple[Callable[..., Any], ...]' = ())

Bases: object

  • bind_to_environment(env: Environment) -> Environment
  • filters: tuple[Callable[..., Any], ...] = ()
  • hvcs_type: str
  • license_name: str
  • mask_initial_release: bool
  • release: Release (see #semantic_release.changelog.release_history.Release)
  • repo_name: str
  • repo_owner: str
  • version: Version (see #semantic_release.version.version.Version)

semantic_release.changelog.context.autofit_text_width(text: str, maxwidth: int = 100, indent_size: int = 0) -> str

format the description text to fit within a specified width ๐Ÿ“โœจ

semantic_release.changelog.context.convert_md_to_rst(md_content: str) -> str

semantic_release.changelog.context.create_pypi_url(package_name: str, version: str = '') -> str

semantic_release.changelog.context.make_changelog_context(hvcs_client: HvcsBase, release_history: ReleaseHistory, mode: ChangelogMode, prev_changelog_file: Path, insertion_flag: str, mask_initial_release: bool) -> ChangelogContext

(that ReleaseHistory is #semantic_release.changelog.release_history.ReleaseHistory, ChangelogMode is #semantic_release.changelog.context.ChangelogMode, and the return ChangelogContext is #semantic_release.changelog.context.ChangelogContext ๐Ÿ’–)

semantic_release.changelog.context.read_file(filepath: str) -> str


๐Ÿ“‚ semantic_release.changelog.release_history module

class semantic_release.changelog.release_history.Release

Bases: TypedDict

  • committer: Actor
  • elements: dict[str, list[ParseResult]]
  • tagged_date: datetime
  • tagger: Actor
  • version: Version (see #semantic_release.version.version.Version)

class semantic_release.changelog.release_history.ReleaseHistory(unreleased: dict[str, list[ParseResult]], released: dict[Version, Release])

Bases: object

  • classmethod from_git_history(repo: Repo, translator: VersionTranslator, commit_parser: CommitParser[ParseResult, ParserOptions], exclude_commit_patterns: Iterable[Pattern[str]] = ()) -> ReleaseHistory (see #semantic_release.changelog.release_history.ReleaseHistory)
  • release(version: Version, tagger: Actor, committer: Actor, tagged_date: datetime) -> ReleaseHistory (see #semantic_release.changelog.release_history.ReleaseHistory)

(wanna know where all those types hang out? #semantic_release.version.version.Version for Version, #semantic_release.version.translator.VersionTranslator for VersionTranslator, #semantic_release.changelog.release_history.Release for Release, and #semantic_release.changelog.release_history.ReleaseHistory for the whole return value ๐Ÿฆ‹)


๐Ÿ“‚ semantic_release.changelog.template module

class semantic_release.changelog.template.ComplexDirectorySandboxedEnvironment(*args: Any, **kwargs: Any)

Bases: SandboxedEnvironment

  • join_path(template: str, parent: str) -> str

add support for complex directory structures in the template directory ๐Ÿ—‚๏ธโœจ

this method overrides the default functionality of the SandboxedEnvironment where all 'include' keywords expect to be in the same directory as the calling template โ€” however this is unintuitive when using a complex directory structure ๐Ÿ™ˆ

this override simulates the changing of directories when you include the template from a child directory. when the child then includes a template, it will make the path relative to the child directory rather than the top level template directory. it's giving actual filesystem behaviour, and honestly? iconic ๐Ÿ‘‘


semantic_release.changelog.template.environment(template_dir: Path | str = '.', block_start_string: str = '{%', block_end_string: str = '%}', variable_start_string: str = '{{', variable_end_string: str = '}}', comment_start_string: str = '{#', comment_end_string: str = '#}', line_statement_prefix: str | None = None, line_comment_prefix: str | None = None, trim_blocks: bool = False, lstrip_blocks: bool = False, newline_sequence: Literal['\n', '\r', '\r\n'] = '\n', keep_trailing_newline: bool = False, extensions: Iterable[str] = (), autoescape: bool | str = True) -> SandboxedEnvironment

create a jinja2.sandbox.SandboxedEnvironment with certain parameter restrictions ๐Ÿ”’

for example the Loader is fixed to FileSystemLoader, although the searchpath is configurable ๐Ÿ‘Œ

autoescape can be a string, in which case it should follow the convention module:attr โ€” in this instance it will be dynamically imported. see <https://jinja.palletsprojects.com/en/3.1.x/api/#jinja2.Environment> for full parameter descriptions ๐Ÿ“š


semantic_release.changelog.template.recursive_render(template_dir: Path, environment: Environment, _root_dir: str | os.PathLike[str] = '.') -> list[str]


๐Ÿ semantic_release.cli package

๐Ÿ“‚ Subpackages

๐Ÿ“‚ semantic_release.cli.commands package

๐Ÿ“‚ Submodules

๐Ÿ“‚ semantic_release.cli.commands.changelog module

semantic_release.cli.commands.changelog.get_license_name_for_release(tag_name: str, project_root: Path) -> str

semantic_release.cli.commands.changelog.post_release_notes(release_tag: str, release_notes: str, prerelease: bool, hvcs_client: RemoteHvcsBase, noop: bool = False) -> None

(that RemoteHvcsBase is #semantic_release.hvcs.remote_hvcs_base.RemoteHvcsBase ๐Ÿซถ)


๐Ÿ“‚ semantic_release.cli.commands.generate_config module


๐Ÿ“‚ semantic_release.cli.commands.main module

class semantic_release.cli.commands.main.Cli(name: str | None = None, commands: MutableMapping[str, Command] | Sequence[Command] | None = None, **attrs: Any)

Bases: Group

Root Group for the semantic-release CLI ๐Ÿ‘‘โœจ

class SubCmds(*values)

Bases: Enum

Subcommand import definitions ๐Ÿ“ฅ

  • CHANGELOG = 'semantic_release.cli.commands.changelog'
  • GENERATE_CONFIG = 'semantic_release.cli.commands.generate_config'
  • PUBLISH = 'semantic_release.cli.commands.publish'
  • VERSION = 'semantic_release.cli.commands.version'
  • get_command(ctx: Context, cmd_name: str) -> Command | None

given a context and a command name, this returns a Command object if it exists, or returns None ๐Ÿซฅ

  • list_commands(ctx: Context) -> list[str]

returns a list of subcommand names in the order they should appear ๐Ÿ“‹


๐Ÿ“‚ semantic_release.cli.commands.publish module

semantic_release.cli.commands.publish.publish_distributions(tag: str, hvcs_client: RemoteHvcsBase, dist_glob_patterns: tuple[str, ...], noop: bool = False) -> None

(and yep, RemoteHvcsBase is still #semantic_release.hvcs.remote_hvcs_base.RemoteHvcsBase ๐Ÿ’œ)

๐Ÿ”ง semantic_release.cli.commands.version module

apply_version_to_source_files

semantic_release.cli.commands.version.apply_version_to_source_files(repo_dir: Path, version_declarations: Sequence[IVersionReplacer], version: Version, noop: bool = False) -> list[str]

bestie this one goes in and rewrites ur version declarations in the source files, and hands u back a list[str] like it's nothing ๐Ÿ’…

build_distributions

semantic_release.cli.commands.version.build_distributions(build_command: str | None, build_command_env: Mapping[str, str] | None = None, noop: bool = False) -> None

Run the build command to build the distributions. fr fr that's the whole job.

Parameters ๐ŸŒธ

  • build_command โ€” The build command to run.
  • build_command_env โ€” The environment variables to use when running the build command.
  • noop โ€” Whether or not to run the build command.

Raises ๐Ÿ˜ญ BuildDistributionsError: if the build command fails

ngl if that build command faceplants, it's throwing BuildDistributionsError at u. no cap.

get_windows_env

semantic_release.cli.commands.version.get_windows_env() -> Mapping[str, str | None]

is_forced_prerelease

semantic_release.cli.commands.version.is_forced_prerelease(as_prerelease: bool, forced_level_bump: LevelBump | None, prerelease: bool) -> bool

Determine if this release is forced to have prerelease on/off. Here's the tea ๐Ÿซ–: if force_prerelease is set then yes. Otherwise if we are forcing a specific level bump without force_prerelease, it's False. Otherwise (when force_level is None) use the value of prerelease.

is_windows

semantic_release.cli.commands.version.is_windows() -> bool

last_released

semantic_release.cli.commands.version.last_released(repo_dir: Path, tag_format: str, add_partial_tags: bool = False) -> tuple[Tag, Version] | None

shell

semantic_release.cli.commands.version.shell(cmd: str, *, env: Mapping[str, str] | None = None, check: bool = True) -> subprocess.CompletedProcess

version_from_forced_level

semantic_release.cli.commands.version.version_from_forced_level(repo_dir: Path, forced_level_bump: LevelBump, translator: VersionTranslator) -> Version

๐Ÿ“ฆ Submodules

semantic_release.cli.changelog_writer module โœจ

apply_user_changelog_template_directory

semantic_release.cli.changelog_writer.apply_user_changelog_template_directory(template_dir: Path, environment: Environment, destination_dir: Path, noop: bool = False) -> list[str]

generate_release_notes

semantic_release.cli.changelog_writer.generate_release_notes(hvcs_client: HvcsBase, release: Release, template_dir: Path, history: ReleaseHistory, style: str, mask_initial_release: bool, license_name: str = '') -> str

get_default_tpl_dir

semantic_release.cli.changelog_writer.get_default_tpl_dir(style: str, sub_dir: str | None = None) -> Path

render_default_changelog_file

semantic_release.cli.changelog_writer.render_default_changelog_file(output_format: ChangelogOutputFormat, changelog_context: ChangelogContext, changelog_style: str) -> str

render_release_notes

semantic_release.cli.changelog_writer.render_release_notes(release_notes_template_file: str, template_env: Environment) -> str

write_changelog_files

semantic_release.cli.changelog_writer.write_changelog_files(runtime_ctx: RuntimeContext, release_history: ReleaseHistory, hvcs_client: HvcsBase, noop: bool = False) -> list[str]

write_default_changelog

semantic_release.cli.changelog_writer.write_default_changelog(changelog_file: Path, destination_dir: Path, output_format: ChangelogOutputFormat, changelog_context: ChangelogContext, changelog_style: str, noop: bool = False) -> str

semantic_release.cli.cli_context module ๐ŸŽ€

class semantic_release.cli.cli_context.CliContextObj

class semantic_release.cli.cli_context.CliContextObj(ctx: click.Context, logger: logging.Logger, global_opts: GlobalCommandLineOptions)

Bases: object

property raw_config: RawConfig โ€” slay, there it is.

property runtime_ctx: RuntimeContext โ€” Lazy load the runtime context. This is done to avoid configuration loading when the command is not run. This is useful for commands like --help and --version. the way it just waits until it's actually needed? iconic resource management ๐Ÿ’–


semantic_release.cli.config module ๐ŸŒˆ

class semantic_release.cli.config.BranchConfig

class semantic_release.cli.config.BranchConfig(*, match: str = '(main|master)', prerelease_token: str = 'rc', prerelease: bool = False)

Bases: BaseModel

  • match: str
  • model_config = {} โ€” Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
  • prerelease: bool
  • prerelease_token: str
  • classmethod validate_match(match: str) -> str

class semantic_release.cli.config.ChangelogConfig

class semantic_release.cli.config.ChangelogConfig(*, changelog_file: str = '', default_templates: DefaultChangelogTemplatesConfig = DefaultChangelogTemplatesConfig(changelog_file='CHANGELOG.md', output_format=<ChangelogOutputFormat.MARKDOWN: 'md'>, mask_initial_release=True), environment: ChangelogEnvironmentConfig = ChangelogEnvironmentConfig(block_start_string='{%', block_end_string='%}', variable_start_string='{{', variable_end_string='}}', comment_start_string='{#', comment_end_string='#}', line_statement_prefix=None, line_comment_prefix=None, trim_blocks=False, lstrip_blocks=False, newline_sequence='\n', keep_trailing_newline=False, extensions=(), autoescape=False), exclude_commit_patterns: Tuple[str, ...]=(), mode: ChangelogMode = ChangelogMode.UPDATE, insertion_flag: str = '', template_dir: str = 'templates')

Bases: BaseModel

  • changelog_file: str โ€” Deprecated! Moved to 'default_templates.changelog_file' ๐Ÿ‘€ pls stop using this one bestie, it's giving legacy.
  • classmethod changelog_file_deprecation_warning(val: str) -> str
  • default_templates: DefaultChangelogTemplatesConfig
  • environment: ChangelogEnvironmentConfig
  • exclude_commit_patterns: Tuple[str, ...]
  • insertion_flag: str
  • load_default_insertion_flag_on_missing() -> Self
  • mode: ChangelogMode
  • model_config = {} โ€” Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
  • move_changelog_file() -> Self
  • template_dir: str
  • classmethod validate_match(patterns: Tuple[str, ...]) -> Tuple[str, ...]

class semantic_release.cli.config.ChangelogEnvironmentConfig

class semantic_release.cli.config.ChangelogEnvironmentConfig(*, block_start_string: str = '{%', block_end_string: str = '%}', variable_start_string: str = '{{', variable_end_string: str = '}}', comment_start_string: str = '{#', comment_end_string: str = '#}', line_statement_prefix: str | None = None, line_comment_prefix: str | None = None, trim_blocks: bool = False, lstrip_blocks: bool = False, newline_sequence: Literal['\n', '\r', '\r\n'] = '\n', keep_trailing_newline: bool = False, extensions: Tuple[str, ...] = (), autoescape: bool | str = False)

Bases: BaseModel

  • autoescape: bool | str
  • block_end_string: str
  • block_start_string: str
  • comment_end_string: str
  • comment_start_string: str
  • extensions: Tuple[str, ...]
  • keep_trailing_newline: bool
  • line_comment_prefix: str | None
  • line_statement_prefix: str | None
  • lstrip_blocks: bool
  • model_config = {} โ€” Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
  • newline_sequence: Literal['\n', '\r', '\r\n']
  • trim_blocks: bool
  • variable_end_string: str
  • variable_start_string: str

class semantic_release.cli.config.ChangelogOutputFormat

class semantic_release.cli.config.ChangelogOutputFormat(*values)

Bases: str, Enum

Supported changelog output formats when using the default templates. ๐Ÿ’…

  • MARKDOWN = 'md'
  • NONE = ''
  • RESTRUCTURED_TEXT = 'rst'

class semantic_release.cli.config.DefaultChangelogTemplatesConfig

class semantic_release.cli.config.DefaultChangelogTemplatesConfig(*, changelog_file: str = 'CHANGELOG.md', output_format: ChangelogOutputFormat = ChangelogOutputFormat.NONE, mask_initial_release: bool = True)

Bases: BaseModel

  • changelog_file: str
  • interpret_output_format() -> Self
  • mask_initial_release: bool
  • model_config = {} โ€” Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
  • output_format: ChangelogOutputFormat

class semantic_release.cli.config.EnvConfigVar

class semantic_release.cli.config.EnvConfigVar(*, env: str, default: str | None = None, default_env: str | None = None)

Bases: BaseModel

  • default: str | None
  • default_env: str | None
  • env: str
  • getvalue() -> str | None
  • model_config = {} โ€” Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class semantic_release.cli.config.GlobalCommandLineOptions

class semantic_release.cli.config.GlobalCommandLineOptions(noop: bool = False, verbosity: int = 0, config_file: str = 'pyproject.toml', strict: bool = False)

Bases: object

A dataclass to hold all the command line options that should be set in the RuntimeContext. look at it. holding all the options. carrying the whole team on its back ๐Ÿซถ

  • config_file: str = 'pyproject.toml'
  • noop: bool = False
  • strict: bool = False
  • verbosity: int = 0

class semantic_release.cli.config.HvcsClient

class semantic_release.cli.config.HvcsClient(*values)

Bases: str, Enum

  • BITBUCKET = 'bitbucket'
  • GITEA = 'gitea'
  • GITHUB = 'github'
  • GITLAB = 'gitlab'

class semantic_release.cli.config.PublishConfig

class semantic_release.cli.config.PublishConfig(*, dist_glob_patterns: Tuple[str, ...] = ('dist/*',), upload_to_vcs_release: bool = True)

Bases: BaseModel

  • dist_glob_patterns: Tuple[str, ...]
  • model_config = {} โ€” Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
  • upload_to_vcs_release: bool
class semantic_release.cli.config.RawConfig(*, assets: List[str] = [], branches: Dict[str, ~semantic_release.cli.config.BranchConfig]={'main': BranchConfig(match='(main|master)', prerelease_token='rc', prerelease=False)}, build_command: str | None = None, build_command_env: List[str] = [], changelog: ChangelogConfig <#semantic_release.cli.config.ChangelogConfig> = ChangelogConfig(changelog_file='', default_templates=DefaultChangelogTemplatesConfig(changelog_file='CHANGELOG.md', output_format=<ChangelogOutputFormat.MARKDOWN: 'md'>, mask_initial_release=True), environment=ChangelogEnvironmentConfig(block_start_string='{%', block_end_string='%}', variable_start_string='{{', variable_end_string='}}', comment_start_string='{#', comment_end_string='#}', line_statement_prefix=None, line_comment_prefix=None, trim_blocks=False, lstrip_blocks=False, newline_sequence='\n', keep_trailing_newline=False, extensions=(), autoescape=False), exclude_commit_patterns=(), mode=<ChangelogMode.UPDATE: 'update'>, insertion_flag='<!-- version list -->', template_dir='templates'), commit_author: EnvConfigVar <#semantic_release.cli.config.EnvConfigVar> | str = EnvConfigVar(env='GIT_COMMIT_AUTHOR', default='semantic-release <semantic-release>', default_env=None), commit_message: str = '{version}\n\nAutomatically generated by python-semantic-release', commit_parser: Annotated[str, ~annotated_types.MinLen(min_length=1)] = 'conventional', commit_parser_options: Dict[str, ~typing.Any]={}, logging_use_named_masks: bool = False, major_on_zero: bool = True, allow_zero_version: bool = False, repo_dir: Path = '.', remote: RemoteConfig <#semantic_release.cli.config.RemoteConfig> = RemoteConfig(name='origin', token=None, url=None, type=<HvcsClient.GITHUB: 'github'>, domain=None, api_domain=None, ignore_token_for_push=False, insecure=False), no_git_verify: bool = False, tag_format: str = 'v{version}', add_partial_tags: bool = False, publish: PublishConfig <#semantic_release.cli.config.PublishConfig> = PublishConfig(dist_glob_patterns=('dist/*', ), upload_to_vcs_release=True), version_toml: Tuple[str, ...] | None=None, version_variables: Tuple[str, ...] | None=None)

Bestie. Look at that signature. It's giving everything, everywhere, all at once ๐Ÿ’… and honestly? We respect it.

Bases: BaseModel

So here's the vibe of RawConfig โ€” it's the raw, straight-off-the-disk pile of config, before anything gets resolved or dressed up. And it comes with all of this:

  • add_partial_tags: bool
  • allow_zero_version: bool
  • classmethod angular_commit_parser_deprecation_warning(val: str) -> str
  • assets: List[str]
  • branches: Dict[str, BranchConfig <#semantic_release.cli.config.BranchConfig>]
  • build_command: str | None
  • build_command_env: List[str]
  • changelog: ChangelogConfig <#semantic_release.cli.config.ChangelogConfig>
  • commit_author: MaybeFromEnv
  • commit_message: str
  • commit_parser: NonEmptyString
  • commit_parser_options: Dict[str, Any]
  • classmethod convert_str_to_path(value: Any) -> Path
  • logging_use_named_masks: bool
  • major_on_zero: bool
  • model_config = {} โ€” this one is literally the config for the model itself, and it should be a dictionary that conforms to [ConfigDict][pydantic.config.ConfigDict]. Nested config for your config, iykyk ๐ŸŒ€
  • no_git_verify: bool
  • publish: PublishConfig <#semantic_release.cli.config.PublishConfig>
  • remote: RemoteConfig <#semantic_release.cli.config.RemoteConfig>
  • classmethod remove_whitespace(val: list[str]) -> list[str]
  • repo_dir: Path
  • set_default_opts() -> Self
  • classmethod tag_commit_parser_deprecation_warning(val: str) -> str
  • tag_format: str
  • classmethod verify_git_repo_dir(dir_path: Path) -> Path
  • version_toml: Tuple[str, ...] | None
  • version_variables: Tuple[str, ...] | None

class semantic_release.cli.config.RemoteConfig(*, name: str = 'origin', token: str | None = None, url: str | None = None, type: HvcsClient <#semantic_release.cli.config.HvcsClient> = HvcsClient.GITHUB, domain: str | None = None, api_domain: str | None = None, ignore_token_for_push: bool = False, insecure: bool = False)

Bases: BaseModel

This is the one that knows where your code actually lives, bestie โ€” the remote. Its little family of members:

  • api_domain: str | None
  • check_insecure_flag(url_str: str, field_name: str) -> None
  • check_url_scheme() -> Self
  • domain: str | None
  • ignore_token_for_push: bool
  • insecure: bool
  • model_config = {} โ€” again, "Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict]." Same energy, different model ๐Ÿซถ
  • name: str
  • classmethod resolve_env_vars(val: Any) -> str | None
  • set_default_token() -> Self
  • token: str | None
  • type: HvcsClient <#semantic_release.cli.config.HvcsClient>
  • url: str | None

class semantic_release.cli.config.RuntimeContext(project_metadata: 'dict[str, Any]', repo_dir: 'Path', commit_parser: 'CommitParser[ParseResult, ParserOptions]', version_translator: 'VersionTranslator', major_on_zero: 'bool', allow_zero_version: 'bool', prerelease: 'bool', no_git_verify: 'bool', assets: 'List[str]', commit_author: 'Actor', commit_message: 'str', changelog_excluded_commit_patterns: 'Tuple[Pattern[str], ...]', version_declarations: 'Tuple[IVersionReplacer, ...]', hvcs_client: 'hvcs.HvcsBase', changelog_insertion_flag: 'str', changelog_mask_initial_release: 'bool', changelog_mode: 'ChangelogMode', changelog_file: 'Path', changelog_style: 'str', changelog_output_format: 'ChangelogOutputFormat', ignore_token_for_push: 'bool', template_environment: 'Environment', template_dir: 'Path', build_command: 'Optional[str]', build_command_env: 'dict[str, str]', dist_glob_patterns: 'Tuple[str, ...]', upload_to_vcs_release: 'bool', global_cli_options: 'GlobalCommandLineOptions', masker: 'MaskingFilter')

Bases: object

And this is where the glow-up happens โ€” RawConfig goes in, a fully-formed RuntimeContext comes out the other side. Main character energy fr fr ๐Ÿ‘‘

  • allow_zero_version: bool
  • apply_log_masking(masker: MaskingFilter <#semantic_release.cli.masking_filter.MaskingFilter>) -> MaskingFilter <#semantic_release.cli.masking_filter.MaskingFilter>
  • assets: List[str]
  • build_command: str | None
  • build_command_env: dict[str, str]
  • changelog_excluded_commit_patterns: Tuple[Pattern[str], ...]
  • changelog_file: Path
  • changelog_insertion_flag: str
  • changelog_mask_initial_release: bool
  • changelog_mode: ChangelogMode <#semantic_release.changelog.context.ChangelogMode>
  • changelog_output_format: ChangelogOutputFormat <#semantic_release.cli.config.ChangelogOutputFormat>
  • changelog_style: str
  • commit_author: Actor
  • commit_message: str
  • commit_parser: CommitParser[ParsedCommit <#semantic_release.commit_parser.token.ParsedCommit> | ParseError <#semantic_release.commit_parser.token.ParseError>, ParserOptions]
  • dist_glob_patterns: Tuple[str, ...]
  • classmethod from_raw_config(raw: RawConfig <#semantic_release.cli.config.RawConfig>, global_cli_options: GlobalCommandLineOptions <#semantic_release.cli.config.GlobalCommandLineOptions>) -> RuntimeContext <#semantic_release.cli.config.RuntimeContext> โ€” the transformation moment โœจ
  • global_cli_options: GlobalCommandLineOptions <#semantic_release.cli.config.GlobalCommandLineOptions>
  • hvcs_client: HvcsBase
  • ignore_token_for_push: bool
  • major_on_zero: bool
  • masker: MaskingFilter <#semantic_release.cli.masking_filter.MaskingFilter>
  • no_git_verify: bool
  • prerelease: bool
  • project_metadata: dict[str, Any]
  • repo_dir: Path
  • static resolve_from_env(param: EnvConfigVar <#semantic_release.cli.config.EnvConfigVar> | str | None) -> str | None
  • static select_branch_options(choices: Dict[str, BranchConfig <#semantic_release.cli.config.BranchConfig>], active_branch: str) -> BranchConfig <#semantic_release.cli.config.BranchConfig>
  • template_dir: Path
  • template_environment: Environment
  • upload_to_vcs_release: bool
  • version_declarations: Tuple[IVersionReplacer <#semantic_release.version.declarations.i_version_replacer.IVersionReplacer>, ...]
  • version_translator: VersionTranslator <#semantic_release.version.translator.VersionTranslator>

๐Ÿงฉ semantic_release.cli.const module

That's the whole module, bestie. It's a constants module and it's gonna keep its little secrets ๐Ÿ™ˆ


๐Ÿ™ semantic_release.cli.github_actions_output module

GitHub Actions output, so your workflow can actually hear what happened. Obsessed.

class semantic_release.cli.github_actions_output.PersistenceMode(*values)

Bases: Enum

The two moods of persistence:

  • PERMANENT = 'permanent'
  • TEMPORARY = 'temporary'
class semantic_release.cli.github_actions_output.VersionGitHubActionsOutput(gh_client: Github <#semantic_release.hvcs.github.Github> | None = None, mode: PersistenceMode <#semantic_release.cli.github_actions_output.PersistenceMode> = PersistenceMode.PERMANENT, released: bool | None = None, version: Version <#semantic_release.version.version.Version> | None = None, commit_sha: str | None = None, release_notes: str | None = None, prev_version: Version <#semantic_release.version.version.Version> | None = None)

Bases: object

Everything it's got going on:

  • OUTPUT_ENV_VAR = 'GITHUB_OUTPUT'
  • property commit_sha: str | None
  • property gh_client: Github <#semantic_release.hvcs.github.Github>
  • property is_prerelease: bool | None
  • property prev_version: Version <#semantic_release.version.version.Version> | None
  • property release_notes: str | None
  • property released: bool | None
  • property tag: str | None
  • to_output_text() -> str
  • property version: Version <#semantic_release.version.version.Version> | None
  • write_if_possible(filename: str | None = None) -> None

๐ŸŽญ semantic_release.cli.masking_filter module

The module that keeps your secrets secrets, periodt. ๐Ÿ”’

class semantic_release.cli.masking_filter.MaskingFilter(_use_named_masks: bool = False, **patterns: Iterable[str | re.Pattern[str]])

Bases: Filter

  • REPLACE_STR = '****' โ€” yes, four stars, tape over the goods ๐Ÿ’€
  • add_mask_for(data: str, name: str = 'redacted') -> MaskingFilter <#semantic_release.cli.masking_filter.MaskingFilter>
  • filter(record: LogRecord) -> bool โ€” "Determine if the specified record is to be logged. Returns True if the record should be logged, or False otherwise. If deemed appropriate, the record may be modified in-place." So it's simultaneously the bouncer and the person who edits the guest list ๐Ÿ˜ญ
  • mask(msg: str) -> str

๐Ÿ› ๏ธ semantic_release.cli.util module

Utilities for command-line functionality โ€” the lil toolbox ๐Ÿงฐ

semantic_release.cli.util.indented(msg: str, prefix: str = ' ') -> str

Convenience function for text-formatting for the console. Just here to make things look pretty, which is honestly so valid of it ๐ŸŒธ

the least-indented line of the msg string gets a prefix slapped onto it, and then the rest of msg stays lined up all consistent and cute โ€” irrespective of how deep the indentation went in the Python source code. ๐Ÿ’… like it genuinely does not care how nested the source was, it just aligns. iconic.


semantic_release.cli.util.load_raw_config_file(config_file: Path | str) -> dict[Any, Any]

okay so this one lowkey just loads your raw configuration as a dict from the filename specified by config_filename, and it's gonna try these parsing methods in order:

  1. try to parse with tomli.load first (guessing it's a TOML file ๐Ÿ“)
  2. then try to parse with json.load (guessing it's a JSON file โœจ)
  3. and if neither of those work? raise InvalidConfiguration, periodt ๐Ÿ’€
btw bestie, this function will also raise FileNotFoundError if that gets raised while it's trying to read the configuration file you specified. ๐Ÿ™ˆ don't say I didn't warn you.

semantic_release.cli.util.noop_report(msg: str) -> None

Rich-prints a msg with a standard prefix, so it can report when an action is not being taken because of a "noop" flag. ๐Ÿซถ

semantic_release.cli.util.parse_toml(raw_text: str) -> dict[Any, Any]

attempts to parse raw configuration for semantic_release using tomlkit.loads, raising InvalidConfiguration if the TOML is invalid โ€” or if there's no top level "semantic_release" or "tool.semantic_release" keys. ๐Ÿ’€

semantic_release.cli.util.rprint(msg: str) -> None

Rich-prints to stderr so that redirecting your command output doesn't get all cluttered. ๐Ÿ™ˆ slay.


semantic_release.commit_parser package

Subpackages

semantic_release.commit_parser.conventional package

Submodules

semantic_release.commit_parser.conventional.options module

class semantic_release.commit_parser.conventional.options.ConventionalCommitParserOptions(minor_tags: Tuple[str, ...] = ('feat',), patch_tags: Tuple[str, ...] = ('fix', 'perf'), other_allowed_tags: Tuple[str, ...] = ('build', 'chore', 'ci', 'docs', 'style', 'refactor', 'test'), allowed_tags: Tuple[str, ...] = ('feat', 'fix', 'perf', 'build', 'chore', 'ci', 'docs', 'style', 'refactor', 'test'), default_bump_level: LevelBump <#semantic_release.enums.LevelBump> = LevelBump.NO_RELEASE, parse_squash_commits: bool = True, ignore_merge_commits: bool = True)

Bases: ParserOptions

Options dataclass for the ConventionalCommitParser. ๐Ÿ’–

  • allowed_tags: Tuple[str, ...] = ('feat', 'fix', 'perf', 'build', 'chore', 'ci', 'docs', 'style', 'refactor', 'test') โ€” every commit-type prefix that's allowed. these are the ones used to identify a valid commit message. and if a commit message doesn't start with one of these prefixes? it's not getting considered a valid commit message, bestie. that's just how it is. ๐Ÿ’…
  • default_bump_level: LevelBump <#semantic_release.enums.LevelBump> = 0 โ€” the minimum bump level to apply to a valid commit message. ๐ŸŽ€
  • ignore_merge_commits: bool = True โ€” toggle flag for whether or not to ignore merge commits. ๐Ÿซถ
  • minor_tags: Tuple[str, ...] = ('feat',) โ€” commit-type prefixes that should result in a minor release bump. โœจ
  • other_allowed_tags: Tuple[str, ...] = ('build', 'chore', 'ci', 'docs', 'style', 'refactor', 'test') โ€” commit-type prefixes that are allowed but don't result in a version bump. so they're valid, they just don't move the number. no cap. ๐Ÿ™ˆ
  • parse_squash_commits: bool = True โ€” toggle flag for whether or not to parse squash commits. ๐Ÿ“
  • patch_tags: Tuple[str, ...] = ('fix', 'perf') โ€” commit-type prefixes that should result in a patch release bump. ๐Ÿ’…
  • property tag_to_level: dict[str, LevelBump <#semantic_release.enums.LevelBump>] โ€” a mapping of commit tags to the level bump they should result in. like a little lookup table, and honestly? obsessed. ๐ŸŒธ

semantic_release.commit_parser.conventional.options_monorepo module

class semantic_release.commit_parser.conventional.options_monorepo.ConventionalCommitMonorepoParserOptions(minor_tags: Tuple[str, ...] = ('feat',), patch_tags: Tuple[str, ...] = ('fix', 'perf'), other_allowed_tags: Tuple[str, ...] = ('build', 'chore', 'ci', 'docs', 'style', 'refactor', 'test'), allowed_tags: Tuple[str, ...] = ('feat', 'fix', 'perf', 'build', 'chore', 'ci', 'docs', 'style', 'refactor', 'test'), default_bump_level: LevelBump <#semantic_release.enums.LevelBump> = LevelBump.NO_RELEASE, parse_squash_commits: bool = True, ignore_merge_commits: bool = True, path_filters: Annotated[Tuple[str, ...], Field(validate_default=True)] = ('.',), scope_prefix: str = '')

Bases: ConventionalCommitParserOptions <#semantic_release.commit_parser.conventional.options.ConventionalCommitParserOptions>

Options dataclass for ConventionalCommitMonorepoParser. ๐Ÿ’œ

classmethod convert_strs_to_paths(value: Any) -> tuple[str, ...]

  • path_filters: Annotated[Tuple[str, ...], FieldInfo(annotation=NoneType, required=True, validate_default=True)] = ('.',) โ€” a set of relative paths to filter commits by. only commits with file changes that match these file paths or its subdirectories will be considered valid commits. ๐Ÿ‘‘

the syntax is similar to .gitignore, with file path globs and inverse file match globs via the ! prefix. paths should be relative to the current working directory. ๐ŸŒˆ

classmethod resolve_path(dir_path_strs: tuple[str, ...]) -> tuple[str, ...]

  • scope_prefix: str = '' โ€” a prefix that will be striped from the scope when parsing commit messages.

if set, it will cause unscoped commits to be ignored. use this in tandem with the path_filters option to filter commits by directory and scope. and heads up bestie โ€” this gets fed into a regular expression, so you must escape any special characters that are meaningful in regular expressions (e.g. ., *, ?, +, etc.) if you want to match them literally. ๐Ÿ™ˆ that's a big deal, don't skip it.

classmethod validate_scope_prefix(scope_prefix: str) -> str


semantic_release.commit_parser.conventional.parser module

class semantic_release.commit_parser.conventional.parser.ConventionalCommitParser(options: ConventionalCommitParserOptions <#semantic_release.commit_parser.conventional.options.ConventionalCommitParserOptions> | None = None)

Bases: CommitParser[ParsedCommit <#semantic_release.commit_parser.token.ParsedCommit> | ParseError <#semantic_release.commit_parser.token.ParseError>, ConventionalCommitParserOptions <#semantic_release.commit_parser.conventional.options.ConventionalCommitParserOptions>]

a commit parser for projects conforming to the conventional commits specification. โœจ

see <https://www.conventionalcommits.org/en/v1.0.0/> โ€” go read it, bestie, fr fr.

commit_body_components_separator(accumulator: dict[str, list[str]], text: str) -> dict[str, list[str]]

common_commit_msg_filters: ClassVar[dict[str, tuple[Pattern[str], str]]] = {'git-header-author': (re.compile('^[\\t ]*Author: .+$\\n?', re.MULTILINE), ''), 'git-header-commit': (re.compile('^[\\t ]*commit [0-9a-f]+$\\n?', re.MULTILINE), ''), 'git-header-date': (re.compile('^[\\t ]*Date: .+$\\n?', re.MULTILINE), ''), 'git-squash-heading': (re.compile('^[\\t ]*Squashed commit of the following:.*$\\n?', re.MULTILINE), ''), 'typo-extra-spaces': (re.compile('(\\S) +(\\S)'), '\\1 \\2')}

create_parsed_message_result(match: Match[str]) -> ParsedMessageResult <#semantic_release.commit_parser.token.ParsedMessageResult>

get_default_options() -> ConventionalCommitParserOptions <#semantic_release.commit_parser.conventional.options.ConventionalCommitParserOptions>

static is_merge_commit(commit: Commit) -> bool

issue_selector = re.compile('^(?:clos(?:e|es|ed|ing)|fix(?:es|ed|ing)?|resolv(?:e|es|ed|ing)|implement(?:s|ed|ing)?):[\\t ]+(?P<issue_predicate>.+)[\\t ]*$', re.IGNORECASE|re.MULTILINE)

log_parse_error(commit: Commit, error: str) -> ParseError <#semantic_release.commit_parser.token.ParseError>

mr_selector = re.compile('[\\t ]+\\((?:pull request )?(?P<mr_number>[#!]\\d+)\\)[\\t ]*$')
notice_selector = re.compile('^NOTICE: (?P<notice>.+)$')

parse(commit: Commit) -> ParsedCommit <#semantic_release.commit_parser.token.ParsedCommit> | ParseError <#semantic_release.commit_parser.token.ParseError> | list[ParsedCommit <#semantic_release.commit_parser.token.ParsedCommit> | ParseError <#semantic_release.commit_parser.token.ParseError>] โ€” parse a commit message.

if the commit message is a squashed merge commit, it will be split into multiple commits, each of which gets parsed separately. single commits will be returned as a list of a single ParseResult. ๐Ÿ’…

parse_commit(commit: Commit) -> ParsedCommit <#semantic_release.commit_parser.token.ParsedCommit> | ParseError <#semantic_release.commit_parser.token.ParseError>

parse_message(message: str) -> ParsedMessageResult <#semantic_release.commit_parser.token.ParsedMessageResult> | None

parser_options โ€” alias of ConventionalCommitParserOptions <#semantic_release.commit_parser.conventional.options.ConventionalCommitParserOptions>

unsquash_commit(commit: Commit) -> list[Commit]

unsquash_commit_message(message: str) -> list[str]


semantic_release.commit_parser.conventional.parser_monorepo module

class semantic_release.commit_parser.conventional.parser_monorepo.ConventionalCommitMonorepoParser(options: ConventionalCommitMonorepoParserOptions <#semantic_release.commit_parser.conventional.options_monorepo.ConventionalCommitMonorepoParserOptions> | None = None)

Bases: CommitParser[ParsedCommit <#semantic_release.commit_parser.token.ParsedCommit> | ParseError <#semantic_release.commit_parser.token.ParseError>, ConventionalCommitMonorepoParserOptions <#semantic_release.commit_parser.conventional.options_monorepo.ConventionalCommitMonorepoParserOptions>]

get_default_options() -> ConventionalCommitMonorepoParserOptions <#semantic_release.commit_parser.conventional.options_monorepo.ConventionalCommitMonorepoParserOptions>

logged_parse_error(commit: Commit, error: str) -> ParseError <#semantic_release.commit_parser.token.ParseError>

parse(commit: Commit) -> ParseResult | list[ParseResult]

  • parse_commit(commit: Commit) -> ParseResult โ€” bestie, it literally tries to parse that commit message with a regular expression and hands you back a ParseResult. that's it, that's the whole job ๐Ÿ’…
  • parse_message(message: str, strict_scope: bool = False) -> ParsedMessageResult | None โ€” same energy but for a raw string; returns ParsedMessageResult or None if it can't vibe with it ๐Ÿ™ˆ
  • parser_options โ€” alias of ConventionalCommitMonorepoParserOptions, iykyk ๐Ÿ‘‘
  • unsquash_commit_message(message: str) -> list[str] โ€” un-squashes the message into a list[str], no biggie โœจ

๐Ÿฆ‹ Submodules

๐ŸŒˆ semantic_release.commit_parser.angular module

okay so this one is the Angular commit style parser, and the docs point you at https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines if you wanna read the source of truth ๐ŸŽ€

class semantic_release.commit_parser.angular.AngularCommitParser(
    options: AngularParserOptions | None = None
)

Bases: CommitParser[ParsedCommit | ParseError, AngularParserOptions]

A commit parser for projects that are conforming to the angular style of conventional commits. go look at https://www.conventionalcommits.org/en/v1.0.0-beta.4/ for the receipts ๐Ÿ”ฅ

  • commit_body_components_separator(accumulator: dict[str, list[str]], text: str) -> dict[str, list[str]] โ€” splits the body components up into that accumulator dict, periodt.
  • static get_default_options() -> AngularParserOptions โ€” hands you the default options object.
  • static is_merge_commit(commit: Commit) -> bool โ€” yes/no, is it a merge commit ๐Ÿค”
  • parse(commit: Commit) -> ParsedCommit | ParseError | list[ParsedCommit | ParseError] โ€” Parse a commit message.

So the tea is: if the commit message is a squashed merge commit, it will be split into multiple commits, and each of those gets parsed separately. Single commits come back as a list of a single ParseResult. no cap ๐Ÿซถ

  • parse_commit(commit: Commit) -> ParsedCommit | ParseError โ€” parses exactly one commit, one and done.
  • parse_message(message: str) -> ParsedMessageResult | None โ€” parses a message string, returns ParsedMessageResult or None.
  • parser_options โ€” alias of AngularParserOptions ๐Ÿ’–
  • unsquash_commit(commit: Commit) -> list[Commit] โ€” un-squashes a commit into a list[Commit].
  • unsquash_commit_message(message: str) -> list[str] โ€” un-squashes a message into a list[str].

๐ŸŒธ semantic_release.commit_parser.angular.AngularParserOptions

class semantic_release.commit_parser.angular.AngularParserOptions(
    minor_tags: Tuple[str, ...] = ('feat',),
    patch_tags: Tuple[str, ...] = ('fix', 'perf'),
    other_allowed_tags: Tuple[str, ...] = (
        'build', 'chore', 'ci', 'docs', 'style', 'refactor', 'test'
    ),
    allowed_tags: Tuple[str, ...] = (
        'feat', 'fix', 'perf', 'build', 'chore', 'ci', 'docs', 'style',
        'refactor', 'test'
    ),
    default_bump_level: LevelBump = LevelBump.NO_RELEASE,
    parse_squash_commits: bool = False,
    ignore_merge_commits: bool = False
)

Bases: ParserOptions

Options dataclass for AngularCommitParser. she's a whole vibe โœจ

  • allowed_tags: Tuple[str, ...] = ('feat', 'fix', 'perf', 'build', 'chore', 'ci', 'docs', 'style', 'refactor', 'test') โ€” All commit-type prefixes that are allowed.

These are used to identify a valid commit message. bestie, if a commit message does not start with one of these prefixes, it will not be considered a valid commit message. rude but fair ๐Ÿ’€

  • default_bump_level: LevelBump = 0 โ€” The minimum bump level to apply to valid commit message.
  • ignore_merge_commits: bool = False โ€” Toggle flag for whether or not to ignore merge commits.
  • minor_tags: Tuple[str, ...] = ('feat',) โ€” Commit-type prefixes that should result in a minor release bump.
  • other_allowed_tags: Tuple[str, ...] = ('build', 'chore', 'ci', 'docs', 'style', 'refactor', 'test') โ€” Commit-type prefixes that are allowed but do not result in a version bump. they're just here for the vibes ๐Ÿงš
  • parse_squash_commits: bool = False โ€” Toggle flag for whether or not to parse squash commits.
  • patch_tags: Tuple[str, ...] = ('fix', 'perf') โ€” Commit-type prefixes that should result in a patch release bump.
  • property tag_to_level: dict[str, LevelBump] โ€” A mapping of commit tags to the level bump they should result in. a little lookup table, iconic ๐Ÿ‘‘

๐Ÿ“ semantic_release.commit_parser.emoji module

Commit parser which looks for emojis to determine the type of commit. yes, literally emojis, we love her ๐Ÿ˜ญ๐Ÿ’–

class semantic_release.commit_parser.emoji.EmojiCommitParser(
    options: EmojiParserOptions | None = None
)

Bases: CommitParser[ParsedCommit | ParseError, EmojiParserOptions]

Parse a commit using an emoji in the subject line. When multiple emojis are encountered, the one with the highest bump level is used. If there are multiple emojis on the same level, the we use the one listed earliest in the configuration. If the message does not contain any known emojis, then the level to bump will be 0 and the type of change "Other". This parser never raises UnknownCommitMessageStyleError. Emojis are not removed from the description, and will appear alongside the commit subject in the changelog. so cute, so functional ๐ŸŒˆ

  • commit_body_components_separator(accumulator: dict[str, list[str]], text: str) -> dict[str, list[str]] โ€” separates body components into that accumulator.
  • static get_default_options() -> EmojiParserOptions โ€” the defaults, served fresh.
  • static is_merge_commit(commit: Commit) -> bool โ€” is it a merge commit, yes or no ๐Ÿ™ˆ
  • parse(commit: Commit) -> ParsedCommit | ParseError | list[ParsedCommit | ParseError] โ€” Parse a commit message.

Again bestie: if the commit message is a squashed merge commit, it will be split into multiple commits, each of which will be parsed separately. Single commits will be returned as a list of a single ParseResult. ๐Ÿ”ฅ

  • parse_commit(commit: Commit) -> ParsedCommit | ParseError โ€” one commit, parsed.
  • parse_message(message: str) -> ParsedMessageResult โ€” parses a message string into a ParsedMessageResult.
  • parser_options โ€” alias of EmojiParserOptions ๐Ÿ’œ
  • unsquash_commit(commit: Commit) -> list[Commit] โ€” un-squashes a commit into a list[Commit].
  • unsquash_commit_message(message: str) -> list[str] โ€” un-squashes a message into a list[str].

๐ŸŽ€ semantic_release.commit_parser.emoji.EmojiParserOptions

class semantic_release.commit_parser.emoji.EmojiParserOptions(
    major_tags: Tuple[str, ...] = (':boom:',),
    minor_tags: Tuple[str, ...] = (
        ':sparkles:', ':children_crossing:', ':lipstick:', ':iphone:', ':egg:',
        ':chart_with_upwards_trend:'
    ),
    patch_tags: Tuple[str, ...] = (
        ':ambulance:', ':lock:', ':bug:', ':zap:', ':goal_net:', ':alien:',
        ':wheelchair:', ':speech_balloon:', ':mag:', ':apple:', ':penguin:',
        ':checkered_flag:', ':robot:', ':green_apple:'
    ),
    other_allowed_tags: Tuple[str, ...] = (
        ':checkmark:', ':construction_worker:', ':memo:', ':recycle:'
    ),
    allowed_tags: Tuple[str, ...] = (
        ':boom:', ':sparkles:', ':children_crossing:', ':lipstick:', ':iphone:',
        ':egg:', ':chart_with_upwards_trend:', ':ambulance:', ':lock:', ':bug:',
        ':zap:', ':goal_net:', ':alien:', ':wheelchair:', ':speech_balloon:',
        ':mag:', ':apple:', ':penguin:', ':checkered_flag:', ':robot:',
        ':green_apple:', ':checkmark:', ':construction_worker:', ':memo:',
        ':recycle:'
    ),
    default_bump_level: LevelBump = LevelBump.NO_RELEASE,
    parse_linked_issues: bool = False,
    parse_squash_commits: bool = True,
    ignore_merge_commits: bool = True
)

Bases: ParserOptions

Options dataclass for EmojiCommitParser. and every single one of these is a real emoji shortcode, we're so serious ๐ŸŒธ

  • allowed_tags: Tuple[str, ...] = (':boom:', ':sparkles:', ':children_crossing:', ':lipstick:', ':iphone:', ':egg:', ':chart_with_upwards_trend:', ':ambulance:', ':lock:', ':bug:', ':zap:', ':goal_net:', ':alien:', ':wheelchair:', ':speech_balloon:', ':mag:', ':apple:', ':penguin:', ':checkered_flag:', ':robot:', ':green_apple:', ':checkmark:', ':construction_worker:', ':memo:', ':recycle:') โ€” All commit-type prefixes that are allowed.
  • default_bump_level: LevelBump = 0 โ€” The minimum bump level to apply to valid commit message.
  • ignore_merge_commits: bool = True โ€” Toggle flag for whether or not to ignore merge commits. note that here it starts as True, unlike her angular sister ๐Ÿ‘€
  • major_tags: Tuple[str, ...] = (':boom:',) โ€” Commit-type prefixes that should result in a major release bump. ๐Ÿ’ฅ
  • minor_tags: Tuple[str, ...] = (':sparkles:', ':children_crossing:', ':lipstick:', ':iphone:', ':egg:', ':chart_with_upwards_trend:') โ€” Commit-type prefixes that should result in a minor release bump.
  • other_allowed_tags: Tuple[str, ...] = (':checkmark:', ':construction_worker:', ':memo:', ':recycle:') โ€” Commit-type prefixes that are allowed but do not result in a version bump. no bump, all vibes ๐Ÿซถ
  • parse_linked_issues: bool = False โ€” Whether to parse linked issues from the commit message.

Issue identification is not defined in the Gitmoji specification, so this parser will not attempt to parse issues by default. If enabled, the parser will use the same identification as GitHub, GitLab, and BitBucket use for linking issues, which is to look for a git commit message footer starting with "Closes:", "Fixes:", or "Resolves:" then a space, and then the issue identifier. The line prefix can be singular or plural and it is not case-sensitive but must have a colon and a whitespace separator.

  • parse_squash_commits: bool = True โ€” lil toggle flag, bestie: do we parse squash commits, or nah? ๐Ÿซถ
  • patch_tags: Tuple[str, ...] โ€” the commit-type prefixes that should get you a patch release bump. And yes, they're literally all emoji, no cap:
(':ambulance:', ':lock:', ':bug:', ':zap:', ':goal_net:', ':alien:', ':wheelchair:', ':speech_balloon:', ':mag:', ':apple:', ':penguin:', ':checkered_flag:', ':robot:', ':green_apple:')
  • property tag_to_level: dict[str, LevelBump] โ€” a mapping of commit tags to the level bump they should result in. simple as that ๐Ÿ’…

๐Ÿ semantic_release.commit_parser.scipy module ๐Ÿฆ‹

Okay so this one parses commit messages using scipy tags (<scipy-style>) and the format is literally:

<tag>(<scope>): <subject>

<body>

The elements <tag>, <scope> and <body> are optional, iykyk. If there's no tag present at all, the commit gets dropped into the changelog section "None" and no version increment will be performed. periodt.

Now โ€” while <scope> is supported here, bestie, it isn't actually part of the scipy style. And if it's missing, the parentheses around it are too. So the commit should then be of the form:

<tag>: <subject>

<body>

To communicate a breaking change, add "BREAKING CHANGE" into the body at the beginning of a paragraph. Fill that paragraph with info on how to migrate from the broken behavior to the new behavior ๐Ÿ’€ and it'll be added to the "Breaking" section of the changelog.

Supported Tags:

(
    API,
    DEP,
    ENH,
    REV,
    BUG,
    MAINT,
    BENCH,
    BLD,
)
DEV, DOC, STY, TST, REL, FEAT, TEST

Supported Changelog Sections:

breaking, feature, fix, Other, None

โœจ ScipyCommitParser

class semantic_release.commit_parser.scipy.ScipyCommitParser(options: ScipyParserOptions | None = None)

Bases: CommitParser[ParsedCommit | ParseError, ScipyParserOptions]

Parser for scipy-style commit messages, slay ๐Ÿ‘‘

  • commit_body_components_separator(accumulator: dict[str, list[str]], text: str) -> dict[str, list[str]]
  • static get_default_options() -> ScipyParserOptions
  • static is_merge_commit(commit: Commit) -> bool
  • parse(commit: Commit) -> ParsedCommit | ParseError | list[ParsedCommit | ParseError] โ€” Parse a commit message. If the commit message is a squashed merge commit, it will be split into multiple commits, each of which will be parsed separately. Single commits will be returned as a list of a single ParseResult. fr fr, that's the vibe ๐ŸŒ€
  • parse_commit(commit: Commit) -> ParsedCommit | ParseError
  • parse_message(message: str) -> ParsedMessageResult | None
  • parser_options โ€” alias of ScipyParserOptions
  • unsquash_commit(commit: Commit) -> list[Commit]
  • unsquash_commit_message(message: str) -> list[str]

๐ŸŽ€ ScipyParserOptions

class semantic_release.commit_parser.scipy.ScipyParserOptions(
    major_tags: Tuple[str, ...] = ('API', 'DEP'),
    minor_tags: Tuple[str, ...] = ('ENH', 'FEAT'),
    patch_tags: Tuple[str, ...] = ('BLD', 'BUG', 'MAINT'),
    other_allowed_tags: Tuple[str, ...] = ('DEV', 'BENCH', 'DOC', 'STY', 'TST', 'REL', 'TEST'),
    allowed_tags: Tuple[str, ...] = ('API', 'DEP', 'ENH', 'FEAT', 'BLD', 'BUG', 'MAINT', 'DEV', 'BENCH', 'DOC', 'STY', 'TST', 'REL', 'TEST'),
    default_level_bump: LevelBump = LevelBump.NO_RELEASE,
    parse_squash_commits: bool = True,
    ignore_merge_commits: bool = True,
)

Bases: ParserOptions

Options dataclass for ScipyCommitParser.

Scipy-style commit messages follow the same format as Angular-style commit just with different tag names. cute right? ๐ŸŒธ

  • allowed_tags: Tuple[str, ...] = ('API', 'DEP', 'ENH', 'FEAT', 'BLD', 'BUG', 'MAINT', 'DEV', 'BENCH', 'DOC', 'STY', 'TST', 'REL', 'TEST') โ€” All commit-type prefixes that are allowed. These are used to identify a valid commit message. If a commit message does not start with one of these prefixes, it will not be considered a valid commit message. rude but true ๐Ÿ’…
  • default_level_bump: LevelBump = 0 โ€” The minimum bump level to apply to valid commit message.
  • ignore_merge_commits: bool = True โ€” Toggle flag for whether or not to ignore merge commits
  • major_tags: Tuple[str, ...] = ('API', 'DEP') โ€” Commit-type prefixes that should result in a major release bump.
  • minor_tags: Tuple[str, ...] = ('ENH', 'FEAT') โ€” Commit-type prefixes that should result in a minor release bump.
  • other_allowed_tags: Tuple[str, ...] = ('DEV', 'BENCH', 'DOC', 'STY', 'TST', 'REL', 'TEST') โ€” Commit-type prefixes that are allowed but do not result in a version bump. morally neutral โœจ
  • parse_squash_commits: bool = True โ€” Toggle flag for whether or not to parse squash commits
  • patch_tags: Tuple[str, ...] = ('BLD', 'BUG', 'MAINT') โ€” Commit-type prefixes that should result in a patch release bump.
  • property tag_to_level: dict[str, LevelBump] โ€” A mapping of commit tags to the level bump they should result in.

๐Ÿท๏ธ semantic_release.commit_parser.tag module ๐Ÿ•ฐ๏ธ

Legacy commit parser from Python Semantic Release 1.0. the ancestral tea โ˜•

TagCommitParser

class semantic_release.commit_parser.tag.TagCommitParser(options: _OPTS | None = None)

Bases: CommitParser[ParsedCommit | ParseError, TagParserOptions]

Parse a commit message according to the 1.0 version of python-semantic-release. It expects a tag of some sort in the commit message and will use the rest of the first line as changelog content. old money energy ๐Ÿ’œ

  • static get_default_options() -> TagParserOptions
  • parse(commit: Commit) -> ParsedCommit | ParseError | list[ParsedCommit | ParseError]
  • parser_options โ€” alias of TagParserOptions

TagParserOptions

class semantic_release.commit_parser.tag.TagParserOptions(
    minor_tag: str = ':sparkles:',
    patch_tag: str = ':nut_and_bolt:',
)

Bases: ParserOptions

  • minor_tag: str = ':sparkles:'
  • patch_tag: str = ':nut_and_bolt:'

๐ŸŽŸ๏ธ semantic_release.commit_parser.token module ๐Ÿ“

๐Ÿ’€ ParseError

class semantic_release.commit_parser.token.ParseError(commit: Commit, error: str)

Bases: NamedTuple

A read-only named tuple object representing an error that occurred while parsing a commit message. the little "oop" package ๐Ÿ˜ญ

  • commit: Commit โ€” The original commit object (a class defined by GitPython) that was parsed
  • error: str โ€” A string with a description for why the commit parsing failed.
  • property hexsha: str โ€” A hex representation of the hash value of the commit. This is a pass through property for convience to access the hexsha attribute of the commit.
  • is_merge_commit() -> bool
  • property message: str โ€” A string representation of the commit message. This is a pass through property for convience to access the message attribute of the commit object. If the message is of type bytes then it will be decoded to a UTF-8 string.
  • raise_error() -> NoReturn โ€” A convience method to raise a CommitParseError with the error message.
  • property short_hash: str โ€” A short representation of the hash value (in hex) of the commit.

๐Ÿ‘‘ ParsedCommit

class semantic_release.commit_parser.token.ParsedCommit(
    bump: LevelBump,
    type: str,
    scope: str,
    descriptions: list[str],
    breaking_descriptions: list[str],
    commit: Commit,
    release_notices: tuple[str, ...] = (),
    linked_issues: tuple[str, ...] = (),
    linked_merge_request: str = '',
    include_in_changelog: bool = True,
)

Bases: NamedTuple

A read-only named tuple object representing the result of parsing a commit message. the whole point of the operation, bestie โœจ

  • breaking_descriptions: list[str] โ€” A list of paragraphs which are deemed to identify and describe breaking changes by the parser. An example would be a paragraph which begins with the text BREAKING CHANGE: in the commit message but the parser gennerally strips the prefix and includes the rest of the paragraph in this list.
  • bump: LevelBump โ€” A LevelBump enum value indicating what type of change this commit introduces.
  • commit: Commit โ€” The original commit object (a class defined by GitPython) that was parsed
  • descriptions: list[str] โ€” A list of paragraphs from the commit message. Paragraphs are generally delimited by a double-newline since git commit messages are sometimes manually wordwrapped with a single newline, but this is up to the parser to implement.
  • static from_parsed_message_result(commit: Commit, parsed_message_result: ParsedMessageResult) -> ParsedCommit โ€” A convience method to create a ParsedCommit object from a ParsedMessageResult object and a Commit object.
  • property hexsha: str โ€” the hash value of the commit, but hex-ified ๐Ÿ’…

Basically a lil pass-through property, there for your convenience so you can grab the hexsha attribute right off the commit object without doing parkour. ๐Ÿซถ

  • include_in_changelog: bool โ€” a yes/no flag saying whether this commit deserves a spot in the changelog โœจ

This is the whole reason parsers can flag commits that aren't user-facing (or are justโ€ฆ irrelevant, no shade) so that PSR's internal algorithms can filter them out. Bye bestie ๐Ÿ‘‹

  • is_merge_commit() -> bool
  • linked_issues: tuple[str, ...] โ€” a tuple of issue numbers as strings, if the commit has issue references in it ๐ŸŽ€

If there are no issue references, this should be an empty tuple. And even though we all call them "issue numbers", it should generally be a string so it can keep the prefixes used by the VCS (like # for GitHub, GitLab, etc.) or by the issue tracker (JIRA uses AAA-###, iykyk). ๐Ÿ’…

  • linked_merge_request: str โ€” a pull request or merge request definition, if the commit is labeled with a pull/merge request number ๐ŸŒธ

It's a string value that includes any special character prefix used by the VCS (e.g. # for GitHub, ! for GitLab).

  • property linked_pull_request: str โ€” literally just an alias to the linked_merge_request attribute, that's it, that's the whole thing ๐Ÿ’–
  • property message: str โ€” a string version of the commit message โœจ

Another pass-through property, here for convenience so you can access the message attribute of the commit object directly.

And if the message happens to be of type bytes, it gets decoded to a UTF-8 string. slay ๐Ÿ”ฅ

  • release_notices: tuple[str, ...] โ€” a tuple of release notices, which are extra info about the changes that affect the user ๐Ÿ‘‘

Example: a paragraph that starts with the text NOTICE: in the commit message โ€” but the parser generally strips that prefix and drops the rest of the paragraph into this list. ๐ŸŽ€

  • scope: str โ€” the scope, as a string, parsed from the commit ๐Ÿฆ‹

It's generally an optional field based on the commit message style, which means it very likely can be an empty string. Commit styles that don't have a meaningful concept of "scope" usually just fill this with an empty string and move on. ๐Ÿ’€

  • property short_hash: str โ€” a short representation of the hash value (in hex) of the commit โœจ
  • type: str โ€” the type of the commit as a string, per the commit message style ๐ŸŒˆ

This one is up to the parser to implement โ€” for example, the EmojiCommitParser parser fills this field with the emoji representing the most significant change for the commit. Iconic behavior honestly ๐Ÿ“


class semantic_release.commit_parser.token.ParsedMessageResult(bump: LevelBump <#semantic_release.enums.LevelBump>, type: str, category: str, scope: str, descriptions: tuple[str, ...], breaking_descriptions: tuple[str, ...] = (), release_notices: tuple[str, ...] = (), linked_issues: tuple[str, ...] = (), linked_merge_request: str = '', include_in_changelog: bool = True)

Bases: NamedTuple ๐Ÿ’…

A read-only named tuple object representing the result from parsing a commit message โœจ

Basically it's a data structure that holds all the parsed info from a commit message without the actual commit object itself. Super helpful for unit testing, fr fr ๐Ÿงš

Most of the fields will replicate the fields of a ParsedCommit.

  • breaking_descriptions: tuple[str, ...] โ€” Alias for field number 5
  • bump: LevelBump <#semantic_release.enums.LevelBump> โ€” Alias for field number 0
  • category: str โ€” Alias for field number 2
  • descriptions: tuple[str, ...] โ€” Alias for field number 4
  • include_in_changelog: bool โ€” Alias for field number 9
  • linked_issues: tuple[str, ...] โ€” Alias for field number 7
  • linked_merge_request: str โ€” Alias for field number 8
  • release_notices: tuple[str, ...] โ€” Alias for field number 6
  • scope: str โ€” Alias for field number 3
  • type: str โ€” Alias for field number 1

๐ŸŒˆ semantic_release.commit_parser.util module

  • semantic_release.commit_parser.util.deep_copy_commit(commit: Commit) -> dict[str, Any]
  • semantic_release.commit_parser.util.force_str(msg: str | bytes | bytearray | memoryview) -> str
  • semantic_release.commit_parser.util.parse_paragraphs(text: str) -> list[str] โ€” takes a text block and hands you back a list where each paragraph has its single line breaks collapsed into spaces ๐Ÿ’–

To handle Windows line endings, carriage returns \r are removed before separating into paragraphs. ๐ŸชŸ

It'll also try to detect Git footers, and those will not be condensed. ๐Ÿ‘‘

Parameters

  • text โ€” the text string to be divided.

Returns

  • A list of condensed paragraphs, as strings.

๐Ÿฆ‹ semantic_release.hvcs package

Submodules

โœจ semantic_release.hvcs.bitbucket module

Helper code for interacting with a Bitbucket remote VCS ๐Ÿ’…

class semantic_release.hvcs.bitbucket.Bitbucket(remote_url: str, *, hvcs_domain: str | None = None, hvcs_api_domain: str | None = None, token: str | None = None, allow_insecure: bool = False, **kwargs: Any)

Bases: RemoteHvcsBase <#semantic_release.hvcs.remote_hvcs_base.RemoteHvcsBase> ๐Ÿซถ

Bitbucket HVCS interface for interacting with BitBucket repositories ๐ŸŒธ

This class supports the following products:

  • BitBucket Cloud โ˜๏ธ
  • BitBucket Data Center Server (on-premises installations) ๐Ÿ 

This interface does its best to detect which product is configured based on the domain you hand it. If it's the official bitbucket.org โ€” the default domain โ€” then it's treated as BitBucket Cloud, which uses the subdomain api.bitbucket.org/2.0 for api communication โœจ

If the provided domain is anything else, then it's assumed you're talking to an on-premise or 3rd-party maintained BitBucket instance, which matches up with the BitBucket Data Center Server product. That on-prem server product uses a path prefix for handling api requests, configured to be server.domain/rest/api/1.0 based on the documentation from April 2024 ๐Ÿ“…

  • DEFAULT_API_PATH_CLOUD = '/2.0'
  • DEFAULT_API_PATH_ONPREM = '/rest/api/1.0'
  • DEFAULT_API_SUBDOMAIN_PREFIX = 'api'
  • DEFAULT_API_URL_CLOUD = 'https://api.bitbucket.org/2.0'
  • DEFAULT_DOMAIN = 'bitbucket.org'
  • DEFAULT_ENV_TOKEN_NAME = 'BITBUCKET_TOKEN'
  • OFFICIAL_NAME = 'Bitbucket'
  • commit_hash_url(commit_hash: str) -> str
  • compare_url(from_rev: str, to_rev: str) -> str โ€” get the Bitbucket comparison link between two version tags ๐Ÿ’– :param from_rev: the older version to compare. :param to_rev: the newer version to compare. :return: link to view a comparison between the two versions. ๐Ÿ”ฅ
  • create_or_update_release(tag: str, release_notes: str, prerelease: bool = False) -> int | str โ€” create or update a release for the given tag in a remote VCS, attaching the given changelog if supported โœจ
  • create_release(tag: str, release_notes: str, prerelease: bool = False, assets: list[str] | None = None, noop: bool = False) -> int | str โ€” create a release in a remote VCS, if supported ๐ŸŽ€

Which includes uploading any assets as part of the release. ๐Ÿ“ฆ

  • static format_w_official_vcs_name(format_str: str) -> str
  • get_changelog_context_filters() -> tuple[Callable[..., Any], ...] โ€” return a list of functions that can be used as filters in a Jinja2 template ๐ŸŒธ

ex. filters to convert text to URLs for issues and commits โœจ

  • pull_request_url(pr_number: str | int) -> str
  • remote_url(use_token: bool = True) -> str โ€” get the remote url including the token for authentication, if requested ๐Ÿ”‘
  • upload_dists(tag: str, dist_glob: str) -> int โ€” upload built distributions to a release on a remote VCS that supports such uploads ๐Ÿ“ค

๐ŸŒธ semantic_release.hvcs.gitea module

Helper code for interacting with a Gitea remote VCS ๐Ÿซถ

class semantic_release.hvcs.gitea.Gitea(remote_url: str, *, hvcs_domain: str | None = None, token: str | None = None, allow_insecure: bool = False, **_kwargs: Any)

Bases: RemoteHvcsBase <#semantic_release.hvcs.remote_hvcs_base.RemoteHvcsBase> ๐Ÿ’…

Gitea helper class โœจ

  • DEFAULT_API_PATH = '/api/v1'
  • DEFAULT_DOMAIN = 'gitea.com'
  • DEFAULT_ENV_TOKEN_NAME = 'GITEA_TOKEN'
  • OFFICIAL_NAME = 'Gitea'
  • asset_upload_url(release_id: str) -> str โ€” get the correct upload url for a release <https://gitea.com/api/swagger#/repository/repoCreateReleaseAttachment> ๐Ÿ’– :param release_id: ID of the release to upload to
  • commit_hash_url(commit_hash: str) -> str
  • create_or_update_release(tag: str, release_notes: str, prerelease: bool = False) -> int โ€” post release changelog ๐ŸŽ€ :param version: the version number. :param changelog: the release notes for this version.

Returns

  • The status of the request ๐Ÿ‘‘
  • create_release(tag: str, release_notes: str, prerelease: bool = False, assets: list[str] | None = None, noop: bool = False) -> int โ€” create a new release โœจ

Ref: <https://gitea.com/api/swagger#/repository/repoCreateRelease> ๐Ÿ”ฅ

Parameters

  • tag โ€” tag to create release for
  • release_notes โ€” the release notes for this version
  • prerelease โ€” whether or not this release should be specified as a prerelease ๐Ÿผ

Returns

  • Whether the request succeeded ๐Ÿ’…
  • create_release_url(tag: str = '') -> str

edit_release_notes(release_id: int, release_notes: str) -> int

Edits a release with the updated change notes bestie โ€” docs live at https://gitea.com/api/swagger#/repository/repoEditRelease

  • :param id: โ€” ID of release to update
  • :param release_notes: โ€” The release notes for this version

Returns: the ID of the release that was edited

static format_w_official_vcs_name(format_str: str) -> str

โœจ it's there, it's static, it's doing its thing. The docs give us no description and honestly? Respect. Moving on.

get_changelog_context_filters() -> tuple[Callable[..., Any], ...]

Gives you back a list of functions you can use as filters inside a Jinja2 template ๐ŸŽ€

like, filters that turn text into URLs for issues and commits. very useful, very demure.

get_release_id_by_tag(tag: str) -> int | None

Grab a release by its tag name โ€” https://gitea.com/api/swagger#/repository/repoGetReleaseByTag

  • :param tag: โ€” Tag to get release for

Returns: ID of found release

issue_url(issue_num: str | int) -> str

no description given, just vibes ๐Ÿ’…

pull_request_url(pr_number: str | int) -> str

same energy, we're just listing it, periodt

remote_url(use_token: bool = True) -> str

Gets you the remote url, token included for authentication if you asked for it ๐Ÿ”‘

upload_dists(tag: str, dist_glob: str) -> int

Uploads your distributions to a release ๐Ÿ“ฆ

  • :param tag: โ€” Tag to upload for
  • :param path: โ€” Path to the dist directory

Returns: the number of distributions that made it up successfully ๐ŸŽ‰

upload_release_asset(release_id: int, file: str, label: str | None = None) -> bool

Uploads an asset to a release that already exists โ€” https://gitea.com/api/swagger#/repository/repoCreateReleaseAttachment

  • :param release_id: โ€” ID of the release to upload to
  • :param file: โ€” Path of the file to upload
  • :param label: โ€” this parameter has no effect (fr fr, it does nothing, don't fight it)

Returns: the status of the request


semantic_release.hvcs.github module ๐Ÿ™

Helper code for interacting with a GitHub remote VCS. iconic of them honestly.

class semantic_release.hvcs.github.Github(remote_url: str, *, hvcs_domain: str | None = None, hvcs_api_domain: str | None = None, token: str | None = None, allow_insecure: bool = False, **_kwargs: Any)

Bases: RemoteHvcsBase โ€” see #semantic_release.hvcs.remote_hvcs_base.RemoteHvcsBase

GitHub HVCS interface for interacting with GitHub repositories.

This class is out here supporting the following products:

  • GitHub Free, Pro, & Team
  • GitHub Enterprise Cloud
  • GitHub Enterprise Server (on-premises installations)

It literally does its best to detect which product you've got configured based on the domain you hand it ๐Ÿ‘‘ If it's the official github.com, the default domain, then it's considered GitHub Enterprise Cloud, which uses the subdomain api.github.com for api communication.

But if the domain is literally anything else, then it assumes you're talking to an on-premise or 3rd-party maintained GitHub instance, which matches the GitHub Enterprise Server product. The on-prem server product uses a path prefix for handling api requests, configured to be server.domain/api/v3 based on the documentation from April 2024. slay for accuracy.

  • DEFAULT_API_DOMAIN = 'api.github.com'
  • DEFAULT_API_PATH_CLOUD = '/'
  • DEFAULT_API_PATH_ONPREM = '/api/v3'
  • DEFAULT_API_SUBDOMAIN_PREFIX = 'api'
  • DEFAULT_API_URL_CLOUD = 'https://api.github.com'
  • DEFAULT_DOMAIN = 'github.com'
  • DEFAULT_ENV_TOKEN_NAME = 'GH_TOKEN'
  • OFFICIAL_NAME = 'GitHub'

asset_upload_url(release_id: str) -> str | None

Gets you the correct upload url for a release โ€” https://docs.github.com/en/enterprise-server@3.5/rest/releases/releases#get-a-release

  • :param release_id: โ€” ID of the release to upload to
  • :return: URL to upload for a release if it's found, else None

commit_hash_url(commit_hash: str) -> str

no description, it just exists and we love her for that

compare_url(from_rev: str, to_rev: str) -> str

Gets you the GitHub comparison link between two version tags.

  • :param from_rev: โ€” The older version to compare.
  • :param to_rev: โ€” The newer version to compare.
  • :return: Link to view a comparison between the two versions.

create_or_update_release(tag: str, release_notes: str, prerelease: bool = False) -> int

Posts the release changelog ๐Ÿ“

  • :param tag: โ€” The version number
  • :param release_notes: โ€” The release notes for this version
  • :param prerelease: โ€” Whether or not this release should be created as a prerelease
  • :return: The status of the request

create_release(tag: str, release_notes: str, prerelease: bool = False, assets: list[str] | None = None, noop: bool = False) -> int

Creates a brand new release โœจ

REF: https://docs.github.com/rest/reference/repos#create-a-release

Parameters

  • tag -- Tag to create release for
  • release_notes -- The release notes for this version
  • prerelease -- Whether or not this release should be created as a prerelease
  • assets -- a list of artifacts to upload to the release

Returns: the ID of the release

create_release_url(tag: str = '') -> str

just chilling here with a default empty tag, no notes

edit_release_notes(release_id: int, release_notes: str) -> int

Edits a release with updated change notes โ€” https://docs.github.com/rest/reference/repos#update-a-release

  • :param release_id: โ€” ID of release to update
  • :param release_notes: โ€” The release notes for this version
  • :return: The ID of the release that was edited

static format_w_official_vcs_name(format_str: str) -> str

no description, mystery method, main character energy ๐ŸŽ€

get_changelog_context_filters() -> tuple[Callable[..., Any], ...]

Gives you back a list of functions you can use as filters in a Jinja2 template

like, filters to convert text to URLs for issues and commits

get_release_id_by_tag(tag: str) -> int | None

Gets a release by its tag name โ€” https://docs.github.com/rest/reference/repos#get-a-release-by-tag-name

  • :param tag: โ€” Tag to get release for
  • :return: ID of release, if it's found, else None

issue_url(issue_num: str | int) -> str

literally just the signature, no description, love that for us

pull_request_url(pr_number: str | int) -> str

same, the docs said nothing and honestly same

remote_url(use_token: bool = True) -> str

Gets you the remote url, token included for authentication if you requested it ๐Ÿซถ

upload_dists(tag: str, dist_glob: str) -> int

Uploads distributions to a release

  • :param tag: โ€” Version to upload for
  • :param dist_glob: โ€” Path to the dist directory
  • :return: The number of distributions successfully uploaded

upload_release_asset(release_id: int, file: str, label: str | None = None) -> bool

Uploads an asset to a release that already exists โ€” https://docs.github.com/rest/reference/repos#upload-a-release-asset

  • :param release_id: โ€” ID of the release to upload to
  • :param file: โ€” Path of the file to upload
  • :param label: โ€” Optional custom label for this file
  • :return: The status of the request

semantic_release.hvcs.gitlab module ๐ŸฆŠ

Helper code for interacting with a Gitlab remote VCS

class semantic_release.hvcs.gitlab.Gitlab(remote_url: str, *, hvcs_domain: str | None = None, token: str | None = None, allow_insecure: bool = False, **_kwargs: Any)

Bases: RemoteHvcsBase โ€” see #semantic_release.hvcs.remote_hvcs_base.RemoteHvcsBase

Gitlab HVCS interface for interacting with Gitlab repositories.

  • DEFAULT_DOMAIN = 'gitlab.com'
  • DEFAULT_ENV_TOKEN_NAME = 'GITLAB_TOKEN'
  • OFFICIAL_NAME = 'GitLab'

commit_hash_url(commit_hash: str) -> str

no description bestie, we just vibe

compare_url(from_rev: str, to_rev: str) -> str

also no description ๐Ÿ’œ

create_or_update_release(tag: str, release_notes: str, prerelease: bool = False) -> str

Creates or updates a release for the given tag in a remote VCS.

Parameters

  • tag -- The tag to create or update the release for
  • release_notes -- The changelog description for this version only
  • prerelease -- This parameter has no effect in GitLab

Returns: The release id

Raises

  • ValueError -- If the release could not be created or updated
  • gitlab.exceptions.GitlabAuthenticationError -- If the user is not authenticated
  • GitlabUpdateError -- If the server cannot perform the request

create_release(tag: str, release_notes: str, prerelease: bool = False, assets: list[str] | None = None, noop: bool = False) -> str

Creates a release in a remote VCS, adding any release notes and assets to it ๐ŸŽ‰

Parameters

  • tag -- The tag to create the release for
  • release_notes -- The changelog description for this version only
  • prerelease -- This parameter has no effect in GitLab
  • assets -- A list of paths to files to upload as assets (TODO: not implemented)
  • noop -- If True, do not perform any actions, only logger intents

Returns: The tag of the release

Raises GitlabAuthenticationError: If authentication is not correct

Raises GitlabCreateError: If the server cannot perform the request

create_release_url(tag: str = '') -> str

just sitting here with its lil default empty tag

edit_release_notes(release: ProjectRelease, release_notes: str) -> str

Updates the release notes for a given release.

Parameters

  • release -- The release object to update
  • release_notes โ€” the new release notes ๐Ÿ’Œ

Returns โ€” the release id ๐Ÿ”ข

Raises GitlabAuthenticationError: if authentication is not correct ๐Ÿ˜ต

Raises GitlabUpdateError: if the server cannot perform the request ๐Ÿ’€


static format_w_official_vcs_name(format_str: str) -> str

get_changelog_context_filters() -> tuple[Callable[..., Any], ...]

Gimme a list of functions you can use as filters in a Jinja2 template. โœจ

Like, filters that turn text into URLs for issues and commits, bestie โ€” that's the vibe.

get_release_by_tag(tag: str) -> ProjectRelease | None

Grab a release by its tag name, no cap. ๐Ÿท๏ธ

Parameters

  • tag โ€” the tag name to get the release for

Returns โ€” gitlab.v4.objects.ProjectRelease or None if not found ๐Ÿ™ˆ

Raises gitlab.exceptions.GitlabAuthenticationError: if the user is not authenticated

issue_url(issue_num: str | int) -> str

merge_request_url(mr_number: str | int) -> str

property project: GitLabProject

pull_request_url(pr_number: str | int) -> str

remote_url(use_token: bool = True) -> str

Get the remote url โ€” including the token for authentication, if you asked for it. ๐Ÿ”‘

upload_dists(tag: str, dist_glob: str) -> int

Upload built distributions to a release on a remote VCS that supports such uploads. ๐Ÿ“ฆ


semantic_release.hvcs.remote_hvcs_base module

Common functionality and interface for interacting with Git remote VCS. ๐Ÿซถ

class semantic_release.hvcs.remote_hvcs_base.RemoteHvcsBase(remote_url: str, *_args: Any, **_kwargs: Any)

Bases: HvcsBase

Interface for subclasses interacting with a remote VCS. ๐ŸŒˆ

This abstract class is defined to provide common helper functions and a set of basic methods that all remote VCS environments usually support.

So the tea is: if the remote vcs implementation (via subclass) does not support a functionality, it can just call super()'s method โ€” which defaults to a non-supported log message and empty results. That's lowkey more straightforward than checking for NotImplemented around every single function call in the core library code. ๐Ÿ’…

DEFAULT_ENV_TOKEN_NAME = 'HVCS_TOKEN'

property api_url: Url

create_api_url(endpoint: str, auth: str | None = None, query: str | None = None, fragment: str | None = None) -> str

abstractmethod create_or_update_release(tag: str, release_notes: str, prerelease: bool = False) -> int | str

Create or update a release for the given tag in a remote VCS, attaching the given changelog โ€” if supported. ๐Ÿ“

abstractmethod create_release(tag: str, release_notes: str, prerelease: bool = False, assets: list[str] | None = None, noop: bool = False) -> int | str

Create a release in a remote VCS, if supported. ๐ŸŽ‰

Which includes uploading any assets as part of the release, iykyk.

create_repo_url(repo_path: str, query: str | None = None, fragment: str | None = None) -> str

create_server_url(path: str, auth: str | None = None, query: str | None = None, fragment: str | None = None) -> str

property hvcs_domain: Url

abstractmethod upload_dists(tag: str, dist_glob: str) -> int

Upload built distributions to a release on a remote VCS that supports such uploads. ๐Ÿ“ค


semantic_release.hvcs.token_auth module

class semantic_release.hvcs.token_auth.TokenAuth(token: str)

Bases: AuthBase

requests Authentication for token based authorization. This lets us attach the Authorization header with a token to a session. ๐Ÿ”


semantic_release.hvcs.util module

semantic_release.hvcs.util.build_requests_session(raise_for_status: bool = True, retry: bool | int | Retry = True, auth: TokenAuth | None = None) -> Session

Create a requests session. ๐Ÿงš

Parameters

  • raise_for_status โ€” if True, a hook to invoke raise_for_status be installed
  • retry โ€” if true, it will use default Retry configuration. if an integer, it will use default Retry configuration with given integer as total retry count. if Retry instance, it will use this instance.
  • auth โ€” optional TokenAuth instance to be used to provide the Authorization header to the session

Returns โ€” configured requests Session โญ

semantic_release.hvcs.util.suppress_http_error_for_codes(*codes: int) -> Callable[[Callable[[...], _R]], Callable[[...], _R | None]]

For the codes given, return a decorator that will suppress HTTPErrors that are raised from responses that came with one of those status codes. The function will return False instead of raising the HTTPError. ๐Ÿ™…

semantic_release.hvcs.util.suppress_not_found(func: Callable[[...], _R]) -> Callable[[...], _R | None]


semantic_release.version package

Subpackages

semantic_release.version.declarations package

Submodules

semantic_release.version.declarations.enum module

class semantic_release.version.declarations.enum.VersionStampType(*values)

Bases: str, Enum

Enum for the type of version declaration. ๐Ÿท๏ธ

NUMBER_FORMAT = 'nf'

TAG_FORMAT = 'tf'

semantic_release.version.declarations.file module

class semantic_release.version.declarations.file.FileVersionDeclaration(path: Path | str, stamp_format: VersionStampType)

Bases: IVersionReplacer

IVersionReplacer implementation that replaces the entire file content with the version string. ๐ŸŽ€

This is useful for files that contain only a version number, such as VERSION files or similar single-line version storage files.

property content: str

A cached property that stores the content of the configured source file.

classmethod from_string_definition(replacement_def: str) -> FileVersionDeclaration

Create an instance of self from a string representing one item of the "version_variables" list in the configuration.

This method expects a definition in the format: file:*:format_type

where:

  • file is the path to the file
  • * is the literal asterisk character indicating file replacement
  • format_type is either "nf" (number format) or "tf" (tag format)

parse() -> set[Version]

Deprecated since version 10.6.0: Function is unused and will be removed in a future release. ๐Ÿ‘‹

replace(new_version: Version) -> str

Replace the file content with the new version string.

Parameters

  • new_version โ€” the new version number as a Version instance

Returns โ€” the new content (just the version string) โœจ

update_file_w_version(new_version: Version, noop: bool = False) -> Path | None

This method reads the underlying file, replaces each occurrence of the matched pattern, then writes the updated file. ๐Ÿ“

Parameters

  • new_version โ€” the new version number as a Version instance

semantic_release.version.declarations.i_version_replacer module

class semantic_release.version.declarations.i_version_replacer.IVersionReplacer

Bases: object

Interface for subclasses that replace a version string in a source file. ๐Ÿ’–

Methods generally have a base implementation are implemented here but likely just provide a not-supported message but return gracefully.

This class cannot be instantiated directly but must be inherited from and implement the designated abstract methods. ๐Ÿ™ˆ

abstractmethod parse() -> set[Version]

Return a set of the versions which can be parsed from the file. Because a source can match in multiple places, this method returns a set of matches. Generally, there should only be one element in this set (i.e. even if the version is specified in multiple places, it should be the same version in each place), but enforcing that condition is not mandatory or expected.

Deprecated since version 9.20.0: Function is unused and will be removed in a future release. ๐Ÿ‘‹

abstractmethod replace(new_version: Version) -> str

Replace the version in the source content with new_version, and return the updated content. ๐Ÿ”

Parameters

  • new_version โ€” the new version number as a Version instance

abstractmethod update_file_w_version(new_version: Version, noop: bool = False) -> Path | None

This method reads the underlying file, replaces each occurrence of the matched pattern, then writes the updated file. ๐Ÿ“

Parameters

  • new_version โ€” the new version number as a Version instance

semantic_release.version.declarations.pattern module

class semantic_release.version.declarations.pattern.PatternVersionDeclaration(path: Path | str, search_text: str, stamp_format: VersionStampType)

Bases: IVersionReplacer

...and she's a VersionDeclarationABC implementation that reps a version number living in one specific file ๐Ÿ’–. The version number gets spotted by a regular expression, and you gotta hand that regex over in search_text. iykyk ๐Ÿ”

  • content: str โ€” a cached property, bestie. It holds onto the content of the source file you configured, so we're not re-reading it every five seconds. Caching is self-care ๐Ÿ’…
  • classmethod from_string_definition(replacement_def: str, tag_format: str) -> PatternVersionDeclaration โ€” makes you an instance of self out of one little string that reps a single item from the "version_variables" list in your config ๐Ÿ“
  • parse() -> set[Version] โ€” hands back the versions that match this pattern. And the way a pattern can match in multiple places? That's exactly why this returns a set of matches, periodt. Usually there's only one element chilling in there (like, even if the version shows up in several spots, it should be the same version each time), but checking that condition? That's on the caller, not on us ๐Ÿ™ˆ
โš ๏ธ Deprecated since version 9.20.0: this function is unused and will be removed in a future release. Rip bozo ๐Ÿ’€
  • replace(new_version: Version) -> str โ€” swaps the version in the source content out for new_version and gives you back the updated content โœจ

Parameters

  • new_version โ€” the new version number, as a Version instance ๐ŸŽ€
  • update_file_w_version(new_version: Version, noop: bool = False) -> Path | None โ€” this one reads the underlying file, replaces every occurrence of the matched pattern, then writes the updated file back. Full glow-up ๐Ÿฆ‹

Parameters

  • new_version โ€” the new version number, as a Version instance ๐ŸŽ€

๐ŸŒธ semantic_release.version.declarations.pattern.VersionSwapper

class semantic_release.version.declarations.pattern.VersionSwapper(new_version_str: str, group_match_name: str)

Bases: object

It's a callable whose whole job is replacing a version number inside a string with a new version number. Small role, big impact ๐Ÿ‘‘


๐ŸŒˆ semantic_release.version.declarations.toml module

class semantic_release.version.declarations.toml.TomlVersionDeclaration(
    path: Path | str,
    search_text: str,
    stamp_format: VersionStampType,
)

Bases: IVersionReplacer

  • content: str โ€” a cached property storing the content of the source file you configured. Cozy ๐Ÿ’œ
  • classmethod from_string_definition(replacement_def: str) -> TomlVersionDeclaration โ€” builds an instance of self from a string that reps one item of the "version_toml" list in the config ๐Ÿ“
  • parse() -> set[Version] โ€” goes looking for the version inside the source content ๐Ÿ”Ž
โš ๏ธ Deprecated since version 9.20.0: function is unused and will be removed in a future release. We're grieving ๐Ÿ’€
  • replace(new_version: Version) -> str โ€” replaces the version in the source content with new_version, returns the updated content, slay โœจ
  • update_file_w_version(new_version: Version, noop: bool = False) -> Path | None โ€” reads the underlying file, replaces each occurrence of the matched pattern, writes the updated file. That's the whole arc ๐ŸŒŸ

Parameters

  • new_version โ€” the new version number, as a Version instance ๐ŸŽ€

๐Ÿงš Submodules

semantic_release.version.algorithm module

semantic_release.version.algorithm.next_version(
    repo: Repo,
    translator: VersionTranslator,
    commit_parser: CommitParser[ParseResult, ParserOptions],
    allow_zero_version: bool,
    major_on_zero: bool,
    prerelease: bool = False,
) -> Version

Evaluates the history inside repo, and based on the tags and commits hanging out in that repo history, it figures out the next semantic version that should be applied to a release. Main character detective energy ๐Ÿ”๐Ÿ‘‘

semantic_release.version.algorithm.tags_and_versions(
    tags: Iterable[Tag],
    translator: VersionTranslator,
) -> list[tuple[Tag, Version]]

Returns a list of 2-tuples, where every element is a tuple (tag, version) built from the tags in the Git repo and their corresponding Version per Version.from_tag. The list comes back sorted according to semver ordering rules. ๐Ÿ“Š

Any tags that translator doesn't match up with? They get ignored, no hard feelings ๐Ÿ™ˆ

semantic_release.version.declaration module

class semantic_release.version.declaration.VersionDeclarationABC(*args, **kwargs)

Bases: ABC

It's the ABC for classes that rep a location where a version gets declared somewhere inside the source tree of the repo ๐ŸŒณ

โš ๏ธ Deprecated since version 9.20.0: this got refactored into a composition paradigm using the new IVersionReplacer interface. This class will be removed in a future release. It's giving legacy ๐Ÿ’€
  • content: str โ€” the content of the source file where the version is stored. This property is cached in the instance variable _content ๐Ÿ’พ
  • abstractmethod parse() -> set[Version] โ€” returns a set of the versions that can be parsed out of the file. Since a source can match in multiple places, this method returns a set of matches. Generally there should only be one element in that set (like, even if the version is specified in multiple places, it should be the same version in each place) โ€” but enforcing that condition is not mandatory or expected, so don't come for us ๐Ÿ’…
  • abstractmethod replace(new_version: Version) -> str โ€” updates the versions. This method reads the underlying file, replaces each occurrence of the matched pattern, then writes the updated file. :param new_version: the new version number as a Version instance ๐ŸŽ€
  • write(content: str) -> None โ€” writes new content back to the source path. Use it alongside .replace(): ๐Ÿ“
>>> class MyVD(VersionDeclarationABC): ...
def parse(self): ...  ...     def replace(self, new_version: Version): ...  ...     def write(self, content: str): ...

>>> new_version = Version.parse("1.2.3")
>>> vd = MyVD("path", r"__version__ = (?P<version>\d+\d+\d+)")
>>> vd.write(vd.replace(new_version))

semantic_release.version.translator module

class semantic_release.version.translator.VersionTranslator(
    tag_format: str = 'v{version}',
    prerelease_token: str = 'rc',
    add_partial_tags: bool = False,
)

Bases: object

This class handles translating from Git tags into their corresponding Version instances. Glow-up translator ๐Ÿ”„โœจ

  • from_string(version_str: str) -> Version โ€” returns a Version instance from a string. It delegates directly to Version.parse, using the translator's own stored values for tag_format and prerelease ๐Ÿซถ
  • from_tag(tag: str) -> Version | None โ€” returns a Version instance from a Git tag, if tag_format matches the format that would have generated the tag from a version. Otherwise it returns None, no explanation, no closure ๐Ÿ˜ญ. For example, a tag of 'v1.2.3' should be matched if tag_format = 'v{version}, but not if tag_format = staging--v{version} ๐Ÿท๏ธ
  • str_to_tag(version_str: str) -> str โ€” formats a version string into a tag name ๐ŸŽ€

semantic_release.version.version module

class semantic_release.version.version.Version(
    major: int,
    minor: int,
    patch: int,
    *,
    prerelease_token: str = 'rc',
    prerelease_revision: int | None = None,
    build_metadata: str = '',
    tag_format: str = 'v{version}',
)

Bases: object

  • as_major_tag() -> str
  • as_minor_tag() -> str
  • as_patch_tag() -> str
  • as_semver_tag() -> str
  • as_tag() -> str
  • bump(level: LevelBump) -> Version โ€” returns a brand new Version instance according to the level you specified to bump ๐Ÿ‘‘. Heads up: this will intentionally drop the build metadata โ€” that should be added elsewhere for the specific build producing this version. So don't @ me about the missing metadata ๐Ÿ™ˆ
  • finalize_version() -> Version
  • is_prerelease: bool โ€” a property, bestie ๐Ÿ’…
  • classmethod parse(version_str: str, tag_format: str = 'v{version}', prerelease_token: str = 'rc') -> Version โ€” parses a version string into a Version instance. Inspired by semver.version:VersionInfo.parse, except this implementation doesn't allow optional minor and patch versions โ€” they're required, no skipping โœ‹

Parameters

  • prerelease_token โ€” will be ignored if the version string is a prerelease; the parsed token from version_str wins instead. Sorry not sorry ๐ŸŽ€
  • tag_format: str โ€” a property โœจ
  • to_prerelease(token: str | None = None, revision: int | None = None) -> Version

๐Ÿฆ‹ Submodules

semantic_release.const module

(no extra deets here, she's just vibing ๐ŸŒธ)

semantic_release.enums module

class semantic_release.enums.LevelBump(*values)

Bases: IntEnum

An IntEnum representing the valid types of bumps for a version. We use an IntEnum specifically so we can order the levels like the queens they are ๐Ÿ‘‘๐Ÿ“ˆ

  • MAJOR = 4
  • MINOR = 3
  • NO_RELEASE = 0
  • PATCH = 2
  • PRERELEASE_REVISION = 1
  • classmethod from_string(val: str) -> LevelBump โ€” bestie, this is how you turn a string into an actual LevelBump โœจ. It grabs the level straight from its string representation. And for backwards-compatibility (yes, the elders were thinking of us), dashes get swapped for underscores, so:
LevelBump.from_string("no-release") == LevelBump.NO_RELEASE

Equally, like, same energy:

LevelBump.from_string("minor") == LevelBump.MINOR

semantic_release.enums module (continued) ๐Ÿ’œ

  • class semantic_release.enums.SemanticReleaseLogLevels(*values) โ€” Bases: IntEnum. It's an IntEnum representing the log levels used by semantic-release. Every value, no skips, here we go ๐ŸŽ€:
    • CRITICAL = 50
    • DEBUG = 10
    • ERROR = 40
    • FATAL = 50
    • INFO = 20
    • SILLY = 5
    • WARNING = 30
ngl the fact that CRITICAL and FATAL are both 50 is giving "we share a locker" energy but okay ๐Ÿ‘‘ they said what they said.

semantic_release.errors module โ€” Custom Errors ๐Ÿ˜ญ

Okay so this is the feelings section. Every single custom exception, its base class, and exactly what it means. Deep breaths ๐Ÿซถ:

  • exception semantic_release.errors.AssetUploadError โ€” Bases: SemanticReleaseBaseError. Raised when there is a failure uploading an asset to a remote hvcs's release artifact storage.
  • exception semantic_release.errors.BuildDistributionsError โ€” Bases: SemanticReleaseBaseError. Raised when there is a failure to build the distribution files.
  • exception semantic_release.errors.CommitParseError โ€” Bases: SemanticReleaseBaseError. Raised when a commit cannot be parsed by a commit parser. Bestie, listen: custom commit parsers should also raise this Exception. That's the rule ๐Ÿ’…
  • exception semantic_release.errors.DetachedHeadGitError โ€” Bases: SemanticReleaseBaseError. Raised when the git repository is in a detached HEAD state.
  • exception semantic_release.errors.GitAddError โ€” Bases: SemanticReleaseBaseError. Raised when there is a failure to add files to the git index.
  • exception semantic_release.errors.GitCommitEmptyIndexError โ€” Bases: SemanticReleaseBaseError. Raised when there is an attempt to commit an empty index.
  • exception semantic_release.errors.GitCommitError โ€” Bases: SemanticReleaseBaseError. Raised when there is a failure to commit the changes.
  • exception semantic_release.errors.GitFetchError โ€” Bases: SemanticReleaseBaseError. Raised when there is a failure to fetch from the git remote.
  • exception semantic_release.errors.GitPushError โ€” Bases: SemanticReleaseBaseError. Raised when there is a failure to push to the git remote.
  • exception semantic_release.errors.GitTagError โ€” Bases: SemanticReleaseBaseError. Raised when there is a failure to tag the release.
  • exception semantic_release.errors.IncompleteReleaseError โ€” Bases: SemanticReleaseBaseError. Raised when there is a failure amongst one of the api requests when creating a release on a remote hvcs.
  • exception semantic_release.errors.InternalError โ€” Bases: SemanticReleaseBaseError. Raised when an internal error occurs, which should never happen. (Not me manifesting an exception that literally says it should never exist ๐Ÿซ )
  • exception semantic_release.errors.InvalidConfiguration โ€” Bases: SemanticReleaseBaseError. Raised when configuration is deemed invalid.
  • exception semantic_release.errors.InvalidParserOptions โ€” Bases: InvalidConfiguration. Raised when the parser options are invalid.
  • exception semantic_release.errors.InvalidVersion โ€” Bases: ValueError, SemanticReleaseBaseError. Raised when Version.parse attempts to parse a string containing an invalid version.
  • exception semantic_release.errors.LocalGitError โ€” Bases: SemanticReleaseBaseError. Raised when there is a failure with local git operations.
  • exception semantic_release.errors.MissingGitRemote โ€” Bases: SemanticReleaseBaseError. Raised when repository is missing the configured remote origin or upstream.
  • exception semantic_release.errors.MissingMergeBaseError โ€” Bases: SemanticReleaseBaseError. Raised when the merge base cannot be found with the current history. Generally because of a shallow git clone.
  • exception semantic_release.errors.NotAReleaseBranch โ€” Bases: InvalidConfiguration. Raised when semantic_release is invoked on a branch which isn't configured for releases.
  • exception semantic_release.errors.ParserLoadError โ€” Bases: SemanticReleaseBaseError. Raised when there is a failure to find, load, or instantiate a custom parser definition.
  • exception semantic_release.errors.SemanticReleaseBaseError โ€” Bases: Exception. The Base Exception from which all other custom Exceptions defined in semantic_release inherit. The mother of the family tree ๐ŸŒธ
  • exception semantic_release.errors.UnexpectedResponse โ€” Bases: SemanticReleaseBaseError. Raised when an HTTP response cannot be parsed properly or the expected structure is not found.
  • exception semantic_release.errors.UnknownUpstreamBranchError โ€” Bases: SemanticReleaseBaseError. Raised when the upstream branch cannot be determined.
  • exception semantic_release.errors.UpstreamBranchChangedError โ€” Bases: SemanticReleaseBaseError. Raised when the upstream branch has changed before pushing.

semantic_release.gitproject module โ€” git operations, but make it cute ๐Ÿฆ‹

Module for git related operations. Say it with me: module for git related operations โœจ

  • class semantic_release.gitproject.GitProject(directory: Path | str = '.', commit_author: Actor | None = None, credential_masker: MaskingFilter | None = None) โ€” Bases: object. The main character fr fr ๐Ÿ‘‘

Methods, all of them, in order:

  • git_add(paths: Sequence[Path | str], force: bool = False, strict: bool = False, noop: bool = False) -> None
  • git_commit(message: str, date: int | None = None, commit_all: bool = False, no_verify: bool = False, noop: bool = False) -> None
  • git_push_branch(remote_url: str, branch: str, noop: bool = False) -> None
  • git_push_tag(remote_url: str, tag: str, noop: bool = False, force: bool = False) -> None
  • git_tag(tag_name: str, message: str, isotimestamp: str, force: bool = False, noop: bool = False) -> None
  • git_unshallow(noop: bool = False) -> None โ€” Convert a shallow clone to a full clone by fetching the full history. ๐Ÿซถ

Parameters:

  • noop โ€” Whether or not to actually run the unshallow command.
  • is_dirty() -> bool
  • is_shallow_clone() -> bool โ€” Check if the repository is a shallow clone.

Returns: True if the repository is a shallow clone, False otherwise.

  • property logger: Logger
  • property project_root: Path
  • verify_upstream_unchanged(local_ref: str = 'HEAD', upstream_ref: str = 'origin', remote_url: str | None = None, noop: bool = False) -> None โ€” Verify that the upstream branch has not changed since the given local reference. Vibe check for your remote, basically ๐Ÿ’…

Parameters:

  • local_ref โ€” The local reference to compare against upstream (default: HEAD)
  • upstream_ref โ€” The name of the upstream remote or specific remote branch (default: origin)
  • remote_url โ€” Optional authenticated remote URL to use for fetching (default: None, uses configured remote)
  • noop โ€” Whether to skip the actual verification (for dry-run mode)

Raises UpstreamBranchChangedError โ€” If the upstream branch has changed. Yeah. It will absolutely tell on you. ๐Ÿ˜ญ


semantic_release.globals module โ€” the global girlies ๐ŸŒˆ

Semantic Release Global Variables. Two of them. Iconic.

  • semantic_release.globals.log_level: SemanticReleaseLogLevels = SemanticReleaseLogLevels.WARNING โ€” int: Logging level for semantic-release.
  • semantic_release.globals.logger: Logger = <Logger semantic_release (WARNING)> โ€” Logger for semantic-release.
Default is WARNING twice over, so if you're wondering why you're not seeing the fun debug lore, that's why bestie ๐Ÿ™ˆ

semantic_release.helpers module โ€” tiny little guys that help ๐Ÿงš

  • class semantic_release.helpers.ParsedGitUrl(scheme: str, netloc: str, namespace: str, repo_name: str) โ€” Bases: NamedTuple. Container for the elements parsed from a git URL ๐ŸŒธ
    • namespace: str โ€” Alias for field number 2
    • netloc: str โ€” Alias for field number 1
    • repo_name: str โ€” Alias for field number 3
    • scheme: str โ€” Alias for field number 0

(yes the field numbers are out of order in the listing, and no we are not fixing it, that's literally what it says ๐Ÿ’€)

  • semantic_release.helpers.check_tag_format(tag_format: str) -> None
  • semantic_release.helpers.dynamic_import(import_path: str) -> Any โ€” Dynamically import an object from a conventionally formatted "module:attribute" string.
  • semantic_release.helpers.format_arg(value: Any) -> str โ€” Helper to format an argument an argument for logging. (the docs really said "an argument an argument", and honestly? same ๐Ÿซถ)
  • semantic_release.helpers.get_number_from_str(string: str, default: int = -1, interpret_hex: bool = False) -> int
  • semantic_release.helpers.logged_function(logger: Logger) -> Callable[[_FuncType[_R]], _FuncType[_R]] โ€” Decorator which adds debug logging of a function's input arguments and return value. The input arguments are logged before the function is called, and the return value is logged once it has completed.

Parameters:

  • logger โ€” Logger to send output to.
  • semantic_release.helpers.parse_git_url(url: str) -> ParsedGitUrl โ€” Attempt to parse a string as a git url http[s]://, git://, file://, or ssh format, into a ParsedGitUrl. ๐Ÿ”ฅ
  • supported examples:
<http://git.mycompany.com/username/myproject.git>
<https://github.com/username/myproject.git> <https://gitlab.com/group/subgroup/myproject.git> <https://git.mycompany.com:4443/username/myproject.git>
git://host.xz/path/to/repo.git/
git://host.xz:9418/path/to/repo.git/
git@github.com:username/myproject.git                  <-- assumes ssh://
ssh://git@github.com:3759/myproject.git                <-- non-standard, but assume user 3759
ssh://git@github.com:username/myproject.git
ssh://git@bitbucket.org:7999/username/myproject.git
git+ssh://git@github.com:username/myproject.git
/Users/username/dev/remote/myproject.git               <-- Posix File paths
file:///Users/username/dev/remote/myproject.git
C:/Users/username/dev/remote/myproject.git             <-- Windows File paths
file:///C:/Users/username/dev/remote/myproject.git

REFERENCE: <https://stackoverflow.com/questions/31801271/what-are-the-supported-git-url-formats>

It raises ValueError if the url can't be parsed, bestie โ€” no vibes for unparseable URLs, sorry. ๐Ÿ˜ญ


๐ŸŒธ semantic_release.helpers.sort_numerically

semantic_release.helpers.sort_numerically(iterable: Iterable[str], reverse: bool = False, allow_hex: bool = False) -> list[str]

๐ŸŒธ semantic_release.helpers.text_reducer

semantic_release.helpers.text_reducer(text: str, filter_pair: tuple[Pattern[str], str]) -> str

Reduce function to apply mulitple filters to a string โœจ (yes it's spelled that way in the original, we don't make the rules ๐Ÿ’…)

๐ŸŒธ semantic_release.helpers.validate_types_in_sequence

semantic_release.helpers.validate_types_in_sequence(sequence: Sequence[Any], types: type <#semantic_release.cli.config.RemoteConfig.type> | tuple[type <#semantic_release.cli.config.RemoteConfig.type>, ...]) -> bool

Validate that all elements in a sequence are of a specific type ๐Ÿ” no liars allowed in the sequence โœ‹


๐Ÿซถ Contributing

Love Python Semantic Release? Want to help out? There are so many ways you can contribute to the project! ๐Ÿ‘‘

You can help by:

  • Reporting bugs and issues ๐Ÿ›
  • Suggesting new features ๐Ÿ’ก
  • Improving the documentation ๐Ÿ“š
  • Reviewing pull requests ๐Ÿ‘€
  • Contributing code ๐Ÿ’ป
  • Helping with translations ๐ŸŒ
  • Spreading the word about Python Semantic Release ๐Ÿ“ฃ
  • Participating in discussions ๐Ÿ’ฌ
  • Testing new features and providing feedback ๐Ÿงช

No matter how you choose to contribute, please check out our Contributing Guidelines <#contributing-guide> and know we appreciate your help! ๐Ÿ’–

Check out all the folks whom already contributed to Python Semantic Release and become one of them today! โญ

Contributors <https://github.com/python-semantic-release/python-semantic-release/graphs/contributors>

๐Ÿ’… Contributing

If you want to contribute that is awesome. Remember to be nice to others in issues and reviews. ๐Ÿซถ

Please remember to write tests for the cool things you create or fix. ๐Ÿงช

Unsure about something? No worries, open an issue <https://github.com/python-semantic-release/python-semantic-release/issues/new>. ๐Ÿ™ˆ

๐Ÿ’ฌ Commit messages

Since python-semantic-release is released with python-semantic-release we need the commit messages to adhere to the Conventional Commits Specification <https://www.conventionalcommits.org/en/v1.0.0>. Although scopes are optional, scopes are expected where applicable. Changes should be committed separately with the commit type they represent, do not combine them all into one commit. ๐Ÿšซ๐Ÿ“ฆ

If you are unsure how to describe the change correctly just try and ask about it in your pr. If we think it should be something else or there is a pull-request without tags we will help out in adding or changing them. ๐ŸŽ€

๐Ÿ‘‘ Releases

This package is released by python-semantic-release on each master build, thus if there are changes that should result in a new release it will happen if the build is green. ๐ŸŸขโœจ

๐Ÿ› ๏ธ Development

Install this module and the development dependencies

pip install -e .[dev,mypy,test]

And if you'd like to build the documentation locally ๐Ÿซง

pip install -e .[docs]
sphinx-autobuild --open-browser docs docs/_build/html

๐Ÿงช Testing

To test your modifications locally:

# Run type-checking, all tests across all supported Python versions
tox

# Run all tests for your current installed Python version (with full error output)
pytest -vv --comprehensive

# Run unit tests for your current installed Python version
pytest
# or
pytest -vv -m unit

# Run end-to-end tests for your current installed Python version (with full error output)
pytest -vv -m e2e [--comprehensive]

The --comprehensive flag is optional and will run all the variations of tests and it does take significantly longer to run. โณ no cap

๐Ÿ—๏ธ Building

This project is designed to be versioned and built by itself using the tool.semantic_release configuration in pyproject.toml. The setting tool.semantic_release.build_command defines the command to run to build the package. ๐Ÿ“ฆ

The following is a copy of the build_command setting which can be run manually to build the package locally:

pip install -e .[build]
python -m build .

๐ŸŒˆ GETTING STARTED

If you haven't done so already, install Python Semantic Release locally following the installation instructions. ๐Ÿ“

If you are using a CI/CD service, you may not have to add Python Semantic Release to your project's dependencies permanently, but for the duration of this guide for the initial setup, you will need to have it installed locally. ๐Ÿ’ป

๐ŸŽ€ Configuring PSR

Python Semantic Release ships with a reasonable default configuration but some aspects MUST be customized to your project. To view the default configuration, run the following command:

semantic-release generate-config

The output of the above command is the default configuration in TOML format without any modifications. If this is fine for your project, then you do not need to configure anything else. โœจ

PSR accepts overrides to the default configuration keys individually. If you don't define the key-value pair in your configuration file, the default value will be used. ๐Ÿ’–

By default, Python Semantic Release will look for configuration overrides in pyproject.toml under the TOML table [tool.semantic_release]. You may specify a different file using the -c/--config option, for example:

# In TOML format with top level table [semantic_release]
semantic-release -c releaserc.toml

# In JSON format with top level object key {"semantic_release": {}}
semantic-release -c releaserc.json

The easiest way to get started is to output the default configuration to a file, delete keys you do not need to override, and then edit the remaining keys to suit your project. ๐Ÿ’…

To set up in pyproject.toml, run the following command:

# In file redirect in bash
semantic-release generate-config --pyproject >> pyproject.toml

# Open your editor to edit the configuration
vim pyproject.toml

See also:

  • cmd-generate-config
  • Configuration <#configuration>

๐Ÿฆ‹ Configuring the Version Stamp Feature

One of the best features of Python Semantic Release is the ability to automatically stamp the new version number into your project files, so you don't have to manually update the version upon each release. The version that is stamped is automatically determined by Python Semantic Release from your commit messages which compliments automated versioning seamlessly. ๐ŸŒŸ

The most crucial version stamp is the one in your project metadata, which is used by the Python Package Index (PyPI) and other package managers to identify the version of your package. ๐Ÿ“ฆ

For Python projects, this is typically the version field in your pyproject.toml file. First, set up your project metadata with the base version value. If you are starting with a brand new project, set project.version = "0.0.0". If you are working on an existing project, set it to the last version number you released. Do not include any v prefix. ๐Ÿšซ

Important: The version number must be a valid SemVer version, which means it should follow the format MAJOR.MINOR.PATCH (e.g., 1.0.0). Python Semantic Release does NOT support Canonical version values defined in the PEP 440 <https://peps.python.org/pep-0440/> specification at this time. See Issue #455 <https://github.com/python-semantic-release/python-semantic-release/issues/455> for more details. Note that you can still define a SemVer version in the project.version field, and when your build is generated, the build tool will automatically generate a PEP 440 compliant version as long as you do NOT use a non-pep440 compliant pre-release token. ๐Ÿ˜ญ

Your project metadata might look like this in pyproject.toml:

[project]
name = "my-package"
version = "0.0.0"  # Set this to the last released version or "0.0.0" for new projects
description = "A sample Python package"

To configure PSR to automatically update this version number, you need to specify the file and value to update in your configuration. Since pyproject.toml uses TOML format, you will add the replacement specification to the tool.semantic_release.version_toml list. Update the following configuration in your pyproject.toml file to include the version variable location:

[tool.semantic_release]
version_toml = ["pyproject.toml:project.version"]

# Alternatively, if you are using poetry's 'version' key, then you would use:
version_toml = ["pyproject.toml:tool.poetry.version"]

If you have other TOML files where you want to stamp the version, you can add them to the version_toml list as well. In the above example, there is an implicit assumption that you only want the version as the raw number format. If you want to specify the full tag value (e.g. v-prefixed version), then include :tf for "tag format" at the end of the version variable specification. ๐Ÿท๏ธ

For non-TOML formatted files (such as JSON or YAML files), you can use the version_variables <#config-version-variables> configuration key instead. This feature uses an advanced Regular Expression to find and replace the version variable in the specified files. ๐Ÿ”โœจ

For Python files, its much more effective to use importlib instead which will allow you to dynamically import the version from your package metadata and not require your project to commit the version number bump to the repository. For example, in your package's base __init__.py

# my_package/__init__.py
from importlib.metadata import version as get_version

__version__ = get_version(__package__)
# Note: __package__ must match your 'project.name' as defined in pyproject.toml

See also:

  • Configuration specification of version_toml <#config-version-toml>
  • Configuration specification of version_variables <#config-version-variables>

๐Ÿ› ๏ธ Using PSR to Build your Project ๐Ÿ› ๏ธ

okay so now that you've stamped the version into your project files, PSR is literally right there to help you build your project artifacts as part of the versioning process. iconic behavior tbh. To turn it on, you need to specify the build command in your configuration โ€” no cap.

When does it fire? After the next version has been determined and stamped into your files, but before a release tag has been created. the timing?! immaculate. โฐโœจ

To set up the build command, add this to your pyproject.toml file:

[tool.semantic_release]
build_command = "python -m build --sdist --wheel ."

See also:

  • build_command <#config-build-command> โ€” the config spec for your build command, bestie.
  • build_command_env <#config-build-command-env> โ€” the config spec for the build command's environment variables. ๐Ÿซถ

๐Ÿ” Choosing a Commit Message Parser ๐Ÿ”

PSR reads your commit messages to figure out what kind of version bump your project needs. and it's not picky โ€” PSR supports multiple commit message parsing styles, so you can pick the one that fits your project's whole vibe.

Choose one of the supported commit parsers from Commit Parsing <#commit-parsing>, or bring your own and configure it in your pyproject.toml file. main character move. ๐Ÿ‘‘

Every commit parser comes with its own default configuration options, so if you wanna customize how the parser behaves, you have to spell out exactly which parser options you want to override:

[tool.semantic_release]
commit_parser = "conventional"

[tool.semantic_release.commit_parser_options]
minor_tags = ["feat"]
patch_tags = ["fix", "perf"]
parse_squash_commits = true
ignore_merge_commits = true

๐ŸŽ€ Choosing your Changelog ๐ŸŽ€

Before a release gets created, PSR generates a changelog from your project's commit messages. And this changelog is extremely customizable โ€” from the format all the way down to the content of each section. PSR ships with a default changelog template that gets used if you don't provide custom templates. For most projects the default is honestly enough, and it has its own set of configuration options to play with. ๐ŸŒธ

For basic customization, you can pick either a traditional Markdown formatted changelog (that's the default) or, if you want to integrate with a Sphinx Documentation project, the reStructuredText (RST) format. You can also choose the file name and the location of where the default changelog gets written.

To set your changelog location and changelog format, add this to your pyproject.toml file:

[tool.semantic_release.changelog.default_templates]
changelog_file = "docs/source/CHANGELOG.rst"
output_format = "rst" # or "md" for Markdown format

Second โ€” and honestly more important โ€” aspect of configuring your changelog: define your Commit Exclusion Patterns. Those are the patterns used to filter commits out of the changelog. Heads up bestie: PSR does NOT (yet) come with a built-in set of exclusion patterns, so you're defining them yourself, fr fr. These commit patterns should be in line with your project's commit parser configuration.

To set commit exclusion patterns for a conventional commits parser, add this to your pyproject.toml file:

[tool.semantic_release.changelog]
# Recommended patterns for conventional commits parser that is scope aware
exclude_commit_patterns = [
    '''chore(?:\([^)]*?\))?: .+''',
    '''ci(?:\([^)]*?\))?: .+''',
    '''refactor(?:\([^)]*?\))?: .+''',
    '''style(?:\([^)]*?\))?: .+''',
    '''test(?:\([^)]*?\))?: .+''',
    '''build\((?!deps\): .+)''',
    '''Initial [Cc]ommit.*''',
]

See also:

  • Changelog <#config-changelog> โ€” go customize your changelog ๐Ÿ’…
  • changelog.mode <#config-changelog-mode> โ€” pick the changelog mode ('update' or 'init')
  • Migrating an Existing Changelog <#changelog-templates-migrating-existing-changelog>

๐ŸŒˆ Defining your Release Branches ๐ŸŒˆ

PSR has a powerful feature for managing release types across multiple branches, so you can set your project up with different release branches for different purposes โ€” pre-release branches, beta branches, your stable releases, the whole crew. ๐Ÿฆ‹

Note: Most projects that do NOT publish pre-releases will be totally fine with PSR's built-in default. don't overthink it.

To define an alpha pre-release branch for when you're working on a fix or a new feature, add this to your pyproject.toml file:

[tool.semantic_release.branches.alpha]
# Matches branches with the prefixes 'feat/', 'fix/', or 'perf/'.
match = "^(feat|fix|perf)/.+"
prerelease = true
prerelease_token = "alpha"

Any time you execute semantic-release version on a branch with the prefix feat/, fix/, or perf/, PSR will determine if a version bump is needed, and if it is, the resulting version will be a pre-release version with the alpha token. For example:

  • Branch main โ€” Version Bump: Patch โ€” Current Version: 1.0.0 โ€” Next Version: 1.0.1
  • Branch fix/bug-1 โ€” Version Bump: Patch โ€” Current Version: 1.0.0 โ€” Next Version: 1.0.1-alpha.1

See also:

  • Multibranch Releases <#multibranch-releases> โ€” learn all about multi-branch releases and how to configure them. ๐Ÿ‘‘

๐Ÿš€ Configuring VCS Releases ๐Ÿš€

You can set Python Semantic Release up to create Releases in your remote version control system, so you can publish assets and release notes for your project. it's giving professional. โœจ

To do that, you need to place an authentication token in the appropriate environment variable, so Python Semantic Release can authenticate with the remote VCS to push tags, create releases, or upload files. ๐Ÿ”

๐Ÿ™ GitHub (GH_TOKEN) ๐Ÿ™

For local publishing to GitHub, you should use a personal access token and store it in your environment variables. Specify the name of the environment variable in your configuration setting remote.token <#config-remote-token>. The default is GH_TOKEN.

To generate a token go to <https://github.com/settings/tokens> and click on "Generate new token".

For Personal Access Token (classic), you will need the repo scope to write (ie. push) to the repository.

For fine-grained Personal Access Tokens, you will need the contents <https://docs.github.com/en/rest/authentication/permissions-required-for-fine-grained-personal-access-tokens#repository-permissions-for-contents> permission.

๐ŸฆŠ GitLab (GITLAB_TOKEN) ๐ŸฆŠ

A personal access token from GitLab. This is used for authenticating when pushing tags, publishing releases etc. This token should be stored in the GITLAB_TOKEN environment variable.

๐Ÿต Gitea (GITEA_TOKEN) ๐Ÿต

A personal access token from Gitea. This token should be stored in the GITEA_TOKEN environment variable.

๐Ÿชฃ Bitbucket (BITBUCKET_TOKEN) ๐Ÿชฃ

Bitbucket does not support uploading releases but can still benefit from automated tags and changelogs. The user has three options to push changes to the repository:

  1. Use SSH keys.
  2. Use an App Secret <https://support.atlassian.com/bitbucket-cloud/docs/push-back-to-your-repository/#App-secret>, store the secret in the BITBUCKET_TOKEN environment variable and the username in BITBUCKET_USER.
  3. Use an Access Token <https://support.atlassian.com/bitbucket-cloud/docs/repository-access-tokens> for the repository and store it in the BITBUCKET_TOKEN environment variable.

See also:

  • Changelog <#config-changelog> โ€” customize your project's changelog.
  • Custom Release Notes <#changelog-templates-custom-release-notes> โ€” customize the published release notes
  • version --vcs-release/--no-vcs-release โ€” enable/disable VCS release creation.

๐Ÿงช Testing your Configuration ๐Ÿงช

It's time to test your configuration! let's gooo ๐Ÿ˜ญ๐Ÿ’–

# 1. Run the command in no-operation mode to see what would happen
semantic-release -v --noop version

# 2. If the output looks reasonable, try to run the command without any history changes
#    '-vv' will give you verbose debug output, which is useful for troubleshooting
#    commit parsing issues.
semantic-release -vv version --no-commit --no-tag

# 3. Evaluate your repository to see the changes that were made but not committed
#    - Check the version number in your pyproject.toml
#    - Check the distribution files from the build command
#    - Check the changelog file for the new release notes

# 4. If everything looks good, make sure to commit/save your configuration changes
git add pyproject.toml
git commit -m "chore(config): configure Python Semantic Release"

# 5. Now, try to run the release command with your history changes but without pushing
semantic-release -v version --no-push --no-vcs-release

# 6. Check the result on your local repository
git status
git log --graph --decorate --all

# 7a. If you are happy with the release history and resulting commit & tag,
#     reverse your changes before trying the full release command.
git tag -d v0.0.1  # replace with the actual version you released
git reset --hard HEAD~1

# 7b. [Optional] Once you have configured a remote VCS token, try
#     running the full release command to update the remote repository.
semantic-release version --push --vcs-release
#    This is optional as you may not want a personal access token set up or make
#    make the release permanent yet.

See also:

  • cmd-version
  • Increasing Verbosity <#troubleshooting-verbosity>

โšก Configuring CI/CD โšก

PSR is meant to help you release at speed! See our CI/CD Configuration guides under the Automated Releases <#automatic> section. go off. ๐Ÿƒโ€โ™€๏ธ๐Ÿ’จ


โœ๏ธ Author โœ๏ธ

Python Semantic Release Team

2026, Python Semantic Release Team


and that's a wrap on the whole page, bestie ๐Ÿ’– this was version 10.6.1, dated Jul 07, 2026 โ€” go forth and release with main character energy, you've got this ๐Ÿซถโœจ

๐Ÿ“ฆ where this page comes from