Ejemplo n.º 1
0
def test_get_fields(a_first, public_only):
    class A(object):
        a = field()
        _d = field(default=5)

    class B(object):
        b = field()

    class C(B, A):
        a = field(default=None)
        c = field(default_factory=copy_field('b'))

    fields = get_fields(C, include_inherited=True, ancestors_first=a_first,
                        _auto_fix_fields=not PY36, public_only=public_only)
    field_names = [f.name for f in fields]
    if a_first:
        assert field_names == ['a', 'b', 'c'] if public_only else ['a', '_d', 'b', 'c']
    else:
        assert field_names == ['a', 'c', 'b'] if public_only else ['a', 'c', 'b', '_d']

    obj = C()
    obj.b = 2

    fields = get_field_values(obj, ancestors_first=a_first if a_first is not None else True, _auto_fix_fields=not PY36,
                              container_type=list, public_only=public_only)
    if a_first is None or a_first:
        assert fields == [('a', None), ('b', 2), ('c', 2)] if public_only else [('a', None), ('_d', 5), ('b', 2), ('c', 2)]
    else:
        assert fields == [('a', None), ('c', 2), ('b', 2)] if public_only else [('a', None), ('c', 2), ('b', 2), ('_d', 5)]
Ejemplo n.º 2
0
    def read_fields(
            cls,
            include=None,  # type: Union[str, Tuple[str]]
            exclude=None,  # type: Union[str, Tuple[str]]
            caller=""  # type: str
    ):
        # type: (...) -> Tuple[Iterable[str], Source]
        """
        Reads and filters the fields from the given class. If that class has pyfields fields, they will be used.
        Otherwise constructor args will be used.

        :param cls:
        :param include:
        :param exclude:
        :param caller:
        :return:
        """
        # Check for pyfields fields
        all_pyfields = get_fields(cls)
        has_pyfields = len(all_pyfields) > 0

        if has_pyfields:
            # source = pyfields
            all_names = tuple(f.name for f in all_pyfields)
            selected_names = filter_names(all_names,
                                          include=include,
                                          exclude=exclude,
                                          caller=caller)
            return selected_names, Source.PYFIELDS
        else:
            # source = init signature
            selected_names, init_fun_sig = read_fields_from_init(
                cls.__init__, include=include, exclude=exclude, caller=caller)
            return selected_names, Source.INIT_ARGS
Ejemplo n.º 3
0
def test_autofields_property_descriptors():
    """Checks that properties and descriptors are correctly ignored by autofields"""
    @autofields
    class Foo(object):
        foo = 1

        @property
        def bar(self):
            return 2

        class MyDesc():
            def __get__(self):
                return 1

        class MyDesc2():
            def __get__(self):
                return 0

            def __set__(self, instance, value):
                return

        m = MyDesc()
        p = MyDesc2()

    fields = get_fields(Foo)
    assert len(fields) == 1
    assert fields[0].name == 'foo'
Ejemplo n.º 4
0
def test_issue_76_bis():
    """ another order issue with @autofields """
    @autofields
    class Foo(object):
        msg = field(type_hint=str)
        age = field(default=12, type_hint=int)

    assert [f.name for f in get_fields(Foo)] == ['msg', 'age']
Ejemplo n.º 5
0
def test_init_order2(a_first):
    """"""
    class A(object):
        a = field()
        d = field(default=5)

    class B(object):
        b = field()

    class C(B, A):
        a = field(default=None)
        c = field(default_factory=copy_field('b'))

        @init_fields(ancestor_fields_first=a_first)
        def __init__(self):
            pass

    fields = get_fields(
        C,
        include_inherited=True,
        ancestors_first=a_first if a_first is not None else True,
        _auto_fix_fields=not PY36)
    field_names = [f.name for f in fields]
    if a_first is None or a_first:
        assert field_names == ['a', 'd', 'b', 'c']
    else:
        assert field_names == ['a', 'c', 'b', 'd']

    # make sure that a and c have default values and therefore just passing b is ok.
    c = C(1)
    assert vars(c) == {'b': 1, 'c': 1, 'a': None, 'd': 5}

    c = C(1, 2, 3)
    if a_first is None or a_first:
        assert vars(c) == {
            'b': 1,  # 1st arg
            'c': 1,  # default: copy of b
            'a': 2,  # 2d arg
            'd': 3  # 3d arg
        }
    else:
        assert vars(c) == {
            'b': 1,  # 1st arg
            'c': 3,  # 3d arg
            'a': 2,  # 2d arg
            'd': 5  # default: 5
        }
