diff --git a/src/archunitpython/common/extraction/extract_graph.py b/src/archunitpython/common/extraction/extract_graph.py
index 5d3d282..13e7494 100644
--- a/src/archunitpython/common/extraction/extract_graph.py
+++ b/src/archunitpython/common/extraction/extract_graph.py
@@ -58,13 +58,11 @@ def extract_graph(
project_path = os.getcwd()
project_path = os.path.abspath(project_path)
- excludes = list(set(exclude_patterns)) if exclude_patterns is not None else list(_DEFAULT_EXCLUDE)
- ignore_type_checking_imports = bool(
- options and options.ignore_type_checking_imports
- )
- cache_key = _build_cache_key(
- project_path, excludes, ignore_type_checking_imports
+ excludes = (
+ list(set(exclude_patterns)) if exclude_patterns is not None else list(_DEFAULT_EXCLUDE)
)
+ ignore_type_checking_imports = bool(options and options.ignore_type_checking_imports)
+ cache_key = _build_cache_key(project_path, excludes, ignore_type_checking_imports)
if options and options.clear_cache:
_graph_cache.pop(cache_key, None)
@@ -105,6 +103,7 @@ def _extract_graph_uncached(
edges: list[Edge] = []
py_files_set = set(py_files)
+ normalized_py_file_set = {_normalize(f) for f in py_files_set}
for file_path in py_files:
# Add self-referencing edge (ensures the file appears as a node)
@@ -119,19 +118,14 @@ def _extract_graph_uncached(
# Extract and resolve imports
imports = _extract_imports(file_path)
for module_name, import_kind in imports:
- if (
- ignore_type_checking_imports
- and import_kind == ImportKind.TYPE_IMPORT
- ):
+ if ignore_type_checking_imports and import_kind == ImportKind.TYPE_IMPORT:
continue
resolved, is_external = _resolve_import(
module_name, file_path, project_path, import_kind
)
if resolved and resolved != _normalize(file_path):
# Check if the resolved path is in our project
- if not is_external and resolved not in {
- _normalize(f) for f in py_files_set
- }:
+ if not is_external and resolved not in normalized_py_file_set:
is_external = True
edges.append(
@@ -156,11 +150,7 @@ def _find_python_files(root: str, exclude: list[str]) -> list[str]:
py_files: list[str] = []
for dirpath, dirnames, filenames in os.walk(root):
# Filter out excluded directories in-place
- dirnames[:] = [
- d
- for d in dirnames
- if not _should_exclude(d, exclude)
- ]
+ dirnames[:] = [d for d in dirnames if not _should_exclude(d, exclude)]
for filename in filenames:
if filename.endswith(".py") and not _should_exclude(filename, exclude):
@@ -240,18 +230,14 @@ def _find_type_checking_ranges(tree: ast.Module) -> list[tuple[int, int]]:
if is_type_checking and node.body:
start = node.body[0].lineno
end = max(
- getattr(n, "end_lineno", n.lineno)
- for n in node.body
- if hasattr(n, "lineno")
+ getattr(n, "end_lineno", n.lineno) for n in node.body if hasattr(n, "lineno")
)
ranges.append((start, end))
- return ranges
+ return sorted(ranges, key=lambda ele: ele[0])
-def _in_type_checking(
- node: ast.AST, ranges: list[tuple[int, int]]
-) -> bool:
+def _in_type_checking(node: ast.AST, ranges: list[tuple[int, int]]) -> bool:
"""Check if a node is inside a TYPE_CHECKING block."""
if not hasattr(node, "lineno"):
return False
diff --git a/src/archunitpython/common/pattern_matching.py b/src/archunitpython/common/pattern_matching.py
index 5ac3304..1956602 100644
--- a/src/archunitpython/common/pattern_matching.py
+++ b/src/archunitpython/common/pattern_matching.py
@@ -50,9 +50,7 @@ def matches_pattern(file_path: str, filter_: Filter) -> bool:
return bool(filter_.regexp.search(target_string))
-def matches_pattern_classname(
- class_name: str, file_path: str, filter_: Filter
-) -> bool:
+def matches_pattern_classname(class_name: str, file_path: str, filter_: Filter) -> bool:
"""Check if a class/file matches a filter, supporting classname target."""
target = filter_.options.target
diff --git a/src/archunitpython/common/projection/cycles/johnsons_apsp.py b/src/archunitpython/common/projection/cycles/johnsons_apsp.py
index 1a86428..91e0601 100644
--- a/src/archunitpython/common/projection/cycles/johnsons_apsp.py
+++ b/src/archunitpython/common/projection/cycles/johnsons_apsp.py
@@ -53,13 +53,9 @@ def _explore_neighbours(self, current_node: NumberNode) -> None:
if self._is_part_of_current_start_cycle(current_node):
self._unblock(current_node)
else:
- for neighbour in CycleUtils.get_outgoing_neighbours(
- current_node, self._graph
- ):
+ for neighbour in CycleUtils.get_outgoing_neighbours(current_node, self._graph):
if self._is_blocked(neighbour):
- self._blocked_map.append(
- _BlockedBy(blocked=current_node, by=neighbour)
- )
+ self._blocked_map.append(_BlockedBy(blocked=current_node, by=neighbour))
def _unblock(self, node: NumberNode) -> None:
self._blocked = [n for n in self._blocked if n is not node]
@@ -74,9 +70,8 @@ def _is_part_of_current_start_cycle(self, current_node: NumberNode) -> bool:
if self._start is None:
return False
for cycle in self._cycles:
- if (
- cycle[0].from_node == self._start.node
- and any(e.from_node == current_node.node for e in cycle)
+ if cycle[0].from_node == self._start.node and any(
+ e.from_node == current_node.node for e in cycle
):
return True
return False
diff --git a/src/archunitpython/common/projection/cycles/tarjan_scc.py b/src/archunitpython/common/projection/cycles/tarjan_scc.py
index 09eae5f..eabea08 100644
--- a/src/archunitpython/common/projection/cycles/tarjan_scc.py
+++ b/src/archunitpython/common/projection/cycles/tarjan_scc.py
@@ -18,9 +18,7 @@ def __init__(self, node_id: int) -> None:
class TarjanSCC:
"""Tarjan's algorithm for finding strongly connected components."""
- def find_strongly_connected_components(
- self, edges: list[NumberEdge]
- ) -> list[list[NumberEdge]]:
+ def find_strongly_connected_components(self, edges: list[NumberEdge]) -> list[list[NumberEdge]]:
"""Find all strongly connected components in the graph.
Returns a list of edge lists, where each inner list contains
@@ -78,9 +76,7 @@ def _visit(self, vertex: _Vertex) -> None:
if scc_vertices:
scc_ids = {v.id for v in scc_vertices}
scc_edges = [
- e
- for e in self._edges
- if e.from_node in scc_ids and e.to_node in scc_ids
+ e for e in self._edges if e.from_node in scc_ids and e.to_node in scc_ids
]
if scc_edges:
self._sccs.append(scc_edges)
diff --git a/src/archunitpython/common/projection/project_cycles.py b/src/archunitpython/common/projection/project_cycles.py
index fd3ded4..9413043 100644
--- a/src/archunitpython/common/projection/project_cycles.py
+++ b/src/archunitpython/common/projection/project_cycles.py
@@ -72,8 +72,7 @@ def _from_domain(self, cycles: list[list[NumberEdge]]) -> ProjectedCycles:
(
se
for se in self._source_edges
- if se.source_label == source_label
- and se.target_label == target_label
+ if se.source_label == source_label and se.target_label == target_label
),
None,
)
diff --git a/src/archunitpython/common/util/logger.py b/src/archunitpython/common/util/logger.py
index 1a2e56e..e42ba81 100644
--- a/src/archunitpython/common/util/logger.py
+++ b/src/archunitpython/common/util/logger.py
@@ -44,9 +44,7 @@ def _ensure_file_handler(self, options: LoggingOptions) -> None:
mode = "a" if options.append_to_log_file else "w"
self._file_handler = logging.FileHandler(str(log_path), mode=mode)
- self._file_handler.setFormatter(
- logging.Formatter("[%(levelname)s] %(message)s")
- )
+ self._file_handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
self._logger.addHandler(self._file_handler)
def _log(self, level: str, options: LoggingOptions | None, message: str) -> None:
diff --git a/src/archunitpython/files/assertion/custom_file_logic.py b/src/archunitpython/files/assertion/custom_file_logic.py
index a3e3e5f..573a872 100644
--- a/src/archunitpython/files/assertion/custom_file_logic.py
+++ b/src/archunitpython/files/assertion/custom_file_logic.py
@@ -83,9 +83,7 @@ def gather_custom_file_violations(
for node in nodes:
# Check if node matches all pre-filters
- if pre_filters and not all(
- matches_pattern(node.label, f) for f in pre_filters
- ):
+ if pre_filters and not all(matches_pattern(node.label, f) for f in pre_filters):
continue
file_info = _build_file_info(node.label)
@@ -94,14 +92,10 @@ def gather_custom_file_violations(
if is_negated:
# shouldNot: violation if condition IS True
if result:
- violations.append(
- CustomFileViolation(message=message, file_info=file_info)
- )
+ violations.append(CustomFileViolation(message=message, file_info=file_info))
else:
# should: violation if condition is NOT True
if not result:
- violations.append(
- CustomFileViolation(message=message, file_info=file_info)
- )
+ violations.append(CustomFileViolation(message=message, file_info=file_info))
return violations
diff --git a/src/archunitpython/files/assertion/depend_on_external_modules.py b/src/archunitpython/files/assertion/depend_on_external_modules.py
index 181cb9a..2cc6e37 100644
--- a/src/archunitpython/files/assertion/depend_on_external_modules.py
+++ b/src/archunitpython/files/assertion/depend_on_external_modules.py
@@ -34,8 +34,7 @@ def gather_depend_on_external_module_violations(
for edge in edges:
source_matches = all(
- matches_pattern(edge.source_label, filter_)
- for filter_ in subject_filters
+ matches_pattern(edge.source_label, filter_) for filter_ in subject_filters
)
if not source_matches:
continue
@@ -49,16 +48,12 @@ def gather_depend_on_external_module_violations(
if is_negated:
if target_matches:
violations.append(
- ViolatingExternalModuleDependency(
- dependency=edge, is_negated=True
- )
+ ViolatingExternalModuleDependency(dependency=edge, is_negated=True)
)
else:
if not target_matches:
violations.append(
- ViolatingExternalModuleDependency(
- dependency=edge, is_negated=False
- )
+ ViolatingExternalModuleDependency(dependency=edge, is_negated=False)
)
return violations
diff --git a/src/archunitpython/files/assertion/depend_on_files.py b/src/archunitpython/files/assertion/depend_on_files.py
index b845882..99d2946 100644
--- a/src/archunitpython/files/assertion/depend_on_files.py
+++ b/src/archunitpython/files/assertion/depend_on_files.py
@@ -41,27 +41,19 @@ def gather_depend_on_file_violations(
violations: list[Violation] = []
for edge in edges:
- source_matches = all(
- matches_pattern(edge.source_label, f) for f in subject_filters
- )
+ source_matches = all(matches_pattern(edge.source_label, f) for f in subject_filters)
if not source_matches:
continue
- target_matches = all(
- matches_pattern(edge.target_label, f) for f in object_filters
- )
+ target_matches = all(matches_pattern(edge.target_label, f) for f in object_filters)
if is_negated:
# shouldNot: violation if dependency EXISTS
if target_matches:
- violations.append(
- ViolatingFileDependency(dependency=edge, is_negated=True)
- )
+ violations.append(ViolatingFileDependency(dependency=edge, is_negated=True))
else:
# should: violation if dependency does NOT match
if not target_matches:
- violations.append(
- ViolatingFileDependency(dependency=edge, is_negated=False)
- )
+ violations.append(ViolatingFileDependency(dependency=edge, is_negated=False))
return violations
diff --git a/src/archunitpython/files/fluentapi/files.py b/src/archunitpython/files/fluentapi/files.py
index c680681..9f31036 100644
--- a/src/archunitpython/files/fluentapi/files.py
+++ b/src/archunitpython/files/fluentapi/files.py
@@ -76,15 +76,11 @@ def in_path(self, path: Pattern) -> "FilesShouldCondition":
def should(self) -> "PositiveMatchPatternFileConditionBuilder":
"""Begin positive assertion (files SHOULD ...)."""
- return PositiveMatchPatternFileConditionBuilder(
- self._project_path, list(self._filters)
- )
+ return PositiveMatchPatternFileConditionBuilder(self._project_path, list(self._filters))
def should_not(self) -> "NegatedMatchPatternFileConditionBuilder":
"""Begin negative assertion (files SHOULD NOT ...)."""
- return NegatedMatchPatternFileConditionBuilder(
- self._project_path, list(self._filters)
- )
+ return NegatedMatchPatternFileConditionBuilder(self._project_path, list(self._filters))
class FilesShouldCondition:
@@ -111,15 +107,11 @@ def in_path(self, path: Pattern) -> "FilesShouldCondition":
def should(self) -> "PositiveMatchPatternFileConditionBuilder":
"""Begin positive assertion (files SHOULD ...)."""
- return PositiveMatchPatternFileConditionBuilder(
- self._project_path, list(self._filters)
- )
+ return PositiveMatchPatternFileConditionBuilder(self._project_path, list(self._filters))
def should_not(self) -> "NegatedMatchPatternFileConditionBuilder":
"""Begin negative assertion (files SHOULD NOT ...)."""
- return NegatedMatchPatternFileConditionBuilder(
- self._project_path, list(self._filters)
- )
+ return NegatedMatchPatternFileConditionBuilder(self._project_path, list(self._filters))
class PositiveMatchPatternFileConditionBuilder:
@@ -135,9 +127,7 @@ def have_no_cycles(self) -> "CycleFreeFileCondition":
def depend_on_files(self) -> "DependOnFileConditionBuilder":
"""Begin dependency assertion - files SHOULD depend on ..."""
- return DependOnFileConditionBuilder(
- self._project_path, self._filters, is_negated=False
- )
+ return DependOnFileConditionBuilder(self._project_path, self._filters, is_negated=False)
def depend_on_external_modules(
self,
@@ -192,9 +182,7 @@ def __init__(self, project_path: str | None, filters: list[Filter]) -> None:
def depend_on_files(self) -> "DependOnFileConditionBuilder":
"""Begin dependency assertion - files SHOULD NOT depend on ..."""
- return DependOnFileConditionBuilder(
- self._project_path, self._filters, is_negated=True
- )
+ return DependOnFileConditionBuilder(self._project_path, self._filters, is_negated=True)
def depend_on_external_modules(
self,
@@ -243,9 +231,7 @@ def adhere_to(
class DependOnFileConditionBuilder:
"""Configure dependency target patterns."""
- def __init__(
- self, project_path: str | None, filters: list[Filter], is_negated: bool
- ) -> None:
+ def __init__(self, project_path: str | None, filters: list[Filter], is_negated: bool) -> None:
self._project_path = project_path
self._filters = filters
self._is_negated = is_negated
@@ -285,9 +271,7 @@ def in_path(self, path: Pattern) -> "DependOnFileCondition":
class DependOnExternalModuleConditionBuilder:
"""Configure external module dependency target patterns."""
- def __init__(
- self, project_path: str | None, filters: list[Filter], is_negated: bool
- ) -> None:
+ def __init__(self, project_path: str | None, filters: list[Filter], is_negated: bool) -> None:
self._project_path = project_path
self._filters = filters
self._is_negated = is_negated
@@ -447,9 +431,7 @@ def check(self, options: CheckOptions | None = None) -> list[Violation]:
if empty is not None:
return empty
- return gather_regex_matching_violations(
- nodes, self._check_filters, self._is_negated
- )
+ return gather_regex_matching_violations(nodes, self._check_filters, self._is_negated)
class CustomFileCheckableCondition:
diff --git a/src/archunitpython/metrics/assertion/metric_thresholds.py b/src/archunitpython/metrics/assertion/metric_thresholds.py
index 52d92d6..1bd70f7 100644
--- a/src/archunitpython/metrics/assertion/metric_thresholds.py
+++ b/src/archunitpython/metrics/assertion/metric_thresholds.py
@@ -31,9 +31,7 @@ class FileCountViolation(Violation):
comparison: MetricComparison
-def check_threshold(
- value: float, threshold: float, comparison: MetricComparison
-) -> bool:
+def check_threshold(value: float, threshold: float, comparison: MetricComparison) -> bool:
"""Check if a value violates a threshold.
Returns True if the value is a VIOLATION.
diff --git a/src/archunitpython/metrics/calculation/distance.py b/src/archunitpython/metrics/calculation/distance.py
index 4b74f76..c95e8dd 100644
--- a/src/archunitpython/metrics/calculation/distance.py
+++ b/src/archunitpython/metrics/calculation/distance.py
@@ -103,8 +103,6 @@ def calculate_distance_metrics_for_project(
average_instability=sum(m.instability for m in metrics) / len(metrics),
average_distance=sum(m.distance for m in metrics) / len(metrics),
files_in_zone_of_pain=sum(1 for m in metrics if m.in_zone_of_pain),
- files_in_zone_of_uselessness=sum(
- 1 for m in metrics if m.in_zone_of_uselessness
- ),
+ files_in_zone_of_uselessness=sum(1 for m in metrics if m.in_zone_of_uselessness),
total_files=len(files),
)
diff --git a/src/archunitpython/metrics/extraction/extract_class_info.py b/src/archunitpython/metrics/extraction/extract_class_info.py
index 24ed42e..e3d08e4 100644
--- a/src/archunitpython/metrics/extraction/extract_class_info.py
+++ b/src/archunitpython/metrics/extraction/extract_class_info.py
@@ -129,9 +129,7 @@ def _extract_class(node: ast.ClassDef, file_path: str) -> ClassInfo:
for item in ast.walk(node):
if isinstance(item, ast.Assign):
for target in item.targets:
- if isinstance(target, ast.Attribute) and isinstance(
- target.value, ast.Name
- ):
+ if isinstance(target, ast.Attribute) and isinstance(target.value, ast.Name):
if target.value.id == "self":
field_name = target.attr
if field_name not in fields:
@@ -142,9 +140,7 @@ def _extract_class(node: ast.ClassDef, file_path: str) -> ClassInfo:
if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
method_name = item.name
accessed = _find_field_accesses(item, set(fields.keys()))
- methods.append(
- MethodInfo(name=method_name, accessed_fields=accessed)
- )
+ methods.append(MethodInfo(name=method_name, accessed_fields=accessed))
# Update field access tracking
for field_name in accessed:
if field_name in fields:
@@ -159,9 +155,7 @@ def _extract_class(node: ast.ClassDef, file_path: str) -> ClassInfo:
)
-def _extract_enhanced_class(
- node: ast.ClassDef, file_path: str
-) -> EnhancedClassInfo:
+def _extract_enhanced_class(node: ast.ClassDef, file_path: str) -> EnhancedClassInfo:
"""Extract EnhancedClassInfo from a ClassDef AST node."""
base = _extract_class(node, file_path)
diff --git a/src/archunitpython/metrics/fluentapi/export_utils.py b/src/archunitpython/metrics/fluentapi/export_utils.py
index 5c4496f..5e88713 100644
--- a/src/archunitpython/metrics/fluentapi/export_utils.py
+++ b/src/archunitpython/metrics/fluentapi/export_utils.py
@@ -35,11 +35,7 @@ def export_as_html(
HTML content as a string. Also writes to file if output_path specified.
"""
opts = options or ExportOptions()
- timestamp = (
- datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- if opts.include_timestamp
- else ""
- )
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if opts.include_timestamp else ""
css = opts.custom_css or _DEFAULT_CSS
@@ -55,7 +51,7 @@ def export_as_html(
{opts.title}
- {f'Generated: {timestamp}
' if timestamp else ''}
+ {f'Generated: {timestamp}
' if timestamp else ""}
| Metric | Value |
diff --git a/src/archunitpython/metrics/fluentapi/metrics.py b/src/archunitpython/metrics/fluentapi/metrics.py
index 27107fd..2824cb4 100644
--- a/src/archunitpython/metrics/fluentapi/metrics.py
+++ b/src/archunitpython/metrics/fluentapi/metrics.py
@@ -97,9 +97,7 @@ def custom_metric(
)
-def _get_filtered_classes(
- project_path: str | None, filters: list[Filter]
-) -> list[ClassInfo]:
+def _get_filtered_classes(project_path: str | None, filters: list[Filter]) -> list[ClassInfo]:
classes = extract_class_info(project_path)
if not filters:
return classes
@@ -119,39 +117,25 @@ def __init__(self, project_path: str | None, filters: list[Filter]) -> None:
self._filters = filters
def method_count(self) -> "ClassMetricThresholdBuilder":
- return ClassMetricThresholdBuilder(
- self._project_path, self._filters, MethodCountMetric()
- )
+ return ClassMetricThresholdBuilder(self._project_path, self._filters, MethodCountMetric())
def field_count(self) -> "ClassMetricThresholdBuilder":
- return ClassMetricThresholdBuilder(
- self._project_path, self._filters, FieldCountMetric()
- )
+ return ClassMetricThresholdBuilder(self._project_path, self._filters, FieldCountMetric())
def lines_of_code(self) -> "FileMetricThresholdBuilder":
- return FileMetricThresholdBuilder(
- self._project_path, self._filters, LinesOfCodeMetric()
- )
+ return FileMetricThresholdBuilder(self._project_path, self._filters, LinesOfCodeMetric())
def statements(self) -> "FileMetricThresholdBuilder":
- return FileMetricThresholdBuilder(
- self._project_path, self._filters, StatementCountMetric()
- )
+ return FileMetricThresholdBuilder(self._project_path, self._filters, StatementCountMetric())
def imports(self) -> "FileMetricThresholdBuilder":
- return FileMetricThresholdBuilder(
- self._project_path, self._filters, ImportCountMetric()
- )
+ return FileMetricThresholdBuilder(self._project_path, self._filters, ImportCountMetric())
def classes(self) -> "FileMetricThresholdBuilder":
- return FileMetricThresholdBuilder(
- self._project_path, self._filters, ClassCountMetric()
- )
+ return FileMetricThresholdBuilder(self._project_path, self._filters, ClassCountMetric())
def functions(self) -> "FileMetricThresholdBuilder":
- return FileMetricThresholdBuilder(
- self._project_path, self._filters, FunctionCountMetric()
- )
+ return FileMetricThresholdBuilder(self._project_path, self._filters, FunctionCountMetric())
class ClassMetricThresholdBuilder:
@@ -340,19 +324,13 @@ def __init__(self, project_path: str | None, filters: list[Filter]) -> None:
self._filters = filters
def abstractness(self) -> "DistanceThresholdBuilder":
- return DistanceThresholdBuilder(
- self._project_path, self._filters, "abstractness"
- )
+ return DistanceThresholdBuilder(self._project_path, self._filters, "abstractness")
def instability(self) -> "DistanceThresholdBuilder":
- return DistanceThresholdBuilder(
- self._project_path, self._filters, "instability"
- )
+ return DistanceThresholdBuilder(self._project_path, self._filters, "instability")
def distance_from_main_sequence(self) -> "DistanceThresholdBuilder":
- return DistanceThresholdBuilder(
- self._project_path, self._filters, "distance"
- )
+ return DistanceThresholdBuilder(self._project_path, self._filters, "distance")
def not_in_zone_of_pain(self) -> "ZoneCondition":
return ZoneCondition(self._project_path, self._filters, "pain")
@@ -440,11 +418,7 @@ def check(self, options: CheckOptions | None = None) -> list[Violation]:
for file_result in files:
dm = calculate_file_distance_metrics(file_result, files)
- in_zone = (
- dm.in_zone_of_pain
- if self._zone_type == "pain"
- else dm.in_zone_of_uselessness
- )
+ in_zone = dm.in_zone_of_pain if self._zone_type == "pain" else dm.in_zone_of_uselessness
if in_zone:
violations.append(
diff --git a/src/archunitpython/slices/fluentapi/slices.py b/src/archunitpython/slices/fluentapi/slices.py
index cf314fb..31d2d28 100644
--- a/src/archunitpython/slices/fluentapi/slices.py
+++ b/src/archunitpython/slices/fluentapi/slices.py
@@ -61,15 +61,11 @@ def defined_by_regex(self, regex: re.Pattern[str]) -> "SliceConditionBuilder":
def should(self) -> "PositiveConditionBuilder":
"""Begin positive assertion (slices SHOULD ...)."""
- return PositiveConditionBuilder(
- self._project_path, self._pattern, self._regex
- )
+ return PositiveConditionBuilder(self._project_path, self._pattern, self._regex)
def should_not(self) -> "NegativeConditionBuilder":
"""Begin negative assertion (slices SHOULD NOT ...)."""
- return NegativeConditionBuilder(
- self._project_path, self._pattern, self._regex
- )
+ return NegativeConditionBuilder(self._project_path, self._pattern, self._regex)
class PositiveConditionBuilder:
@@ -133,9 +129,7 @@ def __init__(
self._regex = regex
self._forbidden_deps: list[tuple[str, str]] = []
- def contain_dependency(
- self, source: str, target: str
- ) -> "NegativeSliceCondition":
+ def contain_dependency(self, source: str, target: str) -> "NegativeSliceCondition":
"""Assert that a specific dependency should NOT exist."""
return NegativeSliceCondition(
self._project_path,
@@ -170,9 +164,7 @@ def check(self, options: CheckOptions | None = None) -> list[Violation]:
mapper = self._get_mapper()
edges = project_edges(graph, mapper)
- return gather_positive_violations(
- edges, rules, contained_nodes, self._coherence_options
- )
+ return gather_positive_violations(edges, rules, contained_nodes, self._coherence_options)
def _get_mapper(self) -> MapFunction:
if self._pattern:
diff --git a/src/archunitpython/slices/uml/generate_rules.py b/src/archunitpython/slices/uml/generate_rules.py
index 851c114..de65704 100644
--- a/src/archunitpython/slices/uml/generate_rules.py
+++ b/src/archunitpython/slices/uml/generate_rules.py
@@ -43,9 +43,7 @@ def generate_rule(puml_content: str) -> tuple[list[Rule], list[str]]:
continue
# Match component declarations: component [Name] or component [Name] #Color
- comp_match = re.match(
- r"component\s+\[([^\]]+)\]", stripped
- )
+ comp_match = re.match(r"component\s+\[([^\]]+)\]", stripped)
if comp_match:
name = comp_match.group(1).strip()
if name not in contained_nodes:
@@ -53,9 +51,7 @@ def generate_rule(puml_content: str) -> tuple[list[Rule], list[str]]:
continue
# Match relationships: [Source] --> [Target] or [Source] -> [Target]
- rel_match = re.match(
- r"\[([^\]]+)\]\s*-+>\s*\[([^\]]+)\]", stripped
- )
+ rel_match = re.match(r"\[([^\]]+)\]\s*-+>\s*\[([^\]]+)\]", stripped)
if rel_match:
source = rel_match.group(1).strip()
target = rel_match.group(2).strip()
diff --git a/src/archunitpython/testing/common/violation_factory.py b/src/archunitpython/testing/common/violation_factory.py
index a3ae6d6..326984f 100644
--- a/src/archunitpython/testing/common/violation_factory.py
+++ b/src/archunitpython/testing/common/violation_factory.py
@@ -65,9 +65,7 @@ def from_violation(violation: Violation) -> TestViolation:
)
if isinstance(violation, ViolatingCycle):
- cycle_str = " -> ".join(
- e.source_label for e in violation.cycle
- )
+ cycle_str = " -> ".join(e.source_label for e in violation.cycle)
return TestViolation(
message="Circular dependency detected",
details=f"Cycle: {cycle_str}",
diff --git a/tests/common/test_extract_graph.py b/tests/common/test_extract_graph.py
index 5339658..38b3f80 100644
--- a/tests/common/test_extract_graph.py
+++ b/tests/common/test_extract_graph.py
@@ -83,11 +83,7 @@ def test_self_referencing_edges(self):
def test_internal_edges_detected(self):
graph = extract_graph(SAMPLE_PROJECT)
- internal_non_self = [
- e
- for e in graph
- if not e.external and e.source != e.target
- ]
+ internal_non_self = [e for e in graph if not e.external and e.source != e.target]
assert len(internal_non_self) > 0
def test_external_edges_detected(self):
@@ -106,11 +102,7 @@ def test_relative_import_resolved(self):
)
# service_b imports from .service (relative)
rel_edges = [
- e
- for e in graph
- if e.source == service_b
- and e.target == service
- and not e.external
+ e for e in graph if e.source == service_b and e.target == service and not e.external
]
assert len(rel_edges) == 1
@@ -121,9 +113,7 @@ def test_caching(self):
def test_cache_clear(self):
graph1 = extract_graph(SAMPLE_PROJECT)
- graph2 = extract_graph(
- SAMPLE_PROJECT, options=CheckOptions(clear_cache=True)
- )
+ graph2 = extract_graph(SAMPLE_PROJECT, options=CheckOptions(clear_cache=True))
assert graph1 is not graph2 # Different objects after cache clear
def test_edge_has_import_kinds(self):
@@ -185,9 +175,7 @@ def test_type_checking_imports_included_by_default(self):
).replace("\\", "/")
edges = [
- edge
- for edge in graph
- if edge.source == service_path and edge.target == models_path
+ edge for edge in graph if edge.source == service_path and edge.target == models_path
]
assert len(edges) == 1
assert ImportKind.TYPE_IMPORT in edges[0].import_kinds
@@ -207,9 +195,7 @@ def test_type_checking_imports_can_be_ignored(self):
).replace("\\", "/")
edges = [
- edge
- for edge in graph
- if edge.source == service_path and edge.target == models_path
+ edge for edge in graph if edge.source == service_path and edge.target == models_path
]
assert edges == []
diff --git a/tests/files/test_files_fluentapi.py b/tests/files/test_files_fluentapi.py
index c17993d..77cac5a 100644
--- a/tests/files/test_files_fluentapi.py
+++ b/tests/files/test_files_fluentapi.py
@@ -33,12 +33,7 @@ def test_no_cycles_in_sample_project(self):
assert len(cycle_violations) == 0
def test_cycle_detection_with_filter(self):
- rule = (
- project_files(FIXTURES_DIR)
- .in_folder("**/services*")
- .should()
- .have_no_cycles()
- )
+ rule = project_files(FIXTURES_DIR).in_folder("**/services*").should().have_no_cycles()
violations = rule.check()
cycle_violations = [v for v in violations if isinstance(v, ViolatingCycle)]
assert len(cycle_violations) == 0
@@ -58,9 +53,7 @@ def test_should_not_depend(self):
.in_folder("**/utils*")
)
violations = rule.check()
- dep_violations = [
- v for v in violations if isinstance(v, ViolatingFileDependency)
- ]
+ dep_violations = [v for v in violations if isinstance(v, ViolatingFileDependency)]
assert len(dep_violations) == 0
def test_should_not_depend_violation(self):
@@ -73,9 +66,7 @@ def test_should_not_depend_violation(self):
.in_folder("**/services*")
)
violations = rule.check()
- dep_violations = [
- v for v in violations if isinstance(v, ViolatingFileDependency)
- ]
+ dep_violations = [v for v in violations if isinstance(v, ViolatingFileDependency)]
assert len(dep_violations) > 0
@@ -92,11 +83,7 @@ def test_should_not_depend_on_specific_external_module(self):
.matching("json")
)
violations = rule.check()
- dep_violations = [
- v
- for v in violations
- if isinstance(v, ViolatingExternalModuleDependency)
- ]
+ dep_violations = [v for v in violations if isinstance(v, ViolatingExternalModuleDependency)]
assert len(dep_violations) == 1
assert dep_violations[0].dependency.target_label == "json"
@@ -109,11 +96,7 @@ def test_should_not_depend_on_unmatched_external_module(self):
.matching("requests")
)
violations = rule.check()
- dep_violations = [
- v
- for v in violations
- if isinstance(v, ViolatingExternalModuleDependency)
- ]
+ dep_violations = [v for v in violations if isinstance(v, ViolatingExternalModuleDependency)]
assert len(dep_violations) == 0
def test_matching_multiple_external_modules_uses_or_semantics(self):
@@ -126,11 +109,7 @@ def test_matching_multiple_external_modules_uses_or_semantics(self):
.matching("typing")
)
violations = rule.check()
- dep_violations = [
- v
- for v in violations
- if isinstance(v, ViolatingExternalModuleDependency)
- ]
+ dep_violations = [v for v in violations if isinstance(v, ViolatingExternalModuleDependency)]
assert {v.dependency.target_label for v in dep_violations} == {
"json",
"typing",
@@ -145,11 +124,7 @@ def test_positive_external_dependency_rule_acts_as_allowlist(self):
.matching("typing")
)
violations = rule.check()
- dep_violations = [
- v
- for v in violations
- if isinstance(v, ViolatingExternalModuleDependency)
- ]
+ dep_violations = [v for v in violations if isinstance(v, ViolatingExternalModuleDependency)]
assert {v.dependency.target_label for v in dep_violations} == {
"json",
"os",
@@ -181,9 +156,7 @@ def test_lines_of_code_limit(self):
.adhere_to(lambda f: f.lines_of_code < 1000, "File too long")
)
violations = rule.check()
- custom_violations = [
- v for v in violations if isinstance(v, CustomFileViolation)
- ]
+ custom_violations = [v for v in violations if isinstance(v, CustomFileViolation)]
assert len(custom_violations) == 0
def test_extension_check(self):
@@ -193,9 +166,7 @@ def test_extension_check(self):
.adhere_to(lambda f: f.extension == ".py", "Must be Python files")
)
violations = rule.check()
- custom_violations = [
- v for v in violations if isinstance(v, CustomFileViolation)
- ]
+ custom_violations = [v for v in violations if isinstance(v, CustomFileViolation)]
assert len(custom_violations) == 0
@@ -204,27 +175,15 @@ def setup_method(self):
clear_graph_cache()
def test_empty_test_violation(self):
- rule = (
- project_files(FIXTURES_DIR)
- .in_folder("**/nonexistent*")
- .should()
- .have_no_cycles()
- )
+ rule = project_files(FIXTURES_DIR).in_folder("**/nonexistent*").should().have_no_cycles()
violations = rule.check()
- empty_violations = [
- v for v in violations if isinstance(v, EmptyTestViolation)
- ]
+ empty_violations = [v for v in violations if isinstance(v, EmptyTestViolation)]
assert len(empty_violations) == 1
def test_empty_test_allowed(self):
from archunitpython.common.fluentapi.checkable import CheckOptions
- rule = (
- project_files(FIXTURES_DIR)
- .in_folder("**/nonexistent*")
- .should()
- .have_no_cycles()
- )
+ rule = project_files(FIXTURES_DIR).in_folder("**/nonexistent*").should().have_no_cycles()
violations = rule.check(CheckOptions(allow_empty_tests=True))
assert len(violations) == 0
@@ -235,11 +194,7 @@ def setup_method(self):
def test_full_chain_no_cycles(self):
violations = (
- project_files(FIXTURES_DIR)
- .in_folder("**/services*")
- .should()
- .have_no_cycles()
- .check()
+ project_files(FIXTURES_DIR).in_folder("**/services*").should().have_no_cycles().check()
)
cycle_violations = [v for v in violations if isinstance(v, ViolatingCycle)]
assert len(cycle_violations) == 0
@@ -253,9 +208,7 @@ def test_full_chain_depend_on(self):
.in_folder("**/utils*")
.check()
)
- dep_violations = [
- v for v in violations if isinstance(v, ViolatingFileDependency)
- ]
+ dep_violations = [v for v in violations if isinstance(v, ViolatingFileDependency)]
assert len(dep_violations) == 0
def test_builder_with_multiple_filters(self):
@@ -330,9 +283,7 @@ def test_type_checking_imports_affect_rules_by_default(self):
.in_folder("**/models*")
)
violations = rule.check()
- dep_violations = [
- v for v in violations if isinstance(v, ViolatingFileDependency)
- ]
+ dep_violations = [v for v in violations if isinstance(v, ViolatingFileDependency)]
assert len(dep_violations) == 1
def test_type_checking_imports_can_be_ignored_for_rules(self):
@@ -346,10 +297,6 @@ def test_type_checking_imports_can_be_ignored_for_rules(self):
.depend_on_files()
.in_folder("**/models*")
)
- violations = rule.check(
- CheckOptions(ignore_type_checking_imports=True)
- )
- dep_violations = [
- v for v in violations if isinstance(v, ViolatingFileDependency)
- ]
+ violations = rule.check(CheckOptions(ignore_type_checking_imports=True))
+ dep_violations = [v for v in violations if isinstance(v, ViolatingFileDependency)]
assert len(dep_violations) == 0
diff --git a/tests/fixtures/metrics_project/service.py b/tests/fixtures/metrics_project/service.py
index 1d2de42..9d0f92b 100644
--- a/tests/fixtures/metrics_project/service.py
+++ b/tests/fixtures/metrics_project/service.py
@@ -19,8 +19,7 @@ def __init__(self):
self.logger = None
@abstractmethod
- def process(self):
- ...
+ def process(self): ...
def get_cache(self):
return self.cache
diff --git a/tests/integration/test_e2e.py b/tests/integration/test_e2e.py
index 8ff854a..9ecc99d 100644
--- a/tests/integration/test_e2e.py
+++ b/tests/integration/test_e2e.py
@@ -123,6 +123,7 @@ def test_common_does_not_depend_on_files(self):
)
violations = rule.check()
from archunitpython.files.assertion.depend_on_files import ViolatingFileDependency
+
dep_v = [v for v in violations if isinstance(v, ViolatingFileDependency)]
assert len(dep_v) == 0, format_violations(dep_v)
@@ -139,6 +140,7 @@ def test_common_does_not_depend_on_slices(self):
)
violations = rule.check()
from archunitpython.files.assertion.depend_on_files import ViolatingFileDependency
+
dep_v = [v for v in violations if isinstance(v, ViolatingFileDependency)]
assert len(dep_v) == 0, format_violations(dep_v)
@@ -155,6 +157,7 @@ def test_common_does_not_depend_on_testing(self):
)
violations = rule.check()
from archunitpython.files.assertion.depend_on_files import ViolatingFileDependency
+
dep_v = [v for v in violations if isinstance(v, ViolatingFileDependency)]
assert len(dep_v) == 0, format_violations(dep_v)
@@ -177,16 +180,12 @@ def test_layered_architecture_check(self):
)
violations = rule.check()
from archunitpython.files.assertion.depend_on_files import ViolatingFileDependency
+
dep_v = [v for v in violations if isinstance(v, ViolatingFileDependency)]
assert len(dep_v) == 0
def test_metrics_on_sample(self):
- rule = (
- metrics(FIXTURES_DIR)
- .count()
- .method_count()
- .should_be_below(100)
- )
+ rule = metrics(FIXTURES_DIR).count().method_count().should_be_below(100)
assert_passes(rule)
def test_custom_condition(self):
diff --git a/tests/metrics/test_export.py b/tests/metrics/test_export.py
index b781e84..cad0fd9 100644
--- a/tests/metrics/test_export.py
+++ b/tests/metrics/test_export.py
@@ -15,9 +15,7 @@ def test_basic_export(self):
def test_custom_title(self):
data = {"Metric": "Value"}
- html = MetricsExporter.export_as_html(
- data, ExportOptions(title="My Report")
- )
+ html = MetricsExporter.export_as_html(data, ExportOptions(title="My Report"))
assert "My Report" in html
def test_custom_css(self):
@@ -29,9 +27,7 @@ def test_custom_css(self):
def test_no_timestamp(self):
data = {"Metric": "Value"}
- html = MetricsExporter.export_as_html(
- data, ExportOptions(include_timestamp=False)
- )
+ html = MetricsExporter.export_as_html(data, ExportOptions(include_timestamp=False))
assert "Generated:" not in html
def test_file_output(self, tmp_path):
diff --git a/tests/metrics/test_metrics.py b/tests/metrics/test_metrics.py
index c63c305..85d7dd9 100644
--- a/tests/metrics/test_metrics.py
+++ b/tests/metrics/test_metrics.py
@@ -40,6 +40,7 @@
# --- TICKET-14: Class Info Extraction ---
+
class TestExtractClassInfo:
def test_extracts_classes(self):
classes = extract_class_info(FIXTURES_DIR)
@@ -85,6 +86,7 @@ def test_enhanced_extraction(self):
# --- TICKET-15: Count Metrics ---
+
class TestCountMetrics:
def test_method_count(self):
ci = ClassInfo(
@@ -125,6 +127,7 @@ def test_function_count(self):
# --- TICKET-16: LCOM Metrics ---
+
def _make_perfect_cohesion():
"""All methods access all fields → perfect cohesion."""
return ClassInfo(
@@ -219,6 +222,7 @@ def test_all_defined(self):
# --- TICKET-17: Distance Metrics ---
+
class TestDistanceMetrics:
def test_project_summary(self):
results = extract_enhanced_class_info(FIXTURES_DIR)
@@ -234,6 +238,7 @@ def test_empty_project(self):
# --- Threshold Checking ---
+
class TestCheckThreshold:
def test_below(self):
assert check_threshold(10, 5, "below") is True # 10 >= 5 → violation
diff --git a/tests/metrics/test_metrics_fluentapi.py b/tests/metrics/test_metrics_fluentapi.py
index fd812ec..278ba82 100644
--- a/tests/metrics/test_metrics_fluentapi.py
+++ b/tests/metrics/test_metrics_fluentapi.py
@@ -15,83 +15,50 @@
class TestCountMetricsFluentAPI:
def test_method_count_below(self):
- violations = (
- metrics(FIXTURES_DIR).count().method_count().should_be_below(50).check()
- )
+ violations = metrics(FIXTURES_DIR).count().method_count().should_be_below(50).check()
metric_violations = [v for v in violations if isinstance(v, MetricViolation)]
assert len(metric_violations) == 0
def test_method_count_violation(self):
- violations = (
- metrics(FIXTURES_DIR).count().method_count().should_be_below(2).check()
- )
+ violations = metrics(FIXTURES_DIR).count().method_count().should_be_below(2).check()
metric_violations = [v for v in violations if isinstance(v, MetricViolation)]
assert len(metric_violations) > 0
def test_field_count_below(self):
- violations = (
- metrics(FIXTURES_DIR).count().field_count().should_be_below(20).check()
- )
+ violations = metrics(FIXTURES_DIR).count().field_count().should_be_below(20).check()
metric_violations = [v for v in violations if isinstance(v, MetricViolation)]
assert len(metric_violations) == 0
def test_lines_of_code_below(self):
- violations = (
- metrics(FIXTURES_DIR)
- .count()
- .lines_of_code()
- .should_be_below(5000)
- .check()
- )
+ violations = metrics(FIXTURES_DIR).count().lines_of_code().should_be_below(5000).check()
file_violations = [v for v in violations if isinstance(v, FileCountViolation)]
assert len(file_violations) == 0
def test_lines_of_code_violation(self):
- violations = (
- metrics(FIXTURES_DIR)
- .count()
- .lines_of_code()
- .should_be_below(5)
- .check()
- )
+ violations = metrics(FIXTURES_DIR).count().lines_of_code().should_be_below(5).check()
file_violations = [v for v in violations if isinstance(v, FileCountViolation)]
assert len(file_violations) > 0
class TestLCOMMetricsFluentAPI:
def test_lcom96b_below(self):
- violations = (
- metrics(FIXTURES_DIR).lcom().lcom96b().should_be_below(1.0).check()
- )
+ violations = metrics(FIXTURES_DIR).lcom().lcom96b().should_be_below(1.0).check()
metric_violations = [v for v in violations if isinstance(v, MetricViolation)]
assert len(metric_violations) == 0
def test_lcom4_below(self):
- violations = (
- metrics(FIXTURES_DIR).lcom().lcom4().should_be_below(10).check()
- )
+ violations = metrics(FIXTURES_DIR).lcom().lcom4().should_be_below(10).check()
metric_violations = [v for v in violations if isinstance(v, MetricViolation)]
assert len(metric_violations) == 0
class TestDistanceMetricsFluentAPI:
def test_abstractness_below(self):
- violations = (
- metrics(FIXTURES_DIR)
- .distance()
- .abstractness()
- .should_be_below(1.0)
- .check()
- )
+ violations = metrics(FIXTURES_DIR).distance().abstractness().should_be_below(1.0).check()
assert isinstance(violations, list)
def test_not_in_zone_of_uselessness(self):
- violations = (
- metrics(FIXTURES_DIR)
- .distance()
- .not_in_zone_of_uselessness()
- .check()
- )
+ violations = metrics(FIXTURES_DIR).distance().not_in_zone_of_uselessness().check()
assert isinstance(violations, list)
diff --git a/tests/slices/test_slices.py b/tests/slices/test_slices.py
index f65df41..f53b327 100644
--- a/tests/slices/test_slices.py
+++ b/tests/slices/test_slices.py
@@ -28,6 +28,7 @@
# --- TICKET-10: Slicing Projections ---
+
class TestSliceByPattern:
def test_basic_pattern(self):
mapper = slice_by_pattern("src/(**)/**")
@@ -59,6 +60,7 @@ def test_self_edge_filtered(self):
class TestSliceByRegex:
def test_regex_capture(self):
import re
+
mapper = slice_by_regex(re.compile(r"src/([^/]+)/"))
edge = Edge(source="src/controllers/ctrl.py", target="src/services/svc.py", external=False)
result = mapper(edge)
@@ -101,6 +103,7 @@ def test_filters_self_edge(self):
# --- TICKET-11: PlantUML Parsing ---
+
class TestGenerateRule:
def test_basic_diagram(self):
puml = """
@@ -170,6 +173,7 @@ def test_export_simple(self):
# --- TICKET-12: Slice Assertions ---
+
class TestGatherViolations:
def test_forbidden_dependency_found(self):
edges = [ProjectedEdge(source_label="ui", target_label="db")]
@@ -202,7 +206,9 @@ def test_ignore_orphan_slices(self):
edges = [ProjectedEdge(source_label="unknown", target_label="db")]
rules = []
violations = gather_positive_violations(
- edges, rules, ["db"],
+ edges,
+ rules,
+ ["db"],
CoherenceOptions(ignore_orphan_slices=True),
)
assert len(violations) == 0
@@ -210,12 +216,14 @@ def test_ignore_orphan_slices(self):
# --- TICKET-13: Slices Fluent API ---
+
class TestSlicesFluentAPI:
def setup_method(self):
clear_graph_cache()
def test_should_not_contain_dependency(self):
import re
+
rule = (
project_slices(FIXTURES_DIR)
.defined_by_regex(re.compile(r"/([^/]+)/[^/]+\.py$"))
@@ -228,6 +236,7 @@ def test_should_not_contain_dependency(self):
def test_adhere_to_diagram_in_file(self):
import re
+
puml_path = os.path.join(FIXTURES_DIR, "architecture.puml")
rule = (
project_slices(FIXTURES_DIR)
@@ -245,6 +254,7 @@ def test_adhere_to_diagram_in_file(self):
def test_adhere_to_diagram_inline(self):
import re
+
puml = """
@startuml
component [controllers]