-
Notifications
You must be signed in to change notification settings - Fork 87
Expand file tree
/
Copy path_dataclass.py
More file actions
50 lines (40 loc) · 1.7 KB
/
_dataclass.py
File metadata and controls
50 lines (40 loc) · 1.7 KB
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
# This is a private module, but setuptools has the explicit permission to use it.
from __future__ import annotations
import warnings
from dataclasses import dataclass, fields
from functools import wraps
from typing import TypeVar
from .compat.py310 import dataclass_transform
_T = TypeVar("_T", bound=type)
@dataclass_transform()
def lenient_dataclass(**dc_kwargs):
"""
Problem this class intends to solve:
- We need to modify __init__ so to achieve backwards compatibility
and keep allowing arbitrary keywords to be ignored
- But we don't want to throw away the dataclass-generated __init__
specially because we don't want to have to redefine all the typing
for the function arguments
"""
@wraps(dataclass)
def _wrap(cls: _T) -> _T:
cls = dataclass(**dc_kwargs)(cls)
# Allowed field names in order
safe = tuple(f.name for f in fields(cls))
orig_init = cls.__init__
@wraps(orig_init)
def _wrapped_init(self, *args, **kwargs):
extra = {repr(k): kwargs.pop(k) for k in tuple(kwargs) if k not in safe}
if extra:
msg = f"""
Please remove unknown `{cls.__name__}` options: {','.join(extra)}
this kind of usage is deprecated and may cause errors in the future.
"""
warnings.warn(msg)
# Ensure default values (e.g. []) are used instead of None:
positional = {k: v for k, v in zip(safe, args) if v is not None}
keywords = {k: v for k, v in kwargs.items() if v is not None}
return orig_init(self, **positional, **keywords)
cls.__init__ = _wrapped_init
return cls
return _wrap