Skip to content

Commit 8d77990

Browse files
authored
chore(dev): tidy dependencies, ensure coverage/type-check run cleanly (#15)
* ci: add Python 3.13 support * ci(pytest): set coverage threshold at 75% * ci(ruff): add linter * ci(ruff): add project section in pyproject required by ruff * test: remove unused imports * refactor: tidy imports and improve type hints
1 parent ae8fa2b commit 8d77990

File tree

8 files changed

+52
-32
lines changed

8 files changed

+52
-32
lines changed

.github/workflows/ci.yaml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
python-version: ["3.10", "3.11", "3.12"]
14+
python-version: ["3.10", "3.11", "3.12", "3.13"]
1515

1616
steps:
1717
- uses: actions/checkout@v4
@@ -27,8 +27,17 @@ jobs:
2727
pip install poetry twine
2828
poetry install --with dev
2929
30-
- name: Run tests
31-
run: poetry run pytest
30+
- name: Lint with Ruff
31+
run: poetry run ruff check .
32+
33+
- name: Run tests with coverage
34+
run: |
35+
poetry run pytest --cov=tfsumpy --cov-report=term
36+
poetry run coverage report --fail-under=75
37+
38+
- name: Coverage reminder
39+
if: success()
40+
run: echo "::notice ::Current threshold is 75%. PRs that raise coverage are welcome!"
3241

3342
- name: Build package
3443
run: poetry build

pyproject.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
[project]
2+
name = "tfsumpy"
3+
version = "0.2.0"
4+
requires-python = ">=3.10,<4.0"
5+
16
[tool.poetry]
27
name = "tfsumpy"
38
version = "0.2.0"
@@ -16,6 +21,7 @@ classifiers = [
1621
"Programming Language :: Python :: 3.10",
1722
"Programming Language :: Python :: 3.11",
1823
"Programming Language :: Python :: 3.12",
24+
"Programming Language :: Python :: 3.13",
1925
"Topic :: Software Development :: Build Tools",
2026
"Topic :: System :: Systems Administration"
2127
]
@@ -35,13 +41,18 @@ jinja2 = "^3.1.6"
3541
[tool.poetry.group.dev.dependencies]
3642
pytest-cov = ">=4.1.0"
3743
pytest-mock = ">=3.6.1"
44+
ruff = ">=0.4.0"
45+
mypy = ">=1.10.0"
46+
types-colorama = "*"
3847
coverage = ">=7.2.0"
3948

4049
[tool.poetry.extras]
4150
dev = [
4251
"pytest-cov",
4352
"pytest-mock",
44-
"coverage"
53+
"coverage",
54+
"ruff",
55+
"mypy"
4556
]
4657

4758
[tool.poetry.scripts]

tests/plan/test_plan_reporter.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import pytest
2-
from unittest.mock import Mock, patch
2+
from unittest.mock import patch
33
from tfsumpy.plan.reporter import PlanReporter
44
import re
55
import json
6-
from datetime import datetime
76

87
@pytest.fixture
98
def reporter():

tfsumpy/__main__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import logging
22
import argparse
33
import warnings
4-
from pathlib import Path
54
from .plan.analyzer import PlanAnalyzer
65
from .plan.reporter import PlanReporter
76
from .context import Context

tfsumpy/analyzer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
from __future__ import annotations
2+
13
from abc import ABC, abstractmethod
2-
from typing import Dict, List, Any
4+
from typing import Any, TYPE_CHECKING
5+
6+
if TYPE_CHECKING:
7+
from .context import Context
38
from dataclasses import dataclass
49

510
@dataclass

tfsumpy/context.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import json
22
import logging
33
import os
4-
from typing import Dict, List, Type, Any
4+
from typing import Dict, List, Any, Optional
55
from .analyzer import AnalyzerInterface, AnalyzerResult
66
from .reporter import ReporterInterface
77

88
class Context:
99
"""Application context that manages analyzers and configuration."""
1010

11-
def __init__(self, config_path: str = None, debug: bool = False):
11+
def __init__(self, config_path: Optional[str] = None, debug: bool = False):
1212
self.config_path = config_path
1313
self.debug = debug
1414

@@ -21,13 +21,13 @@ def __init__(self, config_path: str = None, debug: bool = False):
2121
)
2222

2323
# Initialize components
24-
self.sensitive_patterns = []
25-
self.config = {}
24+
self.sensitive_patterns: List[tuple[str, str]] = []
25+
self.config: Dict[str, Any] = {}
2626

2727
# Initialize analyzer registry
2828
self._analyzers: Dict[str, List[AnalyzerInterface]] = {}
2929
self._reporters: Dict[str, List[ReporterInterface]] = {}
30-
self.plan_data = None
30+
self.plan_data: Optional[Dict[str, Any]] = None
3131

3232
def register_analyzer(self, analyzer: AnalyzerInterface) -> None:
3333
"""Register an analyzer in its category.
@@ -90,7 +90,7 @@ def set_plan_data(self, plan_data: Dict) -> None:
9090
"""
9191
self.plan_data = plan_data
9292

93-
def get_plan_data(self) -> Dict:
93+
def get_plan_data(self) -> Optional[Dict[str, Any]]:
9494
"""Retrieve stored plan data.
9595
9696
Returns:
@@ -122,6 +122,7 @@ def _merge_external_config(self) -> None:
122122
"""Merge external configuration with default configuration"""
123123
self.logger.debug(f"Merging external config file: {self.config_path}")
124124
try:
125+
assert self.config_path is not None
125126
with open(self.config_path, 'r') as f:
126127
external_config = json.load(f)
127128

tfsumpy/plan/analyzer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
from __future__ import annotations
2+
13
import json
24
import logging
3-
from typing import Dict, List
5+
from typing import List, TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from ..context import Context
49
from ..resource import ResourceChange
510
from ..analyzer import AnalyzerInterface, AnalyzerResult
611

tfsumpy/resource.py

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
1-
from dataclasses import dataclass
2-
from typing import List, Dict
1+
from dataclasses import dataclass, field
2+
from typing import Dict, Any
33

44
@dataclass
55
class ResourceChange:
6+
"""Represents a single Terraform resource change."""
7+
68
action: str
79
resource_type: str
810
identifier: str
9-
changes: List[str]
10-
module: str = 'root'
11-
before: dict = None
12-
after: dict = None
13-
14-
def __init__(self, action: str, resource_type: str, identifier: str,
15-
changes: dict, module: str = 'root', before: dict = None,
16-
after: dict = None):
17-
self.action = action
18-
self.resource_type = resource_type
19-
self.identifier = identifier
20-
self.changes = changes
21-
self.module = module
22-
self.before = before or {}
23-
self.after = after or {}
11+
changes: Dict[str, Any] = field(default_factory=dict)
12+
module: str = "root"
13+
before: Dict[str, Any] = field(default_factory=dict)
14+
after: Dict[str, Any] = field(default_factory=dict)

0 commit comments

Comments
 (0)