Skip to content

Commit f07d054

Browse files
authored
Merge branch 'main' into feature/ruff-check-fix-61
2 parents 873ae2d + 29e6950 commit f07d054

12 files changed

Lines changed: 145 additions & 169 deletions

File tree

.github/workflows/release.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
uses: "./.github/workflows/lint.yml"
1818
variants:
1919
uses: "./.github/workflows/variants.yml"
20-
typecheck:
21-
uses: "./.github/workflows/typecheck.yml"
2220

2321
# Always build & lint package.
2422
build-package:
@@ -27,7 +25,6 @@
2725
- lint
2826
- tests
2927
- variants
30-
- typecheck
3128
runs-on: ubuntu-latest
3229
permissions:
3330
attestations: write

.github/workflows/typecheck.yml

Lines changed: 0 additions & 28 deletions
This file was deleted.

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
- Feature: Add optional `ruff check --fix` support to ruff-format target
66
[jensens, 2025-11-11]
7+
- Feature: Add `qa.ty` domain for Astral's ty type checker.
8+
ty is an extremely fast Python type checker (10-100x faster than mypy).
9+
Registers with both CHECK_TARGETS and TYPECHECK_TARGETS for fast feedback.
10+
[jensens]
711

812
## 2.1.0
913

Makefile

Lines changed: 53 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@
88
#: core.mxfiles
99
#: core.packages
1010
#: docs.sphinx
11-
#: qa.coverage
12-
#: qa.isort
13-
#: qa.mypy
1411
#: qa.ruff
1512
#: qa.test
13+
#: qa.ty
1614
#
1715
# SETTINGS (ALL CHANGES MADE BELOW SETTINGS WILL BE LOST)
1816
##############################################################################
@@ -158,6 +156,17 @@ PROJECT_CONFIG?=mx.ini
158156
# Default: false
159157
PACKAGES_ALLOW_PRERELEASES?=false
160158

159+
## qa.ty
160+
161+
# Source folder for type checking.
162+
# Default: src
163+
TY_SRC?=src
164+
165+
# Target Python version for type checking (e.g., 3.12).
166+
# Leave empty to use default detection.
167+
# No default value.
168+
TY_PYTHON_VERSION?=
169+
161170
## qa.test
162171

163172
# The command which gets executed. Defaults to the location the
@@ -174,23 +183,6 @@ TEST_REQUIREMENTS?=pytest
174183
# No default value.
175184
TEST_DEPENDENCY_TARGETS?=
176185

177-
## qa.coverage
178-
179-
# The command which gets executed. Defaults to the location the
180-
# :ref:`run-coverage` template gets rendered to if configured.
181-
# Default: .mxmake/files/run-coverage.sh
182-
COVERAGE_COMMAND?=.mxmake/files/run-coverage.sh
183-
184-
## qa.mypy
185-
186-
# Source folder for code analysis.
187-
# Default: src
188-
MYPY_SRC?=src
189-
190-
# Mypy Python requirements to be installed (via pip).
191-
# Default: types-setuptools
192-
MYPY_REQUIREMENTS?=types-setuptools types-docutils types-PyYAML
193-
194186
## core.help
195187

196188
# Request to show all targets, descriptions and arguments for a given domain.
@@ -406,45 +398,6 @@ FORMAT_TARGETS+=ruff-format
406398
DIRTY_TARGETS+=ruff-dirty
407399
CLEAN_TARGETS+=ruff-clean
408400

409-
##############################################################################
410-
# isort
411-
##############################################################################
412-
413-
# Adjust ISORT_SRC to respect PROJECT_PATH_PYTHON if still at default
414-
ifeq ($(ISORT_SRC),src)
415-
ISORT_SRC:=$(PYTHON_PROJECT_PREFIX)src
416-
endif
417-
418-
ISORT_TARGET:=$(SENTINEL_FOLDER)/isort.sentinel
419-
$(ISORT_TARGET): $(MXENV_TARGET)
420-
@echo "Install isort"
421-
@$(PYTHON_PACKAGE_COMMAND) install isort
422-
@touch $(ISORT_TARGET)
423-
424-
.PHONY: isort-check
425-
isort-check: $(ISORT_TARGET)
426-
@echo "Run isort check"
427-
@isort --check $(ISORT_SRC)
428-
429-
.PHONY: isort-format
430-
isort-format: $(ISORT_TARGET)
431-
@echo "Run isort format"
432-
@isort $(ISORT_SRC)
433-
434-
.PHONY: isort-dirty
435-
isort-dirty:
436-
@rm -f $(ISORT_TARGET)
437-
438-
.PHONY: isort-clean
439-
isort-clean: isort-dirty
440-
@test -e $(MXENV_PYTHON) && $(MXENV_PYTHON) -m pip uninstall -y isort || :
441-
442-
INSTALL_TARGETS+=$(ISORT_TARGET)
443-
CHECK_TARGETS+=isort-check
444-
FORMAT_TARGETS+=isort-format
445-
DIRTY_TARGETS+=isort-dirty
446-
CLEAN_TARGETS+=isort-clean
447-
448401
##############################################################################
449402
# sphinx
450403
##############################################################################
@@ -590,6 +543,47 @@ INSTALL_TARGETS+=packages
590543
DIRTY_TARGETS+=packages-dirty
591544
CLEAN_TARGETS+=packages-clean
592545

