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
15 changes: 12 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
Expand All @@ -27,8 +27,17 @@ jobs:
pip install poetry twine
poetry install --with dev

- name: Run tests
run: poetry run pytest
- name: Lint with Ruff
run: poetry run ruff check .

- name: Run tests with coverage
run: |
poetry run pytest --cov=tfsumpy --cov-report=term
poetry run coverage report --fail-under=75

- name: Coverage reminder
if: success()
run: echo "::notice ::Current threshold is 75%. PRs that raise coverage are welcome!"

- name: Build package
run: poetry build
Expand Down
13 changes: 12 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
[project]
name = "tfsumpy"
version = "0.2.0"
requires-python = ">=3.10,<4.0"

[tool.poetry]
name = "tfsumpy"
version = "0.2.0"
Expand All @@ -16,6 +21,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Build Tools",
"Topic :: System :: Systems Administration"
]
Expand All @@ -35,13 +41,18 @@ jinja2 = "^3.1.6"
[tool.poetry.group.dev.dependencies]
pytest-cov = ">=4.1.0"
pytest-mock = ">=3.6.1"
ruff = ">=0.4.0"
mypy = ">=1.10.0"
types-colorama = "*"
coverage = ">=7.2.0"

[tool.poetry.extras]
dev = [
"pytest-cov",
"pytest-mock",
"coverage"
"coverage",
"ruff",
"mypy"
]

[tool.poetry.scripts]
Expand Down
3 changes: 1 addition & 2 deletions tests/plan/test_plan_reporter.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import pytest
from unittest.mock import Mock, patch
from unittest.mock import patch
from tfsumpy.plan.reporter import PlanReporter
import re
import json
from datetime import datetime

@pytest.fixture
def reporter():
Expand Down
1 change: 0 additions & 1 deletion tfsumpy/__main__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import logging
import argparse
import warnings
from pathlib import Path
from .plan.analyzer import PlanAnalyzer
from .plan.reporter import PlanReporter
from .context import Context
Expand Down
7 changes: 6 additions & 1 deletion tfsumpy/analyzer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Dict, List, Any
from typing import Any, TYPE_CHECKING

if TYPE_CHECKING:
from .context import Context
from dataclasses import dataclass

@dataclass
Expand Down
13 changes: 7 additions & 6 deletions tfsumpy/context.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import json
import logging
import os
from typing import Dict, List, Type, Any
from typing import Dict, List, Any, Optional
from .analyzer import AnalyzerInterface, AnalyzerResult
from .reporter import ReporterInterface

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

def __init__(self, config_path: str = None, debug: bool = False):
def __init__(self, config_path: Optional[str] = None, debug: bool = False):
self.config_path = config_path
self.debug = debug

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

# Initialize components
self.sensitive_patterns = []
self.config = {}
self.sensitive_patterns: List[tuple[str, str]] = []
self.config: Dict[str, Any] = {}

# Initialize analyzer registry
self._analyzers: Dict[str, List[AnalyzerInterface]] = {}
self._reporters: Dict[str, List[ReporterInterface]] = {}
self.plan_data = None
self.plan_data: Optional[Dict[str, Any]] = None

def register_analyzer(self, analyzer: AnalyzerInterface) -> None:
"""Register an analyzer in its category.
Expand Down Expand Up @@ -90,7 +90,7 @@ def set_plan_data(self, plan_data: Dict) -> None:
"""
self.plan_data = plan_data

def get_plan_data(self) -> Dict:
def get_plan_data(self) -> Optional[Dict[str, Any]]:
"""Retrieve stored plan data.

Returns:
Expand Down Expand Up @@ -122,6 +122,7 @@ def _merge_external_config(self) -> None:
"""Merge external configuration with default configuration"""
self.logger.debug(f"Merging external config file: {self.config_path}")
try:
assert self.config_path is not None
with open(self.config_path, 'r') as f:
external_config = json.load(f)

Expand Down
7 changes: 6 additions & 1 deletion tfsumpy/plan/analyzer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from __future__ import annotations

import json
import logging
from typing import Dict, List
from typing import List, TYPE_CHECKING

if TYPE_CHECKING:
from ..context import Context
from ..resource import ResourceChange
from ..analyzer import AnalyzerInterface, AnalyzerResult

Expand Down
25 changes: 8 additions & 17 deletions tfsumpy/resource.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
from dataclasses import dataclass
from typing import List, Dict
from dataclasses import dataclass, field
from typing import Dict, Any

@dataclass
class ResourceChange:
"""Represents a single Terraform resource change."""

action: str
resource_type: str
identifier: str
changes: List[str]
module: str = 'root'
before: dict = None
after: dict = None

def __init__(self, action: str, resource_type: str, identifier: str,
changes: dict, module: str = 'root', before: dict = None,
after: dict = None):
self.action = action
self.resource_type = resource_type
self.identifier = identifier
self.changes = changes
self.module = module
self.before = before or {}
self.after = after or {}
changes: Dict[str, Any] = field(default_factory=dict)
module: str = "root"
before: Dict[str, Any] = field(default_factory=dict)
after: Dict[str, Any] = field(default_factory=dict)
Loading