Skip to content
Open
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
17 changes: 17 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
github-actions:
patterns: [ '*' ]

- package-ecosystem: pip
directory: /
schedule:
interval: weekly
groups:
python-dependencies:
patterns: [ '*' ]
91 changes: 75 additions & 16 deletions .github/workflows/pythonpublish.yml
Original file line number Diff line number Diff line change
@@ -1,34 +1,93 @@
# These workflows will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries

name: Upload Python Package

on:
release:
types: [ created ]
types: [ published ]

permissions:
contents: read

jobs:
deploy:
build:
name: Build and verify distributions
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Check out release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.release.tag_name }}

- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.x'
python-version: '3.14'
cache: pip

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade setuptools wheel twine readme_renderer[md]
pip install -r requirements.txt
python -m pip install uv==0.12.15
uv sync --locked --extra dev

- name: Build and publish
- name: Verify tag and package versions
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
uv run python - <<'PY'
import os
import api2ch

expected_tag = f'v{api2ch.__version__}'
release_tag = os.environ['RELEASE_TAG']
if release_tag != expected_tag:
raise SystemExit(
f'Release tag {release_tag!r} does not match package version {api2ch.__version__!r}'
)
PY

- name: Build distributions
run: uv run python -m build

- name: Check distributions
run: uv run python -m twine check --strict dist/*

- name: Test wheel installation
run: |
python -m venv "${RUNNER_TEMP}/api2ch-wheel"
"${RUNNER_TEMP}/api2ch-wheel/bin/python" -m pip install dist/*.whl
cd "${RUNNER_TEMP}"
"${RUNNER_TEMP}/api2ch-wheel/bin/python" -c "import api2ch; print(api2ch.__version__)"

- name: Test source distribution installation
run: |
python setup.py sdist bdist_wheel
twine upload dist/*
python -m venv "${RUNNER_TEMP}/api2ch-sdist"
"${RUNNER_TEMP}/api2ch-sdist/bin/python" -m pip install dist/*.tar.gz
cd "${RUNNER_TEMP}"
"${RUNNER_TEMP}/api2ch-sdist/bin/python" -c "import api2ch; print(api2ch.__version__)"

- name: Upload distributions
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: python-package-distributions
path: dist/
if-no-files-found: error

publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/api2ch
permissions:
id-token: write

steps:
- name: Download distributions
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: python-package-distributions
path: dist/

- name: Publish distributions to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
166 changes: 144 additions & 22 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,45 +1,167 @@
name: Python Tests

on: [ push, pull_request ]
on:
push:
branches: [ master ]
pull_request:
workflow_dispatch:
inputs:
live_tests:
description: Run tests against the live 2ch API
required: false
type: boolean
default: false

permissions:
contents: read

concurrency:
group: tests-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
test:
name: Python 3.14 / ${{ matrix.os }}
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
python-version: [ 3.6, 3.7, 3.8, 3.9, '3.10' ]

env:
OS: ${{ matrix.os }}
PYTHON: ${{ matrix.python-version }}
os: [ macos-latest, windows-latest ]

steps:
- uses: actions/checkout@v2
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
python-version: '3.14'
cache: pip

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
python -m pip install uv==0.12.15
uv sync --locked --extra dev

- name: Check formatting and lint
run: |
uv run ruff format --check api2ch tests
uv run ruff check api2ch tests

- name: Check types
run: uv run mypy api2ch

- name: Run offline tests
run: uv run pytest tests/ -vl -m "not integration"

coverage:
name: Python 3.14 / ubuntu-latest
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write

steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Lint with flake8
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.14'
cache: pip

- name: Install dependencies
run: |
# Stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
python -m pip install uv==0.12.15
uv sync --locked --extra dev

- name: Test with pytest
- name: Check formatting, lint and types
run: |
pytest -vl --cov=./ --cov-report=xml
uv run ruff format --check api2ch tests
uv run ruff check api2ch tests
uv run mypy api2ch

- name: Run offline tests
run: uv run pytest tests/ -vl -m "not integration" --cov=api2ch --cov-report=xml

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@0b35c9ecc4f0529d0eb674914510c22f85b196b4 # v7.1.0
with:
env_vars: OS,PYTHON
use_oidc: true
fail_ci_if_error: true
files: coverage.xml
flags: unittests

package:
name: Build package
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.14'
cache: pip

- name: Install dependencies
run: |
python -m pip install uv==0.12.15
uv sync --locked --extra dev

- name: Build distributions
run: uv run python -m build

- name: Check distributions
run: uv run python -m twine check --strict dist/*

- name: Test wheel installation
shell: bash
run: |
python -m venv "${RUNNER_TEMP}/api2ch-wheel"
"${RUNNER_TEMP}/api2ch-wheel/bin/python" -m pip install dist/*.whl
cd "${RUNNER_TEMP}"
"${RUNNER_TEMP}/api2ch-wheel/bin/python" -c "import api2ch; print(api2ch.__version__)"

- name: Test source distribution installation
shell: bash
run: |
python -m venv "${RUNNER_TEMP}/api2ch-sdist"
"${RUNNER_TEMP}/api2ch-sdist/bin/python" -m pip install dist/*.tar.gz
cd "${RUNNER_TEMP}"
"${RUNNER_TEMP}/api2ch-sdist/bin/python" -c "import api2ch; print(api2ch.__version__)"

- name: Upload distributions
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: python-package-distributions
path: dist/
if-no-files-found: error
retention-days: 7

integration:
name: Live API tests
if: github.event_name == 'workflow_dispatch' && inputs.live_tests
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.14'
cache: pip

- name: Install dependencies
run: |
python -m pip install uv==0.12.15
uv sync --locked --extra dev

- name: Run live tests
run: uv run pytest tests/ -vl -m integration
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ MANIFEST
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
pip-*/

# Unit test / coverage reports
htmlcov/
Expand All @@ -50,6 +51,7 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
pytest-of-*/

# Translations
*.mo
Expand Down Expand Up @@ -104,6 +106,7 @@ celerybeat.pid
# Environments
.env
.venv
.venv*/
env/
venv/
ENV/
Expand Down
22 changes: 22 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Publishing

Releases are published to PyPI by GitHub Actions. The workflow builds and checks the wheel and source distribution first, then publishes those exact files with PyPI Trusted Publishing. No PyPI password or API token is stored in GitHub.

## One-time setup

1. Create a `pypi` environment in the GitHub repository and add required reviewers to protect it.
2. Add a Trusted Publisher for `api2ch` in PyPI with these values:
- Owner: `uburuntu`
- Repository: `api2ch`
- Workflow: `pythonpublish.yml`
- Environment: `pypi`

## Release

1. Set `api2ch.__version__` to the release version and merge the change.
2. Run the manual live API workflow and confirm that the current contract checks pass.
3. Create a GitHub release whose tag is `v` followed by the same version, for example `v2.0.0`.
4. Publish the GitHub release.
5. Review the completed build job and approve the `pypi` environment deployment.

The workflow stops before publishing if the tag and package versions do not match, or if either distribution fails its metadata or installation checks.
Loading