Module osbot_utils.base_classes.Kwargs_To_Self
Expand source code
# todo: find a way to add these documentations strings to a separate location so that
# the code is not polluted with them (like in the example below)
# the data is available in IDE's code complete
import functools
import inspect
import sys
import types
import typing
from decimal import Decimal
from enum import Enum, EnumMeta
from typing import List
from osbot_utils.base_classes.Type_Safe__List import Type_Safe__List
from osbot_utils.utils.Dev import pprint
from osbot_utils.utils.Json import json_parse
from osbot_utils.utils.Misc import list_set
from osbot_utils.utils.Objects import default_value, value_type_matches_obj_annotation_for_attr, \
raise_exception_on_obj_type_annotation_mismatch, obj_is_attribute_annotation_of_type, enum_from_value, \
obj_is_type_union_compatible, value_type_matches_obj_annotation_for_union_attr
# Backport implementations of get_origin and get_args for Python 3.7
if sys.version_info < (3, 8):
def get_origin(tp):
if isinstance(tp, typing._GenericAlias):
return tp.__origin__
elif tp is typing.Generic:
return typing.Generic
else:
return None
def get_args(tp):
if isinstance(tp, typing._GenericAlias):
return tp.__args__
else:
return ()
else:
from typing import get_origin, get_args
if sys.version_info >= (3, 10):
NoneType = types.NoneType
else:
NoneType = type(None)
immutable_types = (bool, int, float, complex, str, tuple, frozenset, bytes, NoneType, EnumMeta)
#todo: see if we can also add type safety to method execution
# for example if we have an method like def add_node(self, title: str, call_index: int):
# throw an exception if the type of the value passed in is not the same as the one defined in the method
class Kwargs_To_Self: # todo: check if the description below is still relevant (since a lot has changed since it was created)
"""
A mixin class to strictly assign keyword arguments to pre-defined instance attributes during initialization.
This base class provides an __init__ method that assigns values from keyword
arguments to instance attributes. If an attribute with the same name as a key
from the kwargs is defined in the class, it will be set to the value from kwargs.
If the key does not match any predefined attribute names, an exception is raised.
This behavior enforces strict control over the attributes of instances, ensuring
that only predefined attributes can be set at the time of instantiation and avoids
silent attribute creation which can lead to bugs in the code.
Usage:
class MyConfigurableClass(Kwargs_To_Self):
attribute1 = 'default_value'
attribute2 = True
attribute3 : str
attribute4 : list
attribute4 : int = 42
# Other methods can be added here
# Correctly override default values by passing keyword arguments
instance = MyConfigurableClass(attribute1='new_value', attribute2=False)
# This will raise an exception as 'attribute3' is not predefined
# instance = MyConfigurableClass(attribute3='invalid_attribute')
this will also assign the default value to any variable that has a type defined.
In the example above the default values (mapped by __default__kwargs__ and __locals__) will be:
attribute1 = 'default_value'
attribute2 = True
attribute3 = '' # default value of str
attribute4 = [] # default value of list
attribute4 = 42 # defined value in the class
Note:
It is important that all attributes which may be set at instantiation are
predefined in the class. Failure to do so will result in an exception being
raised.
Methods:
__init__(**kwargs): The initializer that handles the assignment of keyword
arguments to instance attributes. It enforces strict
attribute assignment rules, only allowing attributes
that are already defined in the class to be set.
"""
#__lock_attributes__ = False
#__type_safety__ = True
def __init__(self, **kwargs):
"""
Initialize an instance of the derived class, strictly assigning provided keyword
arguments to corresponding instance attributes.
Parameters:
**kwargs: Variable length keyword arguments.
Raises:
Exception: If a key from kwargs does not correspond to any attribute
pre-defined in the class, an exception is raised to prevent
setting an undefined attribute.
"""
# if 'disable_type_safety' in kwargs: # special case
# self.__type_safety__ = kwargs['disable_type_safety'] is False
# del kwargs['disable_type_safety']
for (key, value) in self.__cls_kwargs__().items(): # assign all default values to self
if value is not None: # when the value is explicitly set to None on the class static vars, we can't check for type safety
raise_exception_on_obj_type_annotation_mismatch(self, key, value)
if hasattr(self, key):
existing_value = getattr(self, key)
if existing_value is not None:
setattr(self, key, existing_value)
continue
setattr(self, key, value)
for (key, value) in kwargs.items(): # overwrite with values provided in ctor
if hasattr(self, key):
if value is not None: # prevent None values from overwriting existing values, which is quite common in default constructors
setattr(self, key, value)
else:
raise Exception(f"{self.__class__.__name__} has no attribute '{key}' and cannot be assigned the value '{value}'. "
f"Use {self.__class__.__name__}.__default_kwargs__() see what attributes are available")
def __enter__(self): return self
def __exit__(self, exc_type, exc_val, exc_tb): pass
def __setattr__(self, name, value):
if not hasattr(self, '__annotations__'): # can't do type safety checks if the class does not have annotations
return super().__setattr__(name, value)
# if self.__type_safety__:
# if self.__lock_attributes__:
# todo: this can't work on all, current hypothesis is that this will work for the values that are explicitly set
# if not hasattr(self, name):
# raise AttributeError(f"'[Object Locked] Current object is locked (with __lock_attributes__=True) which prevents new attributes allocations (i.e. setattr calls). In this case {type(self).__name__}' object has no attribute '{name}'") from None
if value is not None:
check_1 = value_type_matches_obj_annotation_for_attr(self, name, value)
check_2 = value_type_matches_obj_annotation_for_union_attr(self, name, value)
if (check_1 is False and check_2 is None or
check_1 is None and check_2 is False or
check_1 is False and check_2 is False ): # fix for type safety assigment on Union vars
raise Exception(f"Invalid type for attribute '{name}'. Expected '{self.__annotations__.get(name)}' but got '{type(value)}'")
else:
if hasattr(self, name) and self.__annotations__.get(name) : # don't allow previously set variables to be set to None
if getattr(self, name) is not None: # unless it is already set to None
raise Exception(f"Can't set None, to a variable that is already set. Invalid type for attribute '{name}'. Expected '{self.__annotations__.get(name)}' but got '{type(value)}'")
super().__setattr__(name, value)
def __attr_names__(self):
return list_set(self.__locals__())
@classmethod
def __cls_kwargs__(cls, include_base_classes=True):
"""Return current class dictionary of class level variables and their values."""
kwargs = {}
for base_cls in inspect.getmro(cls):
if base_cls is object: # Skip the base 'object' class
continue
for k, v in vars(base_cls).items():
# todo: refactor this logic since it is weird to start with a if not..., and then if ... continue (all these should be if ... continue )
if not k.startswith('__') and not isinstance(v, types.FunctionType): # remove instance functions
if isinstance(v, classmethod): # also remove class methods
continue
if type(v) is functools._lru_cache_wrapper: # todo, find better way to handle edge cases like this one (which happens when the @cache decorator is used in a instance method that uses Kwargs_To_Self)
continue
if (k in kwargs) is False: # do not set the value is it has already been set
kwargs[k] = v
if hasattr(base_cls,'__annotations__'): # can only do type safety checks if the class does not have annotations
for var_name, var_type in base_cls.__annotations__.items():
if hasattr(base_cls, var_name) is False: # only add if it has not already been defined
if var_name in kwargs:
continue
var_value = cls.__default__value__(var_type)
kwargs[var_name] = var_value
else:
var_value = getattr(base_cls, var_name)
if var_value is not None: # allow None assignments on ctor since that is a valid use case
if var_type and not isinstance(var_value, var_type): # check type
exception_message = f"variable '{var_name}' is defined as type '{var_type}' but has value '{var_value}' of type '{type(var_value)}'"
raise Exception(exception_message)
if var_type not in immutable_types and var_name.startswith('__') is False: # if var_type is not one of the immutable_types or is an __ internal
#todo: fix type safety bug that I believe is caused here
if obj_is_type_union_compatible(var_type, immutable_types) is False: # if var_type is not something like Optional[Union[int, str]]
if type(var_type) not in immutable_types:
exception_message = f"variable '{var_name}' is defined as type '{var_type}' which is not supported by Kwargs_To_Self, with only the following immutable types being supported: '{immutable_types}'"
raise Exception(exception_message)
if include_base_classes is False:
break
return kwargs
@classmethod
def __default__value__(cls, var_type):
if var_type is typing.Set: # todo: refactor the dict, set and list logic, since they are 90% the same
return set()
if get_origin(var_type) is set:
return set() # todo: add Type_Safe__Set
if var_type is typing.Dict:
return {}
if get_origin(var_type) is dict:
return {} # todo: add Type_Safe__Dict
if var_type is typing.List:
return [] # handle case when List was used with no type information provided
if get_origin(var_type) is list: # if we have list defined as list[type]
item_type = get_args(var_type)[0] # get the type that was defined
return Type_Safe__List(expected_type=item_type) # and used it as expected_type in Type_Safe__List
else:
return default_value(var_type) # for all other cases call default_value, which will try to create a default instance
def __default_kwargs__(self):
"""Return entire (including base classes) dictionary of class level variables and their values."""
kwargs = {}
cls = type(self)
for base_cls in inspect.getmro(cls): # Traverse the inheritance hierarchy and collect class-level attributes
if base_cls is object: # Skip the base 'object' class
continue
for k, v in vars(base_cls).items():
if not k.startswith('__') and not isinstance(v, types.FunctionType): # remove instance functions
if not isinstance(v, classmethod):
kwargs[k] = v
# add the vars defined with the annotations
if hasattr(base_cls,'__annotations__'): # can only do type safety checks if the class does not have annotations
for var_name, var_type in base_cls.__annotations__.items():
var_value = getattr(self, var_name)
kwargs[var_name] = var_value
return kwargs
def __kwargs__(self):
"""Return a dictionary of the current instance's attribute values including inherited class defaults."""
kwargs = {}
# Update with instance-specific values
for key, value in self.__default_kwargs__().items():
kwargs[key] = self.__getattribute__(key)
# if hasattr(self, key):
# kwargs[key] = self.__getattribute__(key)
# else:
# kwargs[key] = value # todo: see if this is stil a valid scenario
return kwargs
def __locals__(self):
"""Return a dictionary of the current instance's attribute values."""
kwargs = self.__kwargs__()
if not isinstance(vars(self), types.FunctionType):
for k, v in vars(self).items():
if not isinstance(v, types.FunctionType) and not isinstance(v,classmethod):
if k.startswith('__') is False:
kwargs[k] = v
return kwargs
@classmethod
def __schema__(cls):
if hasattr(cls,'__annotations__'): # can only do type safety checks if the class does not have annotations
return cls.__annotations__
return {}
# global methods added to any class that base classes this
# todo: see if there should be a prefix on these methods, to make it easier to spot them
# of if these are actually that useful that they should be added like this
def json(self):
return self.serialize_to_dict()
def merge_with(self, target):
original_attrs = {k: v for k, v in self.__dict__.items() if k not in target.__dict__} # Store the original attributes of self that should be retained.
self.__dict__ = target.__dict__ # Set the target's __dict__ to self, now self and target share the same __dict__.
self.__dict__.update(original_attrs) # Reassign the original attributes back to self.
return self
# def locked(self, value=True): # todo: figure out best way to do this (maybe???)
# self.__lock_attributes__ = value # : update, with the latest changes were we don't show internals on __locals__() this might be a good way to do this
# return self
def reset(self):
for k,v in self.__cls_kwargs__().items():
setattr(self, k, v)
def update_from_kwargs(self, **kwargs):
"""Update instance attributes with values from provided keyword arguments."""
for key, value in kwargs.items():
if value is not None:
if hasattr(self,'__annotations__'): # can only do type safety checks if the class does not have annotations
if value_type_matches_obj_annotation_for_attr(self, key, value) is False:
raise Exception(f"Invalid type for attribute '{key}'. Expected '{self.__annotations__.get(key)}' but got '{type(value)}'")
setattr(self, key, value)
return self
def deserialize_from_dict(self, data):
for key, value in data.items():
if hasattr(self, key) and isinstance(getattr(self, key), Kwargs_To_Self):
getattr(self, key).deserialize_from_dict(value) # Recursive call for complex nested objects
else:
if hasattr(self, '__annotations__'): # can only do type safety checks if the class does not have annotations
if obj_is_attribute_annotation_of_type(self, key, EnumMeta): # Handle the case when the value is an Enum
enum_type = getattr(self, '__annotations__').get(key)
if type(value) is not enum_type: # If the value is not already of the target type
value = enum_from_value(enum_type, value) # Try to resolve the value into the enum
setattr(self, key, value) # Direct assignment for primitive types and other structures
return self
def serialize_to_dict(self): # todo: see if we need this method or if the .json() is enough
return serialize_to_dict(self)
def print(self):
pprint(serialize_to_dict(self))
@classmethod
def from_json(cls, json_data):
if type(json_data) is str:
json_data = json_parse(json_data)
if json_data: # if there is no data or is {} then don't create an object (since this could be caused by bad data being provided)
return cls().deserialize_from_dict(json_data)
return None
# todo: see if it is possible to add recursive protection to this logic
def serialize_to_dict(obj):
if isinstance(obj, (str, int, float, bool, bytes, Decimal)) or obj is None:
return obj
elif isinstance(obj, Enum):
return obj.name
elif isinstance(obj, list) or isinstance(obj, List):
return [serialize_to_dict(item) for item in obj]
elif isinstance(obj, dict):
return {key: serialize_to_dict(value) for key, value in obj.items()}
elif hasattr(obj, "__dict__"):
data = {} # todo: look at a more advanced version which saved the type of the object, for example with {'__type__': type(obj).__name__}
for key, value in obj.__dict__.items():
data[key] = serialize_to_dict(value) # Recursive call for complex types
return data
else:
raise TypeError(f"Type {type(obj)} not serializable")
Functions
def serialize_to_dict(obj)
-
Expand source code
def serialize_to_dict(obj): if isinstance(obj, (str, int, float, bool, bytes, Decimal)) or obj is None: return obj elif isinstance(obj, Enum): return obj.name elif isinstance(obj, list) or isinstance(obj, List): return [serialize_to_dict(item) for item in obj] elif isinstance(obj, dict): return {key: serialize_to_dict(value) for key, value in obj.items()} elif hasattr(obj, "__dict__"): data = {} # todo: look at a more advanced version which saved the type of the object, for example with {'__type__': type(obj).__name__} for key, value in obj.__dict__.items(): data[key] = serialize_to_dict(value) # Recursive call for complex types return data else: raise TypeError(f"Type {type(obj)} not serializable")
Classes
class Kwargs_To_Self (**kwargs)
-
A mixin class to strictly assign keyword arguments to pre-defined instance attributes during initialization.
This base class provides an init method that assigns values from keyword arguments to instance attributes. If an attribute with the same name as a key from the kwargs is defined in the class, it will be set to the value from kwargs. If the key does not match any predefined attribute names, an exception is raised.
This behavior enforces strict control over the attributes of instances, ensuring that only predefined attributes can be set at the time of instantiation and avoids silent attribute creation which can lead to bugs in the code.
Usage
class MyConfigurableClass(Kwargs_To_Self): attribute1 = 'default_value' attribute2 = True attribute3 : str attribute4 : list attribute4 : int = 42
# Other methods can be added here
Correctly override default values by passing keyword arguments
instance = MyConfigurableClass(attribute1='new_value', attribute2=False)
This will raise an exception as 'attribute3' is not predefined
instance = MyConfigurableClass(attribute3='invalid_attribute')
this will also assign the default value to any variable that has a type defined. In the example above the default values (mapped by default__kwargs and locals) will be: attribute1 = 'default_value' attribute2 = True attribute3 = '' # default value of str attribute4 = [] # default value of list attribute4 = 42 # defined value in the class
Note
It is important that all attributes which may be set at instantiation are predefined in the class. Failure to do so will result in an exception being raised.
Methods
init(**kwargs): The initializer that handles the assignment of keyword arguments to instance attributes. It enforces strict attribute assignment rules, only allowing attributes that are already defined in the class to be set.
Initialize an instance of the derived class, strictly assigning provided keyword arguments to corresponding instance attributes.
Parameters
**kwargs: Variable length keyword arguments.
Raises
Exception
- If a key from kwargs does not correspond to any attribute pre-defined in the class, an exception is raised to prevent setting an undefined attribute.
Expand source code
class Kwargs_To_Self: # todo: check if the description below is still relevant (since a lot has changed since it was created) """ A mixin class to strictly assign keyword arguments to pre-defined instance attributes during initialization. This base class provides an __init__ method that assigns values from keyword arguments to instance attributes. If an attribute with the same name as a key from the kwargs is defined in the class, it will be set to the value from kwargs. If the key does not match any predefined attribute names, an exception is raised. This behavior enforces strict control over the attributes of instances, ensuring that only predefined attributes can be set at the time of instantiation and avoids silent attribute creation which can lead to bugs in the code. Usage: class MyConfigurableClass(Kwargs_To_Self): attribute1 = 'default_value' attribute2 = True attribute3 : str attribute4 : list attribute4 : int = 42 # Other methods can be added here # Correctly override default values by passing keyword arguments instance = MyConfigurableClass(attribute1='new_value', attribute2=False) # This will raise an exception as 'attribute3' is not predefined # instance = MyConfigurableClass(attribute3='invalid_attribute') this will also assign the default value to any variable that has a type defined. In the example above the default values (mapped by __default__kwargs__ and __locals__) will be: attribute1 = 'default_value' attribute2 = True attribute3 = '' # default value of str attribute4 = [] # default value of list attribute4 = 42 # defined value in the class Note: It is important that all attributes which may be set at instantiation are predefined in the class. Failure to do so will result in an exception being raised. Methods: __init__(**kwargs): The initializer that handles the assignment of keyword arguments to instance attributes. It enforces strict attribute assignment rules, only allowing attributes that are already defined in the class to be set. """ #__lock_attributes__ = False #__type_safety__ = True def __init__(self, **kwargs): """ Initialize an instance of the derived class, strictly assigning provided keyword arguments to corresponding instance attributes. Parameters: **kwargs: Variable length keyword arguments. Raises: Exception: If a key from kwargs does not correspond to any attribute pre-defined in the class, an exception is raised to prevent setting an undefined attribute. """ # if 'disable_type_safety' in kwargs: # special case # self.__type_safety__ = kwargs['disable_type_safety'] is False # del kwargs['disable_type_safety'] for (key, value) in self.__cls_kwargs__().items(): # assign all default values to self if value is not None: # when the value is explicitly set to None on the class static vars, we can't check for type safety raise_exception_on_obj_type_annotation_mismatch(self, key, value) if hasattr(self, key): existing_value = getattr(self, key) if existing_value is not None: setattr(self, key, existing_value) continue setattr(self, key, value) for (key, value) in kwargs.items(): # overwrite with values provided in ctor if hasattr(self, key): if value is not None: # prevent None values from overwriting existing values, which is quite common in default constructors setattr(self, key, value) else: raise Exception(f"{self.__class__.__name__} has no attribute '{key}' and cannot be assigned the value '{value}'. " f"Use {self.__class__.__name__}.__default_kwargs__() see what attributes are available") def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): pass def __setattr__(self, name, value): if not hasattr(self, '__annotations__'): # can't do type safety checks if the class does not have annotations return super().__setattr__(name, value) # if self.__type_safety__: # if self.__lock_attributes__: # todo: this can't work on all, current hypothesis is that this will work for the values that are explicitly set # if not hasattr(self, name): # raise AttributeError(f"'[Object Locked] Current object is locked (with __lock_attributes__=True) which prevents new attributes allocations (i.e. setattr calls). In this case {type(self).__name__}' object has no attribute '{name}'") from None if value is not None: check_1 = value_type_matches_obj_annotation_for_attr(self, name, value) check_2 = value_type_matches_obj_annotation_for_union_attr(self, name, value) if (check_1 is False and check_2 is None or check_1 is None and check_2 is False or check_1 is False and check_2 is False ): # fix for type safety assigment on Union vars raise Exception(f"Invalid type for attribute '{name}'. Expected '{self.__annotations__.get(name)}' but got '{type(value)}'") else: if hasattr(self, name) and self.__annotations__.get(name) : # don't allow previously set variables to be set to None if getattr(self, name) is not None: # unless it is already set to None raise Exception(f"Can't set None, to a variable that is already set. Invalid type for attribute '{name}'. Expected '{self.__annotations__.get(name)}' but got '{type(value)}'") super().__setattr__(name, value) def __attr_names__(self): return list_set(self.__locals__()) @classmethod def __cls_kwargs__(cls, include_base_classes=True): """Return current class dictionary of class level variables and their values.""" kwargs = {} for base_cls in inspect.getmro(cls): if base_cls is object: # Skip the base 'object' class continue for k, v in vars(base_cls).items(): # todo: refactor this logic since it is weird to start with a if not..., and then if ... continue (all these should be if ... continue ) if not k.startswith('__') and not isinstance(v, types.FunctionType): # remove instance functions if isinstance(v, classmethod): # also remove class methods continue if type(v) is functools._lru_cache_wrapper: # todo, find better way to handle edge cases like this one (which happens when the @cache decorator is used in a instance method that uses Kwargs_To_Self) continue if (k in kwargs) is False: # do not set the value is it has already been set kwargs[k] = v if hasattr(base_cls,'__annotations__'): # can only do type safety checks if the class does not have annotations for var_name, var_type in base_cls.__annotations__.items(): if hasattr(base_cls, var_name) is False: # only add if it has not already been defined if var_name in kwargs: continue var_value = cls.__default__value__(var_type) kwargs[var_name] = var_value else: var_value = getattr(base_cls, var_name) if var_value is not None: # allow None assignments on ctor since that is a valid use case if var_type and not isinstance(var_value, var_type): # check type exception_message = f"variable '{var_name}' is defined as type '{var_type}' but has value '{var_value}' of type '{type(var_value)}'" raise Exception(exception_message) if var_type not in immutable_types and var_name.startswith('__') is False: # if var_type is not one of the immutable_types or is an __ internal #todo: fix type safety bug that I believe is caused here if obj_is_type_union_compatible(var_type, immutable_types) is False: # if var_type is not something like Optional[Union[int, str]] if type(var_type) not in immutable_types: exception_message = f"variable '{var_name}' is defined as type '{var_type}' which is not supported by Kwargs_To_Self, with only the following immutable types being supported: '{immutable_types}'" raise Exception(exception_message) if include_base_classes is False: break return kwargs @classmethod def __default__value__(cls, var_type): if var_type is typing.Set: # todo: refactor the dict, set and list logic, since they are 90% the same return set() if get_origin(var_type) is set: return set() # todo: add Type_Safe__Set if var_type is typing.Dict: return {} if get_origin(var_type) is dict: return {} # todo: add Type_Safe__Dict if var_type is typing.List: return [] # handle case when List was used with no type information provided if get_origin(var_type) is list: # if we have list defined as list[type] item_type = get_args(var_type)[0] # get the type that was defined return Type_Safe__List(expected_type=item_type) # and used it as expected_type in Type_Safe__List else: return default_value(var_type) # for all other cases call default_value, which will try to create a default instance def __default_kwargs__(self): """Return entire (including base classes) dictionary of class level variables and their values.""" kwargs = {} cls = type(self) for base_cls in inspect.getmro(cls): # Traverse the inheritance hierarchy and collect class-level attributes if base_cls is object: # Skip the base 'object' class continue for k, v in vars(base_cls).items(): if not k.startswith('__') and not isinstance(v, types.FunctionType): # remove instance functions if not isinstance(v, classmethod): kwargs[k] = v # add the vars defined with the annotations if hasattr(base_cls,'__annotations__'): # can only do type safety checks if the class does not have annotations for var_name, var_type in base_cls.__annotations__.items(): var_value = getattr(self, var_name) kwargs[var_name] = var_value return kwargs def __kwargs__(self): """Return a dictionary of the current instance's attribute values including inherited class defaults.""" kwargs = {} # Update with instance-specific values for key, value in self.__default_kwargs__().items(): kwargs[key] = self.__getattribute__(key) # if hasattr(self, key): # kwargs[key] = self.__getattribute__(key) # else: # kwargs[key] = value # todo: see if this is stil a valid scenario return kwargs def __locals__(self): """Return a dictionary of the current instance's attribute values.""" kwargs = self.__kwargs__() if not isinstance(vars(self), types.FunctionType): for k, v in vars(self).items(): if not isinstance(v, types.FunctionType) and not isinstance(v,classmethod): if k.startswith('__') is False: kwargs[k] = v return kwargs @classmethod def __schema__(cls): if hasattr(cls,'__annotations__'): # can only do type safety checks if the class does not have annotations return cls.__annotations__ return {} # global methods added to any class that base classes this # todo: see if there should be a prefix on these methods, to make it easier to spot them # of if these are actually that useful that they should be added like this def json(self): return self.serialize_to_dict() def merge_with(self, target): original_attrs = {k: v for k, v in self.__dict__.items() if k not in target.__dict__} # Store the original attributes of self that should be retained. self.__dict__ = target.__dict__ # Set the target's __dict__ to self, now self and target share the same __dict__. self.__dict__.update(original_attrs) # Reassign the original attributes back to self. return self # def locked(self, value=True): # todo: figure out best way to do this (maybe???) # self.__lock_attributes__ = value # : update, with the latest changes were we don't show internals on __locals__() this might be a good way to do this # return self def reset(self): for k,v in self.__cls_kwargs__().items(): setattr(self, k, v) def update_from_kwargs(self, **kwargs): """Update instance attributes with values from provided keyword arguments.""" for key, value in kwargs.items(): if value is not None: if hasattr(self,'__annotations__'): # can only do type safety checks if the class does not have annotations if value_type_matches_obj_annotation_for_attr(self, key, value) is False: raise Exception(f"Invalid type for attribute '{key}'. Expected '{self.__annotations__.get(key)}' but got '{type(value)}'") setattr(self, key, value) return self def deserialize_from_dict(self, data): for key, value in data.items(): if hasattr(self, key) and isinstance(getattr(self, key), Kwargs_To_Self): getattr(self, key).deserialize_from_dict(value) # Recursive call for complex nested objects else: if hasattr(self, '__annotations__'): # can only do type safety checks if the class does not have annotations if obj_is_attribute_annotation_of_type(self, key, EnumMeta): # Handle the case when the value is an Enum enum_type = getattr(self, '__annotations__').get(key) if type(value) is not enum_type: # If the value is not already of the target type value = enum_from_value(enum_type, value) # Try to resolve the value into the enum setattr(self, key, value) # Direct assignment for primitive types and other structures return self def serialize_to_dict(self): # todo: see if we need this method or if the .json() is enough return serialize_to_dict(self) def print(self): pprint(serialize_to_dict(self)) @classmethod def from_json(cls, json_data): if type(json_data) is str: json_data = json_parse(json_data) if json_data: # if there is no data or is {} then don't create an object (since this could be caused by bad data being provided) return cls().deserialize_from_dict(json_data) return None
Subclasses
- Mermaid
- Mermaid__Renderer
- Mermaid__Edge__Config
- Mermaid__Node__Config
- Mermaid__Render__Config
- MGraph
- MGraph__Config
- MGraph__Data
- MGraph__Edge
- MGraph__Node
- MGraph__Random_Graphs
- MGraph__Serializer
- CPrint
- Print_Table
- Python_Audit
- SSH
- Dict_To_Css
- Tag__Base
- Event__Queue
- PubSub__Client
- PubSub__Room
- Schema__Event
- Schema__PubSub__Clients
- Sqlite__Cursor
- Sqlite__Database
- Sqlite__Field
- Sqlite__Table
- Sqlite__Table__Create
- Temp_Sqlite__Database__Disk
- Temp_Sqlite__Table
- Sqlite__Cache__Requests
- Sqlite__DB__Json
- Schema__Table__Requests
- Sqlite__Sample_Data__Chinook
- SQL_Builder
- Schema__Table__Config
- Schema__Table__Edges
- Schema__Table__Files
- Schema__Table__Nodes
- Trace_Call
- Trace_Call__Config
- Trace_Call__Handler
- Trace_Call__Print_Lines
- Trace_Call__Print_Traces
- Trace_Call__Stack
- Trace_Call__Stack_Node
- Trace_Call__Stats
- Patch_Print
- Call_Stack
- Frame_Data
Static methods
def from_json(json_data)
-
Expand source code
@classmethod def from_json(cls, json_data): if type(json_data) is str: json_data = json_parse(json_data) if json_data: # if there is no data or is {} then don't create an object (since this could be caused by bad data being provided) return cls().deserialize_from_dict(json_data) return None
Methods
def deserialize_from_dict(self, data)
-
Expand source code
def deserialize_from_dict(self, data): for key, value in data.items(): if hasattr(self, key) and isinstance(getattr(self, key), Kwargs_To_Self): getattr(self, key).deserialize_from_dict(value) # Recursive call for complex nested objects else: if hasattr(self, '__annotations__'): # can only do type safety checks if the class does not have annotations if obj_is_attribute_annotation_of_type(self, key, EnumMeta): # Handle the case when the value is an Enum enum_type = getattr(self, '__annotations__').get(key) if type(value) is not enum_type: # If the value is not already of the target type value = enum_from_value(enum_type, value) # Try to resolve the value into the enum setattr(self, key, value) # Direct assignment for primitive types and other structures return self
def json(self)
-
Expand source code
def json(self): return self.serialize_to_dict()
def merge_with(self, target)
-
Expand source code
def merge_with(self, target): original_attrs = {k: v for k, v in self.__dict__.items() if k not in target.__dict__} # Store the original attributes of self that should be retained. self.__dict__ = target.__dict__ # Set the target's __dict__ to self, now self and target share the same __dict__. self.__dict__.update(original_attrs) # Reassign the original attributes back to self. return self
def print(self)
-
Expand source code
def print(self): pprint(serialize_to_dict(self))
def reset(self)
-
Expand source code
def reset(self): for k,v in self.__cls_kwargs__().items(): setattr(self, k, v)
def serialize_to_dict(self)
-
Expand source code
def serialize_to_dict(self): # todo: see if we need this method or if the .json() is enough return serialize_to_dict(self)
def update_from_kwargs(self, **kwargs)
-
Update instance attributes with values from provided keyword arguments.
Expand source code
def update_from_kwargs(self, **kwargs): """Update instance attributes with values from provided keyword arguments.""" for key, value in kwargs.items(): if value is not None: if hasattr(self,'__annotations__'): # can only do type safety checks if the class does not have annotations if value_type_matches_obj_annotation_for_attr(self, key, value) is False: raise Exception(f"Invalid type for attribute '{key}'. Expected '{self.__annotations__.get(key)}' but got '{type(value)}'") setattr(self, key, value) return self