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)]
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
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'
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']
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 }
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
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
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']
def __len__(cls) -> int: """use len() to get number of fields""" return len(get_fields(cls))
def test_issue_67(): from ._test_py36 import test_issue_67 Frog = test_issue_67() assert len(get_fields(Frog)) == 1 Frog()