mirror of
https://github.com/yweber/lodel2.git
synced 2026-04-01 16:47:16 +02:00
Now ACL and its factory use transwrap
This commit is contained in:
parent
d42a6eeedc
commit
20e7bbdcd1
7 changed files with 104 additions and 117 deletions
26
Lodel/python_factory.py
Normal file
26
Lodel/python_factory.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#-*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
|
||||
## @brief Abstract class designed to generate python code files
|
||||
# @note implemented by leapi.lefactory.LeFactory and acl.factory.AclFactory classes
|
||||
class PythonFactory(object):
|
||||
|
||||
## @brief Instanciate the generator
|
||||
# @param code_filename str : the output filename for python code
|
||||
def __init__(self, code_filename):
|
||||
if self.__class__ == PythonFactory:
|
||||
raise NotImplementedError("Abtract class")
|
||||
self._code_filename = code_filename
|
||||
self._dyn_file = os.path.basename(code_filename)
|
||||
self._modname = os.path.dirname(code_filename).strip('/').replace('/', '.') #Warning Windaube compatibility
|
||||
|
||||
## @brief Create dynamic code python file
|
||||
# @param *args : positional arguments forwarded to generate_python() method
|
||||
# @param **kwargs : named arguments forwarded to generate_python() method
|
||||
def create_pyfile(self, *args, **kwargs):
|
||||
with open(self._code_filename, "w+") as dynfp:
|
||||
dynfp.write(self.generate_python(*args, **kwargs))
|
||||
|
||||
def generate_python(self, *args, **kwargs):
|
||||
raise NotImplemented("Abtract method")
|
||||
122
acl/acl.py
122
acl/acl.py
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
import types
|
||||
import importlib
|
||||
from libs.transwrap.transwrap import DefaultCallCatcher
|
||||
|
||||
## @brief Example implementation of an ACL class
|
||||
class ACL(object):
|
||||
class ACL(DefaultCallCatcher):
|
||||
_singleton = None
|
||||
|
||||
## @brief dummy singleton contructor
|
||||
|
|
@ -18,92 +19,43 @@ class ACL(object):
|
|||
cls()
|
||||
return cls._singleton
|
||||
|
||||
## @brief Callback that checks a call to wrapped classes methods
|
||||
## @brief Method designed to be called when an access is done on a wrapped class
|
||||
# @note fetch the attribute
|
||||
# @param obj : the python object on wich the access is done
|
||||
# @param attr_name str : the name of the accessed attribute
|
||||
# @return the attribute
|
||||
# @throw AttributeError if the attr does not exist
|
||||
@classmethod
|
||||
def check_call(cls, call_object, method_name, args, kwargs):
|
||||
return ACL.instance()._check_call(call_object, method_name, args, kwargs)
|
||||
def attr_get(cls, obj, attr_name):
|
||||
return cls.instance()._attr_get(obj, attr_name)
|
||||
|
||||
## @brief Callback that checks calls to wrapped classes attributes
|
||||
## @brief Method designed to be called when a wrapped class method is called
|
||||
# @note Do the call
|
||||
# @param obj : the python object the method belongs to
|
||||
# @param method : the python bound method
|
||||
# @param args tuple : method call positional arguments
|
||||
# @param kwargs dict : method call named arguments
|
||||
# @return the call returned value
|
||||
@classmethod
|
||||
def check_attr(cls, call_object, attr_name):
|
||||
return ACL.instance()._check_attr(call_object, attr_name)
|
||||
def method_call(cls, obj, method, args, kwargs):
|
||||
return cls.instance()._method_call(obj, method, args, kwargs)
|
||||
|
||||
## @brief instance version of check_attr()
|
||||
# @note this method is the one that fetch and return the attribute
|
||||
def _check_attr(self, call_object, attr_name):
|
||||
print("Getting attribute '%s' on %s" % (attr_name, call_object))
|
||||
return getattr(call_object, attr_name)
|
||||
|
||||
## @brief instance version of check_call()
|
||||
# @note this method is the one that call the method and return the returned value
|
||||
# @param call_object : the object on wich the method is called (class if class methode else instance)
|
||||
# @param method_name str : method name
|
||||
# @param *args : positional method arguments
|
||||
# @param **kwargs : keyword method arguments
|
||||
def _check_call(self, call_object, method_name, args, kwargs):
|
||||
print("Calling '%s' on %s with %s %s" % (method_name, call_object, args, kwargs))
|
||||
#return call_object.__getattribute__(method_name)(*args, **kwargs)
|
||||
if method_name == '__init__':
|
||||
return call_object(*args, **kwargs)
|
||||
return getattr(call_object, method_name)(*args, **kwargs)
|
||||
|
||||
|
||||
## @brief A class designed as a wrapper for a method
|
||||
class AclMethodWrapper(object):
|
||||
|
||||
## @brief Constructor
|
||||
# @param wrapped_object PythonClass : A python class
|
||||
# @param method_name str : the wrapped method name
|
||||
def __init__(self, wrapped_object, method_name):
|
||||
self._wrapped_object = wrapped_object
|
||||
self._method_name = method_name
|
||||
|
||||
## @brief Triggered when the wrapped method is called
|
||||
def __call__(self, *args, **kwargs):
|
||||
return ACL.check_call(self._wrapped_object, self._method_name, args, kwargs)
|
||||
|
||||
## @brief Return a wrapped class
|
||||
#
|
||||
# @param module_name str : LeAPI dyn mopdule name
|
||||
# @param wrapped_class_name str : wrapped class name
|
||||
def get_wrapper(module_name, wrapped_class_name):
|
||||
|
||||
module = importlib.import_module(module_name)
|
||||
wrapped_class = getattr(module, wrapped_class_name)
|
||||
|
||||
## @brief Meta class that handles accesses to wrapped class/static attributes
|
||||
class MetaAclWrapper(type):
|
||||
## @brief Called when we get a class attribute
|
||||
def __getattribute__(cls, name):
|
||||
try:
|
||||
attr = getattr(wrapped_class, name)
|
||||
if isinstance(attr, types.MethodType):
|
||||
return AclMethodWrapper(wrapped_class, name)
|
||||
else:
|
||||
return ACL.check_attr(wrapped_class, name)
|
||||
except Exception as e:
|
||||
#Here we can process the exception or log it
|
||||
raise e
|
||||
|
||||
#class AclWrapper(metaclass=MetaAclWrapper):
|
||||
class AclWrapper(object, metaclass=MetaAclWrapper):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._wrapped_instance = ACL.check_call(wrapped_class, '__init__', args, kwargs)
|
||||
#self._wrapped_instance = wrapped_class(*args, **kwargs)
|
||||
|
||||
def __getattribute__(self, name):
|
||||
try:
|
||||
attr = getattr(
|
||||
super().__getattribute__('_wrapped_instance'),
|
||||
name
|
||||
)
|
||||
if isinstance(attr, types.MethodType):
|
||||
return AclMethodWrapper(super().__getattribute__('_wrapped_instance'), name)
|
||||
else:
|
||||
return ACL.check_attr(super().__getattribute__('_wrapped_instance'), name)
|
||||
except Exception as e:
|
||||
#do what you want
|
||||
raise e
|
||||
return AclWrapper
|
||||
## @brief instance implementation of attr_get()
|
||||
def _attr_get(self, obj, attr_name):
|
||||
if hasattr(obj, '__name__'):
|
||||
print("\tCatched ! %s.%s" % (obj.__name__, attr_name))
|
||||
else:
|
||||
print("\tCatched ! Get %s.%s" % (obj, attr_name))
|
||||
return super().attr_get(obj, attr_name)
|
||||
|
||||
## @brief instance implementation of method_call()
|
||||
def _method_call(self, obj, method, args, kwargs):
|
||||
if obj is method:
|
||||
print("\tCatched ! %s(%s %s)" % (obj.__name__, args, kwargs))
|
||||
else:
|
||||
if hasattr(obj, '__name__'):
|
||||
print("\tCatched ! %s.%s(%s %s)" % (obj.__name__, method.__name__, args,kwargs))
|
||||
else:
|
||||
print("\tCatched ! %s.%s(%s %s)" % (obj, method.__name__, args,kwargs))
|
||||
print("\t\tCallobject = %s method = %s with %s %s" % (obj, method, args, kwargs))
|
||||
return super().method_call(obj, method, args, kwargs)
|
||||
|
|
|
|||
|
|
@ -2,27 +2,32 @@
|
|||
|
||||
import os
|
||||
|
||||
import Lodel.python_factory
|
||||
import EditorialModel
|
||||
from leapi.lecrud import _LeCrud
|
||||
|
||||
class AclFactory(object):
|
||||
## @brief ACL wrapper for leapi dynamically generated classes generator
|
||||
#
|
||||
# This class handles dynamic ACL wrapper for dynamically generated leapi classes.
|
||||
#
|
||||
# The goal of those wrapped leapi classes is to reroute every calls that are made to
|
||||
# the API to the acl.acl.ACL module in order to validate them.
|
||||
class AclFactory(Lodel.python_factory.PythonFactory):
|
||||
|
||||
## @brief Instanciate the generator
|
||||
# @param code_filename str : the output filename for python code
|
||||
def __init__(self, code_filename = 'acl/dyn.py'):
|
||||
self._code_filename = code_filename
|
||||
self._dyn_file = os.path.basename(code_filename)
|
||||
self._modname = os.path.dirname(code_filename).strip('/').replace('/', '.') #Warning Windaube compatibility
|
||||
|
||||
## @brief Create dynamic code python file
|
||||
# @param modle Model : An editorial model
|
||||
# @param leap_dyn_module_name str : Name of leapi dynamic module name
|
||||
def create_pyfile(self, model, leapi_dyn_module_name):
|
||||
with open(self._code_filename, "w+") as dynfp:
|
||||
dynfp.write(self.generate_python(model, leapi_dyn_module_name))
|
||||
super().__init__(code_filename = code_filename)
|
||||
|
||||
## @brief Generate the python code
|
||||
# @param model Model : An editorial model
|
||||
# @param leap_dyn_module_name str : Name of leapi dynamically generated module name
|
||||
def generate_python(self, model, leapi_module_name):
|
||||
result = """## @author acl/factory
|
||||
|
||||
from acl.acl import get_wrapper
|
||||
from libs.transwrap.transwrap import get_wrapper
|
||||
import libs.transwrap
|
||||
from acl.acl import ACL
|
||||
"""
|
||||
|
||||
classes_to_wrap = ['LeCrud', 'LeObject', 'LeClass', 'LeType', 'LeRelation', 'LeHierarch', 'LeRel2Type']
|
||||
|
|
@ -33,7 +38,7 @@ from acl.acl import get_wrapper
|
|||
result += """
|
||||
## @brief Wrapped for {class_to_wrap} LeAPI class
|
||||
# @see {module_name}.{class_to_wrap}
|
||||
class {class_to_wrap}(get_wrapper('{module_name}', '{class_to_wrap}')):
|
||||
class {class_to_wrap}(get_wrapper('{module_name}', '{class_to_wrap}', ACL)):
|
||||
pass
|
||||
|
||||
""".format(
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ def dyn_code_filename(name):
|
|||
return 'dyncode/acl_api.py'
|
||||
|
||||
## @brief Refresh dynamic code
|
||||
#
|
||||
# Generate dynamic leapi code and ACL wrapper for dynamic leapi classes
|
||||
def refreshdyn():
|
||||
import sys
|
||||
import os, os.path
|
||||
|
|
@ -64,7 +66,9 @@ def db_init():
|
|||
em = Model(EmBackendJson(Settings.em_file))
|
||||
em.migrate_handler(mh)
|
||||
|
||||
|
||||
## @brief Generate a graphviz representation of instance's editorial model
|
||||
# @param output_file str : output filename
|
||||
# @param image_format str : takes value in png, jpg, svg (or any accepted graphviz output format)
|
||||
def em_graph(output_file = None, image_format = None):
|
||||
import loader
|
||||
from EditorialModel.model import Model
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import importlib
|
|||
import copy
|
||||
import os.path
|
||||
|
||||
import Lodel.python_factory
|
||||
import EditorialModel
|
||||
from EditorialModel import classtypes
|
||||
from EditorialModel.model import Model
|
||||
|
|
@ -15,16 +16,10 @@ from leapi.lecrud import _LeCrud
|
|||
# @note Contains only static methods
|
||||
#
|
||||
# The name is not good but i've no other ideas for the moment
|
||||
class LeFactory(object):
|
||||
class LeFactory(Lodel.python_factory.PythonFactory):
|
||||
|
||||
output_file = 'dyn.py'
|
||||
modname = None
|
||||
|
||||
|
||||
def __init__(self, code_filename = 'leapi/dyn.py'):
|
||||
self._code_filename = code_filename
|
||||
self._dyn_file = os.path.basename(code_filename)
|
||||
self._modname = os.path.dirname(code_filename).strip('/').replace('/', '.') #Warning Windaube compatibility
|
||||
def __init__(self, code_filename = 'acl/dyn.py'):
|
||||
super().__init__(code_filename = code_filename)
|
||||
|
||||
## @brief Return a call to a FieldType constructor given an EmField
|
||||
# @param emfield EmField : An EmField
|
||||
|
|
@ -36,12 +31,6 @@ class LeFactory(object):
|
|||
repr(emfield._fieldtype_args),
|
||||
)
|
||||
|
||||
## @brief Write generated code to a file
|
||||
# @todo better options/params for file creation
|
||||
def create_pyfile(self, model, datasource_cls, datasource_args):
|
||||
with open(self._code_filename, "w+") as dynfp:
|
||||
dynfp.write(self.generate_python(model, datasource_cls, datasource_args))
|
||||
|
||||
## @brief Generate fieldtypes for concret classes
|
||||
# @param ft_dict dict : key = fieldname value = fieldtype __init__ args
|
||||
# @return (uid_fieldtypes, fieldtypes) designed to be printed in generated code
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class TransWrapTestCase(unittest.TestCase):
|
|||
def setUp(self):
|
||||
DummyCatcher.attr_get = unittest.mock.Mock()
|
||||
DummyCatcher.method_call = unittest.mock.Mock()
|
||||
self.wrap = transwrap.get_wrap(DummyClass, DummyCatcher)
|
||||
self.wrap = transwrap.get_class_wrapper(DummyClass, DummyCatcher)
|
||||
DummyCatcher.method_call.reset_mock()
|
||||
DummyCatcher.attr_get.reset_mock()
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ class AnotherMockTestCase(unittest.TestCase):
|
|||
|
||||
def test_implicit_magic_instance(self):
|
||||
""" Testing implicit magic method call on instances """
|
||||
wrapper = transwrap.get_wrap(DummyClass, DummyCatcher)
|
||||
wrapper = transwrap.get_class_wrapper(DummyClass, DummyCatcher)
|
||||
dumm = wrapper()
|
||||
|
||||
# fetching dumm._instance
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#
|
||||
|
||||
import types
|
||||
import importlib
|
||||
from .magix import MAGIX
|
||||
|
||||
## @brief Default call catcher for wrappers
|
||||
|
|
@ -44,6 +45,16 @@ class DefaultCallCatcher(object):
|
|||
|
||||
return method(*args, **kwargs)
|
||||
|
||||
## @brief Generate a wrapper
|
||||
# @param module_name str : Module name of the class we want to wrap
|
||||
# @param class_name str : The name of the class we want to wrap
|
||||
# @param call_catcher_cls : A child class of DefaultCallCatcher designed to be called by the wrapper
|
||||
# @return A class that is a wrapper for module_name.class_name class
|
||||
def get_wrapper(module_name, class_name, call_catcher_cls = DefaultCallCatcher):
|
||||
module = importlib.import_module(module_name)
|
||||
towrap = getattr(module, class_name)
|
||||
return get_class_wrapper(towrap, call_catcher_cls)
|
||||
|
||||
## @brief Generate a wrapper
|
||||
#
|
||||
# Returns a wrapper for a given class. The wrapper will call the call_catcher
|
||||
|
|
@ -52,7 +63,7 @@ class DefaultCallCatcher(object):
|
|||
# @param towrap Class : Python class to wrap
|
||||
# @param call_catcher_cls : A child class of DefaultCallCatcher designed to be called by the wrapper
|
||||
# @return A class that is a wrapper for towrap
|
||||
def get_wrap(towrap, call_catcher_cls = DefaultCallCatcher):
|
||||
def get_class_wrapper(towrap, call_catcher_cls = DefaultCallCatcher):
|
||||
|
||||
## @brief Function wrapper
|
||||
#
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue