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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/scripts/release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Select a version tag without modifying Git refs."""
import os
from pathlib import Path
import re
import subprocess


def git(*args):
return subprocess.check_output(['git', *args], text=True).strip()


def select():
version = Path('VERSION').read_text().strip()
if not re.fullmatch(r'(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)', version):
raise ValueError('VERSION must be X.Y.Z')
if Path('RELEASE').read_text().strip() != version:
raise ValueError('VERSION and RELEASE differ')
tag = 'v' + version
head = git('rev-parse', 'HEAD')
ref = 'refs/tags/' + tag
exists = subprocess.run(['git', 'show-ref', '--verify', '--quiet', ref]).returncode == 0
if exists:
tagged = git('rev-parse', ref + '^{commit}')
subprocess.run(['git', 'merge-base', '--is-ancestor', tagged, head], check=True)
return tag, head, not exists


if __name__ == '__main__':
requested = os.environ.get('RELEASE_TAG', '')
if os.environ.get('GITHUB_REF', '').startswith('refs/tags/'):
requested = os.environ['GITHUB_REF'].removeprefix('refs/tags/')
if not re.fullmatch(r'v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)', requested):
raise ValueError('Expected vX.Y.Z')
sha = git('rev-parse', 'HEAD')
subprocess.run(['git', 'merge-base', '--is-ancestor', sha, 'origin/main'], check=True)
if git('show', sha + ':VERSION') != requested[1:] or git('show', sha + ':RELEASE') != requested[1:]:
raise ValueError('Tag and version files differ')
tag, create, publish = requested, False, True
elif requested:
if os.environ.get('GITHUB_EVENT_NAME') != 'workflow_dispatch' or os.environ.get('GITHUB_REF') != 'refs/heads/main':
raise ValueError('Manual publication must run from main')
if not re.fullmatch(r'v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)', requested):
raise ValueError('Expected vX.Y.Z')
sha = git('rev-parse', '--verify', 'refs/tags/' + requested + '^{commit}')
subprocess.run(['git', 'merge-base', '--is-ancestor', sha, 'HEAD'], check=True)
if git('show', sha + ':VERSION') != requested[1:] or git('show', sha + ':RELEASE') != requested[1:]:
raise ValueError('Tag and version files differ')
tag, create, publish = requested, False, True
else:
tag, sha, create = select()
publish = create or git('rev-parse', 'refs/tags/' + tag + '^{commit}') == sha
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
output.write(f'tag={tag}\nsha={sha}\ncreate={str(create).lower()}\npublish={str(publish).lower()}\n')
125 changes: 125 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
name: Tests, version tag and PyPI
on:
push:
branches: [main]
tags: ['v*']
pull_request:
workflow_dispatch:
inputs:
tag:
description: 'Existing tag to publish (optional, for example v0.1.0)'
required: false
type: string
permissions:
contents: read
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
python: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- run: python -m pip install -r requirements.txt build PyYAML
- run: python -m unittest discover -s tests -v
- name: Check version consistency
run: |
python - <<'PY'
import ast, pathlib, yaml
version = pathlib.Path('VERSION').read_text().strip()
assert version == pathlib.Path('RELEASE').read_text().strip()
config = yaml.safe_load(pathlib.Path('setup.yml').read_text())
assert config['version'] == config['release'] == version
PY
- run: python -m build
- name: Install and smoke-test wheel
run: |
python -m pip install dist/*.whl
cd "$RUNNER_TEMP"
python -c "import updownio; assert updownio.service('checks', api_key='dummy').timeout == 60"
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python -m pip install -r docs/requirements.txt
- run: python -m sphinx -W --keep-going -b html docs docs/_build/html

tag:
outputs:
publish: ${{ steps.release.outputs.publish }}
sha: ${{ steps.release.outputs.sha }}
tag: ${{ steps.release.outputs.tag }}
needs: [tests, docs]
if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-latest
permissions:
contents: write
concurrency:
group: updownio-tag
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Select tag
id: release
env:
RELEASE_TAG: ${{ inputs.tag }}
run: python .github/scripts/release.py
- name: Create tag after successful tests
if: steps.release.outputs.create == 'true'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.release.outputs.tag }}
RELEASE_SHA: ${{ steps.release.outputs.sha }}
run: gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" -f ref="refs/tags/$TAG" -f sha="$RELEASE_SHA"

build-pypi:
needs: tag
if: needs.tag.outputs.publish == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.tag.outputs.sha }}
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python -m pip install -r requirements.txt build twine
- run: python -m unittest discover -s tests -v
- run: python -m build
- run: python -m twine check --strict dist/*
- uses: actions/upload-artifact@v4
with:
name: pypi-distributions
path: dist/*
if-no-files-found: error
retention-days: 7
publish-pypi:
needs: build-pypi
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/updownio/
permissions:
id-token: write
concurrency:
group: updownio-pypi
cancel-in-progress: false
steps:
- uses: actions/download-artifact@v4
with:
name: pypi-distributions
path: dist/
- name: Publish to PyPI using Trusted Publishing
uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true
10 changes: 10 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
updownio (0.1.0) unstable; urgency=medium

* Isolate service clients and validate configuration.
* Fix arrays, nested headers, empty responses and URL-based lookups.
* Preserve caller data, prevent credential forwarding on redirects.
* Add offline regression tests, CI, automatic tags and PyPI publishing.
* Repair source distributions and document Python 3.10+ support.

-- Adrien DELLE CAVE (Decryptus) <adc@doowan.net> Tue, 22 Sep 2026 12:00:00 +0000

updownio (0.0.7) unstable; urgency=medium

* Fixed README.md.
Expand Down
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include requirements.txt setup.yml VERSION RELEASE CHANGELOG
recursive-include tests *.py
recursive-include docs *.md *.rst *.py *.txt
recursive-include .github/scripts *.py
121 changes: 10 additions & 111 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,111 +1,10 @@
#!/usr/bin/make -f
# -*- makefile -*-

CHANGELOG_PATH ?= "$(CURDIR)/CHANGELOG"
DCH_PATH ?= dch
DPKG_PARSECHANGELOG_PATH ?= dpkg-parsechangelog
EDITOR_PATH ?= vim
FIND_PATH ?= find
GIT_PATH ?= git
GREP_PATH ?= grep
J2_PATH ?= j2
MAKE_PATH ?= make
PYTHON2_PATH ?= python2
PYTHON3_PATH ?= python3
RM_PATH ?= rm
SED_PATH ?= sed
TWINE_PATH ?= twine

export PROJECT_RELEASE=$(shell $(DPKG_PARSECHANGELOG_PATH) -S Version -l $(CHANGELOG_PATH))
export PROJECT_VERSION=$(shell $(DPKG_PARSECHANGELOG_PATH) -S Version -l $(CHANGELOG_PATH) | $(SED_PATH) -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p')
export PROJECT_GIT_RELEASE="$(shell echo -n "$(PROJECT_RELEASE)"| $(SED_PATH) 's/~/-/g')"
export PROJECT_COPYRIGHT_YEAR=$(shell date +'%Y')

changelog:
EDITOR="$(EDITOR_PATH)" $(DCH_PATH) -i -D unstable --no-auto-nmu -m --changelog "$(CHANGELOG_PATH)"
$(MAKE_PATH) release
$(MAKE_PATH) version

release:
$(shell echo $(PROJECT_RELEASE) > "$(CURDIR)/RELEASE")

version:
$(shell echo $(PROJECT_VERSION) > "$(CURDIR)/VERSION")

setup-config:
$(MAKE_PATH) release
$(MAKE_PATH) version
$(J2_PATH) setup.yml.j2 -o setup.yml

docs: setup-config
cd docs && $(MAKE_PATH) html

clean-docs:
cd docs && $(MAKE_PATH) clean

build-git-commit:
$(DPKG_PARSECHANGELOG_PATH) -S Changes -l "$(CHANGELOG_PATH)"|\
$(GREP_PATH) '^\s\+\*'|\
$(SED_PATH) -e 's/^\s\+\*\s\(.\+\)$$/\1/'|\
$(GIT_PATH) commit -a -F - || true

build-git-version:
if [ -d "bin" ]; then \
$(FIND_PATH) bin/ -type f | while read file; \
do \
$(SED_PATH) -i -e "s/^\(__version__\s*=\s*\)['\"]\(.*\)['\"]/\1'${PROJECT_VERSION}'/" "$$file"; \
done \
fi ;

push-git-version:
$(GIT_PATH) push
$(GIT_PATH) tag -a "v$(PROJECT_VERSION)" -m "version: $(PROJECT_VERSION)"
$(GIT_PATH) push origin "v$(PROJECT_VERSION)"

push-git-release:
$(GIT_PATH) push
$(GIT_PATH) tag -a "$(PROJECT_GIT_RELEASE)-release" -m "release: $(PROJECT_GIT_RELEASE)"
$(GIT_PATH) push origin "$(PROJECT_GIT_RELEASE)-release"

git-push:
$(MAKE_PATH) changelog
$(MAKE_PATH) setup-config
$(MAKE_PATH) build-git-version
$(MAKE_PATH) build-git-commit

git-version:
$(MAKE_PATH) git-push
$(MAKE_PATH) push-git-version

git-release:
$(MAKE_PATH) git-push
$(MAKE_PATH) push-git-release

build-pip2: clean-pip
$(PYTHON2_PATH) setup.py bdist_wheel

build-pip3: clean-pip
$(PYTHON3_PATH) setup.py bdist_wheel

push-pip2:
$(TWINE_PATH) upload dist/*

push-pip3:
$(TWINE_PATH) upload dist/*

push-pip:
$(MAKE_PATH) build-pip2
$(MAKE_PATH) push-pip2
$(MAKE_PATH) build-pip3
$(MAKE_PATH) push-pip3

clean-pip:
$(RM_PATH) -rf build/ dist/

clean-python:
$(FIND_PATH) -type f -name "*.pyc" -delete
$(FIND_PATH) -type d -name __pycache__ -exec rm -rf '{}' +

clean: clean-docs clean-pip clean-python

.PHONY: changelog release version
.PHONY: test build docs check
PYTHON ?= python3
test:
$(PYTHON) -m unittest discover -s tests -v
build:
$(PYTHON) -m build
docs:
$(PYTHON) -m sphinx -W --keep-going -b html docs docs/_build/html
check: test build docs
$(PYTHON) -m twine check --strict dist/*
Loading
Loading