dataclasses
Advanced tools
| Metadata-Version: 1.1 | ||
| Name: dataclasses | ||
| Version: 0.4 | ||
| Version: 0.5 | ||
| Summary: An implementation of PEP 557: Data Classes | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/ericvsmith/dataclasses |
+264
-151
| import sys | ||
| import copy | ||
| import types | ||
| from copy import deepcopy | ||
| import inspect | ||
@@ -8,2 +8,3 @@ | ||
| 'field', | ||
| 'Field', | ||
| 'FrozenInstanceError', | ||
@@ -24,7 +25,7 @@ 'InitVar', | ||
| # dataclass decorator takes. For all of these tables, when I talk | ||
| # about init=, repr=, eq=, order=, hash=, or frozen=, I'm referring | ||
| # to the arguments to the @dataclass decorator. When checking if a | ||
| # dunder method already exists, I mean check for an entry in the | ||
| # class's __dict__. I never check to see if an attribute is defined | ||
| # in a base class. | ||
| # about init=, repr=, eq=, order=, unsafe_hash=, or frozen=, I'm | ||
| # referring to the arguments to the @dataclass decorator. When | ||
| # checking if a dunder method already exists, I mean check for an | ||
| # entry in the class's __dict__. I never check to see if an | ||
| # attribute is defined in a base class. | ||
@@ -39,7 +40,2 @@ # Key: | ||
| # +---------+-----------------------------------------+ | ||
| # | add* | Generated method is added only if the | | ||
| # | | existing attribute is None and if the | | ||
| # | | user supplied a __eq__ method in the | | ||
| # | | class definition. | | ||
| # +---------+-----------------------------------------+ | ||
| # | raise | TypeError is raised. | | ||
@@ -121,39 +117,32 @@ # +---------+-----------------------------------------+ | ||
| # +------------------- hash= parameter | ||
| # | +----------- eq= parameter | ||
| # | | +--- frozen= parameter | ||
| # | | | | ||
| # v v v | | | | ||
| # | no | yes | <--- class has __hash__ in __dict__? | ||
| # +=========+=======+=======+========+========+ | ||
| # | 1 None | False | False | | | No __eq__, use the base class __hash__ | ||
| # +---------+-------+-------+--------+--------+ | ||
| # | 2 None | False | True | | | No __eq__, use the base class __hash__ | ||
| # +---------+-------+-------+--------+--------+ | ||
| # | 3 None | True | False | None | | <-- the default, not hashable | ||
| # +---------+-------+-------+--------+--------+ | ||
| # | 4 None | True | True | add | add* | Frozen, so hashable | ||
| # +---------+-------+-------+--------+--------+ | ||
| # | 5 False | False | False | | | | ||
| # +---------+-------+-------+--------+--------+ | ||
| # | 6 False | False | True | | | | ||
| # +---------+-------+-------+--------+--------+ | ||
| # | 7 False | True | False | | | | ||
| # +---------+-------+-------+--------+--------+ | ||
| # | 8 False | True | True | | | | ||
| # +---------+-------+-------+--------+--------+ | ||
| # | 9 True | False | False | add | add* | Has no __eq__, but hashable | ||
| # +---------+-------+-------+--------+--------+ | ||
| # |10 True | False | True | add | add* | Has no __eq__, but hashable | ||
| # +---------+-------+-------+--------+--------+ | ||
| # |11 True | True | False | add | add* | Not frozen, but hashable | ||
| # +---------+-------+-------+--------+--------+ | ||
| # |12 True | True | True | add | add* | Frozen, so hashable | ||
| # +=========+=======+=======+========+========+ | ||
| # +------------------- unsafe_hash= parameter | ||
| # | +----------- eq= parameter | ||
| # | | +--- frozen= parameter | ||
| # | | | | ||
| # v v v | | | | ||
| # | no | yes | <--- class has explicitly defined __hash__ | ||
| # +=======+=======+=======+========+========+ | ||
| # | False | False | False | | | No __eq__, use the base class __hash__ | ||
| # +-------+-------+-------+--------+--------+ | ||
| # | False | False | True | | | No __eq__, use the base class __hash__ | ||
| # +-------+-------+-------+--------+--------+ | ||
| # | False | True | False | None | | <-- the default, not hashable | ||
| # +-------+-------+-------+--------+--------+ | ||
| # | False | True | True | add | | Frozen, so hashable, allows override | ||
| # +-------+-------+-------+--------+--------+ | ||
| # | True | False | False | add | raise | Has no __eq__, but hashable | ||
| # +-------+-------+-------+--------+--------+ | ||
| # | True | False | True | add | raise | Has no __eq__, but hashable | ||
| # +-------+-------+-------+--------+--------+ | ||
| # | True | True | False | add | raise | Not frozen, but hashable | ||
| # +-------+-------+-------+--------+--------+ | ||
| # | True | True | True | add | raise | Frozen, so hashable | ||
| # +=======+=======+=======+========+========+ | ||
| # For boxes that are blank, __hash__ is untouched and therefore | ||
| # inherited from the base class. If the base is object, then | ||
| # id-based hashing is used. | ||
| # Note that a class may have already __hash__=None if it specified an | ||
| # Note that a class may already have __hash__=None if it specified an | ||
| # __eq__ method in the class body (not one that was created by | ||
| # @dataclass). | ||
| # See _hash_action (below) for a coded version of this table. | ||
@@ -165,3 +154,3 @@ | ||
| # A sentinel object for default values to signal that a | ||
| # default-factory will be used. | ||
| # default factory will be used. | ||
| # This is given a nice repr() which will appear in the function | ||
@@ -191,4 +180,8 @@ # signature of dataclasses' constructors. | ||
| # objects. Also used to check if a class is a Data Class. | ||
| _MARKER = '__dataclass_fields__' | ||
| _FIELDS = '__dataclass_fields__' | ||
| # The name of an attribute on the class that stores the parameters to | ||
| # @dataclass. | ||
| _PARAMS = '__dataclass_params__' | ||
| # The name of the function, that if it exists, is called at the end of | ||
@@ -213,3 +206,3 @@ # __init__. | ||
| # convenient if they're available later. | ||
| # When cls._MARKER is filled in with a list of Field objects, the name | ||
| # When cls._FIELDS is filled in with a list of Field objects, the name | ||
| # and type fields will have been populated. | ||
@@ -257,3 +250,45 @@ class Field: | ||
| # This is used to support the PEP 487 __set_name__ protocol in the | ||
| # case where we're using a field that contains a descriptor as a | ||
| # defaul value. For details on __set_name__, see | ||
| # https://www.python.org/dev/peps/pep-0487/#implementation-details. | ||
| # Note that in _process_class, this Field object is overwritten with | ||
| # the default value, so the end result is a descriptor that had | ||
| # __set_name__ called on it at the right time. | ||
| def __set_name__(self, owner, name): | ||
| func = getattr(self.default, '__set_name__', None) | ||
| if func: | ||
| # There is a __set_name__ method on the descriptor, | ||
| # call it. | ||
| func(owner, name) | ||
| class _DataclassParams: | ||
| __slots__ = ('init', | ||
| 'repr', | ||
| 'eq', | ||
| 'order', | ||
| 'unsafe_hash', | ||
| 'frozen', | ||
| ) | ||
| def __init__(self, init, repr, eq, order, unsafe_hash, frozen): | ||
| self.init = init | ||
| self.repr = repr | ||
| self.eq = eq | ||
| self.order = order | ||
| self.unsafe_hash = unsafe_hash | ||
| self.frozen = frozen | ||
| def __repr__(self): | ||
| return ('_DataclassParams(' | ||
| f'init={self.init},' | ||
| f'repr={self.repr},' | ||
| f'eq={self.eq},' | ||
| f'order={self.order},' | ||
| f'unsafe_hash={self.unsafe_hash},' | ||
| f'frozen={self.frozen}' | ||
| ')') | ||
| # This function is used instead of exposing Field creation directly, | ||
@@ -299,2 +334,4 @@ # so that a type checker can be told (via overloads) that this is a | ||
| # Note that we mutate locals when exec() is called. Caller beware! | ||
| # The only callers are internal to this module, so no worries | ||
| # about external callers. | ||
| if locals is None: | ||
@@ -309,2 +346,3 @@ locals = {} | ||
| # Compute the text of the entire function. | ||
| txt = f'def {name}({args}){return_annotation}:\n{body}' | ||
@@ -422,7 +460,6 @@ | ||
| for f in fields: | ||
| # Do not initialize the pseudo-fields, only the real ones. | ||
| line = _field_init(f, frozen, globals, self_name) | ||
| if line is not None: | ||
| # line is None means that this field doesn't require | ||
| # initialization. Just skip it. | ||
| # line is None means that this field doesn't require | ||
| # initialization (it's a pseudo-field). Just skip it. | ||
| if line: | ||
| body_lines.append(line) | ||
@@ -434,3 +471,3 @@ | ||
| if f._field_type is _FIELD_INITVAR) | ||
| body_lines += [f'{self_name}.{_POST_INIT_NAME}({params_str})'] | ||
| body_lines.append(f'{self_name}.{_POST_INIT_NAME}({params_str})') | ||
@@ -443,3 +480,3 @@ # If no body lines, use 'pass'. | ||
| return _create_fn('__init__', | ||
| [self_name] +[_init_param(f) for f in fields if f.init], | ||
| [self_name] + [_init_param(f) for f in fields if f.init], | ||
| body_lines, | ||
@@ -453,3 +490,3 @@ locals=locals, | ||
| return _create_fn('__repr__', | ||
| ['self'], | ||
| ('self',), | ||
| ['return self.__class__.__qualname__ + f"(' + | ||
@@ -461,10 +498,27 @@ ', '.join([f"{f.name}={{self.{f.name}!r}}" | ||
| def _frozen_setattr(self, name, value): | ||
| raise FrozenInstanceError(f'cannot assign to field {name!r}') | ||
| def _frozen_get_del_attr(cls, fields): | ||
| # XXX: globals is modified on the first call to _create_fn, then the | ||
| # modified version is used in the second call. Is this okay? | ||
| globals = {'cls': cls, | ||
| 'FrozenInstanceError': FrozenInstanceError} | ||
| if fields: | ||
| fields_str = '(' + ','.join(repr(f.name) for f in fields) + ',)' | ||
| else: | ||
| # Special case for the zero-length tuple. | ||
| fields_str = '()' | ||
| return (_create_fn('__setattr__', | ||
| ('self', 'name', 'value'), | ||
| (f'if type(self) is cls or name in {fields_str}:', | ||
| ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', | ||
| f'super(cls, self).__setattr__(name, value)'), | ||
| globals=globals), | ||
| _create_fn('__delattr__', | ||
| ('self', 'name'), | ||
| (f'if type(self) is cls or name in {fields_str}:', | ||
| ' raise FrozenInstanceError(f"cannot delete field {name!r}")', | ||
| f'super(cls, self).__delattr__(name)'), | ||
| globals=globals), | ||
| ) | ||
| def _frozen_delattr(self, name): | ||
| raise FrozenInstanceError(f'cannot delete field {name!r}') | ||
| def _cmp_fn(name, op, self_tuple, other_tuple): | ||
@@ -477,3 +531,3 @@ # Create a comparison function. If the fields in the object are | ||
| return _create_fn(name, | ||
| ['self', 'other'], | ||
| ('self', 'other'), | ||
| [ 'if other.__class__ is self.__class__:', | ||
@@ -487,3 +541,3 @@ f' return {self_tuple}{op}{other_tuple}', | ||
| return _create_fn('__hash__', | ||
| ['self'], | ||
| ('self',), | ||
| [f'return hash({self_tuple})']) | ||
@@ -493,7 +547,7 @@ | ||
| def _get_field(cls, a_name, a_type): | ||
| # Return a Field object, for this field name and type. ClassVars | ||
| # Return a Field object for this field name and type. ClassVars | ||
| # and InitVars are also returned, but marked as such (see | ||
| # f._field_type). | ||
| # If the default value isn't derived from field, then it's | ||
| # If the default value isn't derived from Field, then it's | ||
| # only a normal default value. Convert it to a Field(). | ||
@@ -504,2 +558,5 @@ default = getattr(cls, a_name, MISSING) | ||
| else: | ||
| if isinstance(default, types.MemberDescriptorType): | ||
| # This is a field in __slots__, so it has no default value. | ||
| default = MISSING | ||
| f = field(default=default) | ||
@@ -541,5 +598,5 @@ | ||
| # Should I check for other field settings? default_factory | ||
| # seems the most serious to check for. Maybe add others. For | ||
| # example, how about init=False (or really, | ||
| # init=<not-the-default-init-value>)? It makes no sense for | ||
| # seems the most serious to check for. Maybe add others. | ||
| # For example, how about init=False (or really, | ||
| # init=<not-the-default-init-value>)? It makes no sense for | ||
| # ClassVar and InitVar to specify init=<anything>. | ||
@@ -555,17 +612,2 @@ | ||
| def _find_fields(cls): | ||
| # Return a list of Field objects, in order, for this class (and no | ||
| # base classes). Fields are found from __annotations__ (which is | ||
| # guaranteed to be ordered). Default values are from class | ||
| # attributes, if a field has a default. If the default value is | ||
| # a Field(), then it contains additional info beyond (and | ||
| # possibly including) the actual default value. Pseudo-fields | ||
| # ClassVars and InitVars are included, despite the fact that | ||
| # they're not real fields. That's dealt with later. | ||
| annotations = getattr(cls, '__annotations__', {}) | ||
| return [_get_field(cls, a_name, a_type) | ||
| for a_name, a_type in annotations.items()] | ||
| def _set_new_attribute(cls, name, value): | ||
@@ -580,3 +622,51 @@ # Never overwrites an existing attribute. Returns True if the | ||
| def _process_class(cls, repr, eq, order, hash, init, frozen): | ||
| # Decide if/how we're going to create a hash function. Key is | ||
| # (unsafe_hash, eq, frozen, does-hash-exist). Value is the action to | ||
| # take. The common case is to do nothing, so instead of providing a | ||
| # function that is a no-op, use None to signify that. | ||
| def _hash_set_none(cls, fields): | ||
| return None | ||
| def _hash_add(cls, fields): | ||
| flds = [f for f in fields if (f.compare if f.hash is None else f.hash)] | ||
| return _hash_fn(flds) | ||
| def _hash_exception(cls, fields): | ||
| # Raise an exception. | ||
| raise TypeError(f'Cannot overwrite attribute __hash__ ' | ||
| f'in class {cls.__name__}') | ||
| # | ||
| # +-------------------------------------- unsafe_hash? | ||
| # | +------------------------------- eq? | ||
| # | | +------------------------ frozen? | ||
| # | | | +---------------- has-explicit-hash? | ||
| # | | | | | ||
| # | | | | +------- action | ||
| # | | | | | | ||
| # v v v v v | ||
| _hash_action = {(False, False, False, False): None, | ||
| (False, False, False, True ): None, | ||
| (False, False, True, False): None, | ||
| (False, False, True, True ): None, | ||
| (False, True, False, False): _hash_set_none, | ||
| (False, True, False, True ): None, | ||
| (False, True, True, False): _hash_add, | ||
| (False, True, True, True ): None, | ||
| (True, False, False, False): _hash_add, | ||
| (True, False, False, True ): _hash_exception, | ||
| (True, False, True, False): _hash_add, | ||
| (True, False, True, True ): _hash_exception, | ||
| (True, True, False, False): _hash_add, | ||
| (True, True, False, True ): _hash_exception, | ||
| (True, True, True, False): _hash_add, | ||
| (True, True, True, True ): _hash_exception, | ||
| } | ||
| # See https://bugs.python.org/issue32929#msg312829 for an if-statement | ||
| # version of this table. | ||
| def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen): | ||
| # Now that dicts retain insertion order, there's no reason to use | ||
@@ -588,17 +678,41 @@ # an ordered dict. I am leveraging that ordering here, because | ||
| setattr(cls, _PARAMS, _DataclassParams(init, repr, eq, order, | ||
| unsafe_hash, frozen)) | ||
| # Find our base classes in reverse MRO order, and exclude | ||
| # ourselves. In reversed order so that more derived classes | ||
| # override earlier field definitions in base classes. | ||
| # As long as we're iterating over them, see if any are frozen. | ||
| any_frozen_base = False | ||
| has_dataclass_bases = False | ||
| for b in cls.__mro__[-1:0:-1]: | ||
| # Only process classes that have been processed by our | ||
| # decorator. That is, they have a _MARKER attribute. | ||
| base_fields = getattr(b, _MARKER, None) | ||
| # decorator. That is, they have a _FIELDS attribute. | ||
| base_fields = getattr(b, _FIELDS, None) | ||
| if base_fields: | ||
| has_dataclass_bases = True | ||
| for f in base_fields.values(): | ||
| fields[f.name] = f | ||
| if getattr(b, _PARAMS).frozen: | ||
| any_frozen_base = True | ||
| # Annotations that are defined in this class (not in base | ||
| # classes). If __annotations__ isn't present, then this class | ||
| # adds no new annotations. We use this to compute fields that | ||
| # are added by this class. | ||
| # Fields are found from cls_annotations, which is guaranteed to be | ||
| # ordered. Default values are from class attributes, if a field | ||
| # has a default. If the default value is a Field(), then it | ||
| # contains additional info beyond (and possibly including) the | ||
| # actual default value. Pseudo-fields ClassVars and InitVars are | ||
| # included, despite the fact that they're not real fields. | ||
| # That's dealt with later. | ||
| cls_annotations = cls.__dict__.get('__annotations__', {}) | ||
| # Now find fields in our class. While doing so, validate some | ||
| # things, and set the default values (as class attributes) | ||
| # where we can. | ||
| for f in _find_fields(cls): | ||
| cls_fields = [_get_field(cls, name, type) | ||
| for name, type in cls_annotations.items()] | ||
| for f in cls_fields: | ||
| fields[f.name] = f | ||
@@ -622,13 +736,32 @@ | ||
| # Remember all of the fields on our class (including bases). This | ||
| # Do we have any Field members that don't also have annotations? | ||
| for name, value in cls.__dict__.items(): | ||
| if isinstance(value, Field) and not name in cls_annotations: | ||
| raise TypeError(f'{name!r} is a field but has no type annotation') | ||
| # Check rules that apply if we are derived from any dataclasses. | ||
| if has_dataclass_bases: | ||
| # Raise an exception if any of our bases are frozen, but we're not. | ||
| if any_frozen_base and not frozen: | ||
| raise TypeError('cannot inherit non-frozen dataclass from a ' | ||
| 'frozen one') | ||
| # Raise an exception if we're frozen, but none of our bases are. | ||
| if not any_frozen_base and frozen: | ||
| raise TypeError('cannot inherit frozen dataclass from a ' | ||
| 'non-frozen one') | ||
| # Remember all of the fields on our class (including bases). This also | ||
| # marks this class as being a dataclass. | ||
| setattr(cls, _MARKER, fields) | ||
| setattr(cls, _FIELDS, fields) | ||
| # We also need to check if a parent class is frozen: frozen has to | ||
| # be inherited down. | ||
| is_frozen = frozen or cls.__setattr__ is _frozen_setattr | ||
| # Was this class defined with an explicit __hash__? Note that if | ||
| # __eq__ is defined in this class, then python will automatically | ||
| # set __hash__ to None. This is a heuristic, as it's possible | ||
| # that such a __hash__ == None was not auto-generated, but it | ||
| # close enough. | ||
| class_hash = cls.__dict__.get('__hash__', MISSING) | ||
| has_explicit_hash = not (class_hash is MISSING or | ||
| (class_hash is None and '__eq__' in cls.__dict__)) | ||
| # Was this class defined with an __eq__? Used in __hash__ logic. | ||
| auto_hash_test= '__eq__' in cls.__dict__ and getattr(cls.__dict__, '__hash__', MISSING) is None | ||
| # If we're generating ordering methods, we must be generating | ||
@@ -648,3 +781,3 @@ # the eq methods. | ||
| _init_fn(flds, | ||
| is_frozen, | ||
| frozen, | ||
| has_post_init, | ||
@@ -688,44 +821,20 @@ # The name to use for the "self" param | ||
| raise TypeError(f'Cannot overwrite attribute {name} ' | ||
| f'in {cls.__name__}. Consider using ' | ||
| f'in class {cls.__name__}. Consider using ' | ||
| 'functools.total_ordering') | ||
| if is_frozen: | ||
| for name, fn in [('__setattr__', _frozen_setattr), | ||
| ('__delattr__', _frozen_delattr)]: | ||
| if _set_new_attribute(cls, name, fn): | ||
| raise TypeError(f'Cannot overwrite attribute {name} ' | ||
| f'in {cls.__name__}') | ||
| if frozen: | ||
| for fn in _frozen_get_del_attr(cls, field_list): | ||
| if _set_new_attribute(cls, fn.__name__, fn): | ||
| raise TypeError(f'Cannot overwrite attribute {fn.__name__} ' | ||
| f'in class {cls.__name__}') | ||
| # Decide if/how we're going to create a hash function. | ||
| # TODO: Move this table to module scope, so it's not recreated | ||
| # all the time. | ||
| generate_hash = {(None, False, False): ('', ''), | ||
| (None, False, True): ('', ''), | ||
| (None, True, False): ('none', ''), | ||
| (None, True, True): ('fn', 'fn-x'), | ||
| (False, False, False): ('', ''), | ||
| (False, False, True): ('', ''), | ||
| (False, True, False): ('', ''), | ||
| (False, True, True): ('', ''), | ||
| (True, False, False): ('fn', 'fn-x'), | ||
| (True, False, True): ('fn', 'fn-x'), | ||
| (True, True, False): ('fn', 'fn-x'), | ||
| (True, True, True): ('fn', 'fn-x'), | ||
| }[None if hash is None else bool(hash), # Force bool() if not None. | ||
| bool(eq), | ||
| bool(frozen)]['__hash__' in cls.__dict__] | ||
| # No need to call _set_new_attribute here, since we already know if | ||
| # we're overwriting a __hash__ or not. | ||
| if generate_hash == '': | ||
| # Do nothing. | ||
| pass | ||
| elif generate_hash == 'none': | ||
| cls.__hash__ = None | ||
| elif generate_hash in ('fn', 'fn-x'): | ||
| if generate_hash == 'fn' or auto_hash_test: | ||
| flds = [f for f in field_list | ||
| if (f.compare if f.hash is None else f.hash)] | ||
| cls.__hash__ = _hash_fn(flds) | ||
| else: | ||
| assert False, f"can't get here: {generate_hash}" | ||
| hash_action = _hash_action[bool(unsafe_hash), | ||
| bool(eq), | ||
| bool(frozen), | ||
| has_explicit_hash] | ||
| if hash_action: | ||
| # No need to call _set_new_attribute here, since by the time | ||
| # we're here the overwriting is unconditional. | ||
| cls.__hash__ = hash_action(cls, field_list) | ||
@@ -744,3 +853,3 @@ if not getattr(cls, '__doc__'): | ||
| def dataclass(_cls=None, *, init=True, repr=True, eq=True, order=False, | ||
| hash=None, frozen=False): | ||
| unsafe_hash=False, frozen=False): | ||
| """Returns the same class as was passed in, with dunder methods | ||
@@ -753,9 +862,9 @@ added based on the fields defined in the class. | ||
| repr is true, a __repr__() method is added. If order is true, rich | ||
| comparison dunder methods are added. If hash is true, a __hash__() | ||
| method function is added. If frozen is true, fields may not be | ||
| assigned to after instance creation. | ||
| comparison dunder methods are added. If unsafe_hash is true, a | ||
| __hash__() method function is added. If frozen is true, fields may | ||
| not be assigned to after instance creation. | ||
| """ | ||
| def wrap(cls): | ||
| return _process_class(cls, repr, eq, order, hash, init, frozen) | ||
| return _process_class(cls, init, repr, eq, order, unsafe_hash, frozen) | ||
@@ -780,3 +889,3 @@ # See if we're being called as @dataclass or @dataclass(). | ||
| try: | ||
| fields = getattr(class_or_instance, _MARKER) | ||
| fields = getattr(class_or_instance, _FIELDS) | ||
| except AttributeError: | ||
@@ -792,3 +901,3 @@ raise TypeError('must be called with a dataclass type or instance') | ||
| """Returns True if obj is an instance of a dataclass.""" | ||
| return not isinstance(obj, type) and hasattr(obj, _MARKER) | ||
| return not isinstance(obj, type) and hasattr(obj, _FIELDS) | ||
@@ -799,3 +908,3 @@ | ||
| dataclass.""" | ||
| return hasattr(obj, _MARKER) | ||
| return hasattr(obj, _FIELDS) | ||
@@ -826,2 +935,3 @@ | ||
| def _asdict_inner(obj, dict_factory): | ||
@@ -840,3 +950,3 @@ if _is_dataclass_instance(obj): | ||
| else: | ||
| return deepcopy(obj) | ||
| return copy.deepcopy(obj) | ||
@@ -867,2 +977,3 @@ | ||
| def _astuple_inner(obj, tuple_factory): | ||
@@ -881,7 +992,8 @@ if _is_dataclass_instance(obj): | ||
| else: | ||
| return deepcopy(obj) | ||
| return copy.deepcopy(obj) | ||
| def make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, | ||
| repr=True, eq=True, order=False, hash=None, frozen=False): | ||
| repr=True, eq=True, order=False, unsafe_hash=False, | ||
| frozen=False): | ||
| """Return a new dynamically created dataclass. | ||
@@ -906,3 +1018,3 @@ | ||
| The parameters init, repr, eq, order, hash, and frozen are passed to | ||
| The parameters init, repr, eq, order, unsafe_hash, and frozen are passed to | ||
| dataclass(). | ||
@@ -932,4 +1044,5 @@ """ | ||
| return dataclass(cls, init=init, repr=repr, eq=eq, order=order, | ||
| hash=hash, frozen=frozen) | ||
| unsafe_hash=unsafe_hash, frozen=frozen) | ||
| def replace(obj, **changes): | ||
@@ -959,3 +1072,3 @@ """Return a new object replacing specified fields with new values. | ||
| for f in getattr(obj, _MARKER).values(): | ||
| for f in getattr(obj, _FIELDS).values(): | ||
| if not f.init: | ||
@@ -972,7 +1085,7 @@ # Error if this field is specified in changes. | ||
| # Create the new object, which calls __init__() and __post_init__ | ||
| # (if defined), using all of the init fields we've added and/or | ||
| # left in 'changes'. | ||
| # If there are values supplied in changes that aren't fields, this | ||
| # will correctly raise a TypeError. | ||
| # Create the new object, which calls __init__() and | ||
| # __post_init__() (if defined), using all of the init fields | ||
| # we've added and/or left in 'changes'. If there are values | ||
| # supplied in changes that aren't fields, this will correctly | ||
| # raise a TypeError. | ||
| return obj.__class__(**changes) |
+1
-1
| Metadata-Version: 1.1 | ||
| Name: dataclasses | ||
| Version: 0.4 | ||
| Version: 0.5 | ||
| Summary: An implementation of PEP 557: Data Classes | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/ericvsmith/dataclasses |
+27
-4
@@ -0,5 +1,15 @@ | ||
| .. image:: https://img.shields.io/pypi/v/dataclasses.svg | ||
| This is an implementation of PEP 557, Data Classes. It is a backport | ||
| for Python 3.6. Version 0.4 matches Python 3.7 beta 1. | ||
| for Python 3.6. Version 0.5 of this repo matches Python 3.7 beta 3. | ||
| Because dataclasses will be included in Python 3.7, any discussion of | ||
| dataclass features should occur on the python-dev mailing list at | ||
| https://mail.python.org/mailman/listinfo/python-dev. At this point | ||
| this repo should only be used for historical purposes (it's where the | ||
| original dataclasses discussions took place) and for discussion of the | ||
| actual backport to Python 3.6. | ||
| See https://www.python.org/dev/peps/pep-0557/ for the details. | ||
| See https://www.python.org/dev/peps/pep-0557/ for the details of how | ||
| Data Classes work. | ||
@@ -10,5 +20,17 @@ A test file can be found at | ||
| Example:: | ||
| Installation | ||
| ------------- | ||
| .. code-block:: | ||
| pip install dataclasses | ||
| Example Usage | ||
| ------------- | ||
| .. code-block:: | ||
| from dataclasses import dataclass, field | ||
| @dataclass | ||
@@ -37,3 +59,4 @@ class InventoryItem: | ||
| of this writing, it's also true for all other Python implementations | ||
| that claim to be 3.6 compatible, of which there are none. See the | ||
| that claim to be 3.6 compatible, of which there are none. Any new | ||
| 3.6 implementations are expected to have ordered dicts. See the | ||
| analysis at the end of this email: | ||
@@ -40,0 +63,0 @@ |
+1
-0
| [egg_info] | ||
| tag_build = | ||
| tag_date = 0 | ||
| tag_svn_revision = 0 | ||
+1
-1
@@ -5,3 +5,3 @@ from setuptools import setup | ||
| name='dataclasses', | ||
| version='0.4', | ||
| version='0.5', | ||
| description='An implementation of PEP 557: Data Classes', | ||
@@ -8,0 +8,0 @@ url='https://github.com/ericvsmith/dataclasses', |
Sorry, the diff of this file is too big to display
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
134664
14.38%3249
13.44%