Skip to content

Commit ef66fbb

Browse files
committed
Added a test, made a few things private
1 parent 9c6351c commit ef66fbb

File tree

3 files changed

+55
-28
lines changed

3 files changed

+55
-28
lines changed

fgpyo/util/inspect.py

+22-13
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from typing import Type
1212
from typing import Union
1313

14-
if sys.version_info >= (3, 12):
14+
if sys.version_info >= (3, 10):
1515
from typing import TypeAlias
1616
else:
1717
from typing_extensions import TypeAlias
@@ -85,9 +85,18 @@ def is_attr_class(cls: type) -> bool: # type: ignore[arg-type]
8585
return hasattr(cls, "__attrs_attrs__")
8686

8787

88-
MISSING_OR_NONE = {*MISSING, None}
89-
DataclassesOrAttrClass = Union[DataclassesProtocol, AttrsInstance]
88+
_MISSING_OR_NONE = {*MISSING, None}
89+
"""Set of values that are considered missing or None for dataclasses or attr classes"""
90+
_DataclassesOrAttrClass: TypeAlias = Union[DataclassesProtocol, AttrsInstance]
91+
"""
92+
TypeAlias for dataclasses or attr classes. Mostly nonsense because they are not true types, they
93+
are traits, but there is no python trait-tester.
94+
"""
9095
FieldType: TypeAlias = Union[dataclasses.Field, Attribute]
96+
"""
97+
TypeAlias for dataclass Fields or attrs Attributes. It will correspond to the correct type for the
98+
corresponding _DataclassesOrAttrClass
99+
"""
91100

92101

