pylint objspace

This commit is contained in:
Emmanuel Garette 2021-01-11 18:03:06 +01:00
parent 9a19198bde
commit 46807533f3
4 changed files with 186 additions and 118 deletions

View File

@ -335,7 +335,6 @@ class ConstrainteAnnotator:
new_target = self.objectspace.target(variable.xmlfiles) new_target = self.objectspace.target(variable.xmlfiles)
new_target.type = type_ new_target.type = type_
new_target.name = listvar new_target.name = listvar
new_target.index = target.index
new_targets.append(new_target) new_targets.append(new_target)
remove_targets.append(target_idx) remove_targets.append(target_idx)
remove_targets.sort(reverse=True) remove_targets.sort(reverse=True)

View File

@ -64,10 +64,19 @@ class VariableAnnotator:
if variable.type != 'symlink' and not hasattr(variable, 'description'): if variable.type != 'symlink' and not hasattr(variable, 'description'):
variable.description = variable.name variable.description = variable.name
if hasattr(variable, 'value'): if hasattr(variable, 'value'):
for value in variable.value: value_to_del = []
for idx, value in enumerate(variable.value):
if not hasattr(value, 'type'): if not hasattr(value, 'type'):
value.type = variable.type value.type = variable.type
if 'name' not in vars(value):
value_to_del.append(idx)
else:
value.name = CONVERT_OPTION.get(value.type, {}).get('func', str)(value.name) value.name = CONVERT_OPTION.get(value.type, {}).get('func', str)(value.name)
value_to_del.sort(reverse=True)
for idx in value_to_del:
del variable.value[idx]
if not variable.value:
del variable.value
for key, value in RENAME_ATTIBUTES.items(): for key, value in RENAME_ATTIBUTES.items():
setattr(variable, value, getattr(variable, key)) setattr(variable, value, getattr(variable, key))
setattr(variable, key, None) setattr(variable, key, None)

View File

@ -2,12 +2,15 @@
class ConfigError(Exception): class ConfigError(Exception):
pass pass
class FileNotFound(ConfigError): class FileNotFound(ConfigError):
pass pass
class TemplateError(ConfigError): class TemplateError(ConfigError):
pass pass
class TemplateDisabled(TemplateError): class TemplateDisabled(TemplateError):
"""Template is disabled. """Template is disabled.
""" """

View File

