class Wall(object): height: int = field(doc="Height of the wall in mm.", native=native) color: str = field(default='white', doc="Color of the wall.", native=native) __init__ = make_init(height, color)
class Wall(object): height = field(doc="Height of the wall in mm.", native=native) # type: int color = field(default='white', doc="Color of the wall.", native=native) # type: str __init__ = make_init()
class Wall(object): height: int = field(doc="Height of the wall in mm.", native=native) color: str = field(default='white', doc="Color of the wall.", native=native) def post_init(self, foo: str = 'bar'): print(self.height) print("post init man !") __init__ = make_init(height, color, post_init_fun=post_init)
class B(A): b = field(default='world') def a(self): """ purposedly override the base class field name """ pass __init__ = make_init()
class Wall(object): height = field(doc="Height of the wall in mm.") # type: int color = field(default='white', doc="Color of the wall.") # type: str def post_init(self, foo='bar'): print(self.height) print("post init man !") __init__ = make_init(height, post_init_fun=post_init)
class Wall: height = field(doc="Height of the wall in mm.") # type: int color = field(default='white', doc="Color of the wall.") # type: str def post_init(self, msg='hello'): """ After initialization, some print message is done :param msg: the message details to add :return: """ print("post init ! height=%s, color=%s, msg=%s" % (self.height, self.color, msg)) self.non_field_attr = msg # only `height` and `foo` will be in the constructor __init__ = make_init(height, post_init_fun=post_init)
class ColoredWall(Wall): color = field(default='white', doc="Color of the wall.") # type: str __init__ = make_init(Wall.height, color)
class Wall: height = field(doc="Height of the wall in mm.") # type: int __init__ = make_init(height)
class Wall: height = field(doc="Height of the wall in mm.") # type: int color = field(default='white', doc="Color of the wall.") # type: str # only `height` will be in the constructor __init__ = make_init(height)
class Wall: height = field(doc="Height of the wall in mm.") # type: int color = field(default='white', doc="Color of the wall.") # type: str __init__ = make_init()
class MetadataCollection: """ Provides a container to store a collection of Metadata objects. Conversion methods are provided to analyse the MetadataCollection further. """ __init__ = make_init() items: List[Metadata] = field(doc="Collection of Metadata objects") def to_dict(self): """Converts MetadataCollection to List of Dict. :returns: MetadataCollection (List of Dict) """ return [item.to_dict() for item in self.items] def to_json(self): """Converts MetadataCollection to List of JSON. :returns: MetadataCollection (List of JSON) """ return [item.to_json() for item in self.items] def to_geojson(self): """Converts MetadataCollection to list of GeoJSON. :returns: MetadataCollection (List of GeoJSON) """ return [item.to_geojson() for item in self.items] def to_pandas(self): """Converts MetadataCollection to Pandas Dataframe. :returns: MetadataCollection (Pandas Dataframe) """ try: import pandas as pd except ImportError: raise ImportError("to_pandas requires optional dependency Pandas.") pd.set_option("display.max_colwidth", -1) d = [item.to_dict() for item in self.items] return pd.DataFrame(d) def filter(self, filter_dict, type="exact"): """Filters MetadataCollection based on filter_dict. :param filter_dict: Key value pair to use as filter e.g. {"producttype": "S2MSI1C"} (Dictionary). :param type: how to filter match, default 'exact', alternative 'fuzzy'. :returns: self """ k = list(filter_dict.keys())[0] if isinstance(filter_dict[k], list) is False: # make sure that filter value is a list filter_dict[k] = [filter_dict[k]] if type == "exact": # apply exact filter matching self.items = [ item for item in self.items for filter in filter_dict[k] if filter == item.to_geojson()["properties"][k] ] elif type == "fuzzy": # apply fuzzy filter matching self.items = [ item for item in self.items for filter in filter_dict[k] if filter in item.to_geojson()["properties"][k] ] else: raise NotImplementedError( f"{type} is not supported [exact, fuzzy]") return self def save(self, target_dir): """Saves MetadataCollection to GeoJSON files in target_dir with srcid as filenames. :param target_dir: Target directory (String, Path) """ for item in self.items: item.save(target_dir)
class Metadata: """ Provides a container to store metadata. Fields are assigned a default value, checked for dtype, validated and converted if needed. """ __init__ = make_init() id: str = field(check_type=True, read_only=True, doc="Product ID") platformname: Platform = field(check_type=True, default=None, doc="Platform name") producttype: str = field(check_type=True, default="", doc="Product type") orbitdirection: str = field(check_type=True, default="", doc="Orbitdirection") orbitnumber: int = field(check_type=True, default=None, doc="Orbitnumber") relativeorbitnumber: int = field(check_type=True, default=None, doc="Relative orbitnumber") acquisitiondate = field(type_hint=datetime.date, check_type=True, default=None, doc="Acquisitiondate") ingestiondate = field(type_hint=datetime.date, check_type=True, default=None, doc="Ingestiondate") processingdate = field(type_hint=datetime.date, check_type=True, default=None, doc="Processingdate") processingsteps: list = field(check_type=True, default=None, doc="Processingsteps") processingversion: str = field(check_type=True, default="", doc="Processing version") bandlist: list = field(check_type=True, default=None, doc="Bandlist") cloudcoverpercentage: float = field(check_type=True, default=None, doc="Cloudcover [percent]") format: str = field(check_type=True, default="", doc="File format") size: str = field(check_type=True, default="", doc="File size [MB]") srcid: str = field(check_type=True, doc="Source product ID") srcurl: str = field(check_type=True, default="", doc="Source product URL") srcuuid: str = field(check_type=True, doc="Source product UUID") geom: dict = field(check_type=True, default=None, doc="Geometry [multipolygon dict]") @acquisitiondate.converter(accepts=str) @ingestiondate.converter(accepts=str) @processingdate.converter(accepts=str) def _prep_date(self, value): """Converts a date string to datetime.date object. :returns: Datetime.date """ if value is not None: return parse(value) def to_dict(self): """Converts Metadata to Dict. :returns: Metadata (Dict) """ return { "id": self.id, "platformname": None if self.platformname is None else self.platformname.value, "producttype": self.producttype, "orbitdirection": self.orbitdirection, "orbitnumber": self.orbitnumber, "relativeorbitnumber": self.relativeorbitnumber, "acquisitiondate": None if self.acquisitiondate is None else self.acquisitiondate.strftime("%Y/%m/%d"), "ingestiondate": None if self.ingestiondate is None else self.ingestiondate.strftime("%Y/%m/%d"), "processingdate": None if self.processingdate is None else self.processingdate.strftime("%Y/%m/%d"), "processingsteps": self.processingsteps, "processingversion": self.processingversion, "bandlist": self.bandlist, "cloudcoverpercentage": self.cloudcoverpercentage, "format": self.format, "size": self.size, "srcid": self.srcid, "srcurl": self.srcurl, "srcuuid": self.srcuuid, } def to_json(self): """Converts Metadata to JSON. :returns: Metadata (JSON) """ return json.dumps(self.to_dict()) def to_geojson(self): """Converts Metadata to GeoJSON. :returns: Metadata (GeoJSON) """ return geometry.mapping( _GeoInterface({ "type": "Feature", "properties": self.to_dict(), "geometry": self.geom })) def save(self, target_dir): """Saves Metadata to GeoJSON file in target_dir with srcid as filename. :param target_dir: Target directory that holds the downloaded metadata (String, Path) """ g = self.to_geojson() if isinstance(target_dir, str): target_dir = Path(target_dir) with open(target_dir.joinpath(g["properties"]["srcid"] + ".json"), "w") as f: json.dump(g, f)
class Foo(object): a = field(type_hint=int, default=0, check_type=True) b = field(type_hint=int, validators={'is positive': lambda x: x > 0}) c = field(default_factory=copy_field(a)) __init__ = make_init()
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
class WaterAbsFitParams(): """Holds parameters for fit equation: y = mx / (k+x) + 1/[(n/jx) - 1/j] attrs: .params methods: .as_tuple(), .as_dict(), __len__()""" params: ParamsNTup = field(default=ParamsNTup(300.0, 20.0, 250.0, 40.0), check_type=True) std_errs: ErrType = field( # type: ignore default=(0, 0, 0, 0)) def __post_init__(self) -> None: # self.params = self.convert_params() # self.validate_params() self.m = self.params.m self.k = self.params.k self.n = self.params.n self.j = self.params.j __init__ = make_init(post_init_fun=__post_init__) @params.converter # type: ignore def convert_params(self, v: ParamsType) -> ParamsNTup: """Converter function to coerce 4 float list, tuple, set, ndarray to ParamsNTup Also rounds floats to 1 d.p.""" try: rounded_v = tuple((round(x, 1) for x in v)) w = ParamsNTup(*rounded_v) except TypeError as terr: terr.args += ( "Fit parameters should be a ParamsType (ParamsNTup or list | tuple | set | ndarray)", ) raise except Exception: raise return w @params.validator # type: ignore def validate_params(self, v: ParamsType) -> bool: if not isinstance(v, ParamsNTup) or not len(v) == 4 or not isinstance( v[0], (int, float)): raise TypeError( "Fit parameters should by a ParamsNTup (coerced from tuple, list, set, np.ndarray)" ) if not len(v) == 4: raise ValueError( "Fit parameters should be container of len == 4, eg. ParamsNTup" ) if not all(p > 0 for p in v): raise ValueError( "All fit parameters should be positive floats | ints") return True @classmethod def __len__(cls) -> int: """use len() to get number of fields""" return len(get_fields(cls)) def as_tuple(self) -> Tuple[ParamsNTup, ErrType]: """return datclass as Tuple[float X 4]""" d = self.as_dict() t = tuple(d.values()) return cast(Tuple[ParamsNTup, ErrType], t) def as_dict(self) -> Dict[str, Union[ParamsNTup, ErrType]]: """return datclass as Dict[str, float]""" d: Dict[str, Union[ParamsNTup, ErrType]] = get_field_values(self) return d def params_dict(self) -> ParamsTDict: d = self.params._asdict() pd = ParamsTDict(m=d['m'], k=d['k'], n=d['n'], j=d['j']) return pd