Skip to content

Dict traversal and CachedProperty updates. #483

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 16 additions & 42 deletions addons/source-python/packages/source-python/core/cache.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
# ../core/cache.py

"""Provides caching functionality.

.. data:: cached_property
An alias of :class:`core.cache.CachedProperty`.
"""

"""Provides caching functionality."""

# =============================================================================
# >> IMPORTS
# =============================================================================
# Python Imports
# FuncTools
from functools import wraps
# Inspect
from inspect import isdatadescriptor
# Types
from types import MethodType

Expand All @@ -23,55 +20,32 @@
# Source.Python Imports
# Core
from _core._cache import CachedProperty
from _core._cache import cached_property


# =============================================================================
# >> ALL DECLARATION
# =============================================================================
__all__ = [
'CachedProperty',
'cached_property'
'cached_property',
'cached_result'
]


# =============================================================================
# >> FUNCTIONS
# =============================================================================
def cached_result(fget):
"""Decorator used to register a cached method.

:param function fget:
Method that is only called once and its result cached for subsequent
calls.
:rtype: CachedProperty
"""
# Get a dummy object as default cache, so that we can cache None
NONE = object()

def getter(self):
"""Getter function that generates the cached method."""
# Set our cache to the default value
cache = NONE

# Wrap the decorated method as the inner function
@wraps(fget)
def wrapper(self, *args, **kwargs):
"""Calls the decorated method and cache the result."""
nonlocal cache

# Did we cache a result already?
if cache is NONE:

# No cache, let's call the wrapped method and cache the result
cache = fget(self, *args, **kwargs)

# Return the cached result
return cache
def cached_property(fget=None, *args, **kwargs):
"""Decorator used to register or wrap a cached property."""
return (CachedProperty
if not isdatadescriptor(fget)
else CachedProperty.wrap_descriptor
)(fget, *args, **kwargs)

# Bind the wrapper function to the passed instance and return it
return MethodType(wrapper, self)

# Return a cached property bound to the getter function
return CachedProperty(getter, doc=fget.__doc__, unbound=True)
def cached_result(fget, *args, **kwargs):
"""Decorator used to register a cached method."""
return CachedProperty(
lambda self: MethodType(fget, self),
*args, **kwargs
)
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def __setattr__(cls, attr, value):
# assigned attribute is a descriptor defined by a subclass.
del cls.attributes

