# Copyright Louis Paternault 2015-2023
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Share global data between evariste objects.
More information in :ref:`shared`.
.. autoclass:: Shared
:members:
"""
from enum import Enum
from .utils import DeepDict
class ViewPoint(Enum):
"""Point of view: from plugin or from tree?"""
# pylint: disable=too-few-public-methods, invalid-name
plugin = 1
tree = 2
ATTRIBUTES = {
"setup": [ViewPoint.plugin],
"plugin": [ViewPoint.plugin],
"tree": [ViewPoint.tree, ViewPoint.plugin],
}
class SharedDeepDict(DeepDict):
"""DeepDict class, with a factory to build them as shared attributes."""
@classmethod
def from_attr(cls, attr, dictionary=None):
"""Default value for attribute."""
return cls(len(ATTRIBUTES[attr]), dictionary)
class _SubSharedView:
"""View of a shared data.
:param str keyword: Keyword of the point of view (e.g. 'renderer.html').
:param str attr: Object being viewed (e.g. 'setup').
:param ViewPoint viewpoint: Point of view (tree, or plugin).
:param Shared shared: Shared data.
"""
# pylint: disable=too-few-public-methods
def __init__(self, keyword, attr, viewpoint, shared):
self.keyword = keyword
self.viewpoint = viewpoint
self.shared = shared
self.attr = attr
class _SubSharedViewNoDict(_SubSharedView):
"""View of a shared data, not as a dictionary."""
# pylint: disable=too-few-public-methods
def __getitem__(self, key):
return getattr(self.shared, self.attr)[key]
def __setitem__(self, key, value):
getattr(self.shared, self.attr)[key] = value
class _SubSharedViewDict(_SubSharedView):
"""View of a shared data, as a dictionary (of dictionaries of ...)"""
# pylint: disable=too-few-public-methods
def __getitem__(self, key):
shared = getattr(self.shared, self.attr)
for viewpoint in ATTRIBUTES[self.attr]:
if viewpoint == self.viewpoint:
shared = shared[self.keyword]
else:
shared = shared[key]
return shared
def __setitem__(self, key, value):
shared = getattr(self.shared, self.attr)
for viewpoint in ATTRIBUTES[self.attr][:-1]:
if viewpoint == self.viewpoint:
shared = shared[self.keyword]
else:
shared = shared[key]
if ATTRIBUTES[self.attr][-1] == self.viewpoint:
shared[self.keyword] = value
else:
shared[key] = value
class _MetaSharedView(type):
"""Metaclass that creates attributes corresponding to :data:`ATTRIBUTES`."""
def __new__(mcs, *args, **kwargs):
obj = super().__new__(mcs, *args, **kwargs)
for attr in ATTRIBUTES:
setattr(obj, attr, property(fget=obj.getter(attr), fset=obj.setter(attr)))
return obj
class _SharedView(metaclass=_MetaSharedView):
"""View of shared data, from a certain point of view."""
# pylint: disable=too-few-public-methods
def __init__(self, viewpoint, keyword, shared):
self.shared = shared
self.keyword = keyword
self.viewpoint = viewpoint
self.views = {}
for attr in ATTRIBUTES: # pylint: disable=consider-using-dict-items
if viewpoint in ATTRIBUTES[attr]:
if len(ATTRIBUTES[attr]) == 1:
self.views[attr] = _SubSharedViewNoDict(
keyword, attr, viewpoint, shared
)
else:
self.views[attr] = _SubSharedViewDict(
keyword, attr, viewpoint, shared
)
@classmethod
def getter(cls, attr):
"""Returns a getter for this attribute of this point of view."""
def getter(self):
"""Getter"""
if isinstance(self.views[attr], _SubSharedViewNoDict):
return self.views[attr][self.keyword]
return self.views[attr]
return getter
@classmethod
def setter(cls, attr):
"""Returns a setter for this attribute of this point of view."""
def setter(self, value):
"""Setter"""
if isinstance(self.views[attr], _SubSharedViewNoDict):
self.views[attr][self.keyword] = value
else:
# Should not be that difficult to implement, but I do not need
# it right now.
raise NotImplementedError
return setter
[docs]
class Shared:
"""Shared data"""
def __init__(self, builder, **kwargs):
self.builder = builder
for attr in ATTRIBUTES:
if attr in kwargs:
setattr(self, attr, kwargs[attr])
else:
setattr(self, attr, SharedDeepDict.from_attr(attr))
[docs]
def get_plugin_view(self, keyword: str) -> _SharedView:
"""Get this data, from the point of view of a plugin.
Let's define a ``shared`` object, and its "plugin view":
.. code-block:: python
shared = Shared(...)
view = self.get_plugin_view(foo)
Now, when both getting and setting data:
- ``view.plugin`` is equivalent to ``shared.plugin[foo]``;
- ``view.tree[bar]`` is equivalent to ``shared.tree[bar][foo]``;
- ``view.setup`` is equivalent to ``shared.setup[foo]``.
"""
return _SharedView(ViewPoint.plugin, keyword, self)
[docs]
def get_tree_view(self, path: str) -> _SharedView:
"""Get this data, from the point of view of a tree.
Let's define a ``shared`` object, and its "plugin view":
.. code-block:: python
shared = Shared(...)
view = self.get_tree_view(foo)
Now, when both getting and setting data:
- ``view.tree[bar]`` is equivalent to ``shared.tree[foo][bar]``.
"""
return _SharedView(ViewPoint.tree, path, self)