archunitpython
ArchUnitPython - Architecture testing library for Python projects.
1"""ArchUnitPython - Architecture testing library for Python projects.""" 2 3__version__ = "1.6.1" 4 5# Files API 6# Common 7from archunitpython.common import ( 8 CheckOptions, 9 EmptyTestViolation, 10 TechnicalError, 11 UserError, 12 Violation, 13) 14from archunitpython.common.extraction import clear_graph_cache, extract_graph 15from archunitpython.config import ConfiguredRule, rules_from_config 16from archunitpython.files import files, project_files 17from archunitpython.graph import dependency_graph, project_graph 18from archunitpython.layers import layers, project_layers 19 20# Metrics API 21from archunitpython.metrics import metrics 22 23# Slices API 24from archunitpython.slices import project_slices 25 26# Testing 27from archunitpython.testing import assert_passes, format_violations 28 29__all__ = [ 30 # Files 31 "project_files", 32 "files", 33 # Graph 34 "project_graph", 35 "dependency_graph", 36 # Layers 37 "project_layers", 38 "layers", 39 # Config 40 "rules_from_config", 41 "ConfiguredRule", 42 # Slices 43 "project_slices", 44 # Metrics 45 "metrics", 46 # Testing 47 "assert_passes", 48 "format_violations", 49 # Common 50 "Violation", 51 "EmptyTestViolation", 52 "CheckOptions", 53 "TechnicalError", 54 "UserError", 55 "extract_graph", 56 "clear_graph_cache", 57]
42def project_files(project_path: str | None = None) -> "FileConditionBuilder": 43 """Entry point for file-level architecture rules. 44 45 Args: 46 project_path: Root directory of the project to analyze. 47 Defaults to current working directory. 48 """ 49 return FileConditionBuilder(project_path)
Entry point for file-level architecture rules.
Arguments:
- project_path: Root directory of the project to analyze. Defaults to current working directory.
42def project_files(project_path: str | None = None) -> "FileConditionBuilder": 43 """Entry point for file-level architecture rules. 44 45 Args: 46 project_path: Root directory of the project to analyze. 47 Defaults to current working directory. 48 """ 49 return FileConditionBuilder(project_path)
Entry point for file-level architecture rules.
Arguments:
- project_path: Root directory of the project to analyze. Defaults to current working directory.
107def project_graph(project_path: str | None = None) -> ProjectGraphBuilder: 108 """Create a builder for dependency graph reports.""" 109 return ProjectGraphBuilder(project_path)
Create a builder for dependency graph reports.
107def project_graph(project_path: str | None = None) -> ProjectGraphBuilder: 108 """Create a builder for dependency graph reports.""" 109 return ProjectGraphBuilder(project_path)
Create a builder for dependency graph reports.
19def project_layers(project_path: str | None = None) -> "LayeredArchitecture": 20 """Entry point for layer-level architecture rules.""" 21 return LayeredArchitecture(project_path)
Entry point for layer-level architecture rules.
19def project_layers(project_path: str | None = None) -> "LayeredArchitecture": 20 """Entry point for layer-level architecture rules.""" 21 return LayeredArchitecture(project_path)
Entry point for layer-level architecture rules.
30def rules_from_config(config_path: str) -> list[ConfiguredRule]: 31 """Load common architecture rules from a JSON config file. 32 33 The fluent Python API remains the primary interface. Config files provide a 34 lightweight way to share straightforward rules across projects or teams. 35 """ 36 path = Path(config_path) 37 try: 38 raw_config = json.loads(path.read_text(encoding="utf-8")) 39 except OSError as exc: 40 raise UserError(f"Could not read config file: {config_path}") from exc 41 except json.JSONDecodeError as exc: 42 raise UserError(f"Invalid JSON config file: {config_path}") from exc 43 44 if not isinstance(raw_config, dict): 45 raise UserError("Architecture config must be a JSON object.") 46 47 project_path = _optional_string(raw_config, "project_path") or os.getcwd() 48 rules = raw_config.get("rules") 49 if not isinstance(rules, list): 50 raise UserError("Architecture config must define a 'rules' list.") 51 52 base_dir = str(path.parent if path.parent != Path("") else Path.cwd()) 53 resolved_project_path = _resolve_project_path(base_dir, project_path) 54 55 return [_build_rule(resolved_project_path, item, index) for index, item in enumerate(rules, 1)]
Load common architecture rules from a JSON config file.
The fluent Python API remains the primary interface. Config files provide a lightweight way to share straightforward rules across projects or teams.
18@dataclass(frozen=True) 19class ConfiguredRule: 20 """A named rule loaded from a configuration file.""" 21 22 name: str 23 rule: Checkable 24 25 def check(self, options: CheckOptions | None = None) -> list[Violation]: 26 """Run the configured rule.""" 27 return self.rule.check(options)
A named rule loaded from a configuration file.
33def project_slices(project_path: str | None = None) -> "SliceConditionBuilder": 34 """Entry point for slice-level architecture rules. 35 36 Args: 37 project_path: Root directory of the project to analyze. 38 """ 39 return SliceConditionBuilder(project_path)
Entry point for slice-level architecture rules.
Arguments:
- project_path: Root directory of the project to analyze.
53def metrics(project_path: str | None = None) -> "MetricsBuilder": 54 """Entry point for metrics rules.""" 55 return MetricsBuilder(project_path)
Entry point for metrics rules.
40def assert_passes( 41 checkable: Checkable, 42 options: CheckOptions | None = None, 43) -> None: 44 """Assert that an architecture rule passes (no violations). 45 46 Args: 47 checkable: Any object with a check() method (implements Checkable). 48 options: Optional check options. 49 50 Raises: 51 AssertionError: If the rule has violations. 52 """ 53 violations = checkable.check(options) 54 if violations: 55 because = getattr(checkable, "because_reason", None) 56 raise AssertionError(format_violations(violations, because=because))
Assert that an architecture rule passes (no violations).
Arguments:
- checkable: Any object with a check() method (implements Checkable).
- options: Optional check options.
Raises:
- AssertionError: If the rule has violations.
11def format_violations( 12 violations: list[Violation], 13 *, 14 because: str | None = None, 15) -> str: 16 """Format violations into a human-readable string. 17 18 Args: 19 violations: List of violations to format. 20 21 Returns: 22 Formatted string describing all violations. 23 """ 24 if not violations: 25 return "No violations found." 26 27 lines = [f"Found {len(violations)} architecture violation(s):"] 28 if because: 29 lines.extend(["", f"Because: {because}"]) 30 lines.append("") 31 for i, violation in enumerate(violations, 1): 32 tv = ViolationFactory.from_violation(violation) 33 lines.append(f" {i}. {tv.message}") 34 lines.append(f" {tv.details}") 35 lines.append("") 36 37 return "\n".join(lines)
Format violations into a human-readable string.
Arguments:
- violations: List of violations to format.
Returns:
Formatted string describing all violations.
Base class for all architecture violations.
16@dataclass 17class EmptyTestViolation(Violation): 18 """Violation raised when no files match the specified filter patterns.""" 19 20 filters: list[Any] 21 message: str 22 is_negated: bool = False
Violation raised when no files match the specified filter patterns.
13@dataclass(frozen=True) 14class CheckOptions: 15 """Options for controlling rule check execution.""" 16 17 allow_empty_tests: bool = False 18 logging: LoggingOptions | None = None 19 clear_cache: bool = False 20 ignore_type_checking_imports: bool = False
Options for controlling rule check execution.
5class TechnicalError(Exception): 6 """Error caused by technical issues (file system, configuration, etc.).""" 7 8 pass
Error caused by technical issues (file system, configuration, etc.).
Error caused by incorrect API usage.
72def extract_graph( 73 project_path: str | None = None, 74 *, 75 exclude_patterns: list[str] | None = None, 76 options: CheckOptions | None = None, 77) -> Graph: 78 """Extract a dependency graph from a Python project. 79 80 Scans all .py files in the project directory, parses their imports, 81 and resolves them to build a list of Edge objects. 82 83 Args: 84 project_path: Root directory of the project to analyze. 85 Defaults to current working directory. 86 exclude_patterns: Directory/file names to exclude. 87 Defaults to common non-source directories. 88 options: Check options (supports clear_cache). 89 90 Returns: 91 List of Edge objects representing import relationships. 92 """ 93 if project_path is None: 94 project_path = os.getcwd() 95 96 project_path = os.path.abspath(project_path) 97 excludes = _resolve_exclude_patterns(project_path, exclude_patterns) 98 ignore_type_checking_imports = bool(options and options.ignore_type_checking_imports) 99 cache_key = _build_cache_key(project_path, excludes, ignore_type_checking_imports) 100 101 if options and options.clear_cache: 102 _graph_cache.pop(cache_key, None) 103 104 if cache_key in _graph_cache: 105 return _graph_cache[cache_key] 106 107 result = _extract_graph_uncached( 108 project_path, 109 excludes, 110 ignore_type_checking_imports=ignore_type_checking_imports, 111 ) 112 _graph_cache[cache_key] = result 113 return result
Extract a dependency graph from a Python project.
Scans all .py files in the project directory, parses their imports, and resolves them to build a list of Edge objects.
Arguments:
- project_path: Root directory of the project to analyze. Defaults to current working directory.
- exclude_patterns: Directory/file names to exclude. Defaults to common non-source directories.
- options: Check options (supports clear_cache).
Returns:
List of Edge objects representing import relationships.
67def clear_graph_cache(options: CheckOptions | None = None) -> None: 68 """Clear the cached dependency graphs.""" 69 _graph_cache.clear()
Clear the cached dependency graphs.