546+
##############################################################################
547+
# ty
548+
##############################################################################
549+
550+
# Adjust TY_SRC to respect PROJECT_PATH_PYTHON if still at default
551+
ifeq ($(TY_SRC),src)
552+
TY_SRC:=$(PYTHON_PROJECT_PREFIX)src
553+
endif
554+
555+
# Build ty flags
556+
TY_FLAGS:=
557+
ifneq ($(TY_PYTHON_VERSION),)
558+
TY_FLAGS+=--python-version $(TY_PYTHON_VERSION)
559+
endif
560+
561+
TY_TARGET:=$(SENTINEL_FOLDER)/ty.sentinel
562+
$(TY_TARGET): $(MXENV_TARGET)
563+
@echo "Install ty"
564+
@$(PYTHON_PACKAGE_COMMAND) install ty
565+
@touch $(TY_TARGET)
566+
567+
.PHONY: ty
568+
ty: $(PACKAGES_TARGET) $(TY_TARGET)
569+
@echo "Run ty"
570+
@ty check $(TY_FLAGS) $(TY_SRC)
571+
572+
.PHONY: ty-dirty
573+
ty-dirty:
574+
@rm -f $(TY_TARGET)
575+
576+
.PHONY: ty-clean
577+
ty-clean: ty-dirty
578+
@test -e $(MXENV_PYTHON) && $(MXENV_PYTHON) -m pip uninstall -y ty || :
579+
@rm -rf .ty
580+
581+
INSTALL_TARGETS+=$(TY_TARGET)
582+
CHECK_TARGETS+=ty
583+
TYPECHECK_TARGETS+=ty
584+
CLEAN_TARGETS+=ty-clean
585+
DIRTY_TARGETS+=ty-dirty
586+
593587
##############################################################################
594588
# test
595589
##############################################################################
@@ -619,69 +613,6 @@ INSTALL_TARGETS+=$(TEST_TARGET)
619613
CLEAN_TARGETS+=test-clean
620614
DIRTY_TARGETS+=test-dirty
621615

622-
##############################################################################
623-
# coverage
624-
##############################################################################
625-
626-
COVERAGE_TARGET:=$(SENTINEL_FOLDER)/coverage.sentinel
627-
$(COVERAGE_TARGET): $(TEST_TARGET)
628-
@echo "Install Coverage"
629-
@$(PYTHON_PACKAGE_COMMAND) install -U coverage
630-
@touch $(COVERAGE_TARGET)
631-
632-
.PHONY: coverage
633-
coverage: $(FILES_TARGET) $(SOURCES_TARGET) $(PACKAGES_TARGET) $(COVERAGE_TARGET)
634-
@test -z "$(COVERAGE_COMMAND)" && echo "No coverage command defined" && exit 1 || :
635-
@echo "Run coverage using $(COVERAGE_COMMAND)"
636-
@/usr/bin/env bash -c "$(COVERAGE_COMMAND)"
637-
638-
.PHONY: coverage-dirty
639-
coverage-dirty:
640-
@rm -f $(COVERAGE_TARGET)
641-
642-
.PHONY: coverage-clean
643-
coverage-clean: coverage-dirty
644-
@test -e $(MXENV_PYTHON) && $(MXENV_PYTHON) -m pip uninstall -y coverage || :
645-
@rm -rf .coverage htmlcov
646-
647-
INSTALL_TARGETS+=$(COVERAGE_TARGET)
648-
DIRTY_TARGETS+=coverage-dirty
649-
CLEAN_TARGETS+=coverage-clean
650-
651-
##############################################################################
652-
# mypy
653-
##############################################################################
654-
655-
# Adjust MYPY_SRC to respect PROJECT_PATH_PYTHON if still at default
656-
ifeq ($(MYPY_SRC),src)
657-
MYPY_SRC:=$(PYTHON_PROJECT_PREFIX)src
658-
endif
659-
660-
MYPY_TARGET:=$(SENTINEL_FOLDER)/mypy.sentinel
661-
$(MYPY_TARGET): $(MXENV_TARGET)
662-
@echo "Install mypy"
663-
@$(PYTHON_PACKAGE_COMMAND) install mypy $(MYPY_REQUIREMENTS)
664-
@touch $(MYPY_TARGET)
665-
666-
.PHONY: mypy
667-
mypy: $(PACKAGES_TARGET) $(MYPY_TARGET)
668-
@echo "Run mypy"
669-
@mypy $(MYPY_SRC)
670-
671-
.PHONY: mypy-dirty
672-
mypy-dirty:
673-
@rm -f $(MYPY_TARGET)
674-
675-
.PHONY: mypy-clean
676-
mypy-clean: mypy-dirty
677-
@test -e $(MXENV_PYTHON) && $(MXENV_PYTHON) -m pip uninstall -y mypy || :
678-
@rm -rf .mypy_cache
679-
680-
INSTALL_TARGETS+=$(MYPY_TARGET)
681-
TYPECHECK_TARGETS+=mypy
682-
CLEAN_TARGETS+=mypy-clean
683-
DIRTY_TARGETS+=mypy-dirty
684-
685616
##############################################################################
686617
# help
687618
##############################################################################

