1
0
Fork 0
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:
Yann 2016-02-05 16:07:08 +01:00
commit 20e7bbdcd1
7 changed files with 104 additions and 117 deletions

26
Lodel/python_factory.py Normal file
View 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")

View file

@ -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)

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
#