def _init_subclass(cls, serializer: str = None, namespace: str = None, include_metadata: bool = None, isodates: bool = None, abstract: bool = False, allow_blessed_key: bool = None, decimals: bool = None) -> None: if abstract: # Custom base classes can set this to skip class initialization. cls.__is_abstract__ = True return cls.__is_abstract__ = False # Can set serializer/namespace/etc. using: # class X(Record, serializer='json', namespace='com.vandelay.X'): # ... try: custom_options = cls.Options except AttributeError: custom_options = None else: delattr(cls, 'Options') options = getattr(cls, '_options', None) if options is None: options = ModelOptions() else: options = options.clone_defaults() if custom_options: options.__dict__.update(custom_options.__dict__) if serializer is not None: options.serializer = serializer if include_metadata is not None: options.include_metadata = include_metadata if isodates is not None: options.isodates = isodates if decimals is not None: options.decimals = decimals if allow_blessed_key is not None: options.allow_blessed_key = allow_blessed_key options.namespace = namespace or canoname(cls) # Add introspection capabilities cls._contribute_to_options(options) # Add FieldDescriptors for every field. cls._contribute_field_descriptors(cls, options) # Store options on new subclass. cls._options = options cls._contribute_methods() # Register in the global registry, so we can look up # models by namespace. registry[options.namespace] = cls cls._model_init = cls._BUILD_init() if '__init__' not in cls.__dict__: cls.__init__ = cls._model_init
def _contribute_to_options(cls, options: ModelOptions) -> None: # Find attributes and their types, and create indexes for these. # This only happens once when the class is created, so Faust # models are fast at runtime. fields, defaults = annotations( cls, stop=Record, skip_classvar=True, alias_types=ALIAS_FIELD_TYPES, localns={cls.__name__: cls}, ) options.fields = cast(Mapping, fields) options.fieldset = frozenset(fields) options.fieldpos = {i: k for i, k in enumerate(fields.keys())} # extract all default values, but only for actual fields. options.defaults = { k: v.default if isinstance(v, FieldDescriptor) else v for k, v in defaults.items() if k in fields and not (isinstance(v, FieldDescriptor) and v.required) } # Raise error if non-defaults are mixed in with defaults # like namedtuple/dataclasses do. local_defaults = [] for attr_name in cls.__annotations__: if attr_name in cls.__dict__: default_value = cls.__dict__[attr_name] if isinstance(default_value, FieldDescriptorT): if not default_value.required: local_defaults.append(attr_name) else: local_defaults.append(attr_name) else: if local_defaults: raise TypeError( E_NON_DEFAULT_FOLLOWS_DEFAULT.format( cls_name=cls.__name__, field_name=attr_name, fields=pluralize(len(local_defaults), 'field'), default_names=', '.join(local_defaults), )) for field, typ in fields.items(): if is_optional(typ): # Optional[X] also needs to be added to defaults mapping. options.defaults.setdefault(field, None) # Create frozenset index of default fields. options.optionalset = frozenset(options.defaults)
def _contribute_to_options(cls, options: ModelOptions) -> None: # Find attributes and their types, and create indexes for these. # This only happens once when the class is created, so Faust # models are fast at runtime. fields, defaults = annotations( cls, stop=Record, skip_classvar=True, alias_types=ALIAS_FIELD_TYPES, localns={cls.__name__: cls}, ) options.fields = cast(Mapping, fields) options.fieldset = frozenset(fields) options.fieldpos = {i: k for i, k in enumerate(fields.keys())} is_concretely = _is_concretely # extract all default values, but only for actual fields. options.defaults = { k: v.default if isinstance(v, FieldDescriptor) else v for k, v in defaults.items() if k in fields and not (isinstance(v, FieldDescriptor) and v.required) } options.models = {} modelattrs = options.modelattrs = {} for field, typ in fields.items(): is_model, polymorphic_type = _is_model(typ) if is_model: # Extract all model fields options.models[field] = typ # Create mapping of model fields to polymorphic types if # available modelattrs[field] = polymorphic_type if is_optional(typ): # Optional[X] also needs to be added to defaults mapping. options.defaults.setdefault(field, None) # Create frozenset index of default fields. options.optionalset = frozenset(options.defaults) # extract all fields that we want to coerce to a different type # (decimals=True, isodates=True, coercions={MyClass: converter}) # Then move them to options.field_coerce, which is what the # model.__init__ method uses to coerce any fields that need to # be coerced. options.field_coerce = {} if options.isodates: options.coercions.setdefault(DATE_TYPES, iso8601.parse) if options.decimals: options.coercions.setdefault(DECIMAL_TYPES, str_to_decimal) for coerce_types, coerce_handler in options.coercions.items(): options.field_coerce.update({ field: TypeCoerce(typ, coerce_handler) for field, typ in fields.items() if field not in modelattrs and is_concretely(coerce_types, typ) })
def _contribute_to_options(cls, options: ModelOptions) -> None: # Find attributes and their types, and create indexes for these. # This only happens once when the class is created, so Faust # models are fast at runtime. fields, defaults = annotations( cls, stop=Record, skip_classvar=True, alias_types=ALIAS_FIELD_TYPES, localns={cls.__name__: cls}, ) options.fields = cast(Mapping, fields) options.fieldset = frozenset(fields) options.fieldpos = {i: k for i, k in enumerate(fields.keys())} is_date = _is_date is_decimal = _is_decimal # extract all default values, but only for actual fields. options.defaults = { k: v.default if isinstance(v, FieldDescriptor) else v for k, v in defaults.items() if k in fields and not ( isinstance(v, FieldDescriptor) and v.required) } options.optionalset = frozenset(options.defaults) options.models = {} modelattrs = options.modelattrs = {} for field, typ in fields.items(): is_model, concrete_type = _is_model(typ) if is_model: # Extract all model fields options.models[field] = typ # Create mapping of model fields to concrete types if available modelattrs[field] = concrete_type # extract all fields that are not built-in types, # e.g. List[datetime] options.converse = {} if options.isodates: options.converse.update({ field: Converter(typ, cls._parse_iso8601) for field, typ in fields.items() if field not in modelattrs and is_date(typ) }) if options.decimals: options.converse.update({ field: Converter(typ, cls._parse_decimal) for field, typ in fields.items() if field not in modelattrs and is_decimal(typ) })
def _contribute_field_descriptors( cls, target: Type, options: ModelOptions, parent: FieldDescriptorT = None) -> FieldMap: fields = options.fields defaults = options.defaults date_parser = options.date_parser coerce = options.coerce index = {} secret_fields = set() sensitive_fields = set() personal_fields = set() tagged_fields = set() def add_to_tagged_indices(field: str, tag: Type[Tag]) -> None: if tag.is_secret: options.has_secret_fields = True secret_fields.add(field) if tag.is_sensitive: options.has_sensitive_fields = True sensitive_fields.add(field) if tag.is_personal: options.has_personal_fields = True personal_fields.add(field) options.has_tagged_fields = True tagged_fields.add(field) def add_related_to_tagged_indices(field: str, related_model: Type = None) -> None: if related_model is None: return try: related_options = related_model._options except AttributeError: return if related_options.has_secret_fields: options.has_secret_fields = True secret_fields.add(field) if related_options.has_sensitive_fields: options.has_sensitive_fields = True sensitive_fields.add(field) if related_options.has_personal_fields: options.has_personal_fields = True personal_fields.add(field) if related_options.has_tagged_fields: options.has_tagged_fields = True tagged_fields.add(field) for field, typ in fields.items(): try: default, needed = defaults[field], False except KeyError: default, needed = None, True descr = getattr(target, field, None) if is_optional(typ): target_type = remove_optional(typ) else: target_type = typ if descr is None or not isinstance(descr, FieldDescriptorT): DescriptorType, tag = field_for_type(target_type) if tag: add_to_tagged_indices(field, tag) descr = DescriptorType( field=field, type=typ, model=cls, required=needed, default=default, parent=parent, coerce=coerce, date_parser=date_parser, tag=tag, ) else: descr = descr.clone( field=field, type=typ, model=cls, required=needed, default=default, parent=parent, coerce=coerce, ) descr.on_model_attached() for related_model in descr.related_models: add_related_to_tagged_indices(field, related_model) setattr(target, field, descr) index[field] = descr options.secret_fields = frozenset(secret_fields) options.sensitive_fields = frozenset(sensitive_fields) options.personal_fields = frozenset(personal_fields) options.tagged_fields = frozenset(tagged_fields) return index
def _init_subclass( cls, serializer: str = None, namespace: str = None, include_metadata: bool = None, isodates: bool = None, abstract: bool = False, allow_blessed_key: bool = None, decimals: bool = None, coerce: bool = None, coercions: CoercionMapping = None, polymorphic_fields: bool = None, validation: bool = None, date_parser: Callable[[Any], datetime] = None, ) -> None: # Can set serializer/namespace/etc. using: # class X(Record, serializer='json', namespace='com.vandelay.X'): # ... try: custom_options = cls.Options except AttributeError: custom_options = None else: delattr(cls, "Options") options = getattr(cls, "_options", None) if options is None: options = ModelOptions() options.coercions = {} options.defaults = {} else: options = options.clone_defaults() if custom_options: options.__dict__.update(custom_options.__dict__) if coerce is not None: options.coerce = coerce if coercions is not None: options.coercions.update(coercions) if serializer is not None: options.serializer = serializer if include_metadata is not None: options.include_metadata = include_metadata if isodates is not None: options.isodates = isodates if decimals is not None: options.decimals = decimals if allow_blessed_key is not None: options.allow_blessed_key = allow_blessed_key if polymorphic_fields is not None: options.polymorphic_fields = polymorphic_fields if validation is not None: options.validation = validation options.coerce = True # validation implies coerce if date_parser is not None: options.date_parser = date_parser options.namespace = namespace or canoname(cls) if abstract: # Custom base classes can set this to skip class initialization. cls.__is_abstract__ = True cls._options = options cls.__init__ = cls.__abstract_init__ # type: ignore return cls.__is_abstract__ = False # Add introspection capabilities cls._contribute_to_options(options) # Add FieldDescriptors for every field. options.descriptors = cls._contribute_field_descriptors(cls, options) # Store options on new subclass. cls._options = options cls._contribute_methods() # Register in the global registry, so we can look up # models by namespace. registry[options.namespace] = cls codegens = [ ("__init__", cls._BUILD_init, "_model_init"), ("__hash__", cls._BUILD_hash, "_model_hash"), ("__eq__", cls._BUILD_eq, "_model_eq"), ("__ne__", cls._BUILD_ne, "_model_ne"), ("__gt__", cls._BUILD_gt, "_model_gt"), ("__ge__", cls._BUILD_ge, "_model_ge"), ("__lt__", cls._BUILD_lt, "_model_lt"), ("__le__", cls._BUILD_le, "_model_le"), ] for meth_name, meth_gen, attr_name in codegens: # self._model_init = cls._BUILD_init() # if '__init__' not in cls.__dict__: # cls.__init__ = self._model_init meth = meth_gen() setattr(cls, attr_name, meth) if meth_name not in cls.__dict__: setattr(cls, meth_name, meth)
def _contribute_to_options(cls, options: ModelOptions) -> None: # Find attributes and their types, and create indexes for these. # This only happens once when the class is created, so Faust # models are fast at runtime. fields, defaults = annotations( cls, stop=Record, skip_classvar=True, alias_types=ALIAS_FIELD_TYPES, localns={cls.__name__: cls}, ) options.fields = cast(Mapping, fields) options.fieldset = frozenset(fields) options.fieldpos = {i: k for i, k in enumerate(fields.keys())} # extract all default values, but only for actual fields. options.defaults = { k: v.default if isinstance(v, FieldDescriptor) else v for k, v in defaults.items() if k in fields and not (isinstance(v, FieldDescriptor) and v.required) } options.models = {} options.polyindex = {} modelattrs = options.modelattrs = {} def _is_concrete_type(field: str, wanted: IsInstanceArgT) -> bool: typeinfo = options.polyindex[field] try: return issubclass(typeinfo.member_type, wanted) except TypeError: return False # Raise error if non-defaults are mixed in with defaults # like namedtuple/dataclasses do. local_defaults = [] for attr_name in cls.__annotations__: if attr_name in cls.__dict__: default_value = cls.__dict__[attr_name] if isinstance(default_value, FieldDescriptorT): if not default_value.required: local_defaults.append(attr_name) else: local_defaults.append(attr_name) else: if local_defaults: raise TypeError( E_NON_DEFAULT_FOLLOWS_DEFAULT.format( cls_name=cls.__name__, field_name=attr_name, fields=pluralize(len(local_defaults), 'field'), default_names=', '.join(local_defaults), )) for field, typ in fields.items(): is_model, member_type, generic_type = _is_model(typ) options.polyindex[field] = TypeInfo(generic_type, member_type) if is_model: # Extract all model fields options.models[field] = typ # Create mapping of model fields to polymorphic types if # available modelattrs[field] = generic_type if is_optional(typ): # Optional[X] also needs to be added to defaults mapping. options.defaults.setdefault(field, None) # Create frozenset index of default fields. options.optionalset = frozenset(options.defaults) # extract all fields that we want to coerce to a different type # (decimals=True, isodates=True, coercions={MyClass: converter}) # Then move them to options.field_coerce, which is what the # model.__init__ method uses to coerce any fields that need to # be coerced. options.field_coerce = {} if options.isodates: options.coercions.setdefault(DATE_TYPES, iso8601.parse) if options.decimals: options.coercions.setdefault(DECIMAL_TYPES, str_to_decimal) for coerce_types, coerce_handler in options.coercions.items(): options.field_coerce.update({ field: TypeCoerce(typ, coerce_handler) for field, typ in fields.items() if (field not in modelattrs and _is_concrete_type(field, coerce_types)) })