Ejemplo n.º 6
0
def test_autoclass():
    """ Tests the example with autoclass in the doc """
    @autoclass
    class Foo(object):
        msg = field(type_hint=str)
        age = field(default=12, type_hint=int)

    foo = Foo(msg='hello')

    assert [f.name for f in get_fields(Foo)] == ['msg', 'age']

    print(foo)  # automatic string representation
    print(foo.to_dict())  # dict view

    assert str(foo) == "Foo(msg='hello', age=12)"
    assert str(foo.to_dict()) in ("{'msg': 'hello', 'age': 12}",
                                  "{'age': 12, 'msg': 'hello'}")
    assert foo == Foo(msg='hello', age=12)  # comparison (equality)
    assert foo == {'msg': 'hello', 'age': 12}  # comparison with dicts
Ejemplo n.º 7
0
def autoclass_decorate(cls,               # type: Type[T]
                       include=None,      # type: Union[str, Tuple[str]]
                       exclude=None,      # type: Union[str, Tuple[str]]
                       autoargs=AUTO,     # type: bool
                       autoprops=AUTO,    # type: bool
                       autoinit=AUTO,     # type: bool
                       autodict=True,     # type: bool
                       autorepr=AUTO,     # type: bool
                       autoeq=AUTO,       # type: bool
                       autohash=True,     # type: bool
                       autoslots=False,   # type: bool
                       autofields=False,  # type: bool
                       ):
    # type: (...) -> Type[T]
    """

    :param cls: the class on which to execute. Note that it won't be wrapped.
    :param include: a tuple of explicit attributes to include (None means all)
    :param exclude: a tuple of explicit attribute names to exclude. In such case, include should be None.
    :param autoargs: a boolean to enable autoargs on the constructor. By default it is `AUTO` and means "automatic
        configuration". In that case, the behaviour will depend on the class: it will be equivalent to `True` if the
        class defines an `__init__` method and has no `pyfields` fields ; and `False` otherwise.
    :param autoprops: a boolean to enable autoprops on the class. By default it is `AUTO` and means "automatic
        configuration". In that case, the behaviour will depend on the class: it will be equivalent to `True` if the
        class defines an `__init__` method and has no `pyfields` fields ; and `False` otherwise.
    :param autoinit: a boolean to enable autoinit on the class. By default it is `AUTO` and means "automatic
        configuration". In that case, the behaviour will depend on the class: it will be equivalent to `True` if the
        class has `pyfields` fields and does not define an `__init__` method ; and `False` otherwise.
    :param autodict: a boolean to enable autodict on the class (default: True). By default it will be executed with
        `only_known_fields=True`.
    :param autorepr: a boolean to enable autorepr on the class. By default it is `AUTO` and means "automatic
        configuration". In that case, it will be defined as `not autodict`.
    :param autoeq: a boolean to enable autoeq on the class. By default it is `AUTO` and means "automatic
        configuration". In that case, it will be defined as `not autodict`.
    :param autohash: a boolean to enable autohash on the class (default: True). By default it will be executed with
        `only_known_fields=True`.
    :param autoslots: a boolean to enable autoslots on the class (default: False).
    :param autofields: a boolean (default: False) to apply autofields automatically on the class before applying
        `autoclass` (see `pyfields` documentation for details)
    :return:
    """
    # first check that we do not conflict with other known decorators
    check_known_decorators(cls, '@autoclass')

    if autofields:
        if WITH_PYFIELDS:
            cls = apply_autofields(cls)
        else:
            raise ValueError("`autofields=True` can only be used when `pyfields` is installed. Please `pip install "
                             "pyfields`")

    # Get constructor
    init_fun, is_init_inherited = get_constructor(cls)

    # Check for pyfields fields
    if WITH_PYFIELDS:
        all_pyfields = get_fields(cls)
        has_pyfields = len(all_pyfields) > 0  # 'or autofields' ?: cant' find a use case where it would be needed.
    else:
        has_pyfields = False

    # variable and function used below to get the reference list of attributes
    selected_names, init_fun_sig = None, None

    # ------- @autoslots - this replaces the class so do it first
    if autoslots:
        if has_pyfields:
            raise ValueError("autoslots is not available for classes using `pyfields`")
        cls = autoslots_decorate(cls, include=include, exclude=exclude, use_public_names=not autoprops)

    # ------- @autoargs and @autoprops
    if autoargs is AUTO:
        # apply if the init is defined in the class AND if there are no pyfields
        autoargs = (not is_init_inherited) and (not has_pyfields)
    if autoprops is AUTO:
        # apply if there are no pyfields
        autoprops = not has_pyfields

    # a few common variables
    if autoargs or autoprops:
        # retrieve init function signature and filter its parameters according to include/exclude
        # note: pyfields *can* be present, but for explicit @autoargs and @autoprops we do not use it as a reference
        selected_names, init_fun_sig = read_fields_from_init(init_fun, include=include, exclude=exclude,
                                                             caller="@autoclass")

    # apply them
    if autoargs:
        if is_init_inherited:  # no init explicitly defined in the class > error
            raise NoCustomInitError(cls)
        cls.__init__ = _autoargs_decorate(func=init_fun, func_sig=init_fun_sig, att_names=selected_names)
    if autoprops:
        # noinspection PyUnboundLocalVariable
        execute_autoprops_on_class(cls, init_fun=init_fun, init_fun_sig=init_fun_sig, prop_names=selected_names)

    # create a reference list of attribute names and type hints for all subsequent decorators
    if has_pyfields:
        # Use reference list from pyfields now (even if autoargs was executed, it did not use the correct list)
        # noinspection PyUnboundLocalVariable
        all_names = tuple(f.name for f in all_pyfields)
        selected_names = filter_names(all_names, include=include, exclude=exclude, caller="@autoclass")
        selected_fields = tuple(f for f in all_pyfields if f.name in selected_names)

    elif selected_names is None:
        # Create reference list - autoargs was not executed and there are no pyfields: we need something
        selected_names, init_fun_sig = read_fields_from_init(init_fun, include=include, exclude=exclude,
                                                             caller="@autoclass")

    # autoinit
    if autoinit is AUTO:
        # apply if no init is defined in the class AND if there are pyfields
        autoinit = is_init_inherited and has_pyfields

    if autoinit:
        if not has_pyfields:
            raise ValueError("`autoinit` is only available if you class contains `pyfields` fields.")
        # noinspection PyUnboundLocalVariable
        cls.__init__ = make_init(*selected_fields)

    # @autodict or @autorepr
    if autodict:
        if autorepr is not AUTO and autorepr:
            raise ValueError("`autorepr` can not be set to `True` simultaneously with `autodict`. Please set "
                             "`autodict=False`.")
        if autoeq is not AUTO and autoeq:
            raise ValueError("`autoeq` can not be set to `True` simultaneously with `autodict`. Please set "
                             "`autodict=False`.")
        # By default execute with the known list of fields, so equivalent of `only_known_fields=True`.
        # Exclude private fields by default to have a consistent behaviour with autodict
        public_names = tuple(n for n in selected_names if not n.startswith('_'))
        execute_autodict_on_class(cls, selected_names=public_names)
    else:
        if autorepr is AUTO or autorepr:
            # By default execute with the known list of fields, so equivalent of `only_known_fields=True`.
            execute_autorepr_on_class(cls, selected_names=selected_names)
        if autoeq is AUTO or autoeq:
            # By default execute with the known list of fields, so equivalent of `only_known_fields=True`.
            execute_autoeq_on_class(cls, selected_names=selected_names)

    # @autohash
    if autohash:
        # By default execute with the known list of fields, so equivalent of `only_known_fields=True`.
        execute_autohash_on_class(cls, selected_names=selected_names)

    return cls
Ejemplo n.º 8
0
def test_issue_76():
    """ order issue 76 and 77 are fixed """
    from ._test_py36 import test_issue_76
    Foo = test_issue_76()
    assert [f.name for f in get_fields(Foo)] == ['c', 'b', 'a']
Ejemplo n.º 9
0
 def __len__(cls) -> int:
     """use len() to get number of fields"""
     return len(get_fields(cls))
Ejemplo n.º 10
0
def test_issue_67():
    from ._test_py36 import test_issue_67
    Frog = test_issue_67()

    assert len(get_fields(Frog)) == 1
    Frog()