Module xmodel.remote.weak_cache_pool
Introduction
Used to weakly store model objects in a Dependency. Dependency can be activated/enabled temporarily or permanently depending on desired behavior for app.
The weak-cache is nice, because there are situations where various object will reference the same object. Take for instance order and order-lines. The order-lines would have a one-to-one relationship back to the order object, and there is no need to lookup the same order object over and over again if you ask each order-line for it's order-object.
This is where the weak-cache can shine. The ORM can store temporary references to objects by 'id' and check this cache to retrieve them later instead of having to do an actual fetch-request.
Another place this can be useful is when query objects that are in a tree. And objects parent could be referenced by several children.
Quick Start
To use, you can either simply allocate a WeakCachePool
resource and activate it
via @
function dectorator or with
context-manager syntax:
>>> from xmodel.weak_cache_pool import WeakCachePool
>>> @WeakCachePool(enabled=True)
>>> def lambda_event_handler(event, context):
... pass
While this WeakCachePool is enabled, it will store weakly-cached objects in it's self.
When the object is deactivated and thrown away after the lambda_event_handler
is finished
the weak-cache is deallocated.
Next time the function lambda_event_handler
is called, a brand-new WeakCachePool is allocated
and then activated.
If you wish to enable the WeakCachePool
permently, you can enable the current
WeakCachePool
instead of allocating and activating a new one:
>>> WeakCachePool.grab().enabled = True
When you allocate a new WeakCachePool
, the previous one will not be used while the new one
is activated.
This means, any objects you fetch will not use the previous cache.
You are guaranteed to get brand-new fresh objects while the new WeakCachePool
is active
vs what has been previously fetched (ie: parent should not be consulted).
Expand source code
"""
## Introduction
Used to weakly store model objects in a Dependency.
Dependency can be activated/enabled temporarily or permanently depending on desired behavior for
app.
The weak-cache is nice, because there are situations where various object will reference
the same object. Take for instance order and order-lines. The order-lines would have a
one-to-one relationship back to the order object, and there is no need to lookup the same
order object over and over again if you ask each order-line for it's order-object.
This is where the weak-cache can shine. The ORM can store temporary references to objects
by 'id' and check this cache to retrieve them later instead of having to do an actual
fetch-request.
Another place this can be useful is when query objects that are in a tree.
And objects parent could be referenced by several children.
## Quick Start
To use, you can either simply allocate a `WeakCachePool` resource and activate it
via `@` function dectorator or `with` context-manager syntax:
>>> from xmodel.weak_cache_pool import WeakCachePool
>>> @WeakCachePool(enabled=True)
>>> def lambda_event_handler(event, context):
... pass
While this WeakCachePool is enabled, it will store weakly-cached objects in it's self.
When the object is deactivated and thrown away after the `lambda_event_handler` is finished
the weak-cache is deallocated.
Next time the function `lambda_event_handler` is called, a brand-new WeakCachePool is allocated
and then activated.
If you wish to enable the `WeakCachePool` permently, you can enable the current
`WeakCachePool` instead of allocating and activating a new one:
>>> WeakCachePool.grab().enabled = True
When you allocate a new `WeakCachePool`, the previous one will not be used while the new one
is activated. This means, any objects you fetch will not use the previous cache.
You are guaranteed to get brand-new fresh objects while the new `WeakCachePool` is active
vs what has been previously fetched (ie: parent should not be consulted).
"""
from xinject import Dependency
from typing import Type, Dict
import weakref
# Only reason we are using ThreadUsafeDependency is to be ultra-safe,
# for now don't share WeakCachePool cross-thread, associate pool with only one thread of now.
# We may relax this later. See class doc-comment below for more details.
class WeakCachePool(Dependency):
"""
In general, used to enable the weak-cache for the ORM in general.
By default, the weak cache is not enabled.
It's an explicitly opt-in feature, because there may be reference-cycles for the Model
object and so it may not be immediately deallocated.
.. important:: For now, we are making this a ThreadUsafeDependency;
we might relax this later... being ultra-safe.
If some other thread is doing stuff with orm, weakly-cached objects are
cached per-thread for now.
It mostly likely is ok to share weak-cache across thread,
for now I thought it prudent to not dive into that just yet.
## Python Memory Management Details
Python has a referenced-counting system that can deallocate most objects immediately.
However, reference-cycles can't be detected via the referenced-counting system.
Objects in this situation are collected by the garbage collector process, that goes though
and detects reference-cycles that are not longer reachable by a strong-reference.
"""
# Instead of inheriting from `ThreadUnsafeDependency`, we set flag directly ourselves.
# This allows us to be compatible with both v2 and v3 of xinject.
resource_thread_safe = False
# If/when we get copied, we play it safe and don't copy `_obj_weak_cache` for now.
# I might consider doing a shallow-copy of the weak cache (even if a deep-copy is requested)
# at some point in the future. For now, keeping it conservative.
attributes_to_skip_while_copying = {'_obj_weak_cache'}
@property
def enabled(self) -> bool:
""" Enables the weak-cacher so when it's asked to weakly cache an object it will
actually do it.
If you enable us and we were previously disabled, the weak-cache will be cleared;
and we also clear cache if we were previously enabled and get disabled.
"""
return self._enabled
@enabled.setter
def enabled(self, value: bool):
if self._enabled == value:
return
self._enabled = value
self.clear_caches()
_enabled = None
_obj_weak_cache: Dict[Type, weakref.WeakValueDictionary] = None
def __init__(self, enable=False):
self.enabled = bool(enable)
self.clear_caches()
def clear_caches(self):
self._obj_weak_cache = dict()
def set(self, key: str, value):
"""
Just like `xmodel.base.client.BaseClient.cache_set`,
except it will weakly keep the value inside us as a Dependency subclass.
Normally called from `xmodel.base.client.BaseClient.cache_weak_set`,
we implement most of that methods functionality here.
We also don't cache any values if `ModelCacher.enable_weak_cache` is `False`
(the default).
This means, while/if a `ModelCacher` `xinject.context.Dependency` is activated and
enabled, when `xmodel.base.client.BaseClient` uses us to weakly cache something it will
call us and we will weakly set them into into the cache.
When we weakly cache something, we use the value's type + the key to identify it.
You'll need the value's type + key to later retrieve the weakly cached value.
See `xmodel.base.client.BaseClient.cache_weak_get` for more details.
"""
if not self.enabled:
return
# Check to see if we have weak-dict for the value-type...
value_type = type(value)
if value_type not in self._obj_weak_cache:
self._obj_weak_cache[value_type] = weakref.WeakValueDictionary()
self._obj_weak_cache[value_type][key] = value
def get(self, value_type: Type, key: str, default=None):
"""
See `xmodel.base.client.BaseClient.cache_get` documentation for more details.
This gets something out of the weak cache in self.
Normally called from `xmodel.base.client.BaseClient.cache_get`,
for weakly cached objects, we implement most of that methods functionality here.
If key does not exist in cache, then return default [which defaults to None].
The purpose of this is to provide a way to cache something a
`xmodel.base.model.BaseModel` so that when
all other references to it are gone it will automatically be removed out of the cache
(ie: when `xmodel.base.model.BaseModel` object is not used anymore).
A good way to get a key-by-id is via
`xmodel.base.structure.BaseStructure.id_cache_key`.
"""
if not self.enabled:
return None
type_weak_dict = self._obj_weak_cache.get(value_type, None)
if type_weak_dict is None:
return default
return type_weak_dict.get(key, default)
def remove(self, value_type: Type, key: str):
# No need to check if not enabled (optimization).
if not self.enabled:
return
type_weak_dict = self._obj_weak_cache.get(value_type, None)
if type_weak_dict is None:
return
type_weak_dict.pop(key, None)
# Whenever we are activated via a `with` or `@`,
# clear the caches for now just to keep things simple.
# Overriding __enter__ and __exit__ is the easiest way to do that.
def __enter__(self) -> 'WeakCachePool':
self.clear_caches()
return super().__enter__()
def __exit__(self, *args, **kwargs):
self.clear_caches()
return super().__exit__(*args, **kwargs)
def A():
with WeakCachePool() as b:
pass
Functions
def A()
-
Expand source code
def A(): with WeakCachePool() as b: pass
Classes
class WeakCachePool (enable=False)
-
In general, used to enable the weak-cache for the ORM in general.
By default, the weak cache is not enabled.
It's an explicitly opt-in feature, because there may be reference-cycles for the Model object and so it may not be immediately deallocated.
Important: For now, we are making this a ThreadUsafeDependency;
we might relax this later… being ultra-safe. If some other thread is doing stuff with orm, weakly-cached objects are cached per-thread for now. It mostly likely is ok to share weak-cache across thread, for now I thought it prudent to not dive into that just yet.
Python Memory Management Details
Python has a referenced-counting system that can deallocate most objects immediately. However, reference-cycles can't be detected via the referenced-counting system. Objects in this situation are collected by the garbage collector process, that goes though and detects reference-cycles that are not longer reachable by a strong-reference.
Expand source code
class WeakCachePool(Dependency): """ In general, used to enable the weak-cache for the ORM in general. By default, the weak cache is not enabled. It's an explicitly opt-in feature, because there may be reference-cycles for the Model object and so it may not be immediately deallocated. .. important:: For now, we are making this a ThreadUsafeDependency; we might relax this later... being ultra-safe. If some other thread is doing stuff with orm, weakly-cached objects are cached per-thread for now. It mostly likely is ok to share weak-cache across thread, for now I thought it prudent to not dive into that just yet. ## Python Memory Management Details Python has a referenced-counting system that can deallocate most objects immediately. However, reference-cycles can't be detected via the referenced-counting system. Objects in this situation are collected by the garbage collector process, that goes though and detects reference-cycles that are not longer reachable by a strong-reference. """ # Instead of inheriting from `ThreadUnsafeDependency`, we set flag directly ourselves. # This allows us to be compatible with both v2 and v3 of xinject. resource_thread_safe = False # If/when we get copied, we play it safe and don't copy `_obj_weak_cache` for now. # I might consider doing a shallow-copy of the weak cache (even if a deep-copy is requested) # at some point in the future. For now, keeping it conservative. attributes_to_skip_while_copying = {'_obj_weak_cache'} @property def enabled(self) -> bool: """ Enables the weak-cacher so when it's asked to weakly cache an object it will actually do it. If you enable us and we were previously disabled, the weak-cache will be cleared; and we also clear cache if we were previously enabled and get disabled. """ return self._enabled @enabled.setter def enabled(self, value: bool): if self._enabled == value: return self._enabled = value self.clear_caches() _enabled = None _obj_weak_cache: Dict[Type, weakref.WeakValueDictionary] = None def __init__(self, enable=False): self.enabled = bool(enable) self.clear_caches() def clear_caches(self): self._obj_weak_cache = dict() def set(self, key: str, value): """ Just like `xmodel.base.client.BaseClient.cache_set`, except it will weakly keep the value inside us as a Dependency subclass. Normally called from `xmodel.base.client.BaseClient.cache_weak_set`, we implement most of that methods functionality here. We also don't cache any values if `ModelCacher.enable_weak_cache` is `False` (the default). This means, while/if a `ModelCacher` `xinject.context.Dependency` is activated and enabled, when `xmodel.base.client.BaseClient` uses us to weakly cache something it will call us and we will weakly set them into into the cache. When we weakly cache something, we use the value's type + the key to identify it. You'll need the value's type + key to later retrieve the weakly cached value. See `xmodel.base.client.BaseClient.cache_weak_get` for more details. """ if not self.enabled: return # Check to see if we have weak-dict for the value-type... value_type = type(value) if value_type not in self._obj_weak_cache: self._obj_weak_cache[value_type] = weakref.WeakValueDictionary() self._obj_weak_cache[value_type][key] = value def get(self, value_type: Type, key: str, default=None): """ See `xmodel.base.client.BaseClient.cache_get` documentation for more details. This gets something out of the weak cache in self. Normally called from `xmodel.base.client.BaseClient.cache_get`, for weakly cached objects, we implement most of that methods functionality here. If key does not exist in cache, then return default [which defaults to None]. The purpose of this is to provide a way to cache something a `xmodel.base.model.BaseModel` so that when all other references to it are gone it will automatically be removed out of the cache (ie: when `xmodel.base.model.BaseModel` object is not used anymore). A good way to get a key-by-id is via `xmodel.base.structure.BaseStructure.id_cache_key`. """ if not self.enabled: return None type_weak_dict = self._obj_weak_cache.get(value_type, None) if type_weak_dict is None: return default return type_weak_dict.get(key, default) def remove(self, value_type: Type, key: str): # No need to check if not enabled (optimization). if not self.enabled: return type_weak_dict = self._obj_weak_cache.get(value_type, None) if type_weak_dict is None: return type_weak_dict.pop(key, None) # Whenever we are activated via a `with` or `@`, # clear the caches for now just to keep things simple. # Overriding __enter__ and __exit__ is the easiest way to do that. def __enter__(self) -> 'WeakCachePool': self.clear_caches() return super().__enter__() def __exit__(self, *args, **kwargs): self.clear_caches() return super().__exit__(*args, **kwargs)
Ancestors
Class variables
var attributes_to_skip_while_copying
var resource_thread_safe
Static methods
def __init_subclass__(thread_sharable=Default, attributes_to_skip_while_copying: Optional[Iterable[str]] = Default, **kwargs)
-
Inherited from:
Dependency
.__init_subclass__
Args
thread_sharable
- If
False
: While a dependency is lazily auto-created, we will ensure we do it per-thread, and not make it visible …
def grab() ‑> ~T
-
Inherited from:
Dependency
.grab
Gets a potentially shared dependency from the current
udpend.context.XContext
… def proxy() ‑> ~R
-
Inherited from:
Dependency
.proxy
Returns a proxy-object, that when and attribute is asked for, it will proxy it to the current object of
cls
… def proxy_attribute(attribute_name: str) ‑> Any
-
Inherited from:
Dependency
.proxy_attribute
Returns a proxy-object, that when and attribute is asked for, it will proxy it to the current attribute value on the current object of
cls
…
Instance variables
var enabled : bool
-
Enables the weak-cacher so when it's asked to weakly cache an object it will actually do it.
If you enable us and we were previously disabled, the weak-cache will be cleared; and we also clear cache if we were previously enabled and get disabled.
Expand source code
@property def enabled(self) -> bool: """ Enables the weak-cacher so when it's asked to weakly cache an object it will actually do it. If you enable us and we were previously disabled, the weak-cache will be cleared; and we also clear cache if we were previously enabled and get disabled. """ return self._enabled
var obj : Dependency
-
Inherited from:
Dependency
.obj
class property/attribute that will return the current dependency for the subclass it's asked on by calling
Dependency.grab
, passing no extra …
Methods
def __call__(self, func)
-
Inherited from:
Dependency
.__call__
This makes Resource subclasses have an ability to be used as function decorators by default unless this method is overriden to provide some other …
def __copy__(self)
-
Inherited from:
Dependency
.__copy__
Basic shallow copy protection (I am wondering if I should just remove this default copy code) …
def clear_caches(self)
-
Expand source code
def clear_caches(self): self._obj_weak_cache = dict()
def get(self, value_type: Type, key: str, default=None)
-
See
xmodel.base.client.BaseClient.cache_get
documentation for more details. This gets something out of the weak cache in self.Normally called from
xmodel.base.client.BaseClient.cache_get
, for weakly cached objects, we implement most of that methods functionality here.If key does not exist in cache, then return default [which defaults to None].
The purpose of this is to provide a way to cache something a
BaseModel
so that when all other references to it are gone it will automatically be removed out of the cache (ie: whenBaseModel
object is not used anymore).A good way to get a key-by-id is via
BaseStructure.id_cache_key()
.Expand source code
def get(self, value_type: Type, key: str, default=None): """ See `xmodel.base.client.BaseClient.cache_get` documentation for more details. This gets something out of the weak cache in self. Normally called from `xmodel.base.client.BaseClient.cache_get`, for weakly cached objects, we implement most of that methods functionality here. If key does not exist in cache, then return default [which defaults to None]. The purpose of this is to provide a way to cache something a `xmodel.base.model.BaseModel` so that when all other references to it are gone it will automatically be removed out of the cache (ie: when `xmodel.base.model.BaseModel` object is not used anymore). A good way to get a key-by-id is via `xmodel.base.structure.BaseStructure.id_cache_key`. """ if not self.enabled: return None type_weak_dict = self._obj_weak_cache.get(value_type, None) if type_weak_dict is None: return default return type_weak_dict.get(key, default)
def remove(self, value_type: Type, key: str)
-
Expand source code
def remove(self, value_type: Type, key: str): # No need to check if not enabled (optimization). if not self.enabled: return type_weak_dict = self._obj_weak_cache.get(value_type, None) if type_weak_dict is None: return type_weak_dict.pop(key, None)
def set(self, key: str, value)
-
Just like
xmodel.base.client.BaseClient.cache_set
, except it will weakly keep the value inside us as a Dependency subclass.Normally called from
xmodel.base.client.BaseClient.cache_weak_set
, we implement most of that methods functionality here.We also don't cache any values if
ModelCacher.enable_weak_cache
isFalse
(the default).This means, while/if a
ModelCacher
xinject.context.Dependency
is activated and enabled, whenxmodel.base.client.BaseClient
uses us to weakly cache something it will call us and we will weakly set them into into the cache.When we weakly cache something, we use the value's type + the key to identify it. You'll need the value's type + key to later retrieve the weakly cached value.
See
xmodel.base.client.BaseClient.cache_weak_get
for more details.Expand source code
def set(self, key: str, value): """ Just like `xmodel.base.client.BaseClient.cache_set`, except it will weakly keep the value inside us as a Dependency subclass. Normally called from `xmodel.base.client.BaseClient.cache_weak_set`, we implement most of that methods functionality here. We also don't cache any values if `ModelCacher.enable_weak_cache` is `False` (the default). This means, while/if a `ModelCacher` `xinject.context.Dependency` is activated and enabled, when `xmodel.base.client.BaseClient` uses us to weakly cache something it will call us and we will weakly set them into into the cache. When we weakly cache something, we use the value's type + the key to identify it. You'll need the value's type + key to later retrieve the weakly cached value. See `xmodel.base.client.BaseClient.cache_weak_get` for more details. """ if not self.enabled: return # Check to see if we have weak-dict for the value-type... value_type = type(value) if value_type not in self._obj_weak_cache: self._obj_weak_cache[value_type] = weakref.WeakValueDictionary() self._obj_weak_cache[value_type][key] = value