Skip to content

Programmatic Python API for custom release workflows #1407

Description

@bilelomrani1

Feature request

Description

Python-semantic-release operates primarily as a CLI tool, which works well for standard CI/CD pipelines but limits integration with custom Python scripts and applications. This proposal adds a programmatic Python API allowing users to import and call semantic release functionality directly from code.

The API would expose a SemanticReleaseContext configuration object and functions for building release history, rendering changelogs and release notes, and computing the next version.

Use cases

Teams often want to post release notes to Slack, Discord, or other services after a release. Today this requires parsing CLI output or reading generated files. A programmatic API would let them call render_release_notes() and pass the result directly to their notification client.

Monorepo maintainers sometimes need custom release coordination beyond what the CLI supports. A programmatic API lets them orchestrate releases with Python scripts, calling core functions in whatever order their workflow requires.

Testing release configuration currently means mocking subprocess calls. With a programmatic API, teams can import functions directly and write unit tests that verify their commit parser produces expected version bumps.

Possible implementation

A SemanticReleaseContext class would encapsulate configuration, loadable from pyproject.toml or constructed with explicit parameters:

from semantic_release import SemanticReleaseContext

# Load from pyproject.toml
ctx = SemanticReleaseContext.from_config_file()

# Or construct directly
ctx = SemanticReleaseContext(
    repo_dir=Path("."),
    hvcs_client=Github(remote_url="https://github.com/owner/repo"),
    commit_parser=ConventionalCommitParser(),
    version_translator=VersionTranslator(),
)

Core functions would accept the context and return Python objects:

from semantic_release import (
    SemanticReleaseContext,
    build_release_history,
    render_release_notes,
    compute_next_version,
)

ctx = SemanticReleaseContext.from_config_file()
history = build_release_history(ctx)

# Post release notes to Slack
latest_version = list(history.released.keys())[0]
notes = render_release_notes(ctx, history, latest_version)
slack_client.post_message(channel="#releases", text=notes)

# Conditional logic before releasing
next_ver = compute_next_version(ctx)
if next_ver.major > current_version.major:
    require_manual_approval()

Existing approaches in the JS ecosystem

The JavaScript semantic-release package provides a programmatic API returning a result object with release information, commits, and changelog content.

Nx Release programmatic API exposes separate functions for versioning, changelog generation, and publishing through their programmatic API, allowing custom logic between phases.

Both recognize that releases involve special cases a CLI cannot fully address. A programmatic API lets users build exactly what they need.

Invoking the CLI via subprocess.run() is possible but has drawbacks: string parsing is fragile, intermediate results are inaccessible, exceptions become exit codes, and testing is harder. A native Python API provides proper types, meaningful exceptions, IDE integration, and composability with other tools.