src/mxmake/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ def list_command(args: argparse.Namespace):
6767
sys.stdout.write(f"Requested domain not found: {args.domain}\n")
6868
sys.exit(1)
6969

70+
assert domain is not None # type narrowing for ty
71+
7072
sys.stdout.write(f"Domain {topic.name}.{domain.name}:\n")
7173
depends = ", ".join(domain.depends) if domain.depends else "No dependencies"
7274
sys.stdout.write(f" Depends: {depends}\n")

src/mxmake/templates.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,23 @@ def __init__(
5858
# XXX: if environment is None, default to ``get_template_environment``?
5959
self.environment = environment
6060

61-
@abc.abstractproperty
61+
@property
62+
@abc.abstractmethod
6263
def target_folder(self) -> Path:
6364
"""Target folder for rendered template."""
6465

65-
@abc.abstractproperty
66+
@property
67+
@abc.abstractmethod
6668
def target_name(self) -> str:
6769
"""Target file name for rendered template."""
6870

69-
@abc.abstractproperty
71+
@property
72+
@abc.abstractmethod
7073
def template_name(self) -> str:
7174
"""Template name to use."""
7275

73-
@abc.abstractproperty
76+
@property
77+
@abc.abstractmethod
7478
def template_variables(self) -> dict[str, typing.Any]:
7579
"""Variables for template rendering."""
7680

src/mxmake/testing/__init__.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,6 @@ def __init__(
7373

7474

7575
class RenderTestCase(unittest.TestCase):
76-
class Example:
77-
def __init__(self, want):
78-
self.want = want + "\n"
79-
8076
class Failure(Exception):
8177
pass
8278

@@ -96,6 +92,8 @@ def checkOutput(self, want, got, optionflags=None):
9692
if not success:
9793
raise RenderTestCase.Failure(
9894
self._checker.output_difference(
99-
RenderTestCase.Example(want), got, optionflags
95+
doctest.Example(source="", want=want + "\n"),
96+
got,
97+
optionflags,
10098
)
10199
)

src/mxmake/tests/test_templates.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class Template(templates.Template):
5252
def test_Template(self, tempdir: Path):
5353
# cannot instantiate abstract template
5454
with self.assertRaises(TypeError):
55-
templates.Template() # type: ignore
55+
templates.Template()
5656

5757
# create test template
5858
class Template(templates.Template):

src/mxmake/tests/test_topics.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from dataclasses import field
44
from mxmake import testing
55
from mxmake import topics
6+
from pathlib import Path
67

78
import configparser
89
import typing
@@ -57,10 +58,13 @@
5758

5859
@dataclass
5960
class _TestDomain(topics.Domain):
61+
file: str | Path = field(default=Path())
6062
depends_: list[str] = field(default_factory=list)
6163
soft_depends_: list[str] = field(default_factory=list)
6264

6365
def __post_init__(self) -> None:
66+
if isinstance(self.file, str):
67+
self.file = Path(self.file)
6468
self.runtime_depends = self.depends + self.soft_depends
6569

6670
@property
@@ -189,7 +193,9 @@ def test_Topic(self, tmpdir):
189193
self.assertEqual(topic_domains[1].name, "domain-b")
190194
self.assertEqual(topic_domains[1].topic, "topic")
191195

192-
self.assertEqual(topic.domain("domain-a").name, "domain-a")
196+
domain_a = topic.domain("domain-a")
197+
assert domain_a is not None
198+
self.assertEqual(domain_a.name, "domain-a")
193199
self.assertEqual(topic.domain("inexistent"), None)
194200

195201
def test_DomainConflictError(self):
@@ -204,7 +210,7 @@ def test_CircularDependencyDomainError(self):
204210
str(err),
205211
(
206212
"Domains define circular dependencies: [_TestDomain("
207-
"topic='t1', name='f1', file='f1.mk', depends_=['f2'], soft_depends_=[])]"
213+
f"topic='t1', name='f1', file={Path('f1.mk')!r}, depends_=['f2'], soft_depends_=[])]"
208214
),
209215
)
210216

@@ -215,7 +221,7 @@ def test_MissingDependencyDomainError(self):
215221
str(err),
216222
(
217223
"Domain define missing dependency: _TestDomain("
218-
"topic='t', name='t', file='t.mk', depends_=['missing'], soft_depends_=[])"
224+
f"topic='t', name='t', file={Path('t.mk')!r}, depends_=['missing'], soft_depends_=[])"
219225
),
220226
)
221227

0 commit comments

Comments
 (0)