Skip to content

ci: add PyPI publish workflow with trusted publishing#601

Merged
theomonnom merged 6 commits intomainfrom
ci/pypi-publish
Apr 8, 2026
Merged

ci: add PyPI publish workflow with trusted publishing#601
theomonnom merged 6 commits intomainfrom
ci/pypi-publish

Conversation

@theomonnom
Copy link
Copy Markdown
Member

Summary

  • Adds publish.yml workflow — single dropdown to pick version bump type, creates a release PR, and on merge builds + publishes all 3 packages to PyPI via trusted publishing (OIDC)
  • Adds release-gate.yml — blocks merge on release PRs until 2 approvals and verifies author is github-actions[bot]
  • Adds update_versions.py — bumps versions for livekit, livekit-api, and livekit-protocol, updates livekit-api's dependency on livekit-protocol

Setup needed

  1. Create a pypi GitHub environment (Settings → Environments)
  2. Add Release gate as required status check on main branch protection
  3. Configure trusted publishers on PyPI for each package (livekit, livekit-api, livekit-protocol):
    • Owner: livekit, Repo: python-sdks, Workflow: publish.yml, Environment: pypi

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +72 to +83
python .github/update_versions.py bump --bump-type "$key"
;;
patch-rc|minor-rc|major-rc)
bump="${key%-rc}"
python .github/update_versions.py bump --bump-type "$bump"
python .github/update_versions.py bump --pre rc
;;
next-rc)
python .github/update_versions.py bump --pre rc
;;
promote)
python .github/update_versions.py bump --bump-type release
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Click command invoked with spurious bump positional argument causes UsageError

The script defines a single click.command("bump") (not a click.group), but every invocation in the workflow passes bump as a positional argument (e.g., python .github/update_versions.py bump --bump-type patch). Since the click command has no positional parameters—only --pre and --bump-type options—click treats bump as an unexpected extra argument and raises UsageError: Got unexpected extra argument (bump). This was verified by running the script. All 5 invocations in .github/workflows/publish.yml:72-83 are affected, meaning the entire version-bumping workflow is broken and no release PR can be created.

Reproduction
import click, sys

@click.command('bump')
@click.option('--bump-type', default='patch')
def bump(bump_type): print(f'ok: {bump_type}')

sys.argv = ['script.py', 'bump', '--bump-type', 'patch']
bump()  # => UsageError: Got unexpected extra argument (bump)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@theomonnom theomonnom merged commit 315d441 into main Apr 8, 2026
8 of 9 checks passed
@theomonnom theomonnom deleted the ci/pypi-publish branch April 8, 2026 20:39
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 11 additional findings in Devin Review.

Open in Devin Review

Comment on lines +114 to +118
version=$(python -c "
import re, pathlib
m = re.search(r'__version__\s*=\s*[\"'\''](.*?)[\"'\'']', pathlib.Path('${version_file}').read_text())
print(m.group(1))
")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Python syntax error in 'Read new version' step due to incorrect shell quoting of single quotes inside double-quoted string

The python -c "..." command in the "Read new version" step uses the '\'' idiom to embed single quotes, but this technique only works in single-quoted bash strings (break out of single quote, add escaped single quote, re-enter single quote). Here the code is inside a double-quoted bash string (the python -c "..." argument), where single quotes are literal characters — so '\'' passes through as the literal four characters '\''.

This causes the Python raw string r'...' to terminate prematurely at the ' character after [", and Python then encounters a \ which triggers SyntaxError: unexpected character after line continuation character. I confirmed this by running the exact shell command locally.

This completely blocks the release PR creation workflow — the version can never be read, so the step fails and no release PR is created.

Reproduction and error output
$ version_file="livekit-rtc/livekit/rtc/version.py"
$ python3 -c "
import re, pathlib
m = re.search(r'__version__\s*=\s*[\"'\''](.*?)[\"'\'']', pathlib.Path('${version_file}').read_text())
print(m.group(1))
"
  File "<string>", line 3
    m = re.search(r'__version__\s*=\s*["'\''](.*?)["'\'']', ...)
                                          ^
SyntaxError: unexpected character after line continuation character
Prompt for agents
The python -c command in the 'Read new version' step (publish.yml lines 114-118) has broken shell quoting. The single-quote escaping idiom '\'' only works inside single-quoted bash strings, not inside the double-quoted python -c argument.

The simplest fix is to avoid the problematic quoting entirely. Since write_new_version in update_versions.py always writes double quotes around the version string, you can match only double quotes:

  version=$(python -c "
import re, pathlib
m = re.search(r'__version__\s*=\s*\"(.*?)\"', pathlib.Path('${version_file}').read_text())
print(m.group(1))
")

Alternatively, you could use a heredoc to avoid the quoting issues entirely:

  version=$(python3 << PYEOF
import re, pathlib
m = re.search(r'__version__\s*=\s*["\x27](.*?)["\x27]', pathlib.Path('${version_file}').read_text())
print(m.group(1))
PYEOF
)

Or, even better, add a --read-version flag to update_versions.py and call it directly, avoiding the fragile inline Python altogether.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +67 to +70
new_text = re.sub(
r'"livekit-protocol>=[\w.\-]+,',
f'"livekit-protocol>={new_protocol_version},',
old_text,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Major version bump of livekit-protocol creates unsatisfiable dependency constraint in livekit-api

update_api_protocol_dependency only updates the lower bound of the version constraint in livekit-api/pyproject.toml but leaves the upper bound untouched. When doing a major bump (e.g., 1.1.42.0.0), the existing dependency "livekit-protocol>=1.1.1,<2.0.0" at livekit-api/pyproject.toml:33 becomes "livekit-protocol>=2.0.0,<2.0.0" — an impossible constraint that no version can satisfy. This would produce a broken release PR for livekit-api.

Trace through the code

The regex r'"livekit-protocol>=[\w.\-]+,' matches "livekit-protocol>=1.1.1, (up to and including the first comma). The replacement inserts the new version >=2.0.0,, but <2.0.0 remains from the original text, yielding >=2.0.0,<2.0.0.

Prompt for agents
The update_api_protocol_dependency function in .github/update_versions.py only updates the lower bound (>=) of the livekit-protocol dependency in livekit-api/pyproject.toml but does not update the upper bound (<). When the new protocol version crosses a major boundary (e.g., 2.0.0), the constraint becomes >=2.0.0,<2.0.0 which is unsatisfiable.

The fix should also update the upper bound to the next major version. For example, if new_protocol_version is 2.0.0, the upper bound should become <3.0.0. One approach: parse the new version with packaging.version.Version, compute the next major (major+1), and use a regex that replaces both the lower and upper bounds. Alternatively, expand the regex to capture the full constraint and rebuild it.

Relevant code: update_api_protocol_dependency function at .github/update_versions.py:61-74, and the current pyproject.toml constraint at livekit-api/pyproject.toml:33.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant