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
3 changes: 2 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
- v*
branches:
- main
workflow_dispatch:

permissions:
contents: write
Expand All @@ -27,7 +28,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13']

steps:
- uses: actions/checkout@v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Coverage
on: [push, pull_request]
on: [push, pull_request, workflow_dispatch]
jobs:
cov:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ruff.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Ruff
on: [push, pull_request]
on: [push, pull_request, workflow_dispatch]
jobs:
ruff:
runs-on: ubuntu-latest
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [v*]
pull_request:
branches: [v*, main]
workflow_dispatch:

concurrency:
group: test-${{ github.head_ref }}
Expand All @@ -22,7 +23,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13']

steps:
- uses: actions/checkout@v6
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ dist/
.pytest_cache
junit.xml
.idea/
.claude
27 changes: 18 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ classifiers = [
"Development Status :: 4 - Beta",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand All @@ -26,15 +24,15 @@ dependencies = [
"Cython",
"hatchling",
"setuptools",
"typing_extensions; python_version < '3.10'"
"typing_extensions; python_version < '3.12'"
]
description = 'Cython build hooks for hatch'
dynamic = ["version"]
keywords = []
license = "MIT"
name = "hatch-cython"
readme = "README.md"
requires-python = ">=3.8"
requires-python = ">=3.10"

[project.entry-points.hatch]
cython = "hatch_cython.hooks"
Expand Down Expand Up @@ -89,7 +87,7 @@ test = "pytest --junitxml=junit.xml -o junit_family=legacy {args:tests} -v"
test-cov = "coverage run -m pytest --junitxml=junit.xml -o junit_family=legacy -vv {args:tests}"

[tool.hatch.envs.dev]
python = "3.9"
python = "3.10"
path = ".venv"
skip-install = false
extra-dependencies = [
Expand All @@ -108,13 +106,16 @@ detached = true

[tool.hatch.envs.lint.scripts]
all = ["style", "typing"]
fmt = ["black {args:.}", "ruff --fix {args:.}", "style"]
style = ["ruff {args:.}", "black --check --diff {args:.}"]
fmt = ["black {args:.}", "ruff check --ignore PLC0415 --fix {args:.}", "style"]
style = ["ruff check --ignore PLC0415 {args:.}", "black --check --diff {args:.}"]
typing = "mypy --install-types --non-interactive {args:src/hatch_cython tests}"

[tool.hatch.version]
path = "src/hatch_cython/__about__.py"

[tool.mypy]
ignore_missing_imports = true

[tool.ruff]
line-length = 120
target-version = "py310"
Expand All @@ -132,7 +133,11 @@ ignore = [
"PLR0911",
"PLR0912",
"PLR0913",
"PLR0915"
"PLR0915",
"UP006",
"UP007",
"UP035",
"UP045"
]
select = [
"A",
Expand Down Expand Up @@ -171,5 +176,9 @@ known-first-party = ["hatch_cython"]

[tool.ruff.lint.per-file-ignores]
"**/__init__.py" = ["F401"]
# Plugin implements BuildHookInterface — unused args are required by the interface signature
"src/hatch_cython/plugin.py" = ["ARG002"]
# Tests can use magic values, assertions, and relative imports
"tests/**/*" = ["PLR2004", "S101", "TID252"]
"tests/**/*" = ["PLR2004", "S101", "TID252", "T201", "A002", "E501"]
# Test libraries contain intentional patterns for testing
"test_libraries/**/*.py" = ["S603", "PLW1510", "PLW0603", "UP004", "T201", "S101"]
7 changes: 4 additions & 3 deletions src/hatch_cython/config/autoimport.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from dataclasses import dataclass, field
from typing import Optional


@dataclass
class Autoimport:
pkg: str

include: str
libraries: str = field(default=None)
library_dirs: str = field(default=None)
required_call: str = field(default=None)
libraries: Optional[str] = field(default=None)
library_dirs: Optional[str] = field(default=None)
required_call: Optional[str] = field(default=None)


__packages__ = {
Expand Down
45 changes: 20 additions & 25 deletions src/hatch_cython/config/config.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import os
from collections.abc import Generator
from collections.abc import Callable, Generator
from dataclasses import asdict, dataclass, field
from importlib import import_module
from os import path
from typing import Any, Optional

from hatchling.builders.hooks.plugin.interface import BuildHookInterface

Expand All @@ -15,7 +16,6 @@
from hatch_cython.config.platform import ListedArgs, PlatformArgs, parse_platform_args
from hatch_cython.config.templates import Templates, parse_template_kwds
from hatch_cython.constants import DIRECTIVES, EXIST_TRIM, INCLUDE, LTPY311, MUST_UNIQUE
from hatch_cython.types import CallableT, ListStr, UnionT

# fields tracked by this plugin
__known__ = frozenset(
Expand All @@ -36,7 +36,7 @@
"cythonize_kwargs",
"include_all_compiled_src",
"compiled_extensions_as_artifacts",
"intermediate_extensions_as_artifacts"
"intermediate_extensions_as_artifacts",
)
)

Expand All @@ -48,27 +48,22 @@ def parse_from_dict(cls: BuildHookInterface):
kwargs = {}
for key, val in given.items():
if key in __known__:
parsed: any
if key == "files":
val: dict
parsed: FileArgs = FileArgs(**val)
parsed: Any = FileArgs(**val)
elif key == "define_macros":
val: list
parsed: DefineMacros = parse_macros(val)
parsed = parse_macros(val)
elif key == "templates":
val: dict
parsed: Templates = parse_template_kwds(val)
parsed = parse_template_kwds(val)
else:
val: any
parsed: any = val
parsed = val
kwargs[key] = parsed
passed.pop(key)
continue

compile_args = parse_platform_args(kwargs, "compile_args", get_default_compile)
link_args = parse_platform_args(kwargs, "extra_link_args", get_default_link)
envflags = parse_env_args(kwargs)
cfg = Config(**kwargs, compile_args=compile_args, extra_link_args=link_args, envflags=envflags)
cfg = Config(**kwargs, compile_args=compile_args, extra_link_args=link_args, envflags=envflags) # type: ignore[arg-type]

for maybe_dep, spec in passed.copy().items():
is_include = maybe_dep.startswith(INCLUDE)
Expand Down Expand Up @@ -138,12 +133,12 @@ def parse_from_dict(cls: BuildHookInterface):

@dataclass
class Config:
src: UnionT[str, None] = field(default=None)
src: Optional[str] = field(default=None)
files: FileArgs = field(default_factory=FileArgs)
includes: ListStr = field(default_factory=list)
includes: list[str] = field(default_factory=list)
define_macros: DefineMacros = field(default_factory=list)
libraries: ListStr = field(default_factory=list)
library_dirs: ListStr = field(default_factory=list)
libraries: list[str] = field(default_factory=list)
library_dirs: list[str] = field(default_factory=list)
directives: dict = field(default_factory=lambda: DIRECTIVES)
compile_args: ListedArgs = field(default_factory=get_default_compile)
compile_kwargs: dict = field(default_factory=dict)
Expand Down Expand Up @@ -173,9 +168,9 @@ def _post_import_attr(
cls: BuildHookInterface,
im: Autoimport,
att: str,
mod: any,
extend: CallableT[[ListStr], None],
append: CallableT[[str], None],
mod: Any,
extend: Callable[[list[str]], None],
append: Callable[[str], None],
):
attr = getattr(im, att)
if attr is not None:
Expand All @@ -187,9 +182,9 @@ def _post_import_attr(
if isinstance(libraries, str):
append(libraries)
elif isinstance(libraries, (list, Generator)):
extend(libraries)
extend(libraries) # type: ignore[arg-type]
elif isinstance(libraries, dict):
extend(libraries.values())
extend(libraries.values()) # type: ignore[arg-type]
else:
cls.app.display_warning(f"{im.pkg}.{attr} has an invalid type ({type(libraries)})")

Expand Down Expand Up @@ -234,11 +229,11 @@ def resolve_pkg(
cls.app.display_warning(f"{im.pkg}.{im.required_call} is invalid")

def _arg_impl(self, target: ListedArgs):
args = {"any": []}
args: dict = {"any": []}

def with_argvalue(arg: str):
# be careful with e.g. -Ox flags
matched = list(filter(lambda s: arg.startswith(s), MUST_UNIQUE))
matched = [s for s in MUST_UNIQUE if arg.startswith(s)]
if len(matched) != 0:
m = matched[0]
args[m] = arg.split(" ")
Expand All @@ -249,7 +244,7 @@ def with_argvalue(arg: str):
# if compile-arg format, check platform applies
if isinstance(arg, PlatformArgs):
if arg.applies() and arg.is_exist(EXIST_TRIM):
with_argvalue(arg.arg)
with_argvalue(arg.arg) # type: ignore[arg-type]
# else assume string / user knows what theyre doing and add to the call params
else:
with_argvalue(arg)
Expand Down
24 changes: 12 additions & 12 deletions src/hatch_cython/config/files.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import re
from dataclasses import dataclass, field
from typing import Type
from typing import Optional

from hatch_cython.config.platform import PlatformBase
from hatch_cython.types import DictT, ListT, UnionT
from hatch_cython.utils import parse_user_glob


Expand All @@ -18,22 +17,22 @@ class OptInclude(PlatformBase):


def _get_file_list(
cls: Type[UnionT[OptInclude, OptExclude]],
files: ListT[UnionT[str, OptInclude, OptExclude]]
) -> ListT[str]:
cls: "type[OptInclude | OptExclude]",
files: "list[str | OptInclude | OptExclude]",
) -> "list[OptInclude | OptExclude]":
return [
*[cls(**d) for d in files if isinstance(d, dict)],
*[cls(**d) for d in files if isinstance(d, dict)], # type: ignore[arg-type]
*[cls(matches=s) for s in files if isinstance(s, str)],
]


@dataclass
class FileArgs:
targets: ListT[UnionT[str, OptInclude]] = field(default_factory=list)
exclude: ListT[UnionT[str, OptExclude]] = field(default_factory=list)
aliases: DictT[str, str] = field(default_factory=dict)
exclude_compiled_src: ListT[UnionT[str, OptExclude]] = field(default_factory=list)
include_compiled_src: ListT[UnionT[str, OptInclude]] = field(default_factory=list)
targets: list[str | OptInclude] = field(default_factory=list)
exclude: list[str | OptExclude] = field(default_factory=list)
aliases: dict[str, str] = field(default_factory=dict)
exclude_compiled_src: list[str | OptExclude] = field(default_factory=list)
include_compiled_src: list[str | OptInclude] = field(default_factory=list)

def __post_init__(self):
rep = {}
Expand All @@ -49,7 +48,7 @@ def __post_init__(self):
def explicit_targets(self) -> bool:
return len(self.targets) > 0

def matches_alias(self, other: str) -> UnionT[str, None]:
def matches_alias(self, other: str) -> Optional[str]:
matched = [re.match(v, other) for v in self.aliases.keys()]
if any(matched):
first = 0
Expand All @@ -58,3 +57,4 @@ def matches_alias(self, other: str) -> UnionT[str, None]:
break
first += 1
return self.aliases[list(self.aliases.keys())[first]]
return None
Loading
Loading