93102
def get_dataclasses_fields_dict(
@@ -378,7 +387,7 @@ def get_parser() -> partial:
378387

379388

380389
def get_fields_dict(
381-
cls: Union[DataclassesOrAttrClass, Type[DataclassesOrAttrClass]]
390+
cls: Union[_DataclassesOrAttrClass, Type[_DataclassesOrAttrClass]]
382391
) -> Mapping[str, FieldType]:
383392
"""Get the fields dict from either a dataclasses or attr dataclass (or instance)"""
384393
if is_dataclasses_class(cls):
@@ -390,7 +399,7 @@ def get_fields_dict(
390399

391400

392401
def get_fields(
393-
cls: Union[DataclassesOrAttrClass, Type[DataclassesOrAttrClass]]
402+
cls: Union[_DataclassesOrAttrClass, Type[_DataclassesOrAttrClass]]
394403
) -> Tuple[FieldType, ...]:
395404
"""Get the fields tuple from either a dataclasses or attr dataclass (or instance)"""
396405
if is_dataclasses_class(cls):
@@ -401,15 +410,15 @@ def get_fields(
401410
raise TypeError("cls must a dataclasses or attr class")
402411

403412

404-
# TypeVar to allow attr_from to be used with either an attr class or a dataclasses class
405-
AttrFromType = TypeVar("AttrFromType")
413+
_AttrFromType = TypeVar("_AttrFromType")
414+
"""TypeVar to allow attr_from to be used with either an attr class or a dataclasses class"""
406415

407416

408417
def attr_from(
409-
cls: Type[AttrFromType],
418+
cls: Type[_AttrFromType],
410419
kwargs: Dict[str, str],
411420
parsers: Optional[Dict[type, Callable[[str], Any]]] = None,
412-
) -> AttrFromType:
421+
) -> _AttrFromType:
413422
"""Builds an attr or dataclasses class from key-word arguments
414423
415424
Args:
@@ -455,7 +464,7 @@ def attr_from(
455464
set_value
456465
), f"Do not know how to convert string to {attribute.type} for value: {str_value}"
457466
else: # no value, check for a default
458-
assert attribute.default is not None or attribute_is_optional(
467+
assert attribute.default is not None or _attribute_is_optional(
459468
attribute
460469
), f"No value given and no default for attribute `{attribute.name}`"
461470
return_value = attribute.default
@@ -468,13 +477,13 @@ def attr_from(
468477
return cls(**return_values)
469478

470479

471-
def attribute_is_optional(attribute: FieldType) -> bool:
480+
def _attribute_is_optional(attribute: FieldType) -> bool:
472481
"""Returns True if the attribute is optional, False otherwise"""
473482
return typing.get_origin(attribute.type) is Union and isinstance(
474483
None, typing.get_args(attribute.type)
475484
)
476485

477486

478-
def attribute_has_default(attribute: FieldType) -> bool:
487+
def _attribute_has_default(attribute: FieldType) -> bool:
479488
"""Returns True if the attribute has a default value, False otherwise"""
480-
return attribute.default not in MISSING_OR_NONE or attribute_is_optional(attribute)
489+
return attribute.default not in _MISSING_OR_NONE or _attribute_is_optional(attribute)

fgpyo/util/metric.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def read(cls, path: Path, ignore_extra_fields: bool = True) -> Iterator[Any]:
190190
fields_with_defaults = [
191191
field
192192
for field in missing_from_file
193-
if inspect.attribute_has_default(field_name_to_attribute[field])
193+
if inspect._attribute_has_default(field_name_to_attribute[field])
194194
]
195195
# remove optional class fields from the fields
196196
missing_from_file = missing_from_file.difference(fields_with_defaults)

fgpyo/util/tests/test_inspect.py

+32-14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import dataclasses
12
from typing import Dict
23
from typing import List
34
from typing import Optional
@@ -7,12 +8,14 @@
78
import attr
89
import pytest
910

11+
from fgpyo.util.inspect import _attribute_has_default
12+
from fgpyo.util.inspect import _attribute_is_optional
1013
from fgpyo.util.inspect import attr_from
11-
from fgpyo.util.inspect import attribute_has_default
12-
from fgpyo.util.inspect import attribute_is_optional
1314
from fgpyo.util.inspect import dict_parser
1415
from fgpyo.util.inspect import get_fields
1516
from fgpyo.util.inspect import get_fields_dict
17+
from fgpyo.util.inspect import is_attr_class
18+
from fgpyo.util.inspect import is_dataclasses_class
1619
from fgpyo.util.inspect import list_parser
1720
from fgpyo.util.inspect import set_parser
1821
from fgpyo.util.inspect import tuple_parser
@@ -44,22 +47,22 @@ def test_attr_from() -> None:
4447

4548
def test_attribute_is_optional() -> None:
4649
fields_dict = attr.fields_dict(Name)
47-
assert not attribute_is_optional(fields_dict["required"])
48-
assert not attribute_is_optional(fields_dict["custom_parser"])
49-
assert not attribute_is_optional(fields_dict["converted"])
50-
assert attribute_is_optional(fields_dict["optional_no_default"])
51-
assert attribute_is_optional(fields_dict["optional_with_default_none"])
52-
assert attribute_is_optional(fields_dict["optional_with_default_some"])
50+
assert not _attribute_is_optional(fields_dict["required"])
51+
assert not _attribute_is_optional(fields_dict["custom_parser"])
52+
assert not _attribute_is_optional(fields_dict["converted"])
53+
assert _attribute_is_optional(fields_dict["optional_no_default"])
54+
assert _attribute_is_optional(fields_dict["optional_with_default_none"])
55+
assert _attribute_is_optional(fields_dict["optional_with_default_some"])
5356

5457

5558
def test_attribute_has_default() -> None:
5659
fields_dict = attr.fields_dict(Name)
57-
assert not attribute_has_default(fields_dict["required"])
58-
assert not attribute_has_default(fields_dict["custom_parser"])
59-
assert not attribute_has_default(fields_dict["converted"])
60-
assert attribute_has_default(fields_dict["optional_no_default"])
61-
assert attribute_has_default(fields_dict["optional_with_default_none"])
62-
assert attribute_has_default(fields_dict["optional_with_default_some"])
60+
assert not _attribute_has_default(fields_dict["required"])
61+
assert not _attribute_has_default(fields_dict["custom_parser"])
62+
assert not _attribute_has_default(fields_dict["converted"])
63+
assert _attribute_has_default(fields_dict["optional_no_default"])
64+
assert _attribute_has_default(fields_dict["optional_with_default_none"])
65+
assert _attribute_has_default(fields_dict["optional_with_default_some"])
6366

6467

6568
class Foo:
@@ -71,6 +74,11 @@ class Bar:
7174
foo: Foo
7275

7376

77+
@dataclasses.dataclass(frozen=True)
78+
class Baz:
79+
foo: Foo
80+
81+
7482
# Test for regression #94 - the call to attr_from succeeds when the check for None type
7583
# in inspect._get_parser is done incorrectly.
7684
def test_attr_from_custom_type_without_parser_fails() -> None:
@@ -125,3 +133,13 @@ class NonDataClass:
125133

126134
with pytest.raises(TypeError):
127135
attr_from(cls=NonDataClass, kwargs={"x": "1"}, parsers={int: int})
136+
137+
138+
def test_is_attrs_is_dataclasses() -> None:
139+
assert not is_attr_class(Foo)
140+
assert not is_dataclasses_class(Foo)
141+
142+
assert is_attr_class(Bar)
143+
assert not is_dataclasses_class(Bar)
144+
assert is_dataclasses_class(Baz)
145+
assert not is_attr_class(Baz)

0 commit comments

Comments
 (0)