Skip to content

Commit 44c3879

Browse files
committed
git tag
1 parent 626e2e5 commit 44c3879

File tree

3 files changed

+111
-9
lines changed

3 files changed

+111
-9
lines changed

src/git_tag.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import argparse
2+
import subprocess
3+
import tomllib
4+
from pathlib import Path
5+
6+
7+
def extract_version(toml_path: Path | str) -> str:
8+
"""Load toml_path and return the version string.
9+
10+
Checks [project].version (PEP 621) first, then [tool.poetry].version. Raises KeyError if no version field is found.
11+
"""
12+
path = Path(toml_path)
13+
with path.open("rb") as f:
14+
data = tomllib.load(f)
15+
16+
project = data.get("project", {})
17+
if version := project.get("version"):
18+
return version
19+
20+
tool = data.get("tool", {})
21+
poetry = tool.get("poetry", {})
22+
if version := poetry.get("version"):
23+
return version
24+
25+
raise KeyError(f"No version field found in {path!r}") # noqa: TRY003 # not worth a custom exception
26+
27+
28+
def ensure_tag_not_present(tag: str, remote: str) -> None:
29+
try:
30+
_ = subprocess.run( # noqa: S603 # this is trusted input, it's our own arguments being passed in
31+
["git", "ls-remote", "--exit-code", "--tags", remote, f"refs/tags/{tag}"], # noqa: S607 # if `git` isn't in PATH already, then there are bigger problems to solve
32+
check=True,
33+
stdout=subprocess.DEVNULL,
34+
stderr=subprocess.DEVNULL,
35+
)
36+
raise Exception(f"Error: tag '{tag}' exists on remote '{remote}'") # noqa: TRY002,TRY003 # not worth a custom exception
37+
except subprocess.CalledProcessError:
38+
# tag not present, continue
39+
return
40+
41+
42+
def main():
43+
parser = argparse.ArgumentParser(
44+
description=(
45+
"Extract the version from a pyproject.toml file, "
46+
"confirm that git tag v<version> is not present, or "
47+
"create and push the tag to a remote."
48+
)
49+
)
50+
_ = parser.add_argument(
51+
"file",
52+
nargs="?",
53+
default="pyproject.toml",
54+
help="Path to pyproject.toml (default: pyproject.toml)",
55+
)
56+
_ = parser.add_argument(
57+
"--confirm-tag-not-present",
58+
action="store_true",
59+
help=("Check that git tag v<version> is NOT present on the remote. If the tag exists, exit with an error."),
60+
)
61+
_ = parser.add_argument(
62+
"--push-tag-to-remote",
63+
action="store_true",
64+
help=(
65+
"Create git tag v<version> locally and push it to the remote. "
66+
"Internally confirms the tag is not already present."
67+
),
68+
)
69+
_ = parser.add_argument(
70+
"--remote",
71+
default="origin",
72+
help="Name of git remote to query/push (default: origin)",
73+
)
74+
args = parser.parse_args()
75+
76+
ver = extract_version(args.file)
77+
78+
tag = f"v{ver}"
79+
80+
if args.push_tag_to_remote:
81+
ensure_tag_not_present(tag, args.remote)
82+
_ = subprocess.run(["git", "tag", tag], check=True) # noqa: S603,S607 # this is trusted input, it's our own pyproject.toml file. and if `git` isn't in PATH, then there are larger problems anyway
83+
_ = subprocess.run(["git", "push", args.remote, tag], check=True) # noqa: S603,S607 # this is trusted input, it's our own pyproject.toml file. and if `git` isn't in PATH, then there are larger problems anyway
84+
return
85+
86+
if args.confirm_tag_not_present:
87+
ensure_tag_not_present(tag, args.remote)
88+
return
89+
90+
# Default behavior: just print the version
91+
print(ver) # noqa: T201 # specifically printing this out so CI pipelines can read the value from stdout
92+
93+
94+
if __name__ == "__main__":
95+
main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../src/git_tag.py

template/.github/workflows/publish.yaml.jinja

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,7 @@ jobs:
116116
path: dist/
117117
if-no-files-found: error
118118

119-
{% endraw %}{% if python_package_registry == "AWS CodeArtifact" %}{% raw %}
120-
- name: OIDC Auth for Publishing to CodeArtifact
121-
uses: aws-actions/configure-aws-credentials@{% endraw %}{{ gha_configure_aws_credentials }}{% raw %}
122-
with:
123-
role-to-assume: arn:aws:iam::{% endraw %}{{ aws_central_infrastructure_account_id }}{% raw %}:role/GHA-CA-Primary-{% endraw %}{{ repo_name }}{% raw %}
124-
aws-region: {% endraw %}{{ aws_org_home_region }}{% raw %}
125119

126-
{% endraw %}{% endif %}{% raw %}
127120

128121

129122

@@ -133,7 +126,7 @@ jobs:
133126
runs-on: {% endraw %}{{ gha_linux_runner }}{% raw %}
134127
environment:
135128
name: testpypi
136-
url: https://pypi.org/p/{% endraw %}{{ package_name | replace('_', '-') }}{% raw %}
129+
url: https://test.pypi.org/p/{% endraw %}{{ package_name | replace('_', '-') }}{% raw %}
137130
permissions:
138131
attestations: write
139132
id-token: write
@@ -143,7 +136,20 @@ jobs:
143136
with:
144137
name: python-package-distributions
145138
path: dist/
146-
- name: Publish distribution 📦 to PyPI
139+
{% endraw %}{% if python_package_registry == "AWS CodeArtifact" %}{% raw %}
140+
- name: OIDC Auth for Publishing to CodeArtifact
141+
uses: aws-actions/configure-aws-credentials@{% endraw %}{{ gha_configure_aws_credentials }}{% raw %}
142+
with:
143+
role-to-assume: arn:aws:iam::{% endraw %}{{ aws_central_infrastructure_account_id }}{% raw %}:role/GHA-CA-Primary-{% endraw %}{{ repo_name }}{% raw %}
144+
aws-region: {% endraw %}{{ aws_org_home_region }}{% raw %}
145+
146+
- name: Publish distribution to Code Artifact
147+
run: |
148+
. .devcontainer/code-artifact-auth.sh
149+
uv publish --verbose --index code-artifact-primary --username aws --password "$TWINE_PASSWORD"
150+
151+
{% endraw %}{% endif %}{% raw %}
152+
- name: Publish distribution to PyPI
147153
uses: pypa/gh-action-pypi-publish@v1.12.4
148154
with:
149155
attestations: false

0 commit comments

Comments
 (0)