Skip to content

Commit b6fc44a

Browse files
authored
Merge pull request #16 from LeakIX/add-mypy-and-tooling
Add mypy type checking and improve tooling
2 parents 2675ff1 + fac243d commit b6fc44a

6 files changed

Lines changed: 142 additions & 22 deletions

File tree

.editorconfig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = lf
5+
insert_final_newline = true
6+
trim_trailing_whitespace = true
7+
charset = utf-8
8+
indent_style = space
9+
indent_size = 4
10+
11+
[*.{json,yaml,yml,md}]
12+
indent_size = 2
13+
14+
[Makefile]
15+
indent_style = tab

.github/workflows/action.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ jobs:
2727
run: poetry run black --check .
2828
- name: sort-check
2929
run: poetry run isort --check-only .
30+
- name: typecheck
31+
run: poetry run mypy l9format
3032
- name: test
3133
run: poetry run pytest
3234
- name: security-audit

Makefile

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,101 @@
1-
# Makefile for pygins project
1+
# Makefile for l9format-python project
22

3-
.PHONY: test format sort lint security-check all
3+
UNAME_S := $(shell uname -s)
4+
ifeq ($(UNAME_S),Darwin)
5+
SED := $(shell command -v gsed 2>/dev/null)
6+
ifeq ($(SED),)
7+
$(error GNU sed (gsed) not found on macOS. \
8+
Install with: brew install gnu-sed)
9+
endif
10+
else
11+
SED := sed
12+
endif
413