@cached_property(unbound=True)
@cached_property
def attributes(cls):
"""Returns all the attributes available for this class.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
# =============================================================================
# >> IMPORTS
# =============================================================================
# Python Imports
# WeakRef
from weakref import proxy

# Source.Python Imports
# Core
from core.cache import CachedProperty
Expand Down Expand Up @@ -101,7 +97,7 @@ def __init__(self, wrapped_self, wrapper):
func = wrapped_self.__getattr__(wrapper.__name__)
super().__init__(func._manager, func._type_name, func, func._this)
self.wrapper = wrapper
self.wrapped_self = proxy(wrapped_self)
self.wrapped_self = wrapped_self

def __call__(self, *args, **kwargs):
return super().__call__(
Expand Down
166 changes: 67 additions & 99 deletions src/core/modules/core/core_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,13 @@
//-----------------------------------------------------------------------------
CCachedProperty::CCachedProperty(
object fget=object(), object fset=object(), object fdel=object(), object doc=object(),
bool unbound=false, boost::python::tuple args=boost::python::tuple(), object kwargs=object())
boost::python::tuple args=boost::python::tuple(), object kwargs=object())
{
set_getter(fget);
set_setter(fset);
set_deleter(fdel);

m_doc = doc;
m_bUnbound = unbound;

m_args = args;

Expand All @@ -65,52 +64,6 @@ object CCachedProperty::_callable_check(object function, const char *szName)
return function;
}

object CCachedProperty::_prepare_value(object value)
{
if (!PyGen_Check(value.ptr()))
return value;

if (getattr(value, "gi_frame").is_none())
BOOST_RAISE_EXCEPTION(
PyExc_ValueError,
"The given generator is exhausted."
);

list values;
while (true)
{
try
{
values.append(value.attr("__next__")());
}
catch(...)
{
if (!PyErr_ExceptionMatches(PyExc_StopIteration))
throw_error_already_set();

PyErr_Clear();
break;
}
}

return values;
}

void CCachedProperty::_invalidate_cache(PyObject *pRef)
{
try
{
m_cache[handle<>(pRef)].del();
}
catch (...)
{
if (!PyErr_ExceptionMatches(PyExc_KeyError))
throw_error_already_set();

PyErr_Clear();
}
}


object CCachedProperty::get_getter()
{
Expand Down Expand Up @@ -155,81 +108,88 @@ str CCachedProperty::get_name()

object CCachedProperty::get_owner()
{
return m_owner();
return m_owner;
}


object CCachedProperty::get_cached_value(object instance)
{
if (!m_name)
if (!m_name || m_owner.is_none())
BOOST_RAISE_EXCEPTION(
PyExc_AttributeError,
"Unable to retrieve the value of an unbound property."
);

object value;
PyObject *pValue = NULL;
PyObject **ppDict = _PyObject_GetDictPtr(instance.ptr());

if (m_bUnbound)
value = m_cache[
handle<>(
PyWeakref_NewRef(instance.ptr(), NULL)
)
];
else
{
dict cache = extract<dict>(instance.attr("__dict__"));
value = cache[m_name];

if (ppDict && *ppDict) {
pValue = PyDict_GetItem(*ppDict, m_name.ptr());
}

return value;
if (!pValue) {
const char *szName = extract<const char *>(m_name);
BOOST_RAISE_EXCEPTION(
PyExc_KeyError,
"No cached value found for '%s'.",
szName
)
}

return object(handle<>(borrowed(pValue)));
}

void CCachedProperty::set_cached_value(object instance, object value)
{
if (!m_name)
if (!m_name || m_owner.is_none())
BOOST_RAISE_EXCEPTION(
PyExc_AttributeError,
"Unable to assign the value of an unbound property."
);

if (m_bUnbound)
m_cache[handle<>(
PyWeakref_NewRef(
instance.ptr(),
make_function(
boost::bind(&CCachedProperty::_invalidate_cache, this, _1),
default_call_policies(),
boost::mpl::vector2<void, PyObject *>()
).ptr()
)
)] = _prepare_value(value);
else
{
dict cache = extract<dict>(instance.attr("__dict__"));
cache[m_name] = _prepare_value(value);
if (!PyObject_IsInstance(instance.ptr(), m_owner.ptr())) {
const char *szOwner = extract<const char *>(m_owner.attr("__qualname__"));
BOOST_RAISE_EXCEPTION(
PyExc_TypeError,
"Given instance is not of type '%s'.",
szOwner
)
}

if (PyGen_Check(value.ptr())) {
return;
}

PyObject *pDict = PyObject_GenericGetDict(instance.ptr(), NULL);

if (!pDict) {
const char *szOwner = extract<const char *>(m_owner.attr("__qualname__"));
BOOST_RAISE_EXCEPTION(
PyExc_AttributeError,
"'%s' object has no attribute '__dict__'",
szOwner
)
}

PyDict_SetItem(pDict, m_name.ptr(), value.ptr());
Py_XDECREF(pDict);
}

void CCachedProperty::delete_cached_value(object instance)
{
try
{
if (m_bUnbound)
m_cache[
handle<>(
PyWeakref_NewRef(instance.ptr(), NULL)
)
].del();
else
{
dict cache = extract<dict>(instance.attr("__dict__"));
cache[m_name].del();
}
PyObject **ppDict = _PyObject_GetDictPtr(instance.ptr());

if (!ppDict && !*ppDict) {
return;
}
catch (...)
{
if (!PyErr_ExceptionMatches(PyExc_KeyError))

PyDict_DelItem(*ppDict, m_name.ptr());

if (PyErr_Occurred()) {
if (!PyErr_ExceptionMatches(PyExc_KeyError)) {
throw_error_already_set();
}

PyErr_Clear();
}
Expand All @@ -239,6 +199,14 @@ void CCachedProperty::delete_cached_value(object instance)
object CCachedProperty::bind(object self, object owner, str name)
{
CCachedProperty &pSelf = extract<CCachedProperty &>(self);

if (owner.is_none() && !name) {
BOOST_RAISE_EXCEPTION(
PyExc_ValueError,
"Must provide a name and an owner."
)
}

owner.attr(name) = self;
pSelf.__set_name__(owner, name);
return self;
Expand All @@ -247,9 +215,9 @@ object CCachedProperty::bind(object self, object owner, str name)

void CCachedProperty::__set_name__(object owner, str name)
{
if (m_name && !m_owner.is_none())
if (m_name || !m_owner.is_none())
{
const char *szName = extract<const char *>(str(".").join(make_tuple(m_owner().attr("__qualname__"), m_name)));
const char *szName = extract<const char *>(str(".").join(make_tuple(m_owner.attr("__qualname__"), m_name)));
BOOST_RAISE_EXCEPTION(
PyExc_RuntimeError,
"This property was already bound as \"%s\".",
Expand All @@ -258,7 +226,7 @@ void CCachedProperty::__set_name__(object owner, str name)
}

m_name = name;
m_owner = object(handle<>(PyWeakref_NewRef(owner.ptr(), NULL)));
m_owner = owner;
}

object CCachedProperty::__get__(object self, object instance, object owner=object())
Expand Down Expand Up @@ -349,11 +317,11 @@ void CCachedProperty::__setitem__(str item, object value)

CCachedProperty *CCachedProperty::wrap_descriptor(
object descriptor, object owner, str name,
bool unbound, boost::python::tuple args, object kwargs)
boost::python::tuple args, object kwargs)
{
CCachedProperty *pProperty = new CCachedProperty(
descriptor.attr("__get__"), descriptor.attr("__set__"), descriptor.attr("__delete__"),
descriptor.attr("__doc__"), unbound, args, kwargs
descriptor.attr("__doc__"), args, kwargs
);

pProperty->__set_name__(owner, name);
Expand Down
Loading