From 8f2e0f5db987f9697b46252438ecdd0d52d0bcdc Mon Sep 17 00:00:00 2001 From: Maks Snegov Date: Thu, 10 Sep 2020 21:45:06 +0300 Subject: [PATCH] Add helpers module --- setup.py | 11 +++++ spqr/kieran/__init__.py | 6 +++ spqr/kieran/helpers.py | 95 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 setup.py create mode 100644 spqr/kieran/__init__.py create mode 100644 spqr/kieran/helpers.py diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..cc7011e --- /dev/null +++ b/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup, find_packages + +setup( + name="spqr.kieran", + version="0.0.1", + author="Maks Snegov", + author_email="snegov@spqr.link", + description="Helpers for SPQR projects", + packages=find_packages(), + python_requires=">=3.8", +) diff --git a/spqr/kieran/__init__.py b/spqr/kieran/__init__.py new file mode 100644 index 0000000..6b13ff1 --- /dev/null +++ b/spqr/kieran/__init__.py @@ -0,0 +1,6 @@ +from .helpers import ( + nested_dataclass, + ExtraDataclass, + singleton, + ConstEnum +) diff --git a/spqr/kieran/helpers.py b/spqr/kieran/helpers.py new file mode 100644 index 0000000..1dc9760 --- /dev/null +++ b/spqr/kieran/helpers.py @@ -0,0 +1,95 @@ +from dataclasses import dataclass, is_dataclass, fields, asdict +from enum import Enum +from typing import Sequence + + +def nested_dataclass(*args, **kwargs): + def wrapper(cls): + cls = dataclass(cls, **kwargs) + original_init = cls.__init__ + + def __init__(self, *args, **kwargs): + for name, value in kwargs.items(): + field_type = cls.__annotations__.get(name, None) + if is_dataclass(field_type) and isinstance(value, dict): + new_obj = field_type(**value) + kwargs[name] = new_obj + original_init(self, *args, **kwargs) + + cls.__init__ = __init__ + return cls + + return wrapper(args[0]) if args else wrapper + + +@dataclass +class ExtraDataclass: + @classmethod + def from_dict(cls, data: dict): + """ Create dataclass from dict ignoring extra arguments. """ + cl_fields = set([f.name for f in fields(cls)]) + filtered_data = dict() + for k, v in data.items(): + if k in cl_fields: + filtered_data[k] = v + return cls(**filtered_data) + + @classmethod + def from_list(cls, ent_list: Sequence[dict]) -> list: + """ Create list of dataclass instances from list of dicts. """ + return [cls.from_dict(ent) for ent in ent_list] + + def to_dict(self) -> dict: + """ Returns dict with not None values """ + return asdict( + self, dict_factory=lambda d: {k: v for k, v in d if v is not None} + ) + + +def singleton(class_): + instances = {} + + def get_instance(*args, **kwargs): + if class_ not in instances: + instances[class_] = class_(*args, **kwargs) + return instances[class_] + + return get_instance + + +class ConstEnum(str, Enum): + """ + Class is used for gathering string constants in one place. + Attribute values are generated from attribute names. + All attributes values will be considered as strings. + Class name is omitted from attribute value. + + >>> Booze = ConstEnum("Booze", "Whiskey Beer Vodka") + >>> Booze.Beer + Beer + >>> print(Booze.Beer) + Beer + >>> type(Booze.Vodka) + + >>> repr(Booze.Whiskey) + 'Whiskey' + + Also could be used with usual class creation: + >>> from enum import auto + >>> class NotBooze(ConstEnum): + >>> Juice = auto() + >>> Tea = auto() + >>> Coffee = auto() + + >>> repr(NotBooze.Tea) + 'Tea' + """ + + def _generate_next_value_(name, start, count, last_values): + return name + + def __repr__(self): + return self.name + + def __str__(self): + return self.name