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
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: pip
directory: /
schedule:
interval: monthly
groups:
dev-tooling:
patterns: ["ruff", "mypy", "pytest*", "pre-commit", "twine", "types-*", "*-stubs"]
docs:
patterns: ["mkdocs*"]

# Workflow actions are pinned by SHA, so this is the only thing that moves them.
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
groups:
actions:
patterns: ["*"]
84 changes: 84 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: ci

on:
push:
branches: [master]
pull_request:
workflow_dispatch:

# Actions are pinned by commit SHA rather than by tag: a tag can be moved to point at new code,
# a SHA cannot. Dependabot keeps them current.
permissions:
contents: read

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

jobs:
lint:
name: lint and type-check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
enable-cache: true
- run: uv sync --all-extras
- name: ruff check
run: uv run ruff check --output-format=github .
- name: ruff format
run: uv run ruff format --check .
- name: mypy
run: uv run mypy

test:
name: test (py${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
enable-cache: true
python-version: ${{ matrix.python-version }}
- run: uv sync --all-extras
- name: pytest
run: uv run pytest --cov --cov-report=term-missing --cov-report=xml

build:
name: build and verify the distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
enable-cache: true
- run: uv build
- name: twine check
run: uv run --with twine==7.0.0 twine check dist/*
- name: install the wheel into a clean environment and import it
run: |
uv venv /tmp/fresh
VIRTUAL_ENV=/tmp/fresh uv pip install dist/*.whl
/tmp/fresh/bin/python -c "
import pathlib, python_som
assert (pathlib.Path(python_som.__file__).parent / 'py.typed').is_file(), 'py.typed missing from the wheel'
som = python_som.SOM(x=8, y=8, input_len=3, neighborhood_function='mexicanhat', random_seed=0)
h = som.neighborhood((4, 4), 2.0)
assert h[4, 4] == 1.0
assert h.min() < 0, 'the mexican hat must be inhibitory somewhere'
print('wheel import OK, version', python_som.__version__)
"
- name: the cli extra must keep resolving
run: |
uv venv /tmp/extra
VIRTUAL_ENV=/tmp/extra uv pip install "$(echo dist/*.whl)[cli]"
/tmp/extra/bin/python -c "import tqdm; print('cli extra OK,', tqdm.__version__)"
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: dist
path: dist/
48 changes: 48 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: docs

on:
push:
branches: [master]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
name: build the documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
enable-cache: true
- run: uv sync --all-extras
# --strict turns warnings, including broken internal links, into failures.
- run: uv run mkdocs build --strict
- uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
if: github.ref == 'refs/heads/master'
- uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
if: github.ref == 'refs/heads/master'
with:
path: site/

deploy:
name: deploy to GitHub Pages
if: github.ref == 'refs/heads/master'
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
permissions:
pages: write
id-token: write
steps:
- id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
61 changes: 61 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: release

# The filename matters: `release.yml` is registered as the workflow name on the PyPI trusted
# publisher for this project. Renaming this file breaks publishing until PyPI is updated to match.

on:
push:
tags: ["v*"]
workflow_dispatch:

permissions:
contents: read

jobs:
build:
name: build the distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
enable-cache: true
- run: uv build
- name: twine check
run: uv run --with twine==7.0.0 twine check dist/*
- name: the tag and the packaged version must agree
run: |
uv run python - <<'PY'
import os
import pathlib
import sys
import tomllib

tag = os.environ["GITHUB_REF_NAME"].removeprefix("v")
pkg = tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"]
print(f"tag={tag} packaged={pkg}")
if tag != pkg:
sys.exit(f"::error::tag {tag} does not match packaged version {pkg}")
PY
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: dist
path: dist/

publish:
name: publish to PyPI
needs: build
runs-on: ubuntu-latest
# Registered on PyPI as the trusted publisher's environment. It carries a required-reviewer
# rule, so a publish waits for a human, and a v* tag policy, so it cannot run from a branch.
environment: pypi
permissions:
# Mandatory for trusted publishing: this is what lets the job prove its identity to PyPI.
# No API token is stored in the repository or anywhere else.
id-token: write
steps:
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # release/v1
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,12 @@ dmypy.json

# Pyre type checker
.pyre/

# .dump directory
/.dump

# Claude Code working files. CLAUDE.md holds this maintainer's agent conventions rather than
# repository policy, so it stays local instead of being imposed on contributors.
/CLAUDE.md
/CLAUDE.local.md
/.claude/
45 changes: 45 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
default_language_version:
python: python3

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-added-large-files
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
- id: detect-private-key
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.0
hooks:
- id: ruff-check
args: [--fix]
- id: ruff-format

# Keep this rev in step with the `mypy` pin in pyproject.toml's dev extra, or the hook and CI
# will disagree about what type-checks.
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v2.3.0
hooks:
- id: mypy
files: ^(src|tests)/
additional_dependencies:
- numpy>=1.24
- pandas-stubs
- pytest
- types-tqdm

- repo: local
hooks:
# The narrative that used to live in test_iris.ipynb now lives in docs/. Notebooks are not
# diffable, not CI-verifiable, and carry their outputs in the committed blob.
- id: no-notebooks
name: reject committed Jupyter notebooks
entry: Jupyter notebooks are not tracked in this repository; put the narrative in docs/.
language: fail
files: \.ipynb$
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"python.testing.pytestArgs": ["tests"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
Loading
Loading