Metrics¶
Pymetrica reports six metrics today: ALOC, CC, HV, Primitive Obsession,
Maintainability Cost, and Instability.
In the default short report, Pymetrica prints only the top-level values returned by each metric. The long report adds descriptive summaries and, where implemented, per-layer breakdowns.
Abstract Lines Of Code (ALOC)¶
ALOC highlights abstraction-heavy lines rather than concrete operations.
The current implementation counts:
- import statements
__all__assignments- function definitions and their decorators
- class definitions and their decorators
- the logical body size of concrete classes that are never instantiated in the analyzed codebase
For the unused-class body adjustment, Pymetrica currently inspects top-level
class definitions. Classes inheriting from ABC, ABCMeta, Protocol, or
Enum are treated as abstract bases and do not receive the concrete-class body
penalty. A class is considered used when it appears in a direct call, a class
method call, or as a positional call argument.
Reported fields:
aloc_numberaloc_percentage
The long report also includes per-layer ALOC totals.
Cyclomatic Complexity (CC)¶
Cyclomatic Complexity measures how many decision paths exist in the analyzed code.
Pymetrica walks the AST and counts branching and control-flow constructs, then reports:
cc_numberlloc_per_cc
lloc_per_cc normalizes complexity against the amount of logical code. The long
report includes per-layer CC values.
The visitor currently increments complexity for if and ternary expressions,
match statements and cases, boolean and/or chains, for/async for and
while loops, loop else blocks, try handlers and else blocks,
with/async with items, assert statements, and comprehensions.
When cc_fail_threshold is configured, lower lloc_per_cc values fail the
threshold because they indicate more decision points per logical line.
Halstead Volume (HV)¶
Halstead Volume estimates cognitive load from the number of operators and operands in the codebase.
Pymetrica collects operator and operand counts from the AST and reports:
hv_numberhv_per_lloc
The long report also includes per-layer Halstead values.
The visitor counts operators for assignments, augmented assignments, binary,
unary, boolean, and comparison operations, if/for/while, and function and
class definitions. It counts operands from function names, class names, names,
attributes, and constants.
Primitive Obsession (PO)¶
Primitive Obsession highlights type annotations that rely heavily on primitive types instead of domain-specific abstractions.
The current implementation counts annotations for:
- primitive scalar types:
int,float,bool,str, andAny - targeted container types:
dict,list,tuple, andset - containers whose arguments are also primitive or targeted types
Anyas both primitive and targeted
The parser is intentionally narrow today. It recognizes simple names and
attributes such as typing.Any, subscripted built-in containers such as
list[int] or dict[str, Any], and PEP 604 | unions when every member is a
supported primitive or targeted container form. Unsupported annotations,
including many custom types and uppercase typing aliases such as typing.List,
are ignored for this metric.
Short-report fields:
all_primitives_percenttargeted_primitives_percent
The percentages normalize annotation counts against total logical lines of code. The long report summary includes the raw primitive counts and per-layer primitive counts. Failure messages track whether the all-primitive or targeted primitive threshold failed.
Maintainability Cost (MC)¶
Maintainability Cost is Pymetrica's maintainability score. Lower values are better.
It is derived from:
- Halstead volume density
- cyclomatic complexity density
- a small size penalty based on logical lines of code
Reported fields:
maintainability_costraw_line_cost
raw_line_cost is the density-based portion of the score before the additional
size penalty is applied. The long report includes per-layer MC values.
The implemented formula is:
text
hv_density = hv_number / lloc_number
cc_density = cc_number / lloc_number
raw_line_cost = (hv_density * ((cc_density * 100) or 1)) / 20
maintainability_cost = raw_line_cost + (lloc_number * 0.001)
Instability¶
Instability measures how much a layer depends on other layers relative to how much other layers depend on it.
Pymetrica uses the classic ratio:
text
instability = efferent_coupling / (efferent_coupling + afferent_coupling)
Interpretation:
0.0means stable- values closer to
1.0mean more outgoing dependency pressure
Important implementation details:
- instability is calculated per layer
- files at the codebase root are reported under a synthetic
rootlayer - the current dependency analysis is driven by
from ... import ...statements between layers
Short Report vs Long Report¶
The short report is optimized for compact terminal output. It prints only the
values returned by each metric's dict_ property when report content is
rendered. Use -rt BASIC_HOOK when you want threshold failures to affect the
process exit status in CI or hooks.
That means:
- ALOC, CC, HV, PO, and MC short reports omit their per-layer lists
- PO short reports also omit raw counts and failure flags, leaving only the two percentage fields
- instability short output already includes the layer map, because its result is itself a dictionary of layer names to scores
Use pymetrica run-all --long-report DIR_PATH when you want the descriptive
summaries and per-layer breakdowns.
The individual aloc, cc, hv, po, and mc commands already use the
descriptive report format because they operate on one metric at a time. They do
not expose a separate --long-report switch.