コード例 #1
0
def test_annotations__invalid_type():
    class X:
        foo: List

    with pytest.raises(InvalidAnnotation):
        annotations(
            X,
            globalns=globals(),
            localns=locals(),
            invalid_types={List},
            skip_classvar=True,
        )
コード例 #2
0
def test_annotations__no_local_ns_raises():
    class Bar:
        ...

    class X:
        bar: "Bar"

    with pytest.raises(NameError):
        annotations(
            X,
            globalns=None,
            localns=None,
        )
コード例 #3
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)
            })
コード例 #4
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_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)
            })
コード例 #5
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)
コード例 #6
0
    def _extract_signals(
            self, case_cls: Type[_Case],
            base_case: Type[_Case]) -> Iterable[Tuple[str, Type[BaseSignal]]]:
        fields, defaults = annotations(
            case_cls,
            stop=base_case,
            skip_classvar=True,
            localns={case_cls.__name__: case_cls},
        )

        for attr_name, attr_type in fields.items():
            actual_type = getattr(attr_type, '__origin__', attr_type)
            if actual_type is None:  # Python <3.7
                actual_type = attr_type
            try:
                if issubclass(actual_type, BaseSignal):
                    yield attr_name, attr_type
            except TypeError:
                pass
コード例 #7
0
def test_annotations__skip_classvar():
    class X:
        Foo: ClassVar[int] = 3
        foo: "int"
        bar: List["X"]
        baz: Union[List["X"], str]
        mas: int = 3

    fields, defaults = annotations(
        X,
        globalns=globals(),
        localns=locals(),
        skip_classvar=True,
    )

    assert fields == {
        "foo": int,
        "bar": List[X],
        "baz": Union[List[X], str],
        "mas": int,
    }
    assert defaults["mas"] == 3
コード例 #8
0
def test_annotations__skip_classvar():
    class X:
        Foo: ClassVar[int] = 3
        foo: 'int'
        bar: List['X']
        baz: Union[List['X'], str]
        mas: int = 3

    fields, defaults = annotations(
        X,
        globalns=globals(),
        localns=locals(),
        skip_classvar=True,
    )

    assert fields == {
        'foo': int,
        'bar': List[X],
        'baz': Union[List[X], str],
        'mas': int,
    }
    assert defaults['mas'] == 3
コード例 #9
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))
            })