Module jumpscale.core.base.factory
Hierarchal configurations and factories for any Base
type/class.
It is implemented using:
Factory
: the base for all factories, implements an in-memory factory for any type.StoredFactory
: a factory which stores and load configuration according to the current configured store backend.
Default store configured is file system, you can show current store config by doing:
✗ jsctl config get store
store = filesystem
Get all stores configurations:
✗ jsctl config get stores
redis.hostname = "localhost"
redis.port = 6379
filesystem.path = "/home/abom/.config/jumpscale/secureconfig"
whoosh.path = "/home/abom/.config/jumpscale/whoosh_indexes"
For example, set current store to redis:
jsctl config update store redis
This will use current redis config (hostname: localhost, port: 6379).
Expand source code
"""
Hierarchal configurations and factories for any `jumpscale.core.base.meta.Base` type/class.
It is implemented using:
- `Factory`: the base for all factories, implements an in-memory factory for any type.
- `StoredFactory`: a factory which stores and load configuration according to the current configured store backend.
Default store configured is file system, you can show current store config by doing:
```
✗ jsctl config get store
store = filesystem
```
Get all stores configurations:
```
✗ jsctl config get stores
redis.hostname = "localhost"
redis.port = 6379
filesystem.path = "/home/abom/.config/jumpscale/secureconfig"
whoosh.path = "/home/abom/.config/jumpscale/whoosh_indexes"
```
For example, set current store to redis:
```
jsctl config update store redis
```
This will use current redis config (hostname: localhost, port: 6379).
"""
from functools import partial
from jumpscale.core import config, events
from .events import InstanceCreateEvent, InstanceDeleteEvent
from .store import ConfigNotFound, KEY_FIELD_NAME, Location
from .store.filesystem import FileSystemStore
from .store.redis import RedisStore
from .store.whooshfts import WhooshStore
STORES = {"filesystem": FileSystemStore, "redis": RedisStore, "whoosh": WhooshStore}
class DuplicateError(Exception):
"""
raised when you try to create an instance by the same name of an existing one for a factory
"""
class Factory:
"""
Base factory, where you can create/get/list/delete new instances.
All of the operations are done in memory, also instances will be available as properties.
Example:
```python
class Car(Base):
name = fields.String()
color = fields.String()
cars = Factory(Car)
acar = cars.get("mycar")
acar.name = "fiat"
print(cars.mycar.name)
```
"""
def __init__(self, type_, name_=None, parent_instance_=None, parent_factory_=None):
"""
get a new factory given the type to create instances for.
Any factory can have a name, parent `Base` instance and a parent factory.
Args:
type_ (Base): `Base` class type
name_ (str, optional): factory name. Defaults to None.
parent_instance_ (Base, optional): a parent `Base` instance. Defaults to None.
parent_factory_ (Factory, optional): a parent `Factory`. Defaults to None.
"""
self.__name = name_
self.__parent_instance = parent_instance_
self.__parent_factory = parent_factory_
self.type = type_
self.count = 0
@property
def parent_instance(self):
return self.__parent_instance
@property
def name(self):
return self.__name
@property
def parent_factory(self):
return self.__parent_factory
def _set_parent_factory(self, factory):
"""
set current parent factory
Args:
factory (Factory)
"""
self.__parent_factory = factory
def find(self, name):
"""
find an instance with the given name
Args:
name (str): instance name
Raises:
ValueError: in case the name is an internal attribute of this factory, like `get` or `new`.
Returns:
Base or NoneType: an instance or none
"""
instance = getattr(self, name, None)
if instance and not isinstance(instance, self.type):
raise ValueError("{} is an internal attribute".format(name))
return instance
def _create_instance(self, name_, *args, **kwargs):
"""
create a new instance, this method is only responsible for:
- validating the name used is correct (must be a valid identifier and does not start with "__")
- creating the instance from `self.type`
- setting this instance name with the given name
- setting the parent instance to it if this factory have a parent instance
- update the counter and trigger `_created`
Args:
name_ (str): instance name
Raises:
ValueError: in case the name is not a valid identifier (e.g contains spaces) or starts with "__"
Returns:
Base: instance
"""
if not name_.isidentifier():
raise ValueError("{} is not a valid identifier".format(name_))
if name_.startswith("__"):
raise ValueError("name cannot start with '__'")
kwargs["instance_name_"] = name_
kwargs["parent_"] = self.parent_instance
instance = self.type(*args, **kwargs)
self.count += 1
self._created(instance)
return instance
def new(self, name, *args, **kwargs):
"""
get a new instance and make it available as an attribute
Args:
name (str): name
*args: arbitrary arguments passed to the factory `Base` type
*kwargs: arbitrary keyword arguments passed to the factory `Base` type
Raises:
DuplicateError: in case an instance with the same name exists
Returns:
Base: instance
"""
if self.find(name):
raise DuplicateError(f"instance with name {name} already exists")
instance = self._create_instance(name, *args, **kwargs)
setattr(self, name, instance)
return instance
def get(self, name, *args, **kwargs):
"""
get an instance (will create if it does not exist)
if the instance with `name` exists, `args` and `kwargs` are ignored.
Args:
name (str): name
*args: arbitrary arguments passed to the factory `Base` type
*kwargs: arbitrary keyword arguments passed to the factory `Base` type
Raises:
DuplicateError: in case an instance with the same name exists
Returns:
Base: instance
"""
instance = self.find(name)
if instance:
return instance
return self.new(name, *args, **kwargs)
def _delete_instance(self, name):
"""
delete the instance with given name
here, we only remove the attribute
Args:
name (str): instance/attribute name
"""
if hasattr(self, name):
delattr(self, name)
def delete(self, name):
"""
delete an instance (with its attribute)
this will update the count and trigger `_deleted`
Args:
name (str)
"""
self.count -= 1
self._delete_instance(name)
self._deleted(name)
def _deleted(self, name):
"""
called when an instance is deleted, will trigger `InstanceDeleteEvent`
Args:
name (name)
"""
event = InstanceDeleteEvent(name, factory=self)
events.notify(event)
def _created(self, instance):
"""
called when an instance is created, will trigger `InstanceCreateEvent`
Args:
instance (Base)
"""
event = InstanceCreateEvent(instance=instance, factory=self)
events.notify(event)
def list_all(self):
"""
get a set of all instance names
this only get a list of all properties that are of factory `Base` type.
Returns:
set
"""
names = set()
for name, value in self.__dict__.items():
if isinstance(value, self.type) and not name.startswith("__"):
names.add(name)
return names
class StoredFactory(events.Handler, Factory):
"""
Stored factories are a custom type of `Factory`, which uses current configured store backend
to store all instance configurations.
"""
STORE = STORES[config.get("store")]
def __init__(self, type_, name_=None, parent_instance_=None, parent_factory_=None):
"""
get a new stored factory given the type to create and store instances for.
Any factory can have a name, parent `Base` instance and a parent factory.
Once a stored factory is created, it tries to lazy-load all current configuration for given `type_`.
Args:
type_ (Base): `Base` class type
name_ (str, optional): factory name. Defaults to None.
parent_instance_ (Base, optional): a parent `Base` instance. Defaults to None.
parent_factory_ (Factory, optional): a parent `Factory`. Defaults to None.
"""
super().__init__(type_, name_=name_, parent_instance_=parent_instance_, parent_factory_=parent_factory_)
self.__store = None
if not parent_instance_:
# no parent, then load all instance configurations
# if it's a parent, it should trigger this loading
self._load()
# to handle when a parent instance is deleted
events.add_listenter(self, InstanceDeleteEvent)
# if we need to always reload the config from store when getting an instance
self.always_reload = config.get("factory").get("always_reload", False)
@property
def parent_location(self):
if not self.parent_factory:
raise ValueError("cannot get parent location if parent factory is not set")
return self.parent_factory.location
@property
def location(self):
"""
get a unique location for this factory
Returns:
Location: location object
"""
name_list = []
# first, get the location of parent factory if any
if self.parent_factory:
name_list += self.parent_location.name_list
# if we have a parent instance, then this location should be unique
# for this instance
if self.parent_instance:
name_list.append(self.parent_instance.instance_name)
# if this factory have a name, append it too
if self.name:
name_list.append(self.name)
# then we append the location of the type
type_location = Location.from_type(self.type)
name_list += type_location.name_list
return Location(*name_list, type_=self.type)
@property
def store(self):
if not self.__store:
self.__store = self.STORE(self.location)
return self.__store
def _validate_and_save_instance(self, instance):
"""
validate and save a given instance to the store
Args:
instance (Base)
"""
instance.validate()
self.store.save(instance.instance_name, instance._get_data())
if instance.parent and hasattr(instance.parent, "save"):
instance.parent.save()
def handle(self, ev):
"""
handle when the parent instance is deleted
Args:
ev (InstanceDeleteEvent): instance delete event
"""
# do nothing if this is a root factory
if not self.parent_instance or not self.parent_factory:
return
# handle deletion of children
if isinstance(ev, InstanceDeleteEvent):
if ev.factory == self.parent_factory and ev.name == self.parent_instance.instance_name:
for name in self.list_all():
self.delete(name)
def _load_sub_factories(self, instance):
"""
load sub-factories for an instance
this will also set current factory as a parent for any sub-factories this instance have
Args:
instance (Base)
"""
for factory in instance._get_factories().values():
factory._set_parent_factory(self)
factory._load()
def _init_save_and_sub_factories(self, instance):
"""
allow an instance to have a `save` method
also, this method inits the loading of sub-factories for this instance
Args:
instance (Base)
"""
instance.save = partial(self._validate_and_save_instance, instance)
self._load_sub_factories(instance)
def _get_object_from_config(self, name, data):
instance = self._create_instance(name, **data)
self._init_save_and_sub_factories(instance)
return instance
def _load_from_store(self, name):
"""
loads instance from store
Args:
name (str): instance name
Returns:
Base or NoneType: an instance or none
"""
try:
instance_config = self.store.get(name)
except ConfigNotFound:
return
instance = self._get_object_from_config(name, instance_config)
setattr(self, name, instance)
return instance
def find(self, name):
"""
find an instance with the given name
Args:
name (str): instance name
Raises:
ValueError: in case the name is an internal attribute of this factory, like `get` or `new`.
Returns:
Base or NoneType: an instance or none
"""
instance = super().find(name)
if instance:
if self.always_reload:
try:
instance._set_data(self.store.get(name))
except ConfigNotFound:
# no config is written, still not saved
pass
else:
instance = self._load_from_store(name)
return instance
def new(self, name, *args, **kwargs):
"""
get a new instance and make it available as an attribute
this method also initialize sub-factories for this instance.
Args:
name (str): name
*args: arbitrary arguments passed to the factory `Base` type
*kwargs: arbitrary keyword arguments passed to the factory `Base` type
Raises:
DuplicateError: in case an instance with the same name exists
Returns:
Base: instance
"""
instance = super().new(name, *args, **kwargs)
self._init_save_and_sub_factories(instance)
return instance
def get_instance_property(self, name):
"""
return a new property descriptor for a given name, this will help in lazy-loading
as this property will only create and load an instance from the store once accessed.
Args:
name (str): instance name
Returns:
property: property descriptor (object)
"""
inner_name = f"__{name}"
def getter(factory):
# we need to avoid AttributeError which hasattr swallows
# instead we check the internal __dict__
if inner_name in factory.__dict__:
return getattr(factory, inner_name)
instance = self._get_object_from_config(name, factory.store.get(name))
setattr(factory, inner_name, instance)
return instance
return property(getter)
def _load(self):
"""
lazy-load all instance configuration
it will create a new class for this factory with property descriptors for all instances listed by the store
"""
# get a new class based on self.__class__
new_cls = type(self.__class__.__name__, (self.__class__,), {})
# list all instance names in the store and set a property descriptor as an attribute
for name in self.store.list_all():
# it will create the actual instance with configuration from the store when accessed only
setattr(new_cls, name, self.get_instance_property(name))
# update current factory class with this class
self.__class__ = new_cls
def _delete_instance(self, name):
"""
delete an instance
this will delete it from the store too, also, delete its property descriptor if found
Args:
name (str): instance name
"""
self.store.delete(name)
class_prop = getattr(self.__class__, name, None)
if isinstance(class_prop, property):
# created with a property descriptor inside the class, delete it
delattr(self.__class__, name)
# check if the instance was actually created or not (if the property was accessed)
inner_name = f"__{name}"
if hasattr(self, inner_name):
# delete the instance itself too if it was accessed and created
delattr(self, inner_name)
else:
super()._delete_instance(name)
def find_many(self, cursor_=None, limit_=None, **query):
"""
do a search against the store (not loaded objects) with this query.
queries can relate to current store backend used.
Keyword Args:
cursor_ (any, optional): an optional cursor, to start searching from. Defaults to None.
limit_ (int, optional): results limit. Defaults to None.
query: a mapping for field/query, e.g. first_name="aa"
Returns:
tuple: the new cursor, total results count, and a list of objects as a result
"""
if not query:
raise ValueError("at least one query parameter is required, e.g. age=10")
new_cursor, count, result = self.store.find(cursor_=cursor_, limit_=limit_, **query)
return (new_cursor, count, (self._get_object_from_config(data[KEY_FIELD_NAME], data) for data in result))
def list_all(self):
"""
get all instance names (stored or not)
Returns:
set: instance names
"""
names = set(self.store.list_all())
return names.union(super().list_all())
def __iter__(self):
for value in vars(self).values():
if isinstance(value, self.type):
yield value
def __eq__(self, other):
return self.location == other.location
def __hash__(self):
return hash(self.location)
def __str__(self):
"""
readale string for this factory
Returns:
str
"""
return f"{self.__class__.__name__}({self.type.__name__})"
__repr__ = __str__
Classes
class DuplicateError (*args, **kwargs)
-
raised when you try to create an instance by the same name of an existing one for a factory
Expand source code
class DuplicateError(Exception): """ raised when you try to create an instance by the same name of an existing one for a factory """
Ancestors
- builtins.Exception
- builtins.BaseException
class Factory (type_, name_=None, parent_instance_=None, parent_factory_=None)
-
Base factory, where you can create/get/list/delete new instances.
All of the operations are done in memory, also instances will be available as properties.
Example:
class Car(Base): name = fields.String() color = fields.String() cars = Factory(Car) acar = cars.get("mycar") acar.name = "fiat" print(cars.mycar.name)
get a new factory given the type to create instances for.
Any factory can have a name, parent
Base
instance and a parent factory.Args
Expand source code
class Factory: """ Base factory, where you can create/get/list/delete new instances. All of the operations are done in memory, also instances will be available as properties. Example: ```python class Car(Base): name = fields.String() color = fields.String() cars = Factory(Car) acar = cars.get("mycar") acar.name = "fiat" print(cars.mycar.name) ``` """ def __init__(self, type_, name_=None, parent_instance_=None, parent_factory_=None): """ get a new factory given the type to create instances for. Any factory can have a name, parent `Base` instance and a parent factory. Args: type_ (Base): `Base` class type name_ (str, optional): factory name. Defaults to None. parent_instance_ (Base, optional): a parent `Base` instance. Defaults to None. parent_factory_ (Factory, optional): a parent `Factory`. Defaults to None. """ self.__name = name_ self.__parent_instance = parent_instance_ self.__parent_factory = parent_factory_ self.type = type_ self.count = 0 @property def parent_instance(self): return self.__parent_instance @property def name(self): return self.__name @property def parent_factory(self): return self.__parent_factory def _set_parent_factory(self, factory): """ set current parent factory Args: factory (Factory) """ self.__parent_factory = factory def find(self, name): """ find an instance with the given name Args: name (str): instance name Raises: ValueError: in case the name is an internal attribute of this factory, like `get` or `new`. Returns: Base or NoneType: an instance or none """ instance = getattr(self, name, None) if instance and not isinstance(instance, self.type): raise ValueError("{} is an internal attribute".format(name)) return instance def _create_instance(self, name_, *args, **kwargs): """ create a new instance, this method is only responsible for: - validating the name used is correct (must be a valid identifier and does not start with "__") - creating the instance from `self.type` - setting this instance name with the given name - setting the parent instance to it if this factory have a parent instance - update the counter and trigger `_created` Args: name_ (str): instance name Raises: ValueError: in case the name is not a valid identifier (e.g contains spaces) or starts with "__" Returns: Base: instance """ if not name_.isidentifier(): raise ValueError("{} is not a valid identifier".format(name_)) if name_.startswith("__"): raise ValueError("name cannot start with '__'") kwargs["instance_name_"] = name_ kwargs["parent_"] = self.parent_instance instance = self.type(*args, **kwargs) self.count += 1 self._created(instance) return instance def new(self, name, *args, **kwargs): """ get a new instance and make it available as an attribute Args: name (str): name *args: arbitrary arguments passed to the factory `Base` type *kwargs: arbitrary keyword arguments passed to the factory `Base` type Raises: DuplicateError: in case an instance with the same name exists Returns: Base: instance """ if self.find(name): raise DuplicateError(f"instance with name {name} already exists") instance = self._create_instance(name, *args, **kwargs) setattr(self, name, instance) return instance def get(self, name, *args, **kwargs): """ get an instance (will create if it does not exist) if the instance with `name` exists, `args` and `kwargs` are ignored. Args: name (str): name *args: arbitrary arguments passed to the factory `Base` type *kwargs: arbitrary keyword arguments passed to the factory `Base` type Raises: DuplicateError: in case an instance with the same name exists Returns: Base: instance """ instance = self.find(name) if instance: return instance return self.new(name, *args, **kwargs) def _delete_instance(self, name): """ delete the instance with given name here, we only remove the attribute Args: name (str): instance/attribute name """ if hasattr(self, name): delattr(self, name) def delete(self, name): """ delete an instance (with its attribute) this will update the count and trigger `_deleted` Args: name (str) """ self.count -= 1 self._delete_instance(name) self._deleted(name) def _deleted(self, name): """ called when an instance is deleted, will trigger `InstanceDeleteEvent` Args: name (name) """ event = InstanceDeleteEvent(name, factory=self) events.notify(event) def _created(self, instance): """ called when an instance is created, will trigger `InstanceCreateEvent` Args: instance (Base) """ event = InstanceCreateEvent(instance=instance, factory=self) events.notify(event) def list_all(self): """ get a set of all instance names this only get a list of all properties that are of factory `Base` type. Returns: set """ names = set() for name, value in self.__dict__.items(): if isinstance(value, self.type) and not name.startswith("__"): names.add(name) return names
Subclasses
Instance variables
var name
-
Expand source code
@property def name(self): return self.__name
var parent_factory
-
Expand source code
@property def parent_factory(self): return self.__parent_factory
var parent_instance
-
Expand source code
@property def parent_instance(self): return self.__parent_instance
Methods
def delete(self, name)
-
delete an instance (with its attribute)
this will update the count and trigger
_deleted
Args
name (str)
Expand source code
def delete(self, name): """ delete an instance (with its attribute) this will update the count and trigger `_deleted` Args: name (str) """ self.count -= 1 self._delete_instance(name) self._deleted(name)
def find(self, name)
-
find an instance with the given name
Args
name
:str
- instance name
Raises
ValueError
- in case the name is an internal attribute of this factory, like
get
ornew
.
Returns
Base
orNoneType
- an instance or none
Expand source code
def find(self, name): """ find an instance with the given name Args: name (str): instance name Raises: ValueError: in case the name is an internal attribute of this factory, like `get` or `new`. Returns: Base or NoneType: an instance or none """ instance = getattr(self, name, None) if instance and not isinstance(instance, self.type): raise ValueError("{} is an internal attribute".format(name)) return instance
def get(self, name, *args, **kwargs)
-
get an instance (will create if it does not exist)
if the instance with
name
exists,args
andkwargs
are ignored.Args
name
:str
- name
*args
- arbitrary arguments passed to the factory
Base
type *kwargs
- arbitrary keyword arguments passed to the factory
Base
type
Raises
DuplicateError
- in case an instance with the same name exists
Returns
Base
- instance
Expand source code
def get(self, name, *args, **kwargs): """ get an instance (will create if it does not exist) if the instance with `name` exists, `args` and `kwargs` are ignored. Args: name (str): name *args: arbitrary arguments passed to the factory `Base` type *kwargs: arbitrary keyword arguments passed to the factory `Base` type Raises: DuplicateError: in case an instance with the same name exists Returns: Base: instance """ instance = self.find(name) if instance: return instance return self.new(name, *args, **kwargs)
def list_all(self)
-
get a set of all instance names
this only get a list of all properties that are of factory
Base
type.Returns
set
Expand source code
def list_all(self): """ get a set of all instance names this only get a list of all properties that are of factory `Base` type. Returns: set """ names = set() for name, value in self.__dict__.items(): if isinstance(value, self.type) and not name.startswith("__"): names.add(name) return names
def new(self, name, *args, **kwargs)
-
get a new instance and make it available as an attribute
Args
name
:str
- name
*args
- arbitrary arguments passed to the factory
Base
type *kwargs
- arbitrary keyword arguments passed to the factory
Base
type
Raises
DuplicateError
- in case an instance with the same name exists
Returns
Base
- instance
Expand source code
def new(self, name, *args, **kwargs): """ get a new instance and make it available as an attribute Args: name (str): name *args: arbitrary arguments passed to the factory `Base` type *kwargs: arbitrary keyword arguments passed to the factory `Base` type Raises: DuplicateError: in case an instance with the same name exists Returns: Base: instance """ if self.find(name): raise DuplicateError(f"instance with name {name} already exists") instance = self._create_instance(name, *args, **kwargs) setattr(self, name, instance) return instance
class StoredFactory (type_, name_=None, parent_instance_=None, parent_factory_=None)
-
Stored factories are a custom type of
Factory
, which uses current configured store backend to store all instance configurations.get a new stored factory given the type to create and store instances for.
Any factory can have a name, parent
Base
instance and a parent factory.Once a stored factory is created, it tries to lazy-load all current configuration for given
type_
.Args
Expand source code
class StoredFactory(events.Handler, Factory): """ Stored factories are a custom type of `Factory`, which uses current configured store backend to store all instance configurations. """ STORE = STORES[config.get("store")] def __init__(self, type_, name_=None, parent_instance_=None, parent_factory_=None): """ get a new stored factory given the type to create and store instances for. Any factory can have a name, parent `Base` instance and a parent factory. Once a stored factory is created, it tries to lazy-load all current configuration for given `type_`. Args: type_ (Base): `Base` class type name_ (str, optional): factory name. Defaults to None. parent_instance_ (Base, optional): a parent `Base` instance. Defaults to None. parent_factory_ (Factory, optional): a parent `Factory`. Defaults to None. """ super().__init__(type_, name_=name_, parent_instance_=parent_instance_, parent_factory_=parent_factory_) self.__store = None if not parent_instance_: # no parent, then load all instance configurations # if it's a parent, it should trigger this loading self._load() # to handle when a parent instance is deleted events.add_listenter(self, InstanceDeleteEvent) # if we need to always reload the config from store when getting an instance self.always_reload = config.get("factory").get("always_reload", False) @property def parent_location(self): if not self.parent_factory: raise ValueError("cannot get parent location if parent factory is not set") return self.parent_factory.location @property def location(self): """ get a unique location for this factory Returns: Location: location object """ name_list = [] # first, get the location of parent factory if any if self.parent_factory: name_list += self.parent_location.name_list # if we have a parent instance, then this location should be unique # for this instance if self.parent_instance: name_list.append(self.parent_instance.instance_name) # if this factory have a name, append it too if self.name: name_list.append(self.name) # then we append the location of the type type_location = Location.from_type(self.type) name_list += type_location.name_list return Location(*name_list, type_=self.type) @property def store(self): if not self.__store: self.__store = self.STORE(self.location) return self.__store def _validate_and_save_instance(self, instance): """ validate and save a given instance to the store Args: instance (Base) """ instance.validate() self.store.save(instance.instance_name, instance._get_data()) if instance.parent and hasattr(instance.parent, "save"): instance.parent.save() def handle(self, ev): """ handle when the parent instance is deleted Args: ev (InstanceDeleteEvent): instance delete event """ # do nothing if this is a root factory if not self.parent_instance or not self.parent_factory: return # handle deletion of children if isinstance(ev, InstanceDeleteEvent): if ev.factory == self.parent_factory and ev.name == self.parent_instance.instance_name: for name in self.list_all(): self.delete(name) def _load_sub_factories(self, instance): """ load sub-factories for an instance this will also set current factory as a parent for any sub-factories this instance have Args: instance (Base) """ for factory in instance._get_factories().values(): factory._set_parent_factory(self) factory._load() def _init_save_and_sub_factories(self, instance): """ allow an instance to have a `save` method also, this method inits the loading of sub-factories for this instance Args: instance (Base) """ instance.save = partial(self._validate_and_save_instance, instance) self._load_sub_factories(instance) def _get_object_from_config(self, name, data): instance = self._create_instance(name, **data) self._init_save_and_sub_factories(instance) return instance def _load_from_store(self, name): """ loads instance from store Args: name (str): instance name Returns: Base or NoneType: an instance or none """ try: instance_config = self.store.get(name) except ConfigNotFound: return instance = self._get_object_from_config(name, instance_config) setattr(self, name, instance) return instance def find(self, name): """ find an instance with the given name Args: name (str): instance name Raises: ValueError: in case the name is an internal attribute of this factory, like `get` or `new`. Returns: Base or NoneType: an instance or none """ instance = super().find(name) if instance: if self.always_reload: try: instance._set_data(self.store.get(name)) except ConfigNotFound: # no config is written, still not saved pass else: instance = self._load_from_store(name) return instance def new(self, name, *args, **kwargs): """ get a new instance and make it available as an attribute this method also initialize sub-factories for this instance. Args: name (str): name *args: arbitrary arguments passed to the factory `Base` type *kwargs: arbitrary keyword arguments passed to the factory `Base` type Raises: DuplicateError: in case an instance with the same name exists Returns: Base: instance """ instance = super().new(name, *args, **kwargs) self._init_save_and_sub_factories(instance) return instance def get_instance_property(self, name): """ return a new property descriptor for a given name, this will help in lazy-loading as this property will only create and load an instance from the store once accessed. Args: name (str): instance name Returns: property: property descriptor (object) """ inner_name = f"__{name}" def getter(factory): # we need to avoid AttributeError which hasattr swallows # instead we check the internal __dict__ if inner_name in factory.__dict__: return getattr(factory, inner_name) instance = self._get_object_from_config(name, factory.store.get(name)) setattr(factory, inner_name, instance) return instance return property(getter) def _load(self): """ lazy-load all instance configuration it will create a new class for this factory with property descriptors for all instances listed by the store """ # get a new class based on self.__class__ new_cls = type(self.__class__.__name__, (self.__class__,), {}) # list all instance names in the store and set a property descriptor as an attribute for name in self.store.list_all(): # it will create the actual instance with configuration from the store when accessed only setattr(new_cls, name, self.get_instance_property(name)) # update current factory class with this class self.__class__ = new_cls def _delete_instance(self, name): """ delete an instance this will delete it from the store too, also, delete its property descriptor if found Args: name (str): instance name """ self.store.delete(name) class_prop = getattr(self.__class__, name, None) if isinstance(class_prop, property): # created with a property descriptor inside the class, delete it delattr(self.__class__, name) # check if the instance was actually created or not (if the property was accessed) inner_name = f"__{name}" if hasattr(self, inner_name): # delete the instance itself too if it was accessed and created delattr(self, inner_name) else: super()._delete_instance(name) def find_many(self, cursor_=None, limit_=None, **query): """ do a search against the store (not loaded objects) with this query. queries can relate to current store backend used. Keyword Args: cursor_ (any, optional): an optional cursor, to start searching from. Defaults to None. limit_ (int, optional): results limit. Defaults to None. query: a mapping for field/query, e.g. first_name="aa" Returns: tuple: the new cursor, total results count, and a list of objects as a result """ if not query: raise ValueError("at least one query parameter is required, e.g. age=10") new_cursor, count, result = self.store.find(cursor_=cursor_, limit_=limit_, **query) return (new_cursor, count, (self._get_object_from_config(data[KEY_FIELD_NAME], data) for data in result)) def list_all(self): """ get all instance names (stored or not) Returns: set: instance names """ names = set(self.store.list_all()) return names.union(super().list_all()) def __iter__(self): for value in vars(self).values(): if isinstance(value, self.type): yield value def __eq__(self, other): return self.location == other.location def __hash__(self): return hash(self.location) def __str__(self): """ readale string for this factory Returns: str """ return f"{self.__class__.__name__}({self.type.__name__})" __repr__ = __str__
Ancestors
Subclasses
- DropletFactory
- ProjectFactory
- StellarFactory
- ZDBFactory
- StoredFactory
- StoredFactory
- StoredFactory
- StoredFactory
- StoredFactory
- StoredFactory
- StoredFactory
- StoredFactory
- IdentityFactory
- PaymentFactory
- RefundFactory
- ThreebotServerFactory
Class variables
var STORE
-
Filesystem store is an EncryptedConfigStore
It saves the config relative to
config_env.get_store_config("filesystem")
To store every instance config in a different path, it uses the given
Location
.
Instance variables
var location
-
get a unique location for this factory
Returns
Location
- location object
Expand source code
@property def location(self): """ get a unique location for this factory Returns: Location: location object """ name_list = [] # first, get the location of parent factory if any if self.parent_factory: name_list += self.parent_location.name_list # if we have a parent instance, then this location should be unique # for this instance if self.parent_instance: name_list.append(self.parent_instance.instance_name) # if this factory have a name, append it too if self.name: name_list.append(self.name) # then we append the location of the type type_location = Location.from_type(self.type) name_list += type_location.name_list return Location(*name_list, type_=self.type)
var parent_location
-
Expand source code
@property def parent_location(self): if not self.parent_factory: raise ValueError("cannot get parent location if parent factory is not set") return self.parent_factory.location
var store
-
Expand source code
@property def store(self): if not self.__store: self.__store = self.STORE(self.location) return self.__store
Methods
def find_many(self, cursor_=None, limit_=None, **query)
-
do a search against the store (not loaded objects) with this query.
queries can relate to current store backend used.
Keyword Args: cursor_ (any, optional): an optional cursor, to start searching from. Defaults to None. limit_ (int, optional): results limit. Defaults to None. query: a mapping for field/query, e.g. first_name="aa"
Returns
tuple
- the new cursor, total results count, and a list of objects as a result
Expand source code
def find_many(self, cursor_=None, limit_=None, **query): """ do a search against the store (not loaded objects) with this query. queries can relate to current store backend used. Keyword Args: cursor_ (any, optional): an optional cursor, to start searching from. Defaults to None. limit_ (int, optional): results limit. Defaults to None. query: a mapping for field/query, e.g. first_name="aa" Returns: tuple: the new cursor, total results count, and a list of objects as a result """ if not query: raise ValueError("at least one query parameter is required, e.g. age=10") new_cursor, count, result = self.store.find(cursor_=cursor_, limit_=limit_, **query) return (new_cursor, count, (self._get_object_from_config(data[KEY_FIELD_NAME], data) for data in result))
def get_instance_property(self, name)
-
return a new property descriptor for a given name, this will help in lazy-loading as this property will only create and load an instance from the store once accessed.
Args
name
:str
- instance name
Returns
property
- property descriptor (object)
Expand source code
def get_instance_property(self, name): """ return a new property descriptor for a given name, this will help in lazy-loading as this property will only create and load an instance from the store once accessed. Args: name (str): instance name Returns: property: property descriptor (object) """ inner_name = f"__{name}" def getter(factory): # we need to avoid AttributeError which hasattr swallows # instead we check the internal __dict__ if inner_name in factory.__dict__: return getattr(factory, inner_name) instance = self._get_object_from_config(name, factory.store.get(name)) setattr(factory, inner_name, instance) return instance return property(getter)
def handle(self, ev)
-
handle when the parent instance is deleted
Args
ev
:InstanceDeleteEvent
- instance delete event
Expand source code
def handle(self, ev): """ handle when the parent instance is deleted Args: ev (InstanceDeleteEvent): instance delete event """ # do nothing if this is a root factory if not self.parent_instance or not self.parent_factory: return # handle deletion of children if isinstance(ev, InstanceDeleteEvent): if ev.factory == self.parent_factory and ev.name == self.parent_instance.instance_name: for name in self.list_all(): self.delete(name)
def list_all(self)
-
get all instance names (stored or not)
Returns
set
- instance names
Expand source code
def list_all(self): """ get all instance names (stored or not) Returns: set: instance names """ names = set(self.store.list_all()) return names.union(super().list_all())
def new(self, name, *args, **kwargs)
-
get a new instance and make it available as an attribute
this method also initialize sub-factories for this instance.
Args
name
:str
- name
*args
- arbitrary arguments passed to the factory
Base
type *kwargs
- arbitrary keyword arguments passed to the factory
Base
type
Raises
DuplicateError
- in case an instance with the same name exists
Returns
Base
- instance
Expand source code
def new(self, name, *args, **kwargs): """ get a new instance and make it available as an attribute this method also initialize sub-factories for this instance. Args: name (str): name *args: arbitrary arguments passed to the factory `Base` type *kwargs: arbitrary keyword arguments passed to the factory `Base` type Raises: DuplicateError: in case an instance with the same name exists Returns: Base: instance """ instance = super().new(name, *args, **kwargs) self._init_save_and_sub_factories(instance) return instance
Inherited members