Skip to content

Commit 88be57f

Browse files
committed
Update codecov, type hints, and documentation
1 parent 10e4689 commit 88be57f

File tree

2 files changed

+37
-12
lines changed

2 files changed

+37
-12
lines changed

fgpyo/util/inspect.py

+14-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import sys
2+
import types as python_types
23
import typing
34
from typing import Any
45
from typing import Dict
6+
from typing import FrozenSet
57
from typing import Iterable
68
from typing import List
79
from typing import Literal
@@ -31,23 +33,24 @@
3133

3234
import fgpyo.util.types as types
3335

36+
attr: Optional[python_types.ModuleType]
37+
MISSING: FrozenSet[Any]
38+
3439
try:
3540
import attr
3641

3742
_use_attr = True
38-
from attr import NOTHING as ATTR_NOTHING
3943
from attr import fields as get_attr_fields
4044
from attr import fields_dict as get_attr_fields_dict
4145

42-
Attribute = attr.Attribute
46+
Attribute: TypeAlias = attr.Attribute # type: ignore[name-defined, no-redef]
4347
# dataclasses and attr have internal tokens for missing values, join into a set so that we can
4448
# check if a value is missing without knowing the type of backing class
45-
MISSING = {DATACLASSES_MISSING, ATTR_NOTHING}
46-
except ImportError:
49+
MISSING = frozenset({DATACLASSES_MISSING, attr.NOTHING})
50+
except ImportError: # pragma: no cover
4751
_use_attr = False
4852
attr = None
49-
ATTR_NOTHING = None
50-
Attribute = TypeVar("Attribute", bound=object) # type: ignore[misc, assignment]
53+
Attribute: TypeAlias = TypeVar("Attribute", bound=object) # type: ignore[misc, assignment, no-redef] # noqa: E501
5154

5255
# define empty placeholders for getting attr fields as a tuple or dict. They will never be
5356
# called because the import failed; but they're here to ensure that the function is defined in
@@ -62,17 +65,17 @@ def get_attr_fields_dict(cls: type) -> Dict[str, dataclasses.Field]: # type: ig
6265
return {}
6366

6467
# for consistency with successful import of attr, create a set for missing values
65-
MISSING = {DATACLASSES_MISSING}
68+
MISSING = frozenset({DATACLASSES_MISSING})
6669

67-
if TYPE_CHECKING:
70+
if TYPE_CHECKING: # pragma: no cover
6871
from _typeshed import DataclassInstance as DataclassesProtocol
6972
else:
7073

7174
class DataclassesProtocol(Protocol):
7275
__dataclasses_fields__: Dict[str, dataclasses.Field]
7376

7477

75-
if TYPE_CHECKING and _use_attr:
78+
if TYPE_CHECKING and _use_attr: # pragma: no cover
7679
from attr import AttrsInstance
7780
else:
7881

@@ -85,7 +88,7 @@ def is_attr_class(cls: type) -> bool: # type: ignore[arg-type]
8588
return hasattr(cls, "__attrs_attrs__")
8689

8790

88-
_MISSING_OR_NONE = {*MISSING, None}
91+
_MISSING_OR_NONE: FrozenSet[Any] = frozenset({*MISSING, None})
8992
"""Set of values that are considered missing or None for dataclasses or attr classes"""
9093
_DataclassesOrAttrClass: TypeAlias = Union[DataclassesProtocol, AttrsInstance]
9194
"""
@@ -148,7 +151,7 @@ def split_at_given_level(
148151
return out_vals
149152

150153

151-
NoneType = type(None)
154+
NoneType: TypeAlias = type(None) # type: ignore[no-redef]
152155

153156

154157
def list_parser(

fgpyo/util/tests/test_metric.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,23 @@ def make_dataclasses(cls: T) -> T:
5858

5959

6060
class DataBuilder:
61-
"""Holds classes and data for testing, either using attr- or dataclasses-style dataclass"""
61+
"""
62+
Holds classes and data for testing, either using attr- or dataclasses-style dataclass
63+
We need to run each test both with attr and dataclasses classes, so use this class to construct
64+
the test metrics appropriately, governed by the use_attr flag. After construction, the
65+
DataBuilder object will have all the required Metrics:
66+
67+
Attributes:
68+
use_attr: If True use attr classes for Metrics, if False use dataclasses
69+
DummyMetric: Metric with many different field types
70+
Person: Metric with optional name and age string fields
71+
Name: Metric with first and last name string fields and a parse method
72+
NamedPerson: Metric with name (Name Metric) field and age (int) fields, and parsers.
73+
PersonMaybeAge = Person with required name string field and optional age int field
74+
PersonDefault = Person with required name string field and age int field with default value
75+
ListPerson = Person with list[str] name and list[int] age fields
76+
DUMMY_METRICS: a list of 3 different DummyMetrics
77+
"""
6278

6379
def __init__(self, use_attr: bool) -> None:
6480
self.use_attr = use_attr
@@ -186,15 +202,21 @@ class ListPerson(Metric["ListPerson"]):
186202
]
187203

188204

205+
# construct the attr and dataclasses DataBuilder objects
189206
attr_data_and_classes = DataBuilder(use_attr=True)
190207
dataclasses_data_and_classes = DataBuilder(use_attr=False)
191208

209+
# get helper type and helper num_metrics that will be used frequently in tests
192210
AnyDummyMetric = Union[attr_data_and_classes.DummyMetric, dataclasses_data_and_classes.DummyMetric]
193211
num_metrics = len(attr_data_and_classes.DUMMY_METRICS)
194212

195213

196214
@pytest.mark.parametrize("use_attr", [False, True])
197215
def test_is_correct_dataclass_type(use_attr: bool) -> None:
216+
"""
217+
Test that the DataBuilder class works as expected, as do the is_attr_class and
218+
is_dataclasses_class methods
219+
"""
198220
data_and_classes = DataBuilder(use_attr=use_attr)
199221
assert use_attr == data_and_classes.use_attr
200222
assert is_attr_class(data_and_classes.DummyMetric) is use_attr

0 commit comments

Comments
 (0)