-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/metric with dataclass or attr #80
Conversation
a5bec02
to
a1df504
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #80 +/- ##
==========================================
- Coverage 94.00% 87.80% -6.21%
==========================================
Files 33 16 -17
Lines 3403 1738 -1665
Branches 694 370 -324
==========================================
- Hits 3199 1526 -1673
- Misses 135 144 +9
+ Partials 69 68 -1 ☔ View full report in Codecov by Sentry. |
I should add a few tests to cover a handful of helper functions I created in |
569af29
to
dbfb9b8
Compare
I tried to address codecov. There were three categories of issue
The final math makes codecov unhappy. I'm inclined to leave things as they stand. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only documentation requested in places where I couldn't quite understand what some of the variables were necessary for unless I scanned elsewhere in the code. I'm assuming attrs is used elsewhere in fgpyo so we can't make it an install optional can we?
fgpyo/sam/__init__.py
Outdated
|
||
@length.validator | ||
def _validate_length(self, attribute: Any, value: int) -> None: | ||
def __post_init__(self) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Codecov says this branch is uncovered which makes me think attrs
is not calling __post_init__
after initialization. Could you add a unit test to ensure this isn't now dead code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was a typo, should have been __attrs_post_init__
. Good catch, thanks! I added a test.
As for your top-level question, attrs is used extensively throughout fgpyo. The original mandate was to replace attrs usages with dataclasses, but as I mentioned in the top comment, this can't be done until we drop support for python < 3.10.
fgpyo/util/inspect.py
Outdated
def get_attr_fields(cls: type) -> Tuple[dataclasses.Field, ...]: # type: ignore | ||
return () | ||
|
||
def get_attr_fields_dict(cls: type) -> Dict[str, dataclasses.Field]: # type: ignore | ||
return {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docstrings on all public functions please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added them. In this case these functions will never be called, and are basically there for static checking. I added an additional comment explaining this too.
fgpyo/util/inspect.py
Outdated
def get_fields(cls: Type[DataclassesOrAttrClass]) -> Tuple[FieldType, ...]: | ||
if not (is_dataclasses_class(cls) or is_attr_class(cls)): | ||
raise TypeError("cls must a dataclasses or attr class") | ||
return (get_dataclasses_fields(cls) if is_dataclasses_class(cls) else ()) + ( | ||
get_attr_fields(cls) if is_attr_class(cls) else () # type: ignore | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this return a set so no one relies on the order for anything meaningful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turns out that you can't mix attr and dataclasses classes (the constructors are not smart enough to pear into the superclass and set the fields, so you can't even initialize such a hybrid), so I refactored this to simply return one or the other or raise ValueError.
0fa5e0d
to
d77edd7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a masterclass in Python type trickery.
Thanks for your hard and careful work!
My only lingering concern is exposing more inspect code to the public API and then having users accidentally (or purposefully) import some of the code when we probably don't want that to happen. How do you feel about that? Do we have any easy options since the inspect namespace is already "public"?
|
||
def is_attr_class(cls: type) -> bool: # type: ignore[arg-type] | ||
"""Return True if the class is an attr class, and False otherwise""" | ||
return hasattr(cls, "__attrs_attrs__") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this unit tested?
fgpyo/util/inspect.py
Outdated
MISSING_OR_NONE = {*MISSING, None} | ||
DataclassesOrAttrClass = Union[DataclassesProtocol, AttrsInstance] | ||
FieldType: TypeAlias = Union[dataclasses.Field, Attribute] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would still document these. It makes sense what they are, but a reader will wonder what they are meant to be used for. Document like this so Sphinx picks it up:
AttrFromType = TypeVar("AttrFromType")
"""TypeVar to allow attr_from to be used with either an attr class or a dataclasses class."""
@@ -1,16 +1,12 @@ | |||
import functools |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's too late to propose making inspect.py
"private" (e.g. _inspect.py
) but I'm wondering if you think any of the public API is too tempting or dangerous for users to accidentally import and use and rely on. If yes, then we could go and prefix lots of what you added with _
to hide more things.
3bfbdca
to
10e4689
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approve based on conversation to add a little more docs to the test metric module and perhaps a type hint or two in inspect, but don't spend more than 15 minutes, and if it doesn't work it doesn't work.
* Update util.metric and related util.inspect modules to work with dataclasses or attr * Update test_metric to test both dataclasses and attr classes Closes #45
88be57f
to
cf2a4dd
Compare
Solving the issue was not particularly easy, but the real battle is with
mypy
. Reviewers should be aware that both dataclasses and attr modules have weird enough issues with type hints that they basically lie; andmypy
has special code to figure out what's going on. Thus I had to make use of# type: ignore
in multiple locations in actual code, and had to completely excludetest_metric.py
from even being checked.Also in an offline conversation with @tfenne I dropped changing
attr
from a package requirement to a dev/test requirement, because several attr-decorated classes in the main fgpyo codebase use theslots
andkw_only
keywords, which are only supported indataclasses
for python versions >= 3.10.