Activity

  1. added
    featureA new feature or a feature request
    triagewaiting for initial maintainer review
    on Jan 2, 2026
  2. codejedi365 commented on Jan 2, 2026

    @codejedi365
    Contributor

    The undocumented goals of the future are to minimize the command function complexity and instead call an external structure to execute each piece of the workflow. I have not landed on a design yet but that goal changes the core structure of PSR where the CLI functions are only an interface into the grander library.

    I find the idea of a context or defined api a bit interesting as that would define a more rigid exposed contract to the user as opposed to normal python imports (available today because of how python works). Which is limiting to development on my side as I'm not sure what 3rd party scripts are using. So this sounds more ideal.

    In my fork you will find a few different branches that redefine the core structure like a project class that helps to facilitate caching, increase performance, and reduce duplication of actions. I do plan to separate a stamp files command, a separate print out release notes command and rearchitect the config. Other goals are to convert to all composite object structures which separate out which HVCS or parsers used and enable more custom plugins. These are all tangential to your proposal but will significantly impact work as me moving around files and moving functions will be conflicting.

    I'll be slow to respond as I'm busy today but less busy this weekend.

  3. codejedi365 commented on Jan 2, 2026

    @codejedi365
    Contributor

    I don't want to say stop working, I just feel bad that there is conflicting efforts. Most people don't put the work in that you are and just ask for new features. If you find something that you want to work on that I haven't started and you don't anticipate conflicts then let me know and make it a reality.

  4. bilelomrani1 commented on Jan 2, 2026

    @bilelomrani1
    Author

    Hi @codejedi365, thanks for the thoughtful response and for sharing context on the v11 plans.

    I want to clarify that the burst of issues/PRs isn't meant to pressure you 😁 I'm actively exploring this space, that's on my plate right now, and wanted to capture everything while it's fresh. The goal is to let you batch reviews whenever you have time, so we can work asynchronously. No rush on your end.

    To give you the full picture: I'm instrumenting a monorepo with multiple independently-released packages. Our release process needs to be atomic: if any step fails, the whole transaction rolls back (no side effect ie no tag pushed, no artifacts published, no notifications sent, except on a dev channel to log the release error). The workflow looks like:

    1. Manual trigger: we have a Github action workflow with a manual_dispatch trigger to launch release
    2. Determine next version: analyze commits, compute the next semantic version
    3. Create tag: we use dynamic versioning inferred from VCS tags (setuptools-scm style), so the tag must exist before building. This relates to Support for VCS tag-based dynamic versioning during build #1318 PSR currently builds before tagging, which doesn't work for our setup. But I can work around it today by decomposing into multiple CLI calls (something like semantic-release version --skip-build, then uv build, then the rest), so no big deal, that's not actually why I need more flexibility
    4. Build wheel
    5. Build Docker image packages the wheel into a container
    6. Run integration + E2E tests validates the container before release
    7. Publish if all tests pass: push wheel to private PyPI registry, push image to container registry, send Slack notification with release notes to consumers
    8. Rollback on failure if any step fails: delete the local tag (before it's pushed), send error notification to a dev Slack channel, exit with error

    The key requirement is that steps 1-5 must succeed before any persistent external side effects (pushing tag, publishing artifacts, notifying users). This kind of staged, transactional release process is very custom, I'm not expecting PSR to support this out of the box, but would be straightforward with a programmatic API that exposes the building blocks to me.

    I think this pattern (transactional releases with custom validation gates between steps) is broadly useful beyond my specific environment.

    On the other PRs

    The supporting features I submitted stem from the same project:

    These are smaller, more self-contained changes that are less likely to conflict with the v11 refactoring. Happy for you to prioritize these for review if that's easier whenever you have time!

    On the API design

    I'm completely flexible here. My proposal was the most immediate, minimal-change extraction I could think of, but if you have a more comprehensive design in mind for v11, I'm happy to abandon my PR entirely and contribute to your approach instead. I can wait for you to document the v11 architecture and then help implement pieces that align with my needs if that can help, just let me know.

    A few options that work for me:

    1. You document the v11 vision when you have time, and I contribute to areas that overlap with my needs if you're open to it
    2. I focus on the smaller PRs (feat(changelog): Support remote git repositories as template sources #1404, fix(changelog): support custom Jinja extensions in release notes templates #1405, feat(changelog): Add changelog.output_dir configuration option #1406) which seem lower-conflict, while the API discussion continues. If you find time to review them this weekend or whenever, that would be awesome
    3. You point me to specific areas where help would be useful

    Let me know what works best for you. I'm here to contribute, not to create extra work 😆

  5. added
    confirmedPrevent from becoming stale
    and removed
    triagewaiting for initial maintainer review
    on Apr 4, 2026
  6. github-actions commented on Jul 3, 2026

    @github-actions

    It has been 90 days since the last update on this confirmed issue. @python-semantic-release/team can you provide an update on the status of this issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    confirmedPrevent from becoming stalefeatureA new feature or a feature requestneeds-updateNeeds status update from maintainers

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions