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
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* text=auto
.githooks/* text eol=lf
93 changes: 93 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env bash

abort() {
printf '%s\n' "$@" >&2
exit 1
}

tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/morepath-ruff.XXXXXX") ||
abort 'Ruff pre-commit check: could not create a temporary directory.'
trap 'rm -rf "$tmpdir"' EXIT

list_file=$tmpdir/staged-files
staged_file=$tmpdir/staged
formatted_file=$tmpdir/formatted

# Write the list to a file first so a git failure is not silently ignored.
git diff --cached --name-only --diff-filter=ACMR -z -- '*.py' '*.pyi' \
>"$list_file" ||
abort 'Ruff pre-commit check: could not list the staged files.'

python_files=()
while IFS= read -r -d '' path; do
python_files+=("$path")
done <"$list_file"

if (( ${#python_files[@]} == 0 )); then
exit 0
fi

if [[ -x .venv/bin/ruff ]]; then
ruff_cmd=.venv/bin/ruff
elif [[ -f .venv/Scripts/ruff.exe ]]; then
ruff_cmd=.venv/Scripts/ruff.exe
elif command -v ruff >/dev/null 2>&1; then
ruff_cmd=$(command -v ruff)
elif command -v ruff.exe >/dev/null 2>&1; then
ruff_cmd=$(command -v ruff.exe)
else
abort 'Ruff not found in .venv or PATH.' \
"Install it with pip install -e '.[lint]'"
fi

# report <label> <fix label> <fix command> <files...>
report() {
local label=$1 fixlabel=$2 fix=$3
shift 3
printf '\n%s (%d):' "$label" "$#"
printf ' %q' "$@"
printf '\n%s: %s' "$fixlabel" "$fix"
printf ' %q' "$@"
printf '\n'
}

format_files=()
lint_files=()

for path in "${python_files[@]}"; do
git show ":$path" >"$staged_file" ||
abort "Could not read the staged content of $path."

# Format the staged content; a non-zero exit is a real failure here
# (for example a syntax error or a broken config).
# --force-exclude makes ruff honor its exclude settings for stdin input.
errors=$("$ruff_cmd" format --force-exclude --stdin-filename "$path" - \
<"$staged_file" 2>&1 >"$formatted_file") ||
abort "Ruff format failed on $path:" "$errors"

if ! cmp -s "$staged_file" "$formatted_file"; then
format_files+=("$path")
fi

# Lint the formatted content, so that violations the formatter
# fixes anyway (e.g. trailing whitespace) are not reported twice.
output=$("$ruff_cmd" check --force-exclude --no-fix \
--stdin-filename "$path" - <"$formatted_file" 2>&1)
case $? in
0) ;;
1) lint_files+=("$path") ;;
*) abort "Ruff check failed on $path:" "$output" ;;
esac
done

if (( ${#format_files[@]} || ${#lint_files[@]} )); then
printf '%s\n' 'Ruff checks failed - no files were changed.'
if (( ${#format_files[@]} )); then
report 'Needs formatting' 'Fix with' 'ruff format' "${format_files[@]}"
fi
if (( ${#lint_files[@]} )); then
report 'Lint errors' 'Try' 'ruff check --fix' "${lint_files[@]}"
fi
printf '\n%s\n' 'Review and stage the fixes, then retry the commit.'
exit 1
fi
26 changes: 12 additions & 14 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,33 @@ on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

permissions: {}

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
tests:
name: "Python ${{ matrix.python-version }}"
runs-on: "ubuntu-latest"
permissions:
contents: read

strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "pypy3.11"]
python-version: ["3.11", "3.12", "3.13", "3.14", "3.15", "pypy-3.12"]

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: "actions/checkout@v6"
- uses: "actions/setup-python@v6"
- uses: "actions/checkout@v7"
with:
persist-credentials: false
- uses: "actions/setup-python@v7"
with:
python-version: "${{ matrix.python-version }}"
allow-prereleases: true
check-latest: true
- name: "Install dependencies"
run: python -m pip install --upgrade pip tox tox-gh
- name: "Run tox targets for ${{ matrix.python-version }}"
run: "python -m tox"

- name: "Report to coveralls"
# coverage is only created in the py314 environment
# --service=github is a workaround for bug
# https://github.com/coveralls-clients/coveralls-python/issues/251
if: "matrix.python-version == '3.14'"
run: |
pip install coveralls
coveralls --service=github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41 changes: 41 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Release

on:
push:
tags: ["v[0-9]*"]

permissions: {}

jobs:
build:
name: Build
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-python@v7
with:
python-version: "3.14"
- run: python -m pip install build
- run: python -m build
- uses: actions/upload-artifact@v7
with:
name: dist
path: dist/

publish:
name: Publish
needs: build
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v8
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
15 changes: 0 additions & 15 deletions .pre-commit-config.yaml

This file was deleted.

34 changes: 34 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version and other tools you might need
build:
os: ubuntu-lts-latest
tools:
python: "3.14"

# Build documentation in the "doc/" directory with Sphinx
sphinx:
configuration: doc/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Fail on all warnings to avoid broken references
# fail_on_warning: true

# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub

# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- method: pip
path: .
extra_requirements:
- docs
43 changes: 36 additions & 7 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,58 @@ CHANGES
1.0.0 (unreleased)
==================

- Add type hints
- Add type annotations and integrate ``mypy`` and ``pyright``.

- Add partial attribute to directive methods for better typing support
- Switch to ``pyproject.toml`` based project setup.

- Add partial attribute to directive methods for better typing support.

- Update scenarios to Dectate 0.12+ API and add type annotations.

- Fix linting errors.

- Add support for Python 3.10, 3.11, 3.12, 3.13 and 3.14
- Add support for Python 3.11, 3.12, 3.13, 3.14 and 3.15

- Drop support for Python 3.10 and below

- Drop support for Python 3.9 and below
- Support Pypy 3.12.

- Use GitHub Actions instead of Travis for CI.
(Travis no longer gives free credits to open source projects.)

- Cleanup old Python 2 code with pyupgrade_.

.. _pyupgrade: https://github.com/asottile/pyupgrade

- Add tests for 100% coverage.

- Update docs.
- Update docs and fix sphinx config.

- Update scenarios to Dectate 0.12+ API and add type annotations.
- Make an overall cleanup.

.. _pyupgrade: https://github.com/asottile/pyupgrade
- Fix readthedocs integration.

- Replace flake8, black, isort, pyupgrade and pre-commit with Ruff_.

.. _Ruff: https://docs.astral.sh/ruff

- Add a tracked Git pre-commit hook for Ruff checks on staged Python files.

- Remove coveralls integration.

- Adjust the deployment strategy:

* Move the Pypi upload from ``zest.release`` to ``gh-actions``.

* Use ``v`` + version as release tags like ``v1.0.0``.

* So we still use ``zest.release`` but without publishing to Pypi.
The Pypi upload is triggered by pushing the new release tag to GitHub.
This happens in the Release workflow in ``.github/workflows/release.yml``.
With this workflow we get Trusted Publishing with short-lived OIDC tokens
that PyPI issues to one specific repository, workflow file and environment (``pypi``).
We also get PEP 740 provenance and can set required reviewers for the environment
and a ruleset who can create release tags and trigger releases.


0.14 (2020-01-29)
Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
include *.txt *.rst *.cfg *.py *.ini *.toml *.yaml
include .coveragerc
exclude .installed.cfg
exclude .githooks/pre-commit
recursive-include dectate *.py py.typed
recursive-include doc *.rst Makefile *.py *.bat
recursive-include scenarios *.py *.txt
recursive-exclude scenarios/query/.venv *
3 changes: 0 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
:target: https://github.com/morepath/dectate/actions?workflow=CI
:alt: CI Status

.. image:: https://coveralls.io/repos/github/morepath/dectate/badge.svg?branch=master
:target: https://coveralls.io/github/morepath/dectate?branch=master

.. image:: https://img.shields.io/pypi/v/dectate.svg
:target: https://pypi.org/project/dectate/

Expand Down
2 changes: 1 addition & 1 deletion dectate/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Concatenate,
Generic,
ParamSpec,
Self,
TypeVar,
cast,
)
Expand All @@ -16,7 +17,6 @@

if TYPE_CHECKING:
from collections.abc import Callable, Collection, Iterator
from typing_extensions import Self

from .config import Action, Composite, DirectiveAbbreviation
from .types import DirectiveCallable
Expand Down
4 changes: 1 addition & 3 deletions dectate/tests/test_directive.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ def f() -> None:

MyApp.commit()

MyApp.config.my[
0
].directive.directive_name == "foo" # pyright: ignore[reportUnusedExpression]
MyApp.config.my[0].directive.directive_name == "foo" # pyright: ignore[reportUnusedExpression]


def test_conflict_same_directive() -> None:
Expand Down
6 changes: 3 additions & 3 deletions dectate/toposort.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


def topological_sort(
l: Iterable[_T],
items: Iterable[_T],
get_depends: Callable[[_T], Iterable[_T]],
) -> list[_T]:
"""`Topological sort`_.
Expand All @@ -24,7 +24,7 @@ def topological_sort(

.. _DAG: https://en.wikipedia.org/wiki/Directed_acyclic_graph

:param l: an iterable of items to sort
:param items: an iterable of items to sort
:param get_depends: a function that given an item
gives other items that this item depends on. This item
will be sorted after the items it depends on.
Expand All @@ -47,6 +47,6 @@ def visit(n: _T) -> None:
marked.add(n)
result.append(n)

for n in l:
for n in items:
visit(n)
return result
1 change: 0 additions & 1 deletion develop_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# development
-e '.[test,coverage,lint,docs,mypy,pyright]'
pre-commit
tox >= 4

# releaser
Expand Down
Loading
Loading