-
Notifications
You must be signed in to change notification settings - Fork 0
/
sourcery_analytics_metrics_compounders.py
104 lines (72 loc) · 3.39 KB
/
sourcery_analytics_metrics_compounders.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
"""Functions for creating compound metrics."""
# This file is a copy from `sourcery_analytics.metrics.compounders` from the
# `sourcery_analytics` project.
#
# At the time of writing, this file only violates the rule `do-not-use-staticmethod`.
#
# According to the Google Python Style Guide, static functions should be preferred over
# `@staticmethod`-decorated methods.
#
# Also note that some methods lack type annotations, but Sourcery does not flag them as
# violations because they are protected methods, whose name start with an underscore.
#
# File extracted from:
# https://github.com/sourcery-ai/sourcery-analytics/blob/d605829bbc0e3ae84decf21998c26df8980c66b9/sourcery_analytics/metrics/compounders.py
#
# No changes were introduced apart from this comment block.
import typing
import astroid.nodes
from sourcery_analytics.metrics.types import Metric, MetricResult
class Compounder(typing.Protocol):
"""A compounder function produces a compound metric function."""
def __call__(self, *metrics: Metric) -> Metric:
"""Combine multiple metric functions into a single metric function."""
def tuple_metrics(*metrics: Metric) -> "TupleMetric":
"""A compounder which joins the results in a tuple."""
def tupled_metrics(node: astroid.nodes.NodeNG) -> "TupleMetricResult":
return TupleMetricResult(metric(node) for metric in metrics)
return tupled_metrics
def name_metrics(*metrics: Metric) -> "NamedMetric":
"""A compounder which joins the result as a dictionary keyed on the metric names."""
def name_dict(node: astroid.nodes.NodeNG) -> "NamedMetricResult":
return NamedMetricResult({metric.__name__: metric(node) for metric in metrics})
return name_dict
class _CompoundMetricResult:
@staticmethod
def _divone(numerator, denominator):
try:
return numerator / denominator
except (TypeError, ZeroDivisionError):
return None
@staticmethod
def _addone(augend, addend):
if not isinstance(augend, (float, int)) or not isinstance(addend, (float, int)):
return None
return augend + addend
@staticmethod
def _eqone(left, right):
return left == right
class TupleMetricResult(typing.Tuple[MetricResult, ...], _CompoundMetricResult):
"""A compound metric result comprising a tuple of sub-result values."""
def __add__(self, other):
return TupleMetricResult((self._addone(v, u) for v, u in zip(self, other)))
def __truediv__(self, other):
return TupleMetricResult((self._divone(v, other) for v in self))
def __eq__(self, other):
return all(self._eqone(v, u) for v, u in zip(self, other))
class NamedMetricResult(typing.Dict[str, MetricResult], _CompoundMetricResult):
"""A compound metric result mapping sub-metric name to sub-metric result."""
def __add__(self, other):
return NamedMetricResult(
{k: self._addone(self.get(k), other.get(k)) for k in self.keys()}
)
def __truediv__(self, other):
return NamedMetricResult(
{k: self._divone(self.get(k), other) for k in self.keys()}
)
def __iter__(self):
return iter(self.items())
def __eq__(self, other):
return all(self._eqone(u, other[k]) for k, u in self.items())
TupleMetric = typing.Callable[[astroid.nodes.NodeNG], TupleMetricResult]
NamedMetric = typing.Callable[[astroid.nodes.NodeNG], NamedMetricResult]