5-
# Run all quality checks
6-
all: test format sort lint security-check
14+
.PHONY: help
15+
help: ## Show this help
16+
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
17+
awk 'BEGIN {FS = ":.*?## "}; \
18+
{printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
719

8-
# Run tests using pytest
9-
test:
20+
.PHONY: all
21+
all: format sort lint typecheck test security-check ## Run all quality checks
22+
23+
.PHONY: test
24+
test: ## Run tests using pytest
1025
poetry run pytest
1126

12-
# Format code using black
13-
format:
27+
.PHONY: format
28+
format: ## Format code using black
1429
poetry run black .
1530

16-
# Sort imports using isort
17-
sort:
31+
.PHONY: check-format
32+
check-format: ## Check code formatting with black
33+
poetry run black --check .
34+
35+
.PHONY: sort
36+
sort: ## Sort imports using isort
1837
poetry run isort .
1938

20-
# Lint code using ruff
21-
lint:
39+
.PHONY: check-sort
40+
check-sort: ## Check import sorting with isort
41+
poetry run isort --check-only .
42+
43+
.PHONY: lint
44+
lint: ## Lint code using ruff
2245
poetry run ruff check .
2346

24-
# Check for vulnerable dependencies using pip-audit
25-
security-check:
47+
.PHONY: lint-fix
48+
lint-fix: ## Lint and fix code using ruff
49+
poetry run ruff check --fix .
50+
51+
.PHONY: typecheck
52+
typecheck: ## Run mypy type checker
53+
poetry run mypy l9format
54+
55+
.PHONY: security-check
56+
security-check: ## Check for vulnerable dependencies using pip-audit
2657
poetry run pip-audit
2758

59+
.PHONY: fix-trailing-whitespace
60+
fix-trailing-whitespace: ## Remove trailing whitespaces from all files
61+
@echo "Removing trailing whitespaces from all files..."
62+
@find . -type f \( \
63+
-name "*.py" -o -name "*.toml" -o -name "*.md" -o -name "*.yaml" \
64+
-o -name "*.yml" -o -name "*.json" \) \
65+
-not -path "./.git/*" \
66+
-not -path "./.venv/*" \
67+
-not -path "./__pycache__/*" \
68+
-exec sh -c \
69+
'echo "Processing: $$1"; $(SED) -i -e "s/[[:space:]]*$$//" "$$1"' \
70+
_ {} \; && \
71+
echo "Trailing whitespaces removed."
72+
73+
.PHONY: check-trailing-whitespace
74+
check-trailing-whitespace: ## Check for trailing whitespaces in source files
75+
@echo "Checking for trailing whitespaces..."
76+
@files_with_trailing_ws=$$(find . -type f \( \
77+
-name "*.py" -o -name "*.toml" -o -name "*.md" -o -name "*.yaml" \
78+
-o -name "*.yml" -o -name "*.json" \) \
79+
-not -path "./.git/*" \
80+
-not -path "./.venv/*" \
81+
-not -path "./__pycache__/*" \
82+
-exec grep -l '[[:space:]]$$' {} + 2>/dev/null || true); \
83+
if [ -n "$$files_with_trailing_ws" ]; then \
84+
echo "Files with trailing whitespaces found:"; \
85+
echo "$$files_with_trailing_ws" | sed 's/^/ /'; \
86+
echo ""; \
87+
echo "Run 'make fix-trailing-whitespace' to fix automatically."; \
88+
exit 1; \
89+
else \
90+
echo "No trailing whitespaces found."; \
91+
fi
92+
93+
.PHONY: install
94+
install: ## Install dependencies with Poetry
95+
poetry install
96+
97+
.PHONY: clean
98+
clean: ## Clean build artifacts and caches
99+
rm -rf __pycache__ .pytest_cache .mypy_cache .ruff_cache
100+
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
101+
find . -type f -name "*.pyc" -delete 2>/dev/null || true

l9format/l9format.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
def round_decimal(
99
decimal_obj: decimal.Decimal, num_of_places: int = 6
1010
) -> decimal.Decimal:
11-
return decimal_obj.quantize(decimal.Decimal(10) ** -num_of_places).normalize()
11+
return decimal_obj.quantize(
12+
decimal.Decimal(10) ** -num_of_places
13+
).normalize()
1214

1315

1416
class Decimal(fields.Instance):
@@ -32,7 +34,7 @@ class Decimal(fields.Instance):
3234

3335
ty = decimal.Decimal
3436

35-
def __init__(self, resolution=None, **kwargs):
37+
def __init__(self, resolution: int | None = None, **kwargs: object) -> None:
3638
super(Decimal, self).__init__(self.__class__.ty, **kwargs)
3739
self.resolution = resolution
3840

@@ -41,16 +43,16 @@ def serialize(self, value: decimal.Decimal) -> str:
4143
value = round_decimal(value, num_of_places=self.resolution)
4244
return "{0:f}".format(value)
4345

44-
def deserialize(self, value) -> decimal.Decimal:
46+
def deserialize(self, value: object) -> decimal.Decimal:
4547
try:
4648
if self.resolution is not None:
4749
return round_decimal(
48-
decimal.Decimal(value), num_of_places=self.resolution
50+
decimal.Decimal(str(value)), num_of_places=self.resolution
4951
)
5052

51-
return decimal.Decimal(value)
52-
except decimal.DecimalException:
53-
raise Exception("invalid decimal", value=value)
53+
return decimal.Decimal(str(value))
54+
except decimal.DecimalException as e:
55+
raise ValueError(f"invalid decimal: {value}") from e
5456

5557

5658
class L9HttpEvent(Model):

pyproject.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ serde = "^0.9.0"
1818
black = "^26.1.0"
1919
fire = "^0.7.1"
2020
isort = "^7.0.0"
21+
mypy = "^1.19.0"
2122
pip-audit = "^2.10.0"
2223
pytest = "^9.0.2"
2324
ruff = "^0.14.14"
@@ -28,3 +29,27 @@ build-backend = "poetry.core.masonry.api"
2829

2930
[tool.poetry.urls]
3031
"Bug Tracker" = "https://github.com/leakix/l9format-python/issues"
32+
33+
[tool.ruff]
34+
line-length = 80
35+
36+
[tool.black]
37+
line-length = 80
38+
39+
[tool.isort]
40+
profile = "black"
41+
line_length = 80
42+
43+
[tool.mypy]
44+
python_version = "3.13"
45+
warn_return_any = true
46+
warn_unused_configs = true
47+
disallow_untyped_defs = true
48+
disallow_incomplete_defs = true
49+
# serde library lacks type stubs, so we need to ignore import errors
50+
ignore_missing_imports = true
51+
52+
[[tool.mypy.overrides]]
53+
module = "l9format.l9format"
54+
# serde Model/Instance classes appear as Any, causing false positives
55+
disable_error_code = ["misc", "valid-type"]

tests/test_l9format.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
TESTS_DIR = Path(os.path.dirname(__file__))
88

99
IP4SCOUT_FILES = [
10-
f for f in Path.iterdir(TESTS_DIR) if Path.is_file(f) and "ip4scout" in f.name
10+
f
11+
for f in Path.iterdir(TESTS_DIR)
12+
if Path.is_file(f) and "ip4scout" in f.name
1113
]
1214

1315

0 commit comments

Comments
 (0)