Skip to content

✅ test: add tests & CI #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 16, 2025
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
96 changes: 96 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: CI
permissions: read-all

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

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

env:
# Many color libraries just need this to be set to any value, but at least
# one distinguishes color depth, where "3" -> "256-bit color".
FORCE_COLOR: 3

jobs:
format:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683

- name: Install uv
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb

- name: Install the project
run: uv sync --locked --group test

- name: Run lefthook hooks
run: uv run --frozen lefthook run pre-commit

checks:
name: Check Python ${{ matrix.python-version }} on ${{ matrix.runs-on }}
runs-on: ${{ matrix.runs-on }}
needs: [format]
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13"]
runs-on: [ubuntu-latest, macos-latest, windows-latest]

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683

- name: Install uv
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb
with:
python-version: ${{ matrix.python-version }}

- name: Install the project
run: uv sync --locked --group test

- name: Test package
run: >-
uv run --frozen pytest
--cov --cov-report=xml --cov-report=term --durations=20
src docs tests

- name: Upload coverage report
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24
with:
token: ${{ secrets.CODECOV_TOKEN }}

check_oldest:
name: Check Oldest Dependencies
runs-on: ${{ matrix.runs-on }}
needs: [format]
strategy:
fail-fast: false
matrix:
python-version: ["3.11"]
runs-on: [ubuntu-latest]

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683

- name: Install uv
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb
with:
python-version: ${{ matrix.python-version }}
- name: Install the project
run: uv sync --locked --group test --resolution lowest-direct

- name: Test package
run: >-
uv run --frozen pytest
--cov --cov-report=xml --cov-report=term --durations=20
src docs tests

- name: Upload coverage report
uses: codecov/codecov-action@v18283e04ce6e62d37312384ff67231eb8fd56d24
with:
token: ${{ secrets.CODECOV_TOKEN }}
8 changes: 5 additions & 3 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
"""Pytest configuration file."""

from typing import Final

from sybil import Sybil
from sybil.parsers.doctest import DocTestParser

readme_tester = Sybil(
readme_tester: Final = Sybil(
parsers=[DocTestParser()],
pattern="README.md",
)

python_file_tester = Sybil(
python_file_tester: Final = Sybil(
parsers=[DocTestParser()],
pattern="src/**/*.py",
)

pytest_collect_file = (readme_tester + python_file_tester).pytest()
pytest_collect_file: Final = (readme_tester + python_file_tester).pytest()
2 changes: 1 addition & 1 deletion lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ pre-commit:
run: uv run ruff format {staged_files}
- name: mypy
glob: "*.py"
run: uv run mypy {staged_files}
run: uv run --group mypy mypy {staged_files}
11 changes: 11 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
"pytest-github-actions-annotate-failures>=0.3.0",
"sybil>=8.0.0",
]
mypy = [
"mypy>=1.16.0"
]


[tool.hatch]
Expand All @@ -74,6 +77,7 @@ version_tuple = {version_tuple!r}
[tool.mypy]
files = ["src", "tests"]
python_version = "3.10"
mypy_path = "src"

strict = true
disallow_incomplete_defs = true
Expand All @@ -90,6 +94,10 @@ version_tuple = {version_tuple!r}
module = "sybil.*"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false


[tool.pytest.ini_options]
addopts = [
Expand Down Expand Up @@ -130,6 +138,9 @@ version_tuple = {version_tuple!r}
"ISC001", # Conflicts with formatter
]

[tool.ruff.lint.per-file-ignores]
"tests/*.py" = ["ANN201", "D1", "S101"]

[tool.ruff.lint.flake8-import-conventions]
banned-from = ["array_api_typing"]

Expand Down
6 changes: 5 additions & 1 deletion src/array_api_typing/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""Static typing support for the array API standard."""

__all__ = ["HasArrayNamespace", "__version__", "__version_tuple__"]
__all__ = (
"HasArrayNamespace",
"__version__",
"__version_tuple__",
)

from ._namespace import HasArrayNamespace
from ._version import version as __version__, version_tuple as __version_tuple__
4 changes: 1 addition & 3 deletions src/array_api_typing/_namespace.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"""Static typing support for the array API standard."""

__all__ = ["HasArrayNamespace"]
__all__ = ("HasArrayNamespace",)

from types import ModuleType
from typing import Protocol, final
Expand Down
42 changes: 42 additions & 0 deletions tests/test_namespace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from types import SimpleNamespace
from typing import Protocol, runtime_checkable

import array_api_typing as xpt


@runtime_checkable
class CheckableHasArrayNamespace(xpt.HasArrayNamespace, Protocol): # type: ignore[misc]
"""Runtime checkable version of HasArrayNamespace."""


class GoodArray:
"""Example class that implements the HasArrayNamespace protocol."""

def __array_namespace__(self) -> object: # noqa: PLW3201
return SimpleNamespace()


class BadArray:
"""Example class that does not implement the HasArrayNamespace protocol."""


def test_has_namespace_class():
"""Test that GoodArray is a subclass of HasArrayNamespace."""
assert issubclass(GoodArray, CheckableHasArrayNamespace)


def test_has_namespace_instance():
"""Test that an instance of GoodArray is recognized as HasArrayNamespace."""
x = GoodArray()
assert isinstance(x, CheckableHasArrayNamespace)


def test_not_has_namespace_class():
"""Test that BadArray is not a subclass of HasArrayNamespace."""
assert not issubclass(BadArray, CheckableHasArrayNamespace)


def test_not_has_namespace_instance():
"""Test that an instance of BadArray is not recognized as HasArrayNamespace."""
y = BadArray()
assert not isinstance(y, CheckableHasArrayNamespace)
61 changes: 61 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.