Esempio n. 1
0
    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)
            })
Esempio n. 2
0
    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)
Esempio n. 3
0
def _is_of_type(value: Any, typ: Type) -> bool:
    if is_optional(typ):
        # Optional/Union can contain nested types, e.g:
        #    Optional[Union[str, int, Optional[Foo]]]
        #
        # to avoid recursion we add new Union types to a stack
        # and return False only when that stack is exhausted.
        #
        # NOTE: Optional[str] actually returns Union[str, type(None)]
        stack = deque([typ])
        while stack:
            node = stack.popleft()
            concrete_types = []
            for subtype in node.__args__:
                if is_optional(subtype):
                    stack.append(subtype)
                else:
                    concrete_types.append(subtype)
            if isinstance(value, tuple(concrete_types)):
                return True
        return False
    else:
        return isinstance(value, typ)
Esempio n. 4
0
 def inspect_type(cls, typ: Type) -> TypeInfo:
     optional = is_optional(typ)
     if optional:
         args = getattr(typ, '__args__', ())
         union_args: List[Type] = []
         found_none = False
         for arg in args:
             if _is_NoneType(arg):
                 found_none = True
             else:
                 union_args.append(arg)
         if len(union_args) == 1:
             assert found_none
             return _TypeInfo_from_type(union_args[0], optional=True)
         return _TypeInfo_from_type(typ, optional=True)
     return _TypeInfo_from_type(typ, optional=False)
Esempio n. 5
0
    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
Esempio n. 6
0
    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))
            })
Esempio n. 7
0
def test_is_optional(input, expected):
    assert is_optional(input) == expected