@ -1,3 +1,8 @@
"""parse XML files and build a space with objects
it aggregates this files and manage redefine and exists attributes
"""
from typing import Optional
from .i18n import _ from .i18n import _
from .xmlreflector import XMLReflector from .xmlreflector import XMLReflector
from .utils import normalize_family from .utils import normalize_family
@ -19,30 +24,39 @@ FORCED_TEXT_ELTS_AS_NAME = ('choice', 'property', 'value', 'target')
# _____________________________________________________________________________ # _____________________________________________________________________________
# special types definitions for the Object Space's internal representation # special types definitions for the Object Space's internal representation
class RootRougailObject: class RootRougailObject: # pylint: disable=R0903
def __init__(self, xmlfiles): """Root object
"""
def __init__(self,
xmlfiles,
name=None,
):
if not isinstance(xmlfiles, list): if not isinstance(xmlfiles, list):
xmlfiles = [xmlfiles] xmlfiles = [xmlfiles]
self.xmlfiles = xmlfiles self.xmlfiles = xmlfiles
if name:
self.name = name
class Atom(RootRougailObject): class Atom(RootRougailObject): # pylint: disable=R0903
pass """Atomic object (means can only define one time)
"""
class Redefinable(RootRougailObject): class Redefinable(RootRougailObject): # pylint: disable=R0903
pass """Object that could be redefine
"""
class UnRedefinable(RootRougailObject): class UnRedefinable(RootRougailObject): # pylint: disable=R0903
pass """Object that could not be redefine
"""
class ObjSpace: class ObjSpace: # pylint: disable=R0903
""" """
Base object space Base object space
""" """
pass
class RougailObjSpace: class RougailObjSpace:
@ -52,26 +66,24 @@ class RougailObjSpace:
def __init__(self, def __init__(self,
xmlreflector: XMLReflector, xmlreflector: XMLReflector,
) -> None: ) -> None:
self.index = 0
self.xmlreflector = xmlreflector
self.space = ObjSpace() self.space = ObjSpace()
self.paths = Path() self.paths = Path()
self.redefine_variables = None
self.forced_text_elts = set()
self.forced_text_elts_as_name = set(FORCED_TEXT_ELTS_AS_NAME) self.forced_text_elts_as_name = set(FORCED_TEXT_ELTS_AS_NAME)
self.list_conditions = {} self.list_conditions = {}
self.booleans_attributs = [] self.booleans_attributs = []
self.make_object_space_classes() self.make_object_space_classes(xmlreflector)
def make_object_space_classes(self): def make_object_space_classes(self,
xmlreflector: XMLReflector,
) -> None:
"""Create Rougail ObjectSpace class types from DDT file """Create Rougail ObjectSpace class types from DDT file
It enables us to create objects like: It enables us to create objects like:
File(), Variable(), Ip(), Family(), Constraints()... and so on. File(), Variable(), Ip(), Family(), Constraints()... and so on.
""" """
for dtd_elt in self.xmlreflector.dtd.iterelements(): for dtd_elt in xmlreflector.dtd.iterelements():
attrs = {} attrs = {}
if dtd_elt.name in FORCE_REDEFINABLES: if dtd_elt.name in FORCE_REDEFINABLES:
clstype = Redefinable clstype = Redefinable
@ -95,7 +107,6 @@ class RougailObjSpace:
clstype = Redefinable clstype = Redefinable
if dtd_attr.name == 'name' and forced_text_elt: if dtd_attr.name == 'name' and forced_text_elt:
# child.text should be transform has a "name" attribute # child.text should be transform has a "name" attribute
self.forced_text_elts.add(dtd_elt.name)
forced_text_elt = False forced_text_elt = False
if forced_text_elt is True: if forced_text_elt is True:
@ -104,14 +115,19 @@ class RougailObjSpace:
# create ObjectSpace object # create ObjectSpace object
setattr(self, dtd_elt.name, type(dtd_elt.name.capitalize(), (clstype,), attrs)) setattr(self, dtd_elt.name, type(dtd_elt.name.capitalize(), (clstype,), attrs))
for elt in FORCE_ELEMENTS: for elt in FORCE_ELEMENTS:
setattr(self, elt, type(self._get_elt_name(elt), (RootRougailObject,), dict()))
@staticmethod
def _get_elt_name(elt) -> str:
name = elt.capitalize() name = elt.capitalize()
if name.endswith('_'): if name.endswith('_'):
name = name[:-1] name = name[:-1]
setattr(self, elt, type(name, (RootRougailObject,), dict())) return name
def display_xmlfiles(self, @staticmethod
xmlfiles: list, def display_xmlfiles(xmlfiles: list) -> str:
) -> str: """The function format xmlfiles informations to generate errors
"""
if len(xmlfiles) == 1: if len(xmlfiles) == 1:
return '"' + xmlfiles[0] + '"' return '"' + xmlfiles[0] + '"'
return '"' + '", "'.join(xmlfiles[:-1]) + '"' + ' and ' + '"' + xmlfiles[-1] + '"' return '"' + '", "'.join(xmlfiles[:-1]) + '"' + ' and ' + '"' + xmlfiles[-1] + '"'
@ -124,33 +140,42 @@ class RougailObjSpace:
): ):
"""Parses a Rougail XML file and populates the RougailObjSpace """Parses a Rougail XML file and populates the RougailObjSpace
""" """
redefine_variables = []
self._xml_parse(xmlfile,
document,
space,
namespace,
redefine_variables,
)
def _xml_parse(self,
xmlfile,
document,
space,
namespace,
redefine_variables,
) -> None:
# var to check unique family name in a XML file # var to check unique family name in a XML file
family_names = [] family_names = []
for child in document: for child in document:
# this index enables us to reorder objects
if not isinstance(child.tag, str): if not isinstance(child.tag, str):
# doesn't proceed the XML commentaries # doesn't proceed the XML commentaries
continue continue
if child.tag == 'family': if child.tag == 'family':
if child.attrib['name'] in family_names: if child.attrib['name'] in family_names:
raise DictConsistencyError(_(f'Family "{child.attrib["name"]}" is set several times in "{xmlfile}"'), 44) msg = _(f'Family "{child.attrib["name"]}" is set several times in "{xmlfile}"')
raise DictConsistencyError(msg, 44)
family_names.append(child.attrib['name']) family_names.append(child.attrib['name'])
if child.tag == 'variables':
# variables has no name, so force namespace name
child.attrib['name'] = namespace
if child.tag == 'value' and child.text is None:
# remove empty value
continue
# variable objects creation
try: try:
# variable objects creation
variableobj = self.get_variableobj(xmlfile, variableobj = self.get_variableobj(xmlfile,
child, child,
space, space,
namespace, namespace,
redefine_variables,
) )
except SpaceObjShallNotBeUpdated: except SpaceObjShallNotBeUpdated:
continue continue
self.index += 1
self.set_text(child, self.set_text(child,
variableobj, variableobj,
) )
@ -160,9 +185,9 @@ class RougailObjSpace:
) )
self.remove(child, self.remove(child,
variableobj, variableobj,
redefine_variables,
) )
self.set_path(child, self.set_path(namespace,
namespace,
document, document,
variableobj, variableobj,
) )
@ -172,10 +197,11 @@ class RougailObjSpace:
namespace, namespace,
) )
if list(child) != []: if list(child) != []:
self.xml_parse_document(xmlfile, self._xml_parse(xmlfile,
child, child,
variableobj, variableobj,
namespace, namespace,
redefine_variables,
) )
def get_variableobj(self, def get_variableobj(self,
@ -183,41 +209,55 @@ class RougailObjSpace:
child: list, child: list,
space, space,
namespace, namespace,
redefine_variables,
): ):
""" """
retrieves or creates Rougail Object Subspace objects retrieves or creates Rougail Object Subspace objects
""" """
obj = getattr(self, child.tag) obj = getattr(self, child.tag)
name = self._get_name(child, namespace)
if Redefinable in obj.__mro__: if Redefinable in obj.__mro__:
return self.create_or_update_redefinable_object(xmlfile, return self.create_or_update_redefinable_object(xmlfile,
child.attrib, child.attrib,
space, space,
child, child,
name,
namespace, namespace,
redefine_variables,
) )
if Atom in obj.__mro__: if Atom in obj.__mro__:
if child.tag in vars(space): if child.tag in vars(space):
# Atom instance has to be a singleton here # Atom instance has to be a singleton here
# we do not re-create it, we reuse it # we do not re-create it, we reuse it
return getattr(space, child.tag) return getattr(space, child.tag)
return obj(xmlfile) return obj(xmlfile, name)
if child.tag not in vars(space): if child.tag not in vars(space):
setattr(space, child.tag, []) setattr(space, child.tag, [])
return obj(xmlfile) return obj(xmlfile, name)
def _get_name(self,
child,
namespace: str,
) -> Optional[str]:
if child.tag == 'variables':
return namespace
if 'name' in child.attrib:
return child.attrib['name']
if child.text and child.tag in self.forced_text_elts_as_name:
return child.text.strip()
return None
def create_or_update_redefinable_object(self, def create_or_update_redefinable_object(self,
xmlfile, xmlfile,
subspace, subspace,
space, space,
child, child,
name,
namespace, namespace,
redefine_variables,
): ):
if child.tag in self.forced_text_elts_as_name: """A redefinable object could be created or updated
name = child.text """
else:
name = subspace['name']
if child.tag == 'family':
name = normalize_family(name)
existed_var = self.get_existed_obj(name, existed_var = self.get_existed_obj(name,
space, space,
child, child,
@ -229,47 +269,65 @@ class RougailObjSpace:
default_redefine = child.tag in FORCE_REDEFINABLES default_redefine = child.tag in FORCE_REDEFINABLES
redefine = self.convert_boolean(subspace.get('redefine', default_redefine)) redefine = self.convert_boolean(subspace.get('redefine', default_redefine))
if redefine is True: if redefine is True:
if isinstance(existed_var, self.variable): # pylint: disable=E1101
if namespace == Config['variable_namespace']:
redefine_variables.append(name)
else:
redefine_variables.append(space.path + '.' + name)
existed_var.xmlfiles.append(xmlfile) existed_var.xmlfiles.append(xmlfile)
return existed_var return existed_var
exists = self.convert_boolean(subspace.get('exists', True)) exists = self.convert_boolean(subspace.get('exists', True))
if exists is False: if exists is False:
raise SpaceObjShallNotBeUpdated() raise SpaceObjShallNotBeUpdated()
xmlfiles = self.display_xmlfiles(existed_var.xmlfiles) xmlfiles = self.display_xmlfiles(existed_var.xmlfiles)
raise DictConsistencyError(_(f'"{child.tag}" named "{name}" cannot be re-created in "{xmlfile}", already defined in {xmlfiles}'), 45) msg = _(f'"{child.tag}" named "{name}" cannot be re-created in "{xmlfile}", '
# if this object must only be modified if object already exists f'already defined in {xmlfiles}')
raise DictConsistencyError(msg, 45)
# object deos not exists
exists = self.convert_boolean(subspace.get('exists', False)) exists = self.convert_boolean(subspace.get('exists', False))
if exists is True: if exists is True:
# manage object only if already exists, so cancel
raise SpaceObjShallNotBeUpdated() raise SpaceObjShallNotBeUpdated()
redefine = self.convert_boolean(subspace.get('redefine', False)) redefine = self.convert_boolean(subspace.get('redefine', False))
if redefine is False: if redefine is True:
# cannot redefine an inexistant object
msg = _(f'Redefined object in "{xmlfile}": "{name}" does not exist yet')
raise DictConsistencyError(msg, 46)
if child.tag not in vars(space): if child.tag not in vars(space):
setattr(space, child.tag, {}) setattr(space, child.tag, {})
return getattr(self, child.tag)(xmlfile) return getattr(self, child.tag)(xmlfile, name)
raise DictConsistencyError(_(f'Redefined object in "{xmlfile}": "{name}" does not exist yet'), 46)
def get_existed_obj(self, def get_existed_obj(self,
name: str, name: str,
space: str, space: str,
child, child,
namespace: str, namespace: str,
): ) -> None:
if isinstance(space, self.family): """if an object exists, return it
"""
if child.tag == 'family':
name = normalize_family(name)
if isinstance(space, self.family): # pylint: disable=E1101
if namespace != Config['variable_namespace']: if namespace != Config['variable_namespace']:
name = space.path + '.' + name name = space.path + '.' + name
if not self.paths.path_is_defined(name): if not self.paths.path_is_defined(name):
return return None
old_family_name = self.paths.get_variable_family_name(name) old_family_name = self.paths.get_variable_family_name(name)
if namespace != Config['variable_namespace']: if namespace != Config['variable_namespace']:
old_family_name = namespace + '.' + old_family_name old_family_name = namespace + '.' + old_family_name
if space.path != old_family_name: if space.path != old_family_name:
xmlfiles = self.display_xmlfiles(space.xmlfiles) xmlfiles = self.display_xmlfiles(space.xmlfiles)
raise DictConsistencyError(_(f'Variable was previously create in family "{old_family_name}", now it is in "{space.path}" in {xmlfiles}'), 47) msg = _(f'Variable was previously create in family "{old_family_name}", '
f'now it is in "{space.path}" in {xmlfiles}')
raise DictConsistencyError(msg, 47)
return self.paths.get_variable_obj(name) return self.paths.get_variable_obj(name)
children = getattr(space, child.tag, {}) children = getattr(space, child.tag, {})
if name in children: if name in children:
return children[name] return children[name]
return None
def convert_boolean(self, value): @staticmethod
def convert_boolean(value: str) -> bool:
"""Boolean coercion. The Rougail XML may contain srings like `True` or `False` """Boolean coercion. The Rougail XML may contain srings like `True` or `False`
""" """
if isinstance(value, bool): if isinstance(value, bool):
@ -281,15 +339,13 @@ class RougailObjSpace:
def set_text(self, def set_text(self,
child, child,
variableobj, variableobj,
): ) -> None:
if child.text is None: """set text
"""
if child.text is None or child.tag in self.forced_text_elts_as_name:
return return
text = child.text.strip() text = child.text.strip()
if not text: if text:
return
if child.tag in self.forced_text_elts_as_name:
variableobj.name = text
else:
variableobj.text = text variableobj.text = text
def set_attributes(self, def set_attributes(self,
@ -297,6 +353,8 @@ class RougailObjSpace:
child, child,
variableobj, variableobj,
): ):
""" set attributes to an object
"""
redefine = self.convert_boolean(child.attrib.get('redefine', False)) redefine = self.convert_boolean(child.attrib.get('redefine', False))
if redefine and child.tag == 'variable': if redefine and child.tag == 'variable':
# delete old values # delete old values
@ -306,7 +364,9 @@ class RougailObjSpace:
for attr, val in child.attrib.items(): for attr, val in child.attrib.items():
if redefine and attr in UNREDEFINABLE: if redefine and attr in UNREDEFINABLE:
xmlfiles = self.display_xmlfiles(variableobj.xmlfiles[:-1]) xmlfiles = self.display_xmlfiles(variableobj.xmlfiles[:-1])
raise DictConsistencyError(_(f'cannot redefine attribute "{attr}" for variable "{child.attrib["name"]}" in "{xmlfile}", already defined in {xmlfiles}'), 48) msg = _(f'cannot redefine attribute "{attr}" for variable "{child.attrib["name"]}"'
f' in "{xmlfile}", already defined in {xmlfiles}')
raise DictConsistencyError(msg, 48)
if attr in self.booleans_attributs: if attr in self.booleans_attributs:
val = self.convert_boolean(val) val = self.convert_boolean(val)
if attr == 'name' and getattr(variableobj, 'name', None): if attr == 'name' and getattr(variableobj, 'name', None):
@ -317,6 +377,7 @@ class RougailObjSpace:
def remove(self, def remove(self,
child, child,
variableobj, variableobj,
redefine_variables,
): ):
"""Rougail object tree manipulations """Rougail object tree manipulations
""" """
@ -327,67 +388,63 @@ class RougailObjSpace:
self.remove_condition(variableobj.name) self.remove_condition(variableobj.name)
if child.attrib.get('remove_fill', False): if child.attrib.get('remove_fill', False):
self.remove_fill(variableobj.name) self.remove_fill(variableobj.name)
if child.tag == 'fill' and child.attrib['target'] in self.redefine_variables: if child.tag == 'fill' and child.attrib['target'] in redefine_variables:
self.remove_fill(child.attrib['target']) self.remove_fill(child.attrib['target'])
def remove_check(self, name): def remove_check(self, name):
if hasattr(self.space, 'constraints') and hasattr(self.space.constraints, 'check'): """Remove a check with a specified target
"""
remove_checks = [] remove_checks = []
for idx, check in enumerate(self.space.constraints.check): for idx, check in enumerate(self.space.constraints.check): # pylint: disable=E1101
if hasattr(check, 'target') and check.target == name: if check.target == name:
remove_checks.append(idx) remove_checks.append(idx)
remove_checks = list(set(remove_checks))
remove_checks.sort(reverse=True) remove_checks.sort(reverse=True)
for idx in remove_checks: for idx in remove_checks:
self.space.constraints.check.pop(idx) self.space.constraints.check.pop(idx) # pylint: disable=E1101
def remove_condition(self, name): def remove_condition(self,
name: str,
) -> None:
"""Remove a condition with a specified source
"""
remove_conditions = [] remove_conditions = []
for idx, condition in enumerate(self.space.constraints.condition): for idx, condition in enumerate(self.space.constraints.condition): # pylint: disable=E1101
if condition.source == name: if condition.source == name:
remove_conditions.append(idx) remove_conditions.append(idx)
remove_conditions.sort(reverse=True)
for idx in remove_conditions: for idx in remove_conditions:
del self.space.constraints.condition[idx] del self.space.constraints.condition[idx] # pylint: disable=E1101
def remove_fill(self, name): def remove_fill(self,
if hasattr(self.space, 'constraints') and hasattr(self.space.constraints, 'fill'): name: str,
remove_fills= [] ) -> None:
for idx, fill in enumerate(self.space.constraints.fill): """Remove a fill with a specified target
if hasattr(fill, 'target') and fill.target == name: """
remove_fills = []
for idx, fill in enumerate(self.space.constraints.fill): # pylint: disable=E1101
if fill.target == name:
remove_fills.append(idx) remove_fills.append(idx)
remove_fills = list(set(remove_fills))
remove_fills.sort(reverse=True) remove_fills.sort(reverse=True)
for idx in remove_fills: for idx in remove_fills:
self.space.constraints.fill.pop(idx) self.space.constraints.fill.pop(idx) # pylint: disable=E1101
def set_path(self, def set_path(self,
child,
namespace, namespace,
document, document,
variableobj, variableobj,
): ):
"""Fill self.paths attributes """Fill self.paths attributes
""" """
if child.tag == 'variable': if isinstance(variableobj, self.variable): # pylint: disable=E1101
family_name = document.attrib['name'] family_name = normalize_family(document.attrib['name'])
family_name = normalize_family(family_name)
self.paths.add_variable(namespace, self.paths.add_variable(namespace,
child.attrib['name'], variableobj.name,
family_name, family_name,
document.attrib.get('dynamic') is not None, document.attrib.get('dynamic') is not None,
variableobj, variableobj,
) )
if child.attrib.get('redefine', 'False') == 'True': elif isinstance(variableobj, self.family): # pylint: disable=E1101
if namespace == Config['variable_namespace']: family_name = normalize_family(variableobj.name)
self.redefine_variables.append(child.attrib['name'])
else:
self.redefine_variables.append(namespace + '.' + family_name + '.' +
child.attrib['name'])
elif child.tag == 'family':
family_name = normalize_family(child.attrib['name'])
if namespace != Config['variable_namespace']: if namespace != Config['variable_namespace']:
family_name = namespace + '.' + family_name family_name = namespace + '.' + family_name
self.paths.add_family(namespace, self.paths.add_family(namespace,
@ -396,14 +453,14 @@ class RougailObjSpace:
) )
variableobj.path = self.paths.get_family_path(family_name, namespace) variableobj.path = self.paths.get_family_path(family_name, namespace)
def add_to_tree_structure(self, @staticmethod
variableobj, def add_to_tree_structure(variableobj,
space, space,
child, child,
namespace, namespace: str,
): ) -> None:
if not hasattr(variableobj, 'index'): """add a variable to the tree
variableobj.index = self.index """
variableobj.namespace = namespace variableobj.namespace = namespace
if isinstance(variableobj, Redefinable): if isinstance(variableobj, Redefinable):
name = variableobj.name name = variableobj.name