Skip to content

Commit

Permalink
feat: new Variable is parsed from @var_property
Browse files Browse the repository at this point in the history
  • Loading branch information
elhoangvu committed Apr 19, 2024
1 parent 6720dfc commit 6f05f40
Show file tree
Hide file tree
Showing 5 changed files with 628 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/dictrule/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from .generator import Generator
from .rule import Rule
from .context import Context
from .var_property import var_property
from .variable import Variable
from .__version__ import (
__title__,
__description__,
Expand Down Expand Up @@ -55,6 +57,8 @@
"JoinBlockRule",
"JoinEvalRule",
"FormatRule",
"var_property",
"Variable",
"__title__",
"__description__",
"__url__",
Expand Down
129 changes: 129 additions & 0 deletions src/dictrule/var_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""var_property decorator module"""

from typing import (
List,
Any,
Callable,
Optional,
)


class var_property:
"""var_property decorator that defines property for dictrule.Variable.
Examples:
---------
>>> class Person:
... @var_property
... def name(self) -> str:
... return "Zooxy"
... def age(self) -> int:
... return 20
>>> var = dictrule.Variable.from_object(Person())
>>> print(isinstance(var, Variable))
True
>>> print(var.name)
Zooxy
>>> print(var.age)
20
"""

def __init__(
self,
fget: Optional[Callable[[Any], Any]] = None,
fset: Optional[Callable[[Any, Any], None]] = None,
fdel: Optional[Callable[[Any], None]] = None,
doc: Optional[str] = None,
):
"""var_property decorator
Args:
fget (Optional[Callable[[Any], Any]], optional): Getter method. Defaults to None.
fset (Optional[Callable[[Any, Any], None]], optional): Setter method. Defaults to None.
fdel (Optional[Callable[[Any], None]], optional): Delete method. Defaults to None.
doc (Optional[str], optional): Document string. Defaults to None.
"""

self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
self.__name__ = fget.__name__

def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)

def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)

def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)

def getter(self, fget):
"""Getter decorator
Examples:
---------
>>> class Sample:
... @var_property
... def name(self):
... return "Zooxy"
>>> Sample().name
Zooxy
"""
return type(self)(fget, self.fset, self.fdel, self.__doc__)

def setter(self, fset):
"""Setter decorator
Examples:
---------
>>> @name.setter
... def name(self, value: Any):
... self._name = value
"""
return type(self)(self.fget, fset, self.fdel, self.__doc__)

def deleter(self, fdel):
"""Deleter decorator
Examples:
---------
>>> @name.deleter
... def name(self):
... del self._name
"""
return type(self)(self.fget, self.fset, fdel, self.__doc__)

@classmethod
def properties(
cls,
instance: Any,
) -> List[Callable[[Any], Any]]:
"""Fetches all `var_property` of `instance`.
Args:
instance (Any): Instance using `var_property`.
Returns:
List[Callable]: List of `var_property` functions
"""
properties: List[Callable] = []
for name in dir(instance.__class__):
attr = getattr(instance.__class__, name, None)
is_prop = isinstance(attr, var_property)
if not is_prop:
continue

properties.append(attr)
return properties
191 changes: 191 additions & 0 deletions src/dictrule/variable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
"""Variable module"""

from typing import (
Any,
List,
Dict,
Set,
Optional,
)

from .var_property import var_property


class Variable:
"""Parses instances with nested values.
Supported value types:
- An item in Variable.PRIMITIVE_TYPES: [
int,
float,
str,
]
- list
- set
- dict
- Variable
Examples:
---------
>>> class Sample:
... @var_property
... def name(self) -> str:
... return "Zooxy"
... def age(self) -> int:
... return 20
>>> var = Variable.from_var_property_object(Sample())
>>> print(var.name)
Zooxy
>>> print(hasattr(var, "age"))
False
"""

PRIMITIVE_TYPES = set(
[
int,
float,
str,
]
)

@staticmethod
def from_var_property_object(
obj: Any,
) -> "Variable":
"""Parses a Variable from obj where properties are decorated with @var_property.
Args:
obj (Any): The object to parse from @var_property and supported values.
Returns:
Variable: The parsed variable.
"""

return Variable._parse_value(obj)

def add_object(
self,
obj: Any,
name: str,
):
"""Adds a new object to the Variable instance with a given name.
Args:
obj (Any): The object to be added.
name (str): The name for the object.
"""

value = self._parse_value(obj)
setattr(self, name, value)

@staticmethod
def _parse_value(
value: Any,
) -> Optional[Any]:
"""Parses supported values.
Args:
value (Any): The supported value.
Returns:
Optional[Any]: The parsed value.
"""

if value is None:
return

parsed_value = None
if type(value) in Variable.PRIMITIVE_TYPES or isinstance(value, Variable):
parsed_value = value
elif isinstance(value, list):
parsed_value = Variable._parse_list(
value=value,
)
elif isinstance(value, set):
parsed_value = Variable._parse_set(
value=value,
)
elif isinstance(value, dict):
parsed_value = Variable._parse_dict(
value=value,
)
else:
attrs = var_property.properties(value)
if not attrs:
return None

parsed_value = Variable()
for attr in attrs:
setattr(parsed_value, attr.__name__, attr.__get__(value))

return parsed_value

@staticmethod
def _parse_list(
value: List[Any],
):
"""Parses a list value.
Args:
value (List[Any]): The list of values.
Returns:
Optional[List[Any]]: The parsed list.
"""

new_list: List[Any] = []
for v in value:
parsed_value = Variable._parse_value(v)
if parsed_value:
new_list.append(parsed_value)
return new_list

@staticmethod
def _parse_set(
value: Set[Any],
):
"""Parses a set value.
Args:
value (Set[Any]): The set of values.
Returns:
Optional[Set[Any]]: The parsed set.
"""

new_set: Set[Any] = set()
for v in value:
parsed_value = Variable._parse_value(v)
if not parsed_value:
continue

new_set.add(parsed_value)

return new_set

@staticmethod
def _parse_dict(
value: Dict[Any, Any],
):
"""Parses a dictionary value.
Args:
value (Dict[Any, Any]): The dictionary of values.
Returns:
Optional[Dict[str, Any]]: The parsed dictionary.
"""

new_dict: Dict[str, Any] = {}
for k, v in value.items():
parsed_key = Variable._parse_value(k)
if not parsed_key:
continue

parsed_value = Variable._parse_value(v)
if not parsed_value:
continue

new_dict[parsed_key] = parsed_value

return new_dict
Loading

0 comments on commit 6f05f40

Please sign in to comment.