Source code for udataclasses.functions

  1"""Module-level dataclasses functions."""
  2
  3from .constants import FIELDS_NAME
  4from .decorator import _dataclass
  5from .field import Field
  6
  7try:
  8    from typing import Any, Iterable, TypeVar
  9
 10    T = TypeVar("T")
 11except ImportError:
 12    pass
 13
 14
[docs] 15def is_dataclass(obj: object) -> bool: 16 """Check if an object or class is a dataclass.""" 17 cls = obj if isinstance(obj, type) else type(obj) 18 return hasattr(cls, FIELDS_NAME)
19 20
[docs] 21def fields(obj: object) -> tuple[Field, ...]: 22 """Retrieve all the Fields of an object or class. 23 24 Fields are returned in alphabetical order by name. 25 """ 26 cls = obj if isinstance(obj, type) else type(obj) 27 return tuple(sorted(getattr(cls, FIELDS_NAME).values(), key=lambda f: f.name))
28 29
[docs] 30def replace(obj: T, **changes: Any) -> T: 31 """Create a new object with the specified fields replaced.""" 32 fields = getattr(obj, FIELDS_NAME) 33 init_args = {f.name: getattr(obj, f.name) for f in fields.values() if f.init} 34 for name, new_value in changes.items(): 35 field = fields.get(name) 36 if not field: 37 raise TypeError(f"Unknown field: {name}") 38 if not field.init: 39 raise ValueError(f"Cannot replace field defined with init=False: {name}") 40 init_args[name] = new_value 41 return (type(obj))(**init_args)
42 43
[docs] 44def astuple(obj: object, *, tuple_factory: Any = tuple) -> Any: 45 """Intentionally unimplemented as we do not preserve field ordering.""" 46 raise NotImplementedError("astuple() is intentionally not implemented. ")
47 48
[docs] 49def asdict( 50 obj: object, 51 *, 52 dict_factory: Any = dict, 53) -> Any: 54 """Convert dataclass instance to a dict.""" 55 if not is_dataclass(obj): 56 raise TypeError(f"Expected a dataclass, got an object of type {type(obj)}") 57 args: list[tuple[str, Any]] = [] 58 for field in fields(obj): 59 name = field.name 60 value = getattr(obj, name) 61 args.append((name, asdict_value(value, dict_factory))) 62 return dict_factory(args)
63 64 65def asdict_value(obj: object, dict_factory: Any) -> Any: 66 """Internal helper for asdict. 67 68 Converts obj into a for storing into asdict entries, recursing to find 69 nested dataclass instances as needed.""" 70 71 # Types that can simply be copied over without recursion. 72 simple_types = {int, float, bool, complex, bytes, str, type(None)} 73 if type(obj) in simple_types: 74 return obj 75 if is_dataclass(obj): 76 return asdict(obj, dict_factory=dict_factory) 77 if isinstance(obj, (list, tuple)): 78 return (type(obj))(asdict_value(item, dict_factory) for item in obj) 79 if isinstance(obj, dict): 80 return { 81 asdict_value(key, dict_factory): asdict_value(value, dict_factory) 82 for key, value in obj.items() 83 } 84 raise TypeError(f"Unsupported type: {type(obj)}") 85 86
[docs] 87def make_dataclass( 88 cls_name: str, 89 fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]], 90 *, 91 bases: tuple[type, ...] = (), 92 namespace: dict[str, Any] | None = None, 93 init: bool = True, 94 repr: bool = True, 95 eq: bool = True, 96 order: bool = False, 97 unsafe_hash: bool = False, 98 frozen: bool = False, 99) -> type[Any]: 100 """Dynamically create a dataclass.""" 101 # Attributes of dynamically-created class. 102 attrs = dict(**(namespace or {})) 103 for f in fields: 104 # Normalize fields to 3-tuple form. 105 if isinstance(f, str): 106 # str to 3-tuple 107 f = (f, object, Field()) 108 if not isinstance(f, tuple): 109 raise TypeError( 110 f"Field specifier must be a str or tuple. Instead got {type(f)}" 111 ) 112 if len(f) == 2: 113 # 2-tuple to 3-tuple 114 f = (f[0], object, Field()) 115 if len(f) != 3: 116 raise TypeError( 117 f"Field specifier must have length 2 or 3. Instead got {len(f)}" 118 ) 119 name, _, field = f 120 attrs[name] = field 121 return _dataclass( 122 type(cls_name, bases, attrs), 123 init=init, 124 repr=repr, 125 eq=eq, 126 order=order, 127 unsafe_hash=unsafe_hash, 128 frozen=frozen, 129 )