bpo-32505: dataclasses: raise TypeError if a member variable is of type Field, but doesn't have a type annotation. (GH-6192)

If a dataclass has a member variable that's of type Field, but it doesn't have a type annotation, raise TypeError.
This commit is contained in:
Eric V. Smith 2018-03-22 16:28:48 -04:00 committed by GitHub
parent f757b72b25
commit 56970b8ce9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 66 additions and 17 deletions

View file

@ -573,22 +573,6 @@ def _get_field(cls, a_name, a_type):
return f
def _find_fields(cls):
# Return a list of Field objects, in order, for this class (and no
# base classes). Fields are found from the class dict's
# __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.
# If __annotations__ isn't present, then this class adds no new
# annotations.
annotations = cls.__dict__.get('__annotations__', {})
return [_get_field(cls, name, type) for name, type in annotations.items()]
def _set_new_attribute(cls, name, value):
# Never overwrites an existing attribute. Returns True if the
# attribute already exists.
@ -663,10 +647,25 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
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
# If the class attribute (which is the default value for
@ -685,6 +684,11 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
else:
setattr(cls, f.name, f.default)
# 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.