mirror of
https://github.com/yweber/lodel2.git
synced 2026-01-13 02:02:15 +01:00
Merge branch 'master' of git@git.labocleo.org:lodel2
This commit is contained in:
commit
cb95014a9b
27 changed files with 993 additions and 822 deletions
|
|
@ -1,4 +1,9 @@
|
|||
#-*- coding: utf-8 -*-
|
||||
|
||||
## @package lodel.leapi.datahandlers.datas
|
||||
# This module contains specific datahandlers extending the basic ones from the lodel.leapi.datahandlers.datas_base module.
|
||||
|
||||
|
||||
import warnings
|
||||
import inspect
|
||||
import re
|
||||
|
|
@ -12,22 +17,29 @@ LodelContext.expose_modules(globals(), {
|
|||
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
|
||||
|
||||
|
||||
##@brief Data field designed to handle formated strings
|
||||
## @brief Data field designed to handle formated strings
|
||||
class FormatString(Varchar):
|
||||
|
||||
help = 'Automatic string field, designed to use the str % operator to \
|
||||
build its content'
|
||||
help = 'Automatic string field, designed to use the str % operator to build its content'
|
||||
base_type = 'char'
|
||||
|
||||
##@brief Build its content with a field list and a format string
|
||||
# @param format_string str
|
||||
# @param field_list list : List of field to use
|
||||
# @param **kwargs
|
||||
## @brief Constructor
|
||||
# @param _field_list list : List of fields to use
|
||||
# @param _format_string str : formatted string
|
||||
# @param **kwargs : additional options
|
||||
def __init__(self, format_string, field_list, **kwargs):
|
||||
self._field_list = field_list
|
||||
self._format_string = format_string
|
||||
super().__init__(internal='automatic', **kwargs)
|
||||
|
||||
## @brief constructs the formatted string data
|
||||
# The string can be truncated depending on the maximum length defined for this field.
|
||||
#
|
||||
# @param emcomponent EmComponent
|
||||
# @param fname str
|
||||
# @param datas dict
|
||||
# @param cur_value str
|
||||
# @return str
|
||||
def _construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
ret = self._format_string % tuple(
|
||||
datas[fname] for fname in self._field_list)
|
||||
|
|
@ -35,27 +47,28 @@ build its content'
|
|||
warnings.warn("Format field overflow. Truncating value")
|
||||
ret = ret[:self.max_length]
|
||||
return ret
|
||||
|
||||
##@brief Varchar validated by a regex
|
||||
|
||||
|
||||
## @brief Varchar validated by a regex
|
||||
class Regex(Varchar):
|
||||
|
||||
help = 'String field validated with a regex. Takes two options : \
|
||||
max_length and regex'
|
||||
base_type = 'char'
|
||||
|
||||
##@brief A string field validated by a regex
|
||||
# @param regex str : a regex string (passed as argument to re.compile())
|
||||
## @brief A string field validated by a regex
|
||||
# @param regex str : a regex string (passed as argument to re.compile()), default value is an empty string
|
||||
# @param max_length int : the max length for this field (default : 10)
|
||||
# @param **kwargs
|
||||
# @param **kwargs : additional options
|
||||
def __init__(self, regex='', max_length=10, **kwargs):
|
||||
self.regex = regex
|
||||
self.compiled_re = re.compile(regex) # trigger an error if invalid regex
|
||||
super(self.__class__, self).__init__(max_length=max_length, **kwargs)
|
||||
|
||||
##@brief Check and cast value in appropriate type
|
||||
#@param value *
|
||||
#@throw FieldValidationError if value is unappropriate or can not be cast
|
||||
#@return value
|
||||
## @brief Check and cast value in appropriate type
|
||||
# @param value *
|
||||
# @throw FieldValidationError if value is unappropriate or can not be cast
|
||||
# @return str
|
||||
def _check_data_value(self, value):
|
||||
value = super()._check_data_value(value)
|
||||
if not self.compiled_re.match(value) or len(value) > self.max_length:
|
||||
|
|
@ -63,6 +76,10 @@ max_length and regex'
|
|||
raise FieldValidationError(msg)
|
||||
return value
|
||||
|
||||
## @brief checks if another datahandler can override this one
|
||||
#
|
||||
# @param data_handler Datahandler
|
||||
# @return bool
|
||||
def can_override(self, data_handler):
|
||||
if not super().can_override(data_handler):
|
||||
return False
|
||||
|
|
@ -71,36 +88,58 @@ max_length and regex'
|
|||
return False
|
||||
return True
|
||||
|
||||
|
||||
##@brief Handles uniq ID
|
||||
class UniqID(Integer):
|
||||
|
||||
help = 'Fieldtype designed to handle editorial model UID'
|
||||
base_type = 'int'
|
||||
|
||||
##@brief A uid field
|
||||
# @param **kwargs
|
||||
## @brief A uid field
|
||||
#
|
||||
# @param **kwargs dict
|
||||
def __init__(self, **kwargs):
|
||||
kwargs['internal'] = 'automatic'
|
||||
super(self.__class__, self).__init__(primary_key = True, **kwargs)
|
||||
|
||||
## @brief Constructs the field's data
|
||||
# @param emcomponent EmComponent : Component corresponding to the field
|
||||
# @param fname
|
||||
# @param datas
|
||||
# @param cur_value str : current value to use (is retrieved from the datasource if not given)
|
||||
# @return str
|
||||
# @remarks fname and datas are not used and should become non mandatory, cur_value should have a None default value
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
if cur_value is None:
|
||||
#Ask datasource to provide a new uniqID
|
||||
return emcomponent._ro_datasource.new_numeric_id(emcomponent)
|
||||
return cur_value
|
||||
|
||||
|
||||
## @brief Class representing a LeObject subclass
|
||||
class LeobjectSubclassIdentifier(Varchar):
|
||||
|
||||
help = 'Datahandler designed to handle LeObject subclass identifier in DB'
|
||||
base_type = 'varchar'
|
||||
|
||||
## @brief Constructor
|
||||
# @param kwargs dict : additional options
|
||||
# @throw RuntimeError
|
||||
# @todo define the "internal" option that can be given in the kwargs, and document its meaning
|
||||
def __init__(self, **kwargs):
|
||||
if 'internal' in kwargs and not kwargs['internal']:
|
||||
raise RuntimeError(self.__class__.__name__+" datahandler can only \
|
||||
be internal")
|
||||
kwargs['internal'] = True
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
## @brief Returns the class' name
|
||||
# @param emcomponent EmComponent : Component correponding to the field
|
||||
# @param fname
|
||||
# @param datas
|
||||
# @param cur_value
|
||||
# @return str
|
||||
# @remarks fname, datas and cur_value should be given default values as they are not mandatory here.
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
cls = emcomponent
|
||||
if not inspect.isclass(emcomponent):
|
||||
|
|
@ -108,13 +147,13 @@ be internal")
|
|||
return cls.__name__
|
||||
|
||||
|
||||
##@brief Data field designed to handle concatenated fields
|
||||
## @brief Data field designed to handle concatenated fields
|
||||
class Concat(FormatString):
|
||||
help = 'Automatic strings concatenation'
|
||||
base_type = 'char'
|
||||
|
||||
##@brief Build its content with a field list and a separator
|
||||
# @param field_list list : List of field to use
|
||||
## @brief Build its content with a field list and a separator
|
||||
# @param field_list list : List of fields to concatenate
|
||||
# @param separator str
|
||||
# @param **kwargs
|
||||
def __init__(self, field_list, separator=' ', **kwargs):
|
||||
|
|
@ -124,22 +163,34 @@ class Concat(FormatString):
|
|||
**kwargs)
|
||||
|
||||
|
||||
## @brief Datahandler managing a password
|
||||
class Password(Varchar):
|
||||
help = 'Handle passwords'
|
||||
base_type = 'password'
|
||||
pass
|
||||
|
||||
|
||||
## @brief Datahandler turning a string into a list
|
||||
class VarcharList(Varchar):
|
||||
help = 'DataHandler designed to make a list out of a string.'
|
||||
base_type = 'varchar'
|
||||
|
||||
|
||||
## @brief Constructor
|
||||
# @param delimiter str : default value is a whitespace character
|
||||
# @param **kwargs : additional options
|
||||
# @throw LodelException : this exception is raised when the delimiter is not a string
|
||||
def __init__(self, delimiter=' ', **kwargs):
|
||||
if not isinstance(delimiter, str):
|
||||
raise LodelException("The delimiter has to be a string, %s given" % type(delimiter))
|
||||
self.delimiter = str(delimiter)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
## @brief Constructs the field's data
|
||||
# @param emcomponent EmComponent
|
||||
# @param fname
|
||||
# @param datas
|
||||
# @param cur_value : current value to use
|
||||
# @return list
|
||||
# @remarks emcomponent, fname and datas should be given a default value as they seem to be non mandatory
|
||||
def construct_data(self, emcomponent, fname, datas, cur_value):
|
||||
result = cur_value.split(self.delimiter)
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
## @brief Exception classes for datahandlers
|
||||
|
||||
class LodelDataHandlerException(Exception):
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@ LodelContext.expose_modules(globals(), {
|
|||
'LodelFatalError', 'DataNoneValid',
|
||||
'FieldValidationError']})
|
||||
|
||||
|
||||
## @brief Child class of SingleRef. The object referenced must exist
|
||||
class Link(SingleRef):
|
||||
pass
|
||||
|
||||
|
||||
## @brief Child class of MultipleRef where references are represented in the form of a python list
|
||||
# All the objects referenced must exist
|
||||
class List(MultipleRef):
|
||||
|
||||
## @brief instanciates a list reference
|
||||
|
|
@ -97,13 +98,16 @@ class Map(MultipleRef):
|
|||
|
||||
## @brief This Reference class is designed to handler hierarchy with some constraint
|
||||
class Hierarch(MultipleRef):
|
||||
|
||||
|
||||
directly_editable = False
|
||||
|
||||
## @brief Instanciate a data handler handling hierarchical relation with constraints
|
||||
# @param back_reference tuple : Here it is mandatory to have a back ref (like a parent field)
|
||||
# @param max_depth int | None : limit of depth
|
||||
# @param max_childs int | Nine : maximum number of childs by nodes
|
||||
# @param kwargs :
|
||||
# - allowed_classes list | None : list of allowed em classes if None no restriction
|
||||
# - internal bool : if False, the field is not internal
|
||||
def __init__(self, back_reference, max_depth=None, max_childs=None, **kwargs):
|
||||
super().__init__(back_reference=back_reference,
|
||||
max_depth=max_depth,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from lodel.context import LodelContext
|
|||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.exceptions': ['LodelExceptions', 'LodelException']})
|
||||
|
||||
##@brief Handles LeApi error
|
||||
class LeApiError(LodelException):
|
||||
pass
|
||||
|
||||
|
|
@ -13,20 +14,20 @@ class LeApiErrors(LodelExceptions, LeApiError):
|
|||
pass
|
||||
|
||||
|
||||
##@brief When an error concerns a datas
|
||||
##@brief When an error concerns a data
|
||||
class LeApiDataCheckError(LeApiError):
|
||||
pass
|
||||
|
||||
|
||||
##@brief Handles LeApi data errors
|
||||
class LeApiDataCheckErrors(LodelExceptions, LeApiError):
|
||||
pass
|
||||
|
||||
|
||||
##@brief Handles leapi query errors
|
||||
class LeApiQueryError(LeApiError):
|
||||
pass
|
||||
|
||||
|
||||
##@brief Handles mulitple query errors
|
||||
##@brief Handles multiple query errors
|
||||
class LeApiQueryErrors(LodelExceptions, LeApiQueryError):
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,25 @@
|
|||
#-*- coding: utf-8 -*-
|
||||
|
||||
import os, os.path
|
||||
import os
|
||||
import os.path
|
||||
import functools
|
||||
|
||||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.editorial_model.components': ['EmComponent', 'EmClass', 'EmField',
|
||||
'EmGroup'],
|
||||
'EmGroup'],
|
||||
'lodel.leapi.leobject': ['LeObject'],
|
||||
'lodel.leapi.datahandlers.base_classes': ['DataHandler'],
|
||||
'lodel.logger': 'logger'})
|
||||
|
||||
##@brief Generate python module code from a given model
|
||||
# @brief Generates python module code from a given model
|
||||
# @param model lodel.editorial_model.model.EditorialModel
|
||||
|
||||
|
||||
def dyncode_from_em(model):
|
||||
|
||||
|
||||
# Generation of LeObject child classes code
|
||||
cls_code, modules, bootstrap_instr = generate_classes(model)
|
||||
cls_code, bootstrap_instr = generate_classes(model)
|
||||
|
||||
# Header
|
||||
imports = """from lodel.context import LodelContext
|
||||
|
|
@ -25,10 +28,8 @@ LodelContext.expose_modules(globals(), {
|
|||
'lodel.leapi.datahandlers.base_classes': ['DataField'],
|
||||
'lodel.plugin.hooks': ['LodelHook']})
|
||||
"""
|
||||
for module in modules:
|
||||
imports += "import %s\n" % module
|
||||
|
||||
class_list = [ LeObject.name2objname(cls.uid) for cls in get_classes(model) ]
|
||||
# generates the list of all classes in the editorial model
|
||||
class_list = [LeObject.name2objname(cls.uid) for cls in get_classes(model)]
|
||||
|
||||
# formating all components of output
|
||||
res_code = """#-*- coding: utf-8 -*-
|
||||
|
|
@ -41,17 +42,21 @@ dynclasses = {class_list}
|
|||
dynclasses_dict = {class_dict}
|
||||
{common_code}
|
||||
""".format(
|
||||
imports = imports,
|
||||
classes = cls_code,
|
||||
bootstrap_instr = bootstrap_instr,
|
||||
class_list = '[' + (', '.join([cls for cls in class_list]))+']',
|
||||
class_dict = '{' + (', '.join([ "'%s': %s" % (cls, cls)
|
||||
for cls in class_list]))+'}',
|
||||
common_code = common_code(),
|
||||
imports=imports,
|
||||
classes=cls_code,
|
||||
bootstrap_instr=bootstrap_instr,
|
||||
class_list='[' + (', '.join([cls for cls in class_list])) + ']',
|
||||
class_dict='{' + (', '.join(["'%s': %s" % (cls, cls)
|
||||
for cls in class_list])) + '}',
|
||||
common_code=common_code(),
|
||||
)
|
||||
return res_code
|
||||
|
||||
##@brief Return the content of lodel.leapi.lefactory_common
|
||||
# @brief Returns the content of lodel.leapi.lefactory_common
|
||||
#
|
||||
# @return a string
|
||||
|
||||
|
||||
def common_code():
|
||||
res = ""
|
||||
fname = os.path.dirname(__file__)
|
||||
|
|
@ -61,23 +66,32 @@ def common_code():
|
|||
if not line.startswith('#-'):
|
||||
res += line
|
||||
return res
|
||||
|
||||
|
||||
##@brief return A list of EmClass sorted by dependencies
|
||||
|
||||
# @brief return A list of EmClass sorted by dependencies
|
||||
#
|
||||
# The first elts in the list depends on nothing, etc.
|
||||
# The first elts in the list depend on nothing, etc.
|
||||
# @param a list of Emclass instances to be sorted
|
||||
# @return a list of EmClass instances
|
||||
def emclass_sorted_by_deps(emclass_list):
|
||||
def emclass_deps_cmp(cls_a, cls_b):
|
||||
return len(cls_a.parents_recc) - len(cls_b.parents_recc)
|
||||
ret = sorted(emclass_list, key = functools.cmp_to_key(emclass_deps_cmp))
|
||||
ret = sorted(emclass_list, key=functools.cmp_to_key(emclass_deps_cmp))
|
||||
return ret
|
||||
|
||||
##@brief Returns a list of EmClass that will be represented as LeObject child classes
|
||||
def get_classes(model):
|
||||
return [ cls for cls in emclass_sorted_by_deps(model.classes()) if not cls.pure_abstract ]
|
||||
# @brief Returns a list of EmClass instances that will be represented as LeObject child classes
|
||||
# @param model : an EditorialModel instance
|
||||
# @return a list of EmClass instances
|
||||
|
||||
|
||||
def get_classes(model):
|
||||
return [cls for cls in emclass_sorted_by_deps(model.classes()) if not cls.pure_abstract]
|
||||
|
||||
# @brief Given an EmField returns the data_handler constructor suitable for dynamic code
|
||||
# @param a EmField instance
|
||||
# @return a string
|
||||
|
||||
|
||||
##@brief Given an EmField returns the data_handler constructor suitable for dynamic code
|
||||
def data_handler_constructor(emfield):
|
||||
#dh_module_name = DataHandler.module_name(emfield.data_handler_name)+'.DataHandler'
|
||||
get_handler_class_instr = 'DataField.from_name(%s)' % repr(emfield.data_handler_name)
|
||||
|
|
@ -85,60 +99,65 @@ def data_handler_constructor(emfield):
|
|||
for name, val in emfield.data_handler_options.items():
|
||||
if name == 'back_reference' and isinstance(val, tuple):
|
||||
options.append('{optname}: ({leo_name}, {fieldname})'.format(
|
||||
optname = repr(name),
|
||||
leo_name = LeObject.name2objname(val[0]),
|
||||
fieldname = repr(val[1]),))
|
||||
optname=repr(name),
|
||||
leo_name=LeObject.name2objname(val[0]),
|
||||
fieldname=repr(val[1]),))
|
||||
else:
|
||||
options.append(repr(name)+': '+forge_optval(val))
|
||||
options.append(repr(name) + ': ' + forge_optval(val))
|
||||
|
||||
return '{handler_instr}(**{{ {options} }})'.format(
|
||||
handler_instr = get_handler_class_instr,
|
||||
options = ', '.join(options))
|
||||
|
||||
##@brief Return a python repr of option values
|
||||
handler_instr=get_handler_class_instr,
|
||||
options=', '.join(options))
|
||||
|
||||
# @brief Return a python repr of option values
|
||||
# @param A value of any type which represents option
|
||||
# @return a string
|
||||
|
||||
|
||||
def forge_optval(optval):
|
||||
if isinstance(optval, dict):
|
||||
return '{' + (', '.join( [ '%s: %s' % (repr(name), forge_optval(val)) for name, val in optval.items()])) + '}'
|
||||
return '{' + (', '.join(['%s: %s' % (repr(name), forge_optval(val)) for name, val in optval.items()])) + '}'
|
||||
|
||||
if isinstance(optval, (set, list, tuple)):
|
||||
return '[' + (', '.join([forge_optval(val) for val in optval])) + ']'
|
||||
|
||||
|
||||
if isinstance(optval, EmField):
|
||||
return "{leobject}.data_handler({fieldname})".format(
|
||||
leobject = LeObject.name2objname(optval._emclass.uid),
|
||||
fieldname = repr(optval.uid)
|
||||
)
|
||||
elif isinstance(optval, EmClass):
|
||||
leobject=LeObject.name2objname(optval._emclass.uid),
|
||||
fieldname=repr(optval.uid)
|
||||
)
|
||||
if isinstance(optval, EmClass):
|
||||
return LeObject.name2objname(optval.uid)
|
||||
else:
|
||||
return repr(optval)
|
||||
|
||||
##@brief Generate dyncode from an EmClass
|
||||
# @param model EditorialModel :
|
||||
# @todo delete imports. It is never use, consequently changed return parameters.
|
||||
return repr(optval)
|
||||
|
||||
# @brief Generate dyncode from an EmClass
|
||||
# @param model EditorialModel :
|
||||
# @return a tuple with emclass python code, a set containing modules name to import, and a list of python instruction to bootstrap dynamic code, in this order
|
||||
|
||||
|
||||
def generate_classes(model):
|
||||
res = ""
|
||||
imports = list()
|
||||
|
||||
bootstrap = ""
|
||||
# Generating field list for LeObjects generated from EmClass
|
||||
for em_class in get_classes(model):
|
||||
logger.info("Generating a dynamic class for %s" % em_class.uid)
|
||||
uid = list() # List of fieldnames that are part of the EmClass primary key
|
||||
parents = list() # List of parents EmClass
|
||||
# Determine pk
|
||||
uid = list() # List for fieldnames that are part of the EmClass primary key
|
||||
parents = list() # List for em_class's parents
|
||||
# Determines primary key
|
||||
for field in em_class.fields():
|
||||
if field.data_handler_instance.is_primary_key():
|
||||
uid.append(field.uid)
|
||||
# Determine parent for inheritance
|
||||
# Determines parentsfor inheritance
|
||||
if len(em_class.parents) > 0:
|
||||
for parent in em_class.parents:
|
||||
parents.append(LeObject.name2objname(parent.uid))
|
||||
parents.append(LeObject.name2objname(parent.uid))
|
||||
else:
|
||||
parents.append('LeObject')
|
||||
datasource_name = em_class.datasource
|
||||
|
||||
# Dynamic code generation for LeObject childs classes
|
||||
|
||||
# Dynamic code generation for LeObject child classes
|
||||
em_cls_code = """
|
||||
class {clsname}({parents}):
|
||||
_abstract = {abstract}
|
||||
|
|
@ -150,12 +169,12 @@ class {clsname}({parents}):
|
|||
_child_classes = None
|
||||
|
||||
""".format(
|
||||
clsname = LeObject.name2objname(em_class.uid),
|
||||
parents = ', '.join(parents),
|
||||
abstract = 'True' if em_class.abstract else 'False',
|
||||
uid_list = repr(uid),
|
||||
datasource_name = repr(datasource_name),
|
||||
)
|
||||
clsname=LeObject.name2objname(em_class.uid),
|
||||
parents=', '.join(parents),
|
||||
abstract='True' if em_class.abstract else 'False',
|
||||
uid_list=repr(uid),
|
||||
datasource_name=repr(datasource_name),
|
||||
)
|
||||
res += em_cls_code
|
||||
# Dyncode fields bootstrap instructions
|
||||
child_classes = model.get_class_childs(em_class.uid)
|
||||
|
|
@ -163,14 +182,14 @@ class {clsname}({parents}):
|
|||
child_classes = 'tuple()'
|
||||
else:
|
||||
child_classes = '(%s,)' % (', '.join(
|
||||
[ LeObject.name2objname(emcls.uid) for emcls in child_classes]))
|
||||
[LeObject.name2objname(emcls.uid) for emcls in child_classes]))
|
||||
bootstrap += """{classname}._set__fields({fields})
|
||||
{classname}._child_classes = {child_classes}
|
||||
""".format(
|
||||
classname = LeObject.name2objname(em_class.uid),
|
||||
fields = '{' + (', '.join(['\n\t%s: %s' % (repr(emfield.uid),data_handler_constructor(emfield)) for emfield in em_class.fields()])) + '}',
|
||||
child_classes = child_classes,
|
||||
)
|
||||
classname=LeObject.name2objname(em_class.uid),
|
||||
fields='{' + (', '.join(['\n\t%s: %s' % (repr(emfield.uid),
|
||||
data_handler_constructor(emfield)) for emfield in em_class.fields()])) + '}',
|
||||
child_classes=child_classes,
|
||||
)
|
||||
bootstrap += "\n"
|
||||
return res, set(imports), bootstrap
|
||||
|
||||
return res, bootstrap
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
#- All lines that begins with #- will be deleted from dynamically generated
|
||||
#- code...
|
||||
|
||||
##@brief Return a dynamically generated class given it's name
|
||||
##@brief Returns a dynamically generated class given its name
|
||||
#@param name str : The dynamic class name
|
||||
#@return False or a child class of LeObject
|
||||
def name2class(name):
|
||||
|
|
@ -14,7 +14,7 @@ def name2class(name):
|
|||
return dynclasses_dict[name]
|
||||
|
||||
|
||||
##@brief Return a dynamically generated class given it's name
|
||||
##@brief Returns a dynamically generated class given its name
|
||||
#@note Case insensitive version of name2class
|
||||
#@param name str
|
||||
#@return False or a child class of LeObject
|
||||
|
|
@ -26,11 +26,10 @@ def lowername2class(name):
|
|||
return new_dict[name]
|
||||
|
||||
|
||||
##@brief Trigger dynclasses datasources initialisation
|
||||
##@brief Triggers dynclasses datasources initialisation
|
||||
@LodelHook("lodel2_plugins_loaded")
|
||||
def lodel2_dyncode_datasources_init(self, caller, payload):
|
||||
for cls in dynclasses:
|
||||
cls._init_datasources()
|
||||
LodelContext.expose_modules(globals(), {'lodel.plugin.hooks': ['LodelHook']})
|
||||
LodelHook.call_hook("lodel2_dyncode_loaded", __name__, dynclasses)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
#-*- coding: utf-8 -*-
|
||||
|
||||
## @package lodel.leapi.leobject
|
||||
# This module is centered around the basic LeObject class, which is the main class for all the objects managed by lodel.
|
||||
|
||||
|
||||
import importlib
|
||||
import warnings
|
||||
import copy
|
||||
|
|
@ -22,20 +26,16 @@ LodelContext.expose_modules(globals(), {
|
|||
'lodel.plugin': ['Plugin', 'DatasourcePlugin'],
|
||||
'lodel.leapi.datahandlers.base_classes': ['DatasConstructor', 'Reference']})
|
||||
|
||||
# @brief Stores the name of the field present in each LeObject that indicates
|
||||
# the name of LeObject subclass represented by this object
|
||||
## @brief Stores the name of the field present in each LeObject that indicates the name of LeObject subclass represented by this object
|
||||
CLASS_ID_FIELDNAME = "classname"
|
||||
|
||||
# @brief Wrapper class for LeObject getter & setter
|
||||
|
||||
## @brief Wrapper class for LeObject getter & setter
|
||||
#
|
||||
# This class intend to provide easy & friendly access to LeObject fields values
|
||||
# without name collision problems
|
||||
# This class intend to provide easy & friendly access to LeObject fields values without name collision problems
|
||||
# @note Wrapped methods are : LeObject.data() & LeObject.set_data()
|
||||
|
||||
|
||||
class LeObjectValues(object):
|
||||
|
||||
# @brief Construct a new LeObjectValues
|
||||
# @param fieldnames_callback method
|
||||
# @param set_callback method : The LeObject.set_datas() method of corresponding LeObject class
|
||||
# @param get_callback method : The LeObject.get_datas() method of corresponding LeObject class
|
||||
|
|
@ -43,47 +43,49 @@ class LeObjectValues(object):
|
|||
self._setter = set_callback
|
||||
self._getter = get_callback
|
||||
|
||||
# @brief Provide read access to datas values
|
||||
## @brief Provides read access to datas values
|
||||
# @note Read access should be provided for all fields
|
||||
# @param fname str : Field name
|
||||
# @return method
|
||||
def __getattribute__(self, fname):
|
||||
getter = super().__getattribute__('_getter')
|
||||
return getter(fname)
|
||||
|
||||
# @brief Provide write access to datas values
|
||||
## @brief Provides write access to datas values
|
||||
# @note Write acces shouldn't be provided for internal or immutable fields
|
||||
# @param fname str : Field name
|
||||
# @param fval * : the field value
|
||||
# @return method
|
||||
def __setattribute__(self, fname, fval):
|
||||
setter = super().__getattribute__('_setter')
|
||||
return setter(fname, fval)
|
||||
|
||||
|
||||
## @brief Represents a handled object in Lodel.
|
||||
class LeObject(object):
|
||||
|
||||
# @brief boolean that tells if an object is abtract or not
|
||||
## @brief boolean that tells if an object is abtract or not
|
||||
_abstract = None
|
||||
# @brief A dict that stores DataHandler instances indexed by field name
|
||||
## @brief A dict that stores DataHandler instances indexed by field name
|
||||
_fields = None
|
||||
# @brief A tuple of fieldname (or a uniq fieldname) representing uid
|
||||
## @brief A tuple of fieldname (or a uniq fieldname) representing uid
|
||||
_uid = None
|
||||
# @brief Read only datasource ( see @ref lodel2_datasources )
|
||||
## @brief Read only datasource ( see @ref lodel2_datasources )
|
||||
_ro_datasource = None
|
||||
# @brief Read & write datasource ( see @ref lodel2_datasources )
|
||||
## @brief Read & write datasource ( see @ref lodel2_datasources )
|
||||
_rw_datasource = None
|
||||
# @brief Store the list of child classes
|
||||
## @brief Store the list of child classes
|
||||
_child_classes = None
|
||||
# @brief Name of the datasource plugin
|
||||
## @brief Name of the datasource plugin
|
||||
_datasource_name = None
|
||||
|
||||
def __new__(cls, **kwargs):
|
||||
|
||||
self = object.__new__(cls)
|
||||
# @brief A dict that stores fieldvalues indexed by fieldname
|
||||
## @brief A dict that stores fieldvalues indexed by fieldname
|
||||
self.__datas = {fname: None for fname in self._fields}
|
||||
# @brief Store a list of initianilized fields when instanciation not complete else store True
|
||||
## @brief Store a list of initianilized fields when instanciation not complete else store True
|
||||
self.__initialized = list()
|
||||
# @brief Datas accessor. Instance of @ref LeObjectValues
|
||||
## @brief Datas accessor. Instance of @ref LeObjectValues
|
||||
self.d = LeObjectValues(self.fieldnames, self.set_data, self.data)
|
||||
for fieldname, fieldval in kwargs.items():
|
||||
self.__datas[fieldname] = fieldval
|
||||
|
|
@ -92,8 +94,10 @@ class LeObject(object):
|
|||
self.__set_initialized()
|
||||
return self
|
||||
|
||||
# @brief Construct an object representing an Editorial component
|
||||
# @note Can be considered as EmClass instance
|
||||
# @param **kwargs
|
||||
# @throw NotImplementedError when the class being instanciated is noted as abstract and then should not be instanciated.
|
||||
# @throw LeApiError in case of missing or invalid data.
|
||||
def __init__(self, **kwargs):
|
||||
if self._abstract:
|
||||
raise NotImplementedError(
|
||||
|
|
@ -130,19 +134,21 @@ class LeObject(object):
|
|||
# Fields datas handling methods #
|
||||
#-----------------------------------#
|
||||
|
||||
# @brief Property method True if LeObject is initialized else False
|
||||
## @brief Property method True if LeObject is initialized else False
|
||||
# @return bool
|
||||
@property
|
||||
def initialized(self):
|
||||
return self.__is_initialized
|
||||
|
||||
# @return The uid field name
|
||||
## @brief Returns the uid field name
|
||||
# @return str
|
||||
@classmethod
|
||||
def uid_fieldname(cls):
|
||||
return cls._uid
|
||||
|
||||
# @brief Return a list of fieldnames
|
||||
# @param include_ro bool : if True include read only field names
|
||||
# @return a list of str
|
||||
## @brief Returns a list of fieldnames
|
||||
# @param include_ro bool : if True includes the read only field names
|
||||
# @return list of string
|
||||
@classmethod
|
||||
def fieldnames(cls, include_ro=False):
|
||||
if not include_ro:
|
||||
|
|
@ -150,13 +156,17 @@ class LeObject(object):
|
|||
else:
|
||||
return list(cls._fields.keys())
|
||||
|
||||
## @brief Returns a name, capitalizing the first character of each word
|
||||
# @param name str
|
||||
# @return str
|
||||
@classmethod
|
||||
def name2objname(cls, name):
|
||||
return name.title()
|
||||
|
||||
# @brief Return the datahandler asssociated with a LeObject field
|
||||
# @param fieldname str : The fieldname
|
||||
## @brief Returns the datahandler asssociated with a LeObject field
|
||||
# @param fieldname str : The field's name
|
||||
# @return A data handler instance
|
||||
# @throw NameError when the given field name doesn't exist
|
||||
#@todo update class of exception raised
|
||||
@classmethod
|
||||
def data_handler(cls, fieldname):
|
||||
|
|
@ -164,9 +174,9 @@ class LeObject(object):
|
|||
raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__))
|
||||
return cls._fields[fieldname]
|
||||
|
||||
# @brief Getter for references datahandlers
|
||||
#@param with_backref bool : if true return only references with back_references
|
||||
#@return <code>{'fieldname': datahandler, ...}</code>
|
||||
## @brief Returns a dictionary containing the reference datahandlers
|
||||
# @param with_backref bool : if true return only references with back_references
|
||||
# @return dict : <code>{'fieldname': datahandler, ...}</code>
|
||||
@classmethod
|
||||
def reference_handlers(cls, with_backref=True):
|
||||
return {fname: fdh
|
||||
|
|
@ -174,11 +184,12 @@ class LeObject(object):
|
|||
if fdh.is_reference() and
|
||||
(not with_backref or fdh.back_reference is not None)}
|
||||
|
||||
# @brief Return a LeObject child class from a name
|
||||
## @brief Returns a LeObject child class from a name
|
||||
# @warning This method has to be called from dynamically generated LeObjects
|
||||
# @param leobject_name str : LeObject name
|
||||
# @return A LeObject child class
|
||||
# @throw NameError if invalid name given
|
||||
# @throw NotImplementedError if the method is abstract (if we use the LeObject class)
|
||||
# @throw LeApiError if an unexisting name is given
|
||||
@classmethod
|
||||
def name2class(cls, leobject_name):
|
||||
if cls.__module__ == 'lodel.leapi.leobject':
|
||||
|
|
@ -189,14 +200,16 @@ class LeObject(object):
|
|||
except (AttributeError, TypeError):
|
||||
raise LeApiError("No LeObject named '%s'" % leobject_name)
|
||||
|
||||
## @brief Checks if the class is abstract or not
|
||||
# @return bool
|
||||
@classmethod
|
||||
def is_abstract(cls):
|
||||
return cls._abstract
|
||||
|
||||
# @brief Field data handler getter
|
||||
#@param fieldname str : The field name
|
||||
#@return A datahandler instance
|
||||
#@throw NameError if the field doesn't exist
|
||||
## @brief Field data handler getter
|
||||
# @param fieldname str : The field name
|
||||
# @return A datahandler instance
|
||||
# @throw NameError if the field doesn't exist
|
||||
@classmethod
|
||||
def field(cls, fieldname):
|
||||
try:
|
||||
|
|
@ -204,8 +217,9 @@ class LeObject(object):
|
|||
except KeyError:
|
||||
raise NameError("No field named '%s' in %s" % (fieldname,
|
||||
cls.__name__))
|
||||
# @return A dict with fieldname as key and datahandler as instance
|
||||
|
||||
## @brief Returns the fields' datahandlers as a dictionary
|
||||
# @param include_ro bool : if True, includes the read-only fields (default value : False)
|
||||
# @return dict
|
||||
@classmethod
|
||||
def fields(cls, include_ro=False):
|
||||
if include_ro:
|
||||
|
|
@ -214,14 +228,12 @@ class LeObject(object):
|
|||
return {fname: cls._fields[fname] for fname in cls._fields\
|
||||
if not cls._fields[fname].is_internal()}
|
||||
|
||||
# @brief Return the list of parents classes
|
||||
## @brief Return the list of parents classes
|
||||
#
|
||||
#@note the first item of the list is the current class, the second is it's
|
||||
# parent etc...
|
||||
#@param cls
|
||||
#@warning multiple inheritance broken by this method
|
||||
#@return a list of LeObject child classes
|
||||
#@todo multiple parent capabilities implementation
|
||||
# @note the first item of the list is the current class, the second is its parent etc...
|
||||
# @warning multiple inheritance broken by this method
|
||||
# @return a list of LeObject child classes
|
||||
# @todo multiple parent capabilities implementation
|
||||
@classmethod
|
||||
def hierarch(cls):
|
||||
res = [cls]
|
||||
|
|
@ -234,16 +246,15 @@ class LeObject(object):
|
|||
res.append(cur)
|
||||
return res
|
||||
|
||||
# @brief Return a tuple a child classes
|
||||
#@return a tuple of child classes
|
||||
## @brief Returns a tuple of child classes
|
||||
# @return tuple
|
||||
@classmethod
|
||||
def child_classes(cls):
|
||||
return copy.copy(cls._child_classes)
|
||||
|
||||
# @brief Return the parent class that is the "source" of uid
|
||||
## @brief Returns the parent class that defines the unique id
|
||||
#
|
||||
# The method goal is to return the parent class that defines UID.
|
||||
#@return a LeObject child class or false if no UID defined
|
||||
# @return a LeObject child class or false if no UID defined
|
||||
@classmethod
|
||||
def uid_source(cls):
|
||||
if cls._uid is None or len(cls._uid) == 0:
|
||||
|
|
@ -259,11 +270,11 @@ class LeObject(object):
|
|||
prev = pcls
|
||||
return prev
|
||||
|
||||
# @brief Initialise both datasources (ro and rw)
|
||||
## @brief Initialise both datasources (ro and rw)
|
||||
#
|
||||
# This method is used once at dyncode load to replace the datasource string
|
||||
# by a datasource instance to avoid doing this operation for each query
|
||||
#@see LeObject::_init_datasource()
|
||||
# @see LeObject::_init_datasource()
|
||||
@classmethod
|
||||
def _init_datasources(cls):
|
||||
if isinstance(cls._datasource_name, str):
|
||||
|
|
@ -291,16 +302,16 @@ class LeObject(object):
|
|||
log_msg %= (ro_ds, cls.__name__)
|
||||
logger.debug(log_msg)
|
||||
|
||||
# @brief Return the uid of the current LeObject instance
|
||||
#@return the uid value
|
||||
#@warning Broke multiple uid capabilities
|
||||
## @brief Returns the uid of the current LeObject instance
|
||||
# @return str
|
||||
# @warning Broke multiple uid capabilities
|
||||
def uid(self):
|
||||
return self.data(self._uid[0])
|
||||
|
||||
# @brief Read only access to all datas
|
||||
## @brief Returns the value of a field
|
||||
# @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
|
||||
# @param field_name str : field name
|
||||
# @return the Value
|
||||
# @param field_name str : field's name
|
||||
# @return the value
|
||||
# @throw RuntimeError if the field is not initialized yet
|
||||
# @throw NameError if name is not an existing field name
|
||||
def data(self, field_name):
|
||||
|
|
@ -311,18 +322,19 @@ class LeObject(object):
|
|||
"The field %s is not initialized yet (and have no value)" % field_name)
|
||||
return self.__datas[field_name]
|
||||
|
||||
# @brief Read only access to all datas
|
||||
#@return a dict representing datas of current instance
|
||||
## @brief Returns a dictionary containing all the fields' values
|
||||
# @return dict
|
||||
def datas(self, internal=False):
|
||||
return {fname: self.data(fname) for fname in self.fieldnames(internal)}
|
||||
|
||||
# @brief Datas setter
|
||||
## @brief Datas setter
|
||||
# @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
|
||||
# @param fname str : field name
|
||||
# @param fname str : field's name
|
||||
# @param fval * : field value
|
||||
# @return the value that is really set
|
||||
# @throw NameError if fname is not valid
|
||||
# @throw AttributeError if the field is not writtable
|
||||
# @throw LeApiErrors if the data check generates an error
|
||||
def set_data(self, fname, fval):
|
||||
if fname not in self.fieldnames(include_ro=False):
|
||||
if fname not in self._fields.keys():
|
||||
|
|
@ -353,18 +365,18 @@ class LeObject(object):
|
|||
else:
|
||||
self.__datas[fname] = val
|
||||
|
||||
# @brief Update the __initialized attribute according to LeObject internal state
|
||||
## @brief Updates the __initialized attribute according to LeObject internal state
|
||||
#
|
||||
# Check the list of initialized fields and set __initialized to True if all fields initialized
|
||||
# Checks the list of initialized fields and sets __initialized at True if all fields initialized
|
||||
def __set_initialized(self):
|
||||
if isinstance(self.__initialized, list):
|
||||
expected_fields = self.fieldnames(include_ro=False) + self._uid
|
||||
if set(expected_fields) == set(self.__initialized):
|
||||
self.__is_initialized = True
|
||||
|
||||
# @brief Designed to be called when datas are modified
|
||||
## @brief Designed to be called when datas are modified
|
||||
#
|
||||
# Make different checks on the LeObject given it's state (fully initialized or not)
|
||||
# Makes different checks on the LeObject given it's state (fully initialized or not)
|
||||
# @return None if checks succeded else return an exception list
|
||||
def __check_modified_values(self):
|
||||
err_list = dict()
|
||||
|
|
@ -409,24 +421,24 @@ class LeObject(object):
|
|||
# Other methods #
|
||||
#--------------------#
|
||||
|
||||
# @brief Temporary method to set private fields attribute at dynamic code generation
|
||||
## @brief Temporary method to set private fields attribute at dynamic code generation
|
||||
#
|
||||
# This method is used in the generated dynamic code to set the _fields attribute
|
||||
# at the end of the dyncode parse
|
||||
# @warning This method is deleted once the dynamic code loaded
|
||||
# @param field_list list : list of EmField instance
|
||||
# @param cls
|
||||
# @param field_list list : list of EmField instance
|
||||
@classmethod
|
||||
def _set__fields(cls, field_list):
|
||||
cls._fields = field_list
|
||||
|
||||
# @brief Check that datas are valid for this type
|
||||
## @brief Checks if the data is valid for this type
|
||||
# @param datas dict : key == field name value are field values
|
||||
# @param complete bool : if True expect that datas provide values for all non internal fields
|
||||
# @param allow_internal bool : if True don't raise an error if a field is internal
|
||||
# @param complete bool : if True expects that values are provided for all non internal fields
|
||||
# @param allow_internal bool : if True does not raise an error if a field is internal
|
||||
# @param cls
|
||||
# @return Checked datas
|
||||
# @throw LeApiDataCheckError if errors reported during check
|
||||
# @throw LeApiDataCheckErrors if errors are reported during check
|
||||
@classmethod
|
||||
def check_datas_value(cls, datas, complete=False, allow_internal=True):
|
||||
err_l = dict() # Error storing
|
||||
|
|
@ -459,14 +471,13 @@ class LeObject(object):
|
|||
raise LeApiDataCheckErrors("Error while checking datas", err_l)
|
||||
return checked_datas
|
||||
|
||||
# @brief Check and prepare datas
|
||||
## @brief Checks and prepares all the data
|
||||
#
|
||||
# @warning when complete = False we are not able to make construct_datas() and _check_data_consistency()
|
||||
#
|
||||
# @param datas dict : {fieldname : fieldvalue, ...}
|
||||
# @param complete bool : If True you MUST give all the datas
|
||||
# @param allow_internal : Wether or not interal fields are expected in datas
|
||||
# @param cls
|
||||
# @param complete bool : If True you MUST give all the datas (default value : False)
|
||||
# @param allow_internal : Wether or not interal fields are expected in datas (default value : True)
|
||||
# @return Datas ready for use
|
||||
# @todo: complete is very unsafe, find a way to get rid of it
|
||||
@classmethod
|
||||
|
|
@ -482,9 +493,8 @@ construction and consitency when datas are not complete\n")
|
|||
cls._check_datas_consistency(ret_datas)
|
||||
return ret_datas
|
||||
|
||||
# @brief Construct datas values
|
||||
## @brief Constructs datas values
|
||||
#
|
||||
# @param cls
|
||||
# @param datas dict : Datas that have been returned by LeCrud.check_datas_value() methods
|
||||
# @return A new dict of datas
|
||||
# @todo IMPLEMENTATION
|
||||
|
|
@ -498,12 +508,12 @@ construction and consitency when datas are not complete\n")
|
|||
}
|
||||
return ret
|
||||
|
||||
# @brief Check datas consistency
|
||||
## @brief Checks datas consistency
|
||||
#
|
||||
# @warning assert that datas is complete
|
||||
# @param cls
|
||||
# @param datas dict : Datas that have been returned by LeCrud._construct_datas() method
|
||||
# @throw LeApiDataCheckError if fails
|
||||
# @throw LeApiDataCheckError in case of failure
|
||||
@classmethod
|
||||
def _check_datas_consistency(cls, datas):
|
||||
err_l = []
|
||||
|
|
@ -516,27 +526,28 @@ construction and consitency when datas are not complete\n")
|
|||
if len(err_l) > 0:
|
||||
raise LeApiDataCheckError("Datas consistency checks fails", err_l)
|
||||
|
||||
# @brief Check datas consistency
|
||||
## @brief Checks data consistency
|
||||
#
|
||||
# @warning assert that datas is complete
|
||||
# @param cls
|
||||
# @param datas dict : Datas that have been returned by LeCrud.prepare_datas() method
|
||||
# @param datas dict : Data that have been returned by prepare_datas() method
|
||||
# @param type_query str : Type of query to be performed , default value : insert
|
||||
@classmethod
|
||||
def make_consistency(cls, datas, type_query='insert'):
|
||||
for fname, dh in cls._fields.items():
|
||||
ret = dh.make_consistency(fname, datas, type_query)
|
||||
|
||||
# @brief Add a new instance of LeObject
|
||||
# @return a new uid en case of success, False otherwise
|
||||
## @brief Adds a new instance of LeObject
|
||||
# @param datas dict : LeObject's data
|
||||
# @return a new uid in case of success, False otherwise
|
||||
@classmethod
|
||||
def insert(cls, datas):
|
||||
query = LeInsertQuery(cls)
|
||||
return query.execute(datas)
|
||||
|
||||
# @brief Update an instance of LeObject
|
||||
## @brief Update an instance of LeObject
|
||||
#
|
||||
#@param datas : list of new datas
|
||||
# @param datas : list of new datas
|
||||
# @return LeObject
|
||||
def update(self, datas=None):
|
||||
datas = self.datas(internal=False) if datas is None else datas
|
||||
uids = self._uid
|
||||
|
|
@ -555,9 +566,9 @@ construction and consitency when datas are not complete\n")
|
|||
|
||||
return result
|
||||
|
||||
# @brief Delete an instance of LeObject
|
||||
## @brief Delete an instance of LeObject
|
||||
#
|
||||
#@return 1 if the objet has been deleted
|
||||
# @return 1 if the objet has been deleted
|
||||
def delete(self):
|
||||
uids = self._uid
|
||||
query_filter = list()
|
||||
|
|
@ -570,9 +581,9 @@ construction and consitency when datas are not complete\n")
|
|||
|
||||
return result
|
||||
|
||||
# @brief Delete instances of LeObject
|
||||
#@param query_filters list
|
||||
#@returns the number of deleted items
|
||||
## @brief Deletes instances of LeObject
|
||||
# @param query_filters list
|
||||
# @return the number of deleted items
|
||||
@classmethod
|
||||
def delete_bundle(cls, query_filters):
|
||||
deleted = 0
|
||||
|
|
@ -589,16 +600,16 @@ construction and consitency when datas are not complete\n")
|
|||
deleted += result
|
||||
return deleted
|
||||
|
||||
# @brief Get instances of LeObject
|
||||
## @brief Gets instances of LeObject
|
||||
#
|
||||
#@param query_filters dict : (filters, relational filters), with filters is a list of tuples : (FIELD, OPERATOR, VALUE) )
|
||||
#@param field_list list|None : list of string representing fields see
|
||||
#@ref leobject_filters
|
||||
#@param order list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
|
||||
#@param group list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
|
||||
#@param limit int : The maximum number of returned results
|
||||
#@param offset int : offset
|
||||
#@return a list of items (lists of (fieldname, fieldvalue))
|
||||
# @param query_filters dict : (filters, relational filters), with filters is a list of tuples : (FIELD, OPERATOR, VALUE) )
|
||||
# @param field_list list|None : list of string representing fields see
|
||||
# @ref leobject_filters
|
||||
# @param order list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
|
||||
# @param group list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
|
||||
# @param limit int : The maximum number of returned results
|
||||
# @param offset int : offset (default value : 0)
|
||||
# @return a list of items (lists of (fieldname, fieldvalue))
|
||||
@classmethod
|
||||
def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0):
|
||||
if field_list is not None:
|
||||
|
|
@ -628,7 +639,10 @@ construction and consitency when datas are not complete\n")
|
|||
|
||||
return objects
|
||||
|
||||
# @brief Retrieve an object given an UID
|
||||
## @brief Retrieves an object given an UID
|
||||
# @param uid str : Unique ID of the searched LeObject
|
||||
# @return LeObject
|
||||
# @throw LodelFatalError if the class does not have such a UID defined or if duplicates are found
|
||||
#@todo broken multiple UID
|
||||
@classmethod
|
||||
def get_from_uid(cls, uid):
|
||||
|
|
@ -645,7 +659,7 @@ construction and consitency when datas are not complete\n")
|
|||
while len(res_cp) > 0:
|
||||
cur_res = res_cp.pop()
|
||||
if cur_res.uid() in [r.uid() for r in res_cp]:
|
||||
logger.error("DOUBLON detected in query results !!!")
|
||||
logger.error("Duplicates detected in query results !!!")
|
||||
else:
|
||||
res.append(cur_res)
|
||||
if len(res) > 1:
|
||||
|
|
|
|||
|
|
@ -8,20 +8,22 @@ import warnings
|
|||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.leapi.exceptions': ['LeApiError', 'LeApiErrors',
|
||||
'LeApiDataCheckError', 'LeApiDataCheckErrors', 'LeApiQueryError',
|
||||
'LeApiQueryErrors'],
|
||||
'LeApiDataCheckError', 'LeApiDataCheckErrors', 'LeApiQueryError',
|
||||
'LeApiQueryErrors'],
|
||||
'lodel.plugin.hooks': ['LodelHook'],
|
||||
'lodel.logger': ['logger']})
|
||||
|
||||
##@todo check datas when running query
|
||||
# @todo check data when running query
|
||||
|
||||
|
||||
class LeQuery(object):
|
||||
|
||||
##@brief Hookname prefix
|
||||
# @brief Hookname prefix
|
||||
_hook_prefix = None
|
||||
##@brief arguments for the LeObject.check_data_value()
|
||||
# @brief arguments for the LeObject.check_data_value()
|
||||
_data_check_args = {'complete': False, 'allow_internal': False}
|
||||
|
||||
##@brief Abstract constructor
|
||||
# @brief Abstract constructor
|
||||
# @param target_class LeObject : class of object the query is about
|
||||
def __init__(self, target_class):
|
||||
from .leobject import LeObject
|
||||
|
|
@ -29,77 +31,80 @@ class LeQuery(object):
|
|||
raise NotImplementedError("Abstract class")
|
||||
if not inspect.isclass(target_class) or \
|
||||
not issubclass(target_class, LeObject):
|
||||
raise TypeError("target class has to be a child class of LeObject but %s given"% target_class)
|
||||
raise TypeError(
|
||||
"target class has to be a child class of LeObject but %s given" % target_class)
|
||||
self._target_class = target_class
|
||||
self._ro_datasource = target_class._ro_datasource
|
||||
self._rw_datasource = target_class._rw_datasource
|
||||
|
||||
##@brief Execute a query and return the result
|
||||
#@param **datas
|
||||
# @brief Executes a query and returns the result
|
||||
#@param **data
|
||||
#@return the query result
|
||||
#@see LeQuery._query()
|
||||
#@todo check that the check_datas_value is not duplicated/useless
|
||||
def execute(self, datas):
|
||||
if not datas is None:
|
||||
def execute(self, data):
|
||||
if data is not None:
|
||||
self._target_class.check_datas_value(
|
||||
datas,
|
||||
**self._data_check_args)
|
||||
self._target_class.prepare_datas(datas) #not yet implemented
|
||||
data,
|
||||
**self._data_check_args)
|
||||
self._target_class.prepare_datas(data) # not yet implemented
|
||||
if self._hook_prefix is None:
|
||||
raise NotImplementedError("Abstract method")
|
||||
LodelHook.call_hook(self._hook_prefix+'pre',
|
||||
self._target_class,
|
||||
datas)
|
||||
ret = self._query(datas=datas)
|
||||
ret = LodelHook.call_hook(self._hook_prefix+'post',
|
||||
self._target_class,
|
||||
ret)
|
||||
LodelHook.call_hook(self._hook_prefix + 'pre',
|
||||
self._target_class,
|
||||
data)
|
||||
ret = self._query(data=data)
|
||||
ret = LodelHook.call_hook(self._hook_prefix + 'post',
|
||||
self._target_class,
|
||||
ret)
|
||||
return ret
|
||||
|
||||
##@brief Childs classes implements this method to execute the query
|
||||
#@param **datas
|
||||
# @brief Child classes implement this method to execute the query
|
||||
#@param **data
|
||||
#@return query result
|
||||
def _query(self, **datas):
|
||||
def _query(self, **data):
|
||||
raise NotImplementedError("Asbtract method")
|
||||
|
||||
##@return a dict with query infos
|
||||
# @return a dict with query infos
|
||||
def dump_infos(self):
|
||||
return {'target_class': self._target_class}
|
||||
|
||||
def __repr__(self):
|
||||
ret = "<{classname} target={target_class}>"
|
||||
return ret.format(
|
||||
classname=self.__class__.__name__,
|
||||
target_class = self._target_class)
|
||||
classname=self.__class__.__name__,
|
||||
target_class=self._target_class)
|
||||
|
||||
# @brief Abstract class handling query with filters
|
||||
|
||||
|
||||
##@brief Abstract class handling query with filters
|
||||
class LeFilteredQuery(LeQuery):
|
||||
##@brief The available operators used in query definitions
|
||||
# @brief The available operators used in query definitions
|
||||
_query_operators = [
|
||||
' = ',
|
||||
' <= ',
|
||||
' >= ',
|
||||
' != ',
|
||||
' < ',
|
||||
' > ',
|
||||
' in ',
|
||||
' not in ',
|
||||
' like ',
|
||||
' not like ']
|
||||
' = ',
|
||||
' <= ',
|
||||
' >= ',
|
||||
' != ',
|
||||
' < ',
|
||||
' > ',
|
||||
' in ',
|
||||
' not in ',
|
||||
' like ',
|
||||
' not like ']
|
||||
|
||||
##@brief Regular expression to process filters
|
||||
# @brief Regular expression to process filters
|
||||
_query_re = None
|
||||
|
||||
##@brief Abtract constructor for queries with filter
|
||||
# @brief Abtract constructor for queries with filter
|
||||
#@param target_class LeObject : class of object the query is about
|
||||
#@param query_filters list : with a tuple (only one filter) or a list of
|
||||
# tuple or a dict: {OP,list(filters)} with OP = 'OR' or 'AND for tuple
|
||||
# (FIELD,OPERATOR,VALUE)
|
||||
def __init__(self, target_class, query_filters=None):
|
||||
super().__init__(target_class)
|
||||
##@brief The query filter tuple(std_filter, relational_filters)
|
||||
# @brief The query filter tuple(std_filter, relational_filters)
|
||||
self._query_filter = None
|
||||
##@brief Stores potential subqueries (used when a query implies
|
||||
# @brief Stores potential subqueries (used when a query implies
|
||||
# more than one datasource.
|
||||
#
|
||||
# Subqueries are tuple(target_class_ref_field, LeGetQuery)
|
||||
|
|
@ -107,11 +112,11 @@ class LeFilteredQuery(LeQuery):
|
|||
query_filters = [] if query_filters is None else query_filters
|
||||
self.set_query_filter(query_filters)
|
||||
|
||||
##@brief Abstract FilteredQuery execution method
|
||||
# @brief Abstract FilteredQuery execution method
|
||||
#
|
||||
# This method takes care to execute subqueries before calling super execute
|
||||
def execute(self, datas=None):
|
||||
#copy originals filters
|
||||
def execute(self, data=None):
|
||||
# copy originals filters
|
||||
orig_filters = copy.copy(self._query_filter)
|
||||
std_filters, rel_filters = self._query_filter
|
||||
|
||||
|
|
@ -123,17 +128,17 @@ class LeFilteredQuery(LeQuery):
|
|||
try:
|
||||
|
||||
filters, rel_filters = self._query_filter
|
||||
res = super().execute(datas)
|
||||
res = super().execute(data)
|
||||
except Exception as e:
|
||||
#restoring filters even if an exception is raised
|
||||
# restoring filters even if an exception is raised
|
||||
self.__query_filter = orig_filters
|
||||
|
||||
raise e #reraise
|
||||
#restoring filters
|
||||
raise e # reraise
|
||||
# restoring filters
|
||||
self._query_filter = orig_filters
|
||||
return res
|
||||
|
||||
##@brief Add filter(s) to the query
|
||||
# @brief Add filter(s) to the query
|
||||
#
|
||||
# This method is also able to slice query if different datasources are
|
||||
# implied in the request
|
||||
|
|
@ -144,39 +149,39 @@ class LeFilteredQuery(LeQuery):
|
|||
def set_query_filter(self, query_filter):
|
||||
if isinstance(query_filter, str):
|
||||
query_filter = [query_filter]
|
||||
#Query filter prepration
|
||||
# Query filter prepration
|
||||
filters_orig, rel_filters = self._prepare_filters(query_filter)
|
||||
# Here we now that each relational filter concern only one datasource
|
||||
# thank's to _prepare_relational_fields
|
||||
|
||||
#Multiple datasources detection
|
||||
# Multiple datasources detection
|
||||
self_ds_name = self._target_class._datasource_name
|
||||
result_rel_filters = list() # The filters that will stay in the query
|
||||
result_rel_filters = list() # The filters that will stay in the query
|
||||
other_ds_filters = dict()
|
||||
for rfilter in rel_filters:
|
||||
(rfield, ref_dict), op, value = rfilter
|
||||
#rfield : the field in self._target_class
|
||||
tmp_rel_filter = dict() #designed to stores rel_field of same DS
|
||||
# rfield : the field in self._target_class
|
||||
tmp_rel_filter = dict() # designed to stores rel_field of same DS
|
||||
# First step : simplification
|
||||
# Trying to delete relational filters done on referenced class uid
|
||||
for tclass, tfield in copy.copy(ref_dict).items():
|
||||
#tclass : reference target class
|
||||
#tfield : referenced field from target class
|
||||
# tclass : reference target class
|
||||
# tfield : referenced field from target class
|
||||
#
|
||||
# !!!WARNING!!!
|
||||
# The line below brake multi UID support
|
||||
#
|
||||
if tfield == tclass.uid_fieldname()[0]:
|
||||
#This relational filter can be simplified as
|
||||
# This relational filter can be simplified as
|
||||
# ref_field, op, value
|
||||
# Note : we will have to dedup filters_orig
|
||||
filters_orig.append((rfield, op, value))
|
||||
del(ref_dict[tclass])
|
||||
if len(ref_dict) == 0:
|
||||
continue
|
||||
#Determine what to do with other relational filters given
|
||||
# Determine what to do with other relational filters given
|
||||
# referenced class datasource
|
||||
#Remember : each class in a relational filter has the same
|
||||
# Remember : each class in a relational filter has the same
|
||||
# datasource
|
||||
tclass = list(ref_dict.keys())[0]
|
||||
cur_ds = tclass._datasource_name
|
||||
|
|
@ -189,23 +194,23 @@ class LeFilteredQuery(LeQuery):
|
|||
other_ds_filters[cur_ds] = list()
|
||||
other_ds_filters[cur_ds].append(
|
||||
((rfield, ref_dict), op, value))
|
||||
#deduplication of std filters
|
||||
# deduplication of std filters
|
||||
filters_cp = set()
|
||||
if not isinstance(filters_orig, set):
|
||||
for i, cfilt in enumerate(filters_orig):
|
||||
a, b, c = cfilt
|
||||
if isinstance(c, list): #list are not hashable
|
||||
if isinstance(c, list): # list are not hashable
|
||||
newc = tuple(c)
|
||||
else:
|
||||
newc = c
|
||||
old_len = len(filters_cp)
|
||||
filters_cp |= set((a,b,newc))
|
||||
filters_cp |= set((a, b, newc))
|
||||
if len(filters_cp) == old_len:
|
||||
del(filters_orig[i])
|
||||
# Sets _query_filter attribute of self query
|
||||
self._query_filter = (filters_orig, result_rel_filters)
|
||||
|
||||
#Sub queries creation
|
||||
# Sub queries creation
|
||||
subq = list()
|
||||
for ds, rfilters in other_ds_filters.items():
|
||||
for rfilter in rfilters:
|
||||
|
|
@ -218,7 +223,7 @@ class LeFilteredQuery(LeQuery):
|
|||
subq.append((rfield, query))
|
||||
self.subqueries = subq
|
||||
|
||||
##@return informations
|
||||
# @return informations
|
||||
def dump_infos(self):
|
||||
ret = super().dump_infos()
|
||||
ret['query_filter'] = self._query_filter
|
||||
|
|
@ -238,16 +243,16 @@ class LeFilteredQuery(LeQuery):
|
|||
res += '>'
|
||||
return res
|
||||
|
||||
## @brief Prepare filters for datasource
|
||||
# @brief Prepare filters for datasource
|
||||
#
|
||||
#A filter can be a string or a tuple with len = 3.
|
||||
# A filter can be a string or a tuple with len = 3.
|
||||
#
|
||||
#This method divide filters in two categories :
|
||||
# This method divide filters in two categories :
|
||||
#
|
||||
#@par Simple filters
|
||||
#
|
||||
#Those filters concerns fields that represent object values (a title,
|
||||
#the content, etc.) They are composed of three elements : FIELDNAME OP
|
||||
# Those filters concerns fields that represent object values (a title,
|
||||
# the content, etc.) They are composed of three elements : FIELDNAME OP
|
||||
# VALUE . Where :
|
||||
#- FIELDNAME is the name of the field
|
||||
#- OP is one of the authorized comparison operands (see
|
||||
|
|
@ -256,14 +261,14 @@ class LeFilteredQuery(LeQuery):
|
|||
#
|
||||
#@par Relational filters
|
||||
#
|
||||
#Those filters concerns on reference fields (see the corresponding
|
||||
#abstract datahandler @ref lodel.leapi.datahandlers.base_classes.Reference)
|
||||
#The filter as quite the same composition than simple filters :
|
||||
# Those filters concerns on reference fields (see the corresponding
|
||||
# abstract datahandler @ref lodel.leapi.datahandlers.base_classes.Reference)
|
||||
# The filter as quite the same composition than simple filters :
|
||||
# FIELDNAME[.REF_FIELD] OP VALUE . Where :
|
||||
#- FIELDNAME is the name of the reference field
|
||||
#- REF_FIELD is an optionnal addon to the base field. It indicate on wich
|
||||
#field of the referenced object the comparison as to be done. If no
|
||||
#REF_FIELD is indicated the comparison will be done on identifier.
|
||||
# field of the referenced object the comparison as to be done. If no
|
||||
# REF_FIELD is indicated the comparison will be done on identifier.
|
||||
#
|
||||
#@param cls
|
||||
#@param filters_l list : This list of str or tuple (or both)
|
||||
|
|
@ -271,11 +276,11 @@ class LeFilteredQuery(LeQuery):
|
|||
#@todo move this doc in another place (a dedicated page ?)
|
||||
#@warning Does not supports multiple UID for an EmClass
|
||||
def _prepare_filters(self, filters_l):
|
||||
filters=list()
|
||||
filters = list()
|
||||
res_filters = list()
|
||||
rel_filters = list()
|
||||
err_l = dict()
|
||||
#Splitting in tuple if necessary
|
||||
# Splitting in tuple if necessary
|
||||
for i, fil in enumerate(filters_l):
|
||||
if len(fil) == 3 and not isinstance(fil, str):
|
||||
filters.append(tuple(fil))
|
||||
|
|
@ -286,7 +291,7 @@ class LeFilteredQuery(LeQuery):
|
|||
err_l["filter %d" % i] = e
|
||||
|
||||
for field, operator, value in filters:
|
||||
err_key = "%s %s %s" % (field, operator, value) #to push in err_l
|
||||
err_key = "%s %s %s" % (field, operator, value) # to push in err_l
|
||||
# Spliting field name to be able to detect a relational field
|
||||
field_spl = field.split('.')
|
||||
if len(field_spl) == 2:
|
||||
|
|
@ -310,12 +315,12 @@ field name" % field)
|
|||
# inconsistency
|
||||
err_l[field] = NameError("The field '%s' in %s is not \
|
||||
a relational field, but %s.%s was present in the filter"
|
||||
% (field,
|
||||
self._target_class.__name__,
|
||||
field,
|
||||
ref_field))
|
||||
% (field,
|
||||
self._target_class.__name__,
|
||||
field,
|
||||
ref_field))
|
||||
if field_datahandler.is_reference():
|
||||
#Relationnal field
|
||||
# Relationnal field
|
||||
if ref_field is None:
|
||||
# ref_field default value
|
||||
#
|
||||
|
|
@ -350,14 +355,14 @@ field to use for the relational filter"
|
|||
value, error = field_datahandler.check_data_value(value)
|
||||
if isinstance(error, Exception):
|
||||
value = value_orig
|
||||
res_filters.append((field,operator, value))
|
||||
res_filters.append((field, operator, value))
|
||||
if len(err_l) > 0:
|
||||
raise LeApiDataCheckErrors(
|
||||
"Error while preparing filters : ",
|
||||
err_l)
|
||||
"Error while preparing filters : ",
|
||||
err_l)
|
||||
return (res_filters, rel_filters)
|
||||
|
||||
## @brief Check and split a query filter
|
||||
# @brief Check and split a query filter
|
||||
# @note The query_filter format is "FIELD OPERATOR VALUE"
|
||||
# @param query_filter str : A query_filter string
|
||||
# @param cls
|
||||
|
|
@ -382,18 +387,18 @@ field to use for the relational filter"
|
|||
raise ValueError(msg % query_filter)
|
||||
return result
|
||||
|
||||
## @brief Compile the regex for query_filter processing
|
||||
# @brief Compile the regex for query_filter processing
|
||||
# @note Set _LeObject._query_re
|
||||
@classmethod
|
||||
def __compile_query_re(cls):
|
||||
op_re_piece = '(?P<operator>(%s)'
|
||||
op_re_piece %= cls._query_operators[0].replace(' ', '\s')
|
||||
for operator in cls._query_operators[1:]:
|
||||
op_re_piece += '|(%s)'%operator.replace(' ', '\s')
|
||||
op_re_piece += '|(%s)' % operator.replace(' ', '\s')
|
||||
op_re_piece += ')'
|
||||
|
||||
re_full = '^\s*(?P<field>([a-z_][a-z0-9\-_]*\.)?[a-z_][a-z0-9\-_]*)\s*'
|
||||
re_full += op_re_piece+'\s*(?P<value>.*)\s*$'
|
||||
re_full += op_re_piece + '\s*(?P<value>.*)\s*$'
|
||||
|
||||
cls._query_re = re.compile(re_full, flags=re.IGNORECASE)
|
||||
pass
|
||||
|
|
@ -407,10 +412,10 @@ field to use for the relational filter"
|
|||
msg %= (fieldname, target_class.__name__)
|
||||
return NameError(msg)
|
||||
|
||||
##@brief Prepare a relational filter
|
||||
# @brief Prepare a relational filter
|
||||
#
|
||||
#Relational filters are composed of a tuple like the simple filters
|
||||
#but the first element of this tuple is a tuple to :
|
||||
# Relational filters are composed of a tuple like the simple filters
|
||||
# but the first element of this tuple is a tuple to :
|
||||
#
|
||||
#<code>((FIELDNAME, {REF_CLASS: REF_FIELD}), OP, VALUE)</code>
|
||||
# Where :
|
||||
|
|
@ -419,9 +424,9 @@ field to use for the relational filter"
|
|||
# - REF_CLASS as key. It's a LeObject child class
|
||||
# - REF_FIELD as value. The name of the referenced field in the REF_CLASS
|
||||
#
|
||||
#Visibly the REF_FIELD value of the dict will vary only when
|
||||
#no REF_FIELD is explicitly given in the filter string notation
|
||||
#and REF_CLASSES has differents uid
|
||||
# Visibly the REF_FIELD value of the dict will vary only when
|
||||
# no REF_FIELD is explicitly given in the filter string notation
|
||||
# and REF_CLASSES has differents uid
|
||||
#
|
||||
#@par String notation examples
|
||||
#<pre>contributeur IN (1,2,3,5)</pre> will be transformed into :
|
||||
|
|
@ -439,7 +444,7 @@ field to use for the relational filter"
|
|||
#
|
||||
#@param fieldname str : The relational field name
|
||||
#@param ref_field str|None : The referenced field name (if None use
|
||||
#uniq identifiers as referenced field
|
||||
# uniq identifiers as referenced field
|
||||
#@return a well formed relational filter tuple or an Exception instance
|
||||
def _prepare_relational_fields(self, fieldname, ref_field=None):
|
||||
datahandler = self._target_class.field(fieldname)
|
||||
|
|
@ -467,12 +472,12 @@ the relational filter %s"
|
|||
logger.debug(msg)
|
||||
if len(ref_dict) == 0:
|
||||
return NameError("No field named '%s' in referenced objects [%s]"
|
||||
% (ref_field,
|
||||
','.join([rc.__name__ for rc in ref_classes])))
|
||||
% (ref_field,
|
||||
','.join([rc.__name__ for rc in ref_classes])))
|
||||
return (fieldname, ref_dict)
|
||||
|
||||
|
||||
##@brief A query to insert a new object
|
||||
# @brief A query to insert a new object
|
||||
class LeInsertQuery(LeQuery):
|
||||
_hook_prefix = 'leapi_insert_'
|
||||
_data_check_args = {'complete': True, 'allow_internal': False}
|
||||
|
|
@ -483,49 +488,49 @@ class LeInsertQuery(LeQuery):
|
|||
abstract LeObject : %s" % target_class)
|
||||
super().__init__(target_class)
|
||||
|
||||
## @brief Implements an insert query operation, with only one insertion
|
||||
# @param datas : datas to be inserted
|
||||
def _query(self, datas):
|
||||
datas = self._target_class.prepare_datas(datas, True, False)
|
||||
id_inserted = self._rw_datasource.insert(self._target_class, datas)
|
||||
# @brief Implements an insert query operation, with only one insertion
|
||||
# @param data : data to be inserted
|
||||
def _query(self, data):
|
||||
data = self._target_class.prepare_datas(data, True, False)
|
||||
id_inserted = self._rw_datasource.insert(self._target_class, data)
|
||||
return id_inserted
|
||||
"""
|
||||
## @brief Implements an insert query operation, with multiple insertions
|
||||
# @param datas : list of **datas to be inserted
|
||||
def _query(self, datas):
|
||||
# @param data : list of **data to be inserted
|
||||
def _query(self, data):
|
||||
nb_inserted = self._datasource.insert_multi(
|
||||
self._target_class,datas_list)
|
||||
self._target_class,data_list)
|
||||
if nb_inserted < 0:
|
||||
raise LeApiQueryError("Multiple insertions error")
|
||||
return nb_inserted
|
||||
"""
|
||||
|
||||
## @brief Execute the insert query
|
||||
def execute(self, datas):
|
||||
return super().execute(datas=datas)
|
||||
# @brief Execute the insert query
|
||||
def execute(self, data):
|
||||
return super().execute(data=data)
|
||||
|
||||
|
||||
##@brief A query to update datas for a given object
|
||||
# @brief A query to update data for a given object
|
||||
#
|
||||
#@todo Change behavior, Huge optimization problem when updating using filters
|
||||
#and not instance. We have to run a GET and then 1 update by fecthed object...
|
||||
# and not instance. We have to run a GET and then 1 update by fecthed object...
|
||||
class LeUpdateQuery(LeFilteredQuery):
|
||||
_hook_prefix = 'leapi_update_'
|
||||
_data_check_args = {'complete': False, 'allow_internal': False}
|
||||
|
||||
##@brief Instanciate an update query
|
||||
# @brief Instanciate an update query
|
||||
#
|
||||
#If a class and not an instance is given, no query_filters are expected
|
||||
#and the update will be fast and simple. Else we have to run a get query
|
||||
#before updating (to fetch datas, update them and then, construct them
|
||||
#and check their consistency)
|
||||
# If a class and not an instance is given, no query_filters are expected
|
||||
# and the update will be fast and simple. Else we have to run a get query
|
||||
# before updating (to fetch data, update them and then, construct them
|
||||
# and check their consistency)
|
||||
#@param target LeObject clas or instance
|
||||
#@param query_filters list|None
|
||||
#@todo change strategy with instance update. We have to accept datas for
|
||||
#the execute method
|
||||
#@todo change strategy with instance update. We have to accept data for
|
||||
# the execute method
|
||||
def __init__(self, target, query_filters=None):
|
||||
##@brief This attr is set only if the target argument is an
|
||||
#instance of a LeObject subclass
|
||||
# @brief This attr is set only if the target argument is an
|
||||
# instance of a LeObject subclass
|
||||
self.__leobject_instance_datas = None
|
||||
target_class = target
|
||||
|
||||
|
|
@ -542,16 +547,16 @@ target to LeUpdateQuery constructor"
|
|||
|
||||
super().__init__(target_class, query_filters)
|
||||
|
||||
##@brief Implements an update query
|
||||
#@param datas dict : datas to update
|
||||
# @brief Implements an update query
|
||||
#@param data dict : data to be updated
|
||||
#@returns the number of updated items
|
||||
#@todo change stategy for instance update. Datas should be allowed
|
||||
#for execute method (and query)
|
||||
def _query(self, datas):
|
||||
#@todo change stategy for instance update. Data should be allowed
|
||||
# for execute method (and query)
|
||||
def _query(self, data):
|
||||
uid_name = self._target_class._uid[0]
|
||||
if self.__leobject_instance_datas is not None:
|
||||
#Instance update
|
||||
#Building query_filter
|
||||
# Instance update
|
||||
# Building query_filter
|
||||
filters = [(
|
||||
uid_name,
|
||||
'=',
|
||||
|
|
@ -560,59 +565,60 @@ target to LeUpdateQuery constructor"
|
|||
self._target_class, filters, [],
|
||||
self.__leobject_instance_datas)
|
||||
else:
|
||||
#Update by filters, we have to fetch datas before updating
|
||||
# Update by filters, we have to fetch data before updating
|
||||
res = self._ro_datasource.select(
|
||||
self._target_class, self._target_class.fieldnames(True),
|
||||
self._query_filter[0],
|
||||
self._query_filter[1])
|
||||
#Checking and constructing datas
|
||||
upd_datas = dict()
|
||||
# Checking and constructing data
|
||||
upd_data = dict()
|
||||
for res_data in res:
|
||||
res_data.update(datas)
|
||||
res_datas = self._target_class.prepare_datas(
|
||||
res_data.update(data)
|
||||
res_data = self._target_class.prepare_datas(
|
||||
res_data, True, True)
|
||||
filters = [(uid_name, '=', res_data[uid_name])]
|
||||
res = self._rw_datasource.update(
|
||||
self._target_class, filters, [],
|
||||
res_datas)
|
||||
res_data)
|
||||
return res
|
||||
|
||||
## @brief Execute the update query
|
||||
def execute(self, datas=None):
|
||||
if self.__leobject_instance_datas is not None and datas is not None:
|
||||
raise LeApiQueryError("No datas expected when running an update \
|
||||
# @brief Execute the update query
|
||||
def execute(self, data=None):
|
||||
if self.__leobject_instance_datas is not None and data is not None:
|
||||
raise LeApiQueryError("No data expected when running an update \
|
||||
query on an instance")
|
||||
if self.__leobject_instance_datas is None and datas is None:
|
||||
raise LeApiQueryError("Datas are mandatory when running an update \
|
||||
if self.__leobject_instance_datas is None and data is None:
|
||||
raise LeApiQueryError("Data are mandatory when running an update \
|
||||
query on a class with filters")
|
||||
return super().execute(datas=datas)
|
||||
return super().execute(data=data)
|
||||
|
||||
|
||||
##@brief A query to delete an object
|
||||
# @brief A query to delete an object
|
||||
class LeDeleteQuery(LeFilteredQuery):
|
||||
_hook_prefix = 'leapi_delete_'
|
||||
|
||||
def __init__(self, target_class, query_filter):
|
||||
super().__init__(target_class, query_filter)
|
||||
|
||||
## @brief Execute the delete query
|
||||
# @param datas
|
||||
def execute(self, datas=None):
|
||||
# @brief Execute the delete query
|
||||
# @param data
|
||||
def execute(self, data=None):
|
||||
return super().execute()
|
||||
|
||||
##@brief Implements delete query operations
|
||||
# @param datas
|
||||
# @brief Implements delete query operations
|
||||
# @param data
|
||||
#@returns the number of deleted items
|
||||
def _query(self, datas=None):
|
||||
def _query(self, data=None):
|
||||
filters, rel_filters = self._query_filter
|
||||
nb_deleted = self._rw_datasource.delete(
|
||||
self._target_class, filters, rel_filters)
|
||||
return nb_deleted
|
||||
|
||||
|
||||
class LeGetQuery(LeFilteredQuery):
|
||||
_hook_prefix = 'leapi_get_'
|
||||
|
||||
##@brief Instanciate a new get query
|
||||
# @brief Instanciate a new get query
|
||||
#@param target_class LeObject : class of object the query is about
|
||||
#@param query_filters dict : {OP, list of query filters}
|
||||
# or tuple (FIELD, OPERATOR, VALUE) )
|
||||
|
|
@ -624,33 +630,33 @@ class LeGetQuery(LeFilteredQuery):
|
|||
# - offset int : offset
|
||||
def __init__(self, target_class, query_filters, **kwargs):
|
||||
super().__init__(target_class, query_filters)
|
||||
##@brief The fields to get
|
||||
# @brief The fields to get
|
||||
self._field_list = None
|
||||
##@brief An equivalent to the SQL ORDER BY
|
||||
# @brief An equivalent to the SQL ORDER BY
|
||||
self._order = None
|
||||
##@brief An equivalent to the SQL GROUP BY
|
||||
# @brief An equivalent to the SQL GROUP BY
|
||||
self._group = None
|
||||
##@brief An equivalent to the SQL LIMIT x
|
||||
# @brief An equivalent to the SQL LIMIT x
|
||||
self._limit = None
|
||||
##@brief An equivalent to the SQL LIMIT x, OFFSET
|
||||
# @brief An equivalent to the SQL LIMIT x, OFFSET
|
||||
self._offset = 0
|
||||
|
||||
# Checking kwargs and assigning default values if there is some
|
||||
for argname in kwargs:
|
||||
if argname not in (
|
||||
'field_list', 'order', 'group', 'limit', 'offset'):
|
||||
'field_list', 'order', 'group', 'limit', 'offset'):
|
||||
raise TypeError("Unexpected argument '%s'" % argname)
|
||||
|
||||
if 'field_list' not in kwargs:
|
||||
self.set_field_list(target_class.fieldnames(include_ro = True))
|
||||
self.set_field_list(target_class.fieldnames(include_ro=True))
|
||||
else:
|
||||
self.set_field_list(kwargs['field_list'])
|
||||
|
||||
if 'order' in kwargs:
|
||||
#check kwargs['order']
|
||||
# check kwargs['order']
|
||||
self._order = kwargs['order']
|
||||
if 'group' in kwargs:
|
||||
#check kwargs['group']
|
||||
# check kwargs['group']
|
||||
self._group = kwargs['group']
|
||||
if 'limit' in kwargs and kwargs['limit'] is not None:
|
||||
try:
|
||||
|
|
@ -669,7 +675,7 @@ class LeGetQuery(LeFilteredQuery):
|
|||
msg = "offset argument expected to be an integer >= 0"
|
||||
raise ValueError(msg)
|
||||
|
||||
##@brief Set the field list
|
||||
# @brief Set the field list
|
||||
# @param field_list list | None : If None use all fields
|
||||
# @return None
|
||||
# @throw LeApiQueryError if unknown field given
|
||||
|
|
@ -682,41 +688,41 @@ class LeGetQuery(LeFilteredQuery):
|
|||
msg = "No field named '%s' in %s"
|
||||
msg %= (fieldname, self._target_class.__name__)
|
||||
expt = NameError(msg)
|
||||
err_l[fieldname] = expt
|
||||
err_l[fieldname] = expt
|
||||
if len(err_l) > 0:
|
||||
msg = "Error while setting field_list in a get query"
|
||||
raise LeApiQueryErrors(msg = msg, exceptions = err_l)
|
||||
raise LeApiQueryErrors(msg=msg, exceptions=err_l)
|
||||
self._field_list = list(set(field_list))
|
||||
|
||||
##@brief Execute the get query
|
||||
def execute(self, datas=None):
|
||||
# @brief Execute the get query
|
||||
def execute(self, data=None):
|
||||
return super().execute()
|
||||
|
||||
##@brief Implements select query operations
|
||||
# @brief Implements select query operations
|
||||
# @returns a list containing the item(s)
|
||||
def _query(self, datas=None):
|
||||
# select datas corresponding to query_filter
|
||||
def _query(self, data=None):
|
||||
# select data corresponding to query_filter
|
||||
fl = list(self._field_list) if self._field_list is not None else None
|
||||
l_datas=self._ro_datasource.select(
|
||||
target = self._target_class,
|
||||
field_list = fl,
|
||||
filters = self._query_filter[0],
|
||||
relational_filters = self._query_filter[1],
|
||||
order = self._order,
|
||||
group = self._group,
|
||||
limit = self._limit,
|
||||
offset = self._offset)
|
||||
return l_datas
|
||||
l_data = self._ro_datasource.select(
|
||||
target=self._target_class,
|
||||
field_list=fl,
|
||||
filters=self._query_filter[0],
|
||||
relational_filters=self._query_filter[1],
|
||||
order=self._order,
|
||||
group=self._group,
|
||||
limit=self._limit,
|
||||
offset=self._offset)
|
||||
return l_data
|
||||
|
||||
##@return a dict with query infos
|
||||
# @return a dict with query infos
|
||||
def dump_infos(self):
|
||||
ret = super().dump_infos()
|
||||
ret.update({ 'field_list' : self._field_list,
|
||||
'order' : self._order,
|
||||
'group' : self._group,
|
||||
'limit' : self._limit,
|
||||
'offset': self._offset,
|
||||
})
|
||||
ret.update({'field_list': self._field_list,
|
||||
'order': self._order,
|
||||
'group': self._group,
|
||||
'limit': self._limit,
|
||||
'offset': self._offset,
|
||||
})
|
||||
return ret
|
||||
|
||||
def __repr__(self):
|
||||
|
|
@ -725,7 +731,7 @@ field_list={field_list} order={order} group={group} limit={limit} \
|
|||
offset={offset}"
|
||||
res = res.format(**self.dump_infos())
|
||||
if len(self.subqueries) > 0:
|
||||
for n,subq in enumerate(self.subqueries):
|
||||
for n, subq in enumerate(self.subqueries):
|
||||
res += "\n\tSubquerie %d : %s"
|
||||
res %= (n, subq)
|
||||
res += ">"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue