tiramisu/tiramisu/todict.py

1073 lines
40 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
import warnings
import sys
from copy import copy
2019-06-21 23:04:04 +02:00
from itertools import chain
from .error import ValueWarning, ValueErrorWarning, PropertiesOptionError, ConfigError
from .setting import undefined
2024-10-31 08:53:58 +01:00
# from .option.syndynoption import SynDynOption
2024-04-24 15:39:17 +02:00
from . import RegexpOption, ChoiceOption, ParamOption
from .i18n import _
2024-10-31 08:53:58 +01:00
CONVERT_WEB_TYPE = {
"bool": "boolean",
"str": "string",
"int": "integer",
}
INPUTS = [
"string",
"integer",
"filename",
"password",
"email",
"username",
"ip",
"domainname",
]
# return always warning (even if same warning is already returned)
warnings.simplefilter("always", ValueWarning)
warnings.simplefilter("always", ValueErrorWarning)
class Callbacks(object):
def __init__(self, tiramisu_web):
self.tiramisu_web = tiramisu_web
self.clearable = tiramisu_web.clearable
self.remotable = tiramisu_web.remotable
self.callbacks = []
2024-10-31 08:53:58 +01:00
def add(self, path, childapi, schema, force_store_value):
if self.remotable == "all" or childapi.isoptiondescription():
2019-05-09 20:32:43 +02:00
return
2024-10-31 08:53:58 +01:00
# callback, callback_params = childapi.callbacks()
# if callback is None: # FIXME ? and force_store_value and self.clearable != 'all':
# return
# self.callbacks.append((callback, callback_params, path, childapi, schema, force_store_value))
2019-05-09 20:32:43 +02:00
def process_properties(self, form):
2024-10-31 08:53:58 +01:00
for (
callback,
callback_params,
path,
childapi,
schema,
force_store_value,
) in self.callbacks:
2023-06-19 17:19:07 +02:00
if childapi.isfollower():
self.tiramisu_web.set_remotable(path, form, childapi)
2019-06-21 23:04:04 +02:00
continue
2019-05-09 20:32:43 +02:00
has_option = False
if callback_params is not None:
2024-10-31 08:53:58 +01:00
for callback_param in chain(
callback_params.args, callback_params.kwargs.values()
):
2019-05-09 20:32:43 +02:00
if isinstance(callback_param, ParamOption):
has_option = True
2024-10-31 08:53:58 +01:00
if "expire" in childapi.properties():
self.tiramisu_web.set_remotable(
callback_param.impl_getpath(), form
)
if not has_option and form.get(path, {}).get("remote", False) == False:
if "expire" in childapi.properties():
self.tiramisu_web.set_remotable(path, form, childapi)
elif childapi.owner.isdefault():
2019-05-09 20:32:43 +02:00
# get calculated value and set clearable
2024-10-31 08:53:58 +01:00
schema[path]["value"] = childapi.value.get()
if self.clearable == "minimum":
form.setdefault(path, {})["clearable"] = True
2019-05-09 20:32:43 +02:00
def manage_callbacks(self, form):
2019-10-27 11:09:15 +01:00
pass
2024-10-31 08:53:58 +01:00
# for callback, callback_params, path, childapi, schema, force_store_value in self.callbacks:
2019-10-27 11:09:15 +01:00
# if callback_params is not None:
# for callback_param in chain(callback_params.args, callback_params.kwargs.values()):
# if isinstance(callback_param, ParamOption) and callback.__name__ == 'tiramisu_copy':
2023-06-19 17:19:07 +02:00
# opt_path = callback_param.impl_getpath()
2019-10-27 11:09:15 +01:00
# if form.get(opt_path, {}).get('remote') is not True:
# form.setdefault(opt_path, {})
# form[opt_path].setdefault('copy', []).append(path)
2024-10-31 08:53:58 +01:00
def process(self, form):
self.process_properties(form)
2019-05-09 20:32:43 +02:00
self.manage_callbacks(form)
class Consistencies(object):
def __init__(self, tiramisu_web):
2019-06-21 23:04:04 +02:00
self.not_equal = {}
self.tiramisu_web = tiramisu_web
def add(self, path, childapi, form):
return
2023-06-19 17:19:07 +02:00
if not childapi.isoptiondescription():
for consistency in childapi.consistencies():
cons_id, func, all_cons_opts, params = consistency
2024-10-31 08:53:58 +01:00
if func == "_cons_not_equal" and params.get("transitive", True) is True:
2019-06-21 23:04:04 +02:00
options_path = []
for option in all_cons_opts:
2019-06-21 23:04:04 +02:00
options_path.append(option()._path)
for idx, option in enumerate(all_cons_opts):
option = option()
2019-06-21 23:04:04 +02:00
paths = options_path.copy()
paths.pop(idx)
2024-10-31 08:53:58 +01:00
warnings_only = params.get("warnings_only") or getattr(
option, "_warnings_only", False
)
self.not_equal.setdefault(option._path, {}).setdefault(
warnings_only, []
).extend(paths)
2019-06-21 23:04:04 +02:00
else:
for option in all_cons_opts:
self.tiramisu_web.set_remotable(option()._path, form)
def process(self, form):
2019-06-21 23:04:04 +02:00
for path in self.not_equal:
2019-07-06 07:18:32 +02:00
if self.tiramisu_web.is_remote(path, form):
continue
if path not in form:
form[path] = {}
2019-06-21 23:04:04 +02:00
for warnings_only in self.not_equal[path]:
options = self.not_equal[path][warnings_only]
2024-10-31 08:53:58 +01:00
if "not_equal" not in form[path]:
form[path]["not_equal"] = []
obj = {"options": options}
2019-06-21 23:04:04 +02:00
if warnings_only:
2024-10-31 08:53:58 +01:00
obj["warnings"] = True
form[path]["not_equal"].append(obj)
class Requires(object):
def __init__(self, tiramisu_web):
self.requires = {}
self.options = {}
self.tiramisu_web = tiramisu_web
2019-07-04 20:43:47 +02:00
self.action_hide = self.tiramisu_web.config._config_bag.properties
def set_master_remote(self, childapi, path, form):
2023-06-19 17:19:07 +02:00
if childapi.isoptiondescription():
2019-07-06 07:18:32 +02:00
isfollower = False
else:
2023-06-19 17:19:07 +02:00
isfollower = childapi.isfollower()
2019-07-06 07:18:32 +02:00
if isfollower:
2024-10-31 08:53:58 +01:00
parent_path = path.rsplit(".", 1)[0]
parent = self.tiramisu_web.config.unrestraint.option(parent_path)
leader = parent.list()[0]
2023-06-19 17:19:07 +02:00
self.tiramisu_web.set_remotable(leader.path(), form, leader)
2019-07-06 07:18:32 +02:00
2024-10-31 08:53:58 +01:00
def manage_requires(
self,
childapi,
path,
form,
current_action,
):
for requires in childapi.property.get(uncalculated=True):
2019-12-02 10:41:43 +01:00
if not isinstance(requires, str):
2024-10-31 08:53:58 +01:00
option = requires.params.kwargs["condition"].option
expected = [requires.params.kwargs["expected"].value]
2019-12-02 10:41:43 +01:00
action = requires.params.args[0].value
2024-10-31 08:53:58 +01:00
if "reverse_condition" in requires.params.kwargs:
inverse = requires.params.kwargs["reverse_condition"].value
2019-12-02 10:41:43 +01:00
else:
inverse = False
transitive = True
same_action = True
2024-10-31 08:53:58 +01:00
operator = "or"
2019-12-02 10:41:43 +01:00
if 1 == 1:
2024-10-31 08:53:58 +01:00
# len_to_long = len(requires) > 1
# for require in requires:
# options, action, inverse, transitive, same_action, operator = require
# if not len_to_long:
# len_to_long = len(options) > 1
# for option, expected in options:
2019-07-04 20:43:47 +02:00
if isinstance(option, tuple):
2024-10-31 08:53:58 +01:00
for option_param in chain(
option[1].args, option[1].kwargs.values()
):
2019-07-04 20:43:47 +02:00
if isinstance(option_param, ParamOption):
2024-10-31 08:53:58 +01:00
self.tiramisu_web.set_remotable(
option_param.option.impl_getpath(), form
)
self.set_master_remote(childapi, path, form)
2024-10-31 08:53:58 +01:00
# elif len_to_long:
# self.tiramisu_web.set_remotable(option.impl_getpath(), form)
# self.set_master_remote(childapi, path, form)
2019-07-04 20:43:47 +02:00
else:
option_path = option.impl_getpath()
if action in self.action_hide:
2024-10-31 08:53:58 +01:00
require_option = (
self.tiramisu_web.config.unrestraint.option(option_path)
)
if (
transitive is False
or same_action is False
or operator == "and"
):
2019-07-04 20:43:47 +02:00
# transitive to "False" not supported yet for a requirement
# same_action to "False" not supported yet for a requirement
# operator "and" not supported yet for a requirement
2024-10-31 08:53:58 +01:00
self.tiramisu_web.set_remotable(
option_path, form, require_option
)
self.set_master_remote(childapi, path, form)
2024-10-31 08:53:58 +01:00
# if require_option.option.requires():
# for reqs in require_option.option.requires():
# for req in reqs:
# for subopt, subexp in req[0]:
# if not isinstance(subopt, tuple):
# self.tiramisu_web.set_remotable(subopt.impl_getpath(), form)
# self.set_master_remote(childapi, path, form)
2019-07-04 20:43:47 +02:00
if inverse:
2024-10-31 08:53:58 +01:00
act = "show"
inv_act = "hide"
2019-06-21 23:04:04 +02:00
else:
2024-10-31 08:53:58 +01:00
act = "hide"
inv_act = "show"
2023-05-11 15:44:48 +02:00
if isinstance(option, ChoiceOption):
2024-10-31 08:53:58 +01:00
require_option = (
self.tiramisu_web.config.unrestraint.option(
option_path
)
)
values = self.tiramisu_web.get_enum(
require_option,
require_option.ismulti(),
option_path,
require_option.property.get(),
)
2019-07-06 07:18:32 +02:00
for value in values:
if value not in expected:
2024-10-31 08:53:58 +01:00
self.requires.setdefault(
path, {"expected": {}}
)["expected"].setdefault(value, {}).setdefault(
inv_act, []
).append(
option_path
)
2019-07-06 07:18:32 +02:00
if current_action is None:
current_action = action
elif current_action != action:
self.tiramisu_web.set_remotable(option_path, form)
self.set_master_remote(childapi, path, form)
2019-07-04 20:43:47 +02:00
for exp in expected:
2024-10-31 08:53:58 +01:00
self.requires.setdefault(path, {"expected": {}})[
"expected"
].setdefault(exp, {}).setdefault(act, []).append(
option_path
)
self.requires[path].setdefault("default", {}).setdefault(
inv_act, []
).append(option_path)
2019-05-09 20:32:43 +02:00
else:
self.tiramisu_web.set_remotable(option_path, form)
self.set_master_remote(childapi, path, form)
def add(self, path, childapi, form):
2024-10-31 08:53:58 +01:00
# collect id of all options
2023-06-19 17:19:07 +02:00
child = childapi.get()
2024-10-31 08:53:58 +01:00
# if isinstance(child, SynDynOption):
# child = child.opt
self.options[child] = path
current_action = None
2024-10-31 08:53:58 +01:00
self.manage_requires(
childapi,
path,
form,
current_action,
)
def process(self, form):
dependencies = {}
for path, values in self.requires.items():
2024-10-31 08:53:58 +01:00
if "default" in values:
for option in values["default"].get("show", []):
if path == option:
self.tiramisu_web.set_remotable(path, form)
2019-07-06 07:18:32 +02:00
if not self.tiramisu_web.is_remote(option, form):
2024-10-31 08:53:58 +01:00
dependencies.setdefault(
option, {"default": {}, "expected": {}}
)["default"].setdefault("show", [])
if path not in dependencies[option]["default"]["show"]:
dependencies[option]["default"]["show"].append(path)
for option in values["default"].get("hide", []):
if path == option:
self.tiramisu_web.set_remotable(path, form)
2019-07-06 07:18:32 +02:00
if not self.tiramisu_web.is_remote(option, form):
2024-10-31 08:53:58 +01:00
dependencies.setdefault(
option, {"default": {}, "expected": {}}
)["default"].setdefault("hide", [])
if path not in dependencies[option]["default"]["hide"]:
dependencies[option]["default"]["hide"].append(path)
for expected, actions in values["expected"].items():
if expected is None:
2024-10-31 08:53:58 +01:00
expected = ""
for option in actions.get("show", []):
if path == option:
self.tiramisu_web.set_remotable(path, form)
2019-07-06 07:18:32 +02:00
if not self.tiramisu_web.is_remote(option, form):
2024-10-31 08:53:58 +01:00
dependencies.setdefault(option, {"expected": {}})[
"expected"
].setdefault(expected, {}).setdefault("show", [])
if (
path
not in dependencies[option]["expected"][expected]["show"]
):
dependencies[option]["expected"][expected]["show"].append(
path
)
for option in actions.get("hide", []):
if path == option:
self.tiramisu_web.set_remotable(path, form)
2019-07-06 07:18:32 +02:00
if not self.tiramisu_web.is_remote(option, form):
2024-10-31 08:53:58 +01:00
dependencies.setdefault(option, {"expected": {}})[
"expected"
].setdefault(expected, {}).setdefault("hide", [])
if (
path
not in dependencies[option]["expected"][expected]["hide"]
):
dependencies[option]["expected"][expected]["hide"].append(
path
)
for path, dependency in dependencies.items():
2024-10-31 08:53:58 +01:00
form.setdefault(path, {})["dependencies"] = dependency
class TiramisuDict:
# propriete:
# hidden
# mandatory
# editable
# FIXME model:
# #optionnel mais qui bouge
# choices/suggests
# warning
#
# #bouge
# owner
# properties
2024-10-31 08:53:58 +01:00
def __init__(self, config, root=None, clearable="all", remotable="minimum"):
self.config = config
self.root = root
self.requires = None
self.callbacks = None
self.consistencies = None
2024-10-31 08:53:58 +01:00
# all, minimum, none
self.clearable = clearable
2024-10-31 08:53:58 +01:00
# all, minimum, none
self.remotable = remotable
2024-10-31 08:53:58 +01:00
def add_help(self, obj, childapi):
hlp = childapi.information.get("help", None)
if hlp is not None:
2024-10-31 08:53:58 +01:00
obj["help"] = hlp
def get_list(self, root, subchildapi):
2024-10-31 08:53:58 +01:00
# ret = []
for childapi in subchildapi.list():
2023-06-19 17:19:07 +02:00
childname = childapi.name()
if root is None:
path = childname
else:
2024-10-31 08:53:58 +01:00
path = root + "." + childname
2023-06-19 17:19:07 +02:00
yield (path, childapi)
2024-10-31 08:53:58 +01:00
# return ret
2019-07-06 07:18:32 +02:00
def is_remote(self, path, form):
2024-10-31 08:53:58 +01:00
if self.remotable == "all":
2019-07-06 07:18:32 +02:00
return True
else:
2024-10-31 08:53:58 +01:00
return path in form and form[path].get("remote", False) == True
2019-07-06 07:18:32 +02:00
def set_remotable(self, path, form, childapi=None):
2024-10-31 08:53:58 +01:00
if self.remotable == "none":
raise ValueError(
_('option {} only works when remotable is not "none"').format(path)
)
form.setdefault(path, {})["remote"] = True
2019-06-21 23:04:04 +02:00
if childapi is None:
childapi = self.config.unrestraint.option(path)
2023-06-19 17:19:07 +02:00
if childapi.isfollower():
2024-10-31 08:53:58 +01:00
parent_path = path.rsplit(".", 1)[0]
parent = self.config.unrestraint.option(parent_path)
leader = parent.list()[0]
2024-10-31 08:53:58 +01:00
form.setdefault(leader.path(), {})["remote"] = True
def walk(
self,
root,
subchildapi,
schema,
model,
form,
order,
updates_status,
init=False,
):
2019-06-21 23:04:04 +02:00
error = None
if init:
if form is not None:
self.requires = Requires(self)
self.consistencies = Consistencies(self)
self.callbacks = Callbacks(self)
else:
init = False
2019-06-21 23:04:04 +02:00
try:
if subchildapi is None:
if root is None:
subchildapi = self.config.unrestraint
else:
2019-06-21 23:04:04 +02:00
subchildapi = self.config.unrestraint.option(root)
isleadership = False
else:
2023-06-19 17:19:07 +02:00
isleadership = subchildapi.isleadership()
2019-06-21 23:04:04 +02:00
leader_len = None
for path, childapi in self.get_list(root, subchildapi):
2019-06-21 23:04:04 +02:00
if isleadership and leader_len is None:
leader_len = childapi.value.len()
2019-06-21 23:04:04 +02:00
one_is_remote = False
2023-06-19 17:19:07 +02:00
if not childapi.isoptiondescription() and childapi.isfollower():
props_no_requires = set()
else:
props_no_requires = set(childapi.property.get())
2019-06-21 23:04:04 +02:00
if form is not None:
2024-10-31 08:53:58 +01:00
self.requires.add(
path,
childapi,
form,
)
self.consistencies.add(
path,
childapi,
form,
)
self.callbacks.add(
path,
childapi,
schema,
"force_store_value" in props_no_requires,
)
if (
model is not None
and childapi.isoptiondescription()
or not childapi.issymlinkoption()
):
self.gen_model(
model,
childapi,
path,
leader_len,
updates_status,
)
2019-06-21 23:04:04 +02:00
if order is not None:
order.append(path)
2023-06-19 17:19:07 +02:00
if childapi.isoptiondescription():
2024-10-31 08:53:58 +01:00
web_type = "optiondescription"
2023-06-19 17:19:07 +02:00
if childapi.isleadership():
2024-10-31 08:53:58 +01:00
type_ = "array"
2019-06-21 23:04:04 +02:00
else:
2024-10-31 08:53:58 +01:00
type_ = "object"
2019-06-21 23:04:04 +02:00
if schema is not None:
2024-10-31 08:53:58 +01:00
schema[path] = {"properties": {}, "type": type_}
subschema = schema[path]["properties"]
2019-06-21 23:04:04 +02:00
else:
subschema = schema
2024-10-31 08:53:58 +01:00
self.walk(
path,
childapi,
subschema,
model,
form,
order,
updates_status,
)
else:
2023-06-19 17:19:07 +02:00
child = childapi.get()
2019-06-21 23:04:04 +02:00
childtype = child.__class__.__name__
2024-10-31 08:53:58 +01:00
if childtype == "SynDynOption":
2019-12-25 20:44:56 +01:00
childtype = child.opt.__class__.__name__
2023-06-19 17:19:07 +02:00
if childapi.issymlinkoption():
2024-10-31 08:53:58 +01:00
web_type = "symlink"
2019-07-04 20:43:47 +02:00
value = None
defaultmulti = None
is_multi = False
2019-04-22 10:51:44 +02:00
else:
2023-06-19 17:19:07 +02:00
web_type = childapi.get().__class__.__name__.lower()[:-6]
web_type = CONVERT_WEB_TYPE.get(web_type, web_type)
value = childapi.value.default()
2019-06-21 23:04:04 +02:00
if value == []:
value = None
2023-06-19 17:19:07 +02:00
is_multi = childapi.ismulti()
2019-06-21 23:04:04 +02:00
if is_multi:
defaultmulti = childapi.value.defaultmulti()
2019-06-21 23:04:04 +02:00
if defaultmulti == []:
defaultmulti = None
2019-04-22 10:51:44 +02:00
else:
2019-06-21 23:04:04 +02:00
defaultmulti = None
if schema is not None:
2024-10-31 08:53:58 +01:00
self.gen_schema(
schema,
childapi,
path,
props_no_requires,
value,
defaultmulti,
is_multi,
web_type,
form,
)
2019-06-21 23:04:04 +02:00
if form is not None:
2024-10-31 08:53:58 +01:00
self.gen_form(
form,
web_type,
path,
child,
childapi,
childtype,
)
if schema is not None:
2024-10-31 08:53:58 +01:00
if web_type != "symlink":
schema[path]["title"] = childapi.description()
self.add_help(schema[path], childapi)
2019-06-21 23:04:04 +02:00
except Exception as err:
import traceback
2024-10-31 08:53:58 +01:00
traceback.print_exc()
2019-06-21 23:04:04 +02:00
if not init:
raise err
error = err
if init and form is not None:
self.callbacks.process(form)
self.requires.process(form)
self.consistencies.process(form)
del self.requires
del self.consistencies
2019-06-21 23:04:04 +02:00
del self.callbacks
if error:
msg = str(error)
del error
2024-10-31 08:53:58 +01:00
raise ConfigError(
_("unable to transform tiramisu object to dict: {}").format(msg)
)
def gen_schema(
self,
schema,
childapi,
path,
props_no_requires,
value,
defaultmulti,
is_multi,
web_type,
form,
):
schema[path] = {"type": web_type}
2023-06-19 17:19:07 +02:00
if childapi.issymlinkoption():
sym_option = childapi.get()
2024-10-31 08:53:58 +01:00
schema[path]["opt_path"] = sym_option.impl_getopt().impl_getpath()
else:
2019-06-21 23:04:04 +02:00
if defaultmulti is not None:
2024-10-31 08:53:58 +01:00
schema[path]["defaultmulti"] = defaultmulti
if is_multi:
2024-10-31 08:53:58 +01:00
schema[path]["isMulti"] = is_multi
2023-06-19 17:19:07 +02:00
if childapi.issubmulti():
2024-10-31 08:53:58 +01:00
schema[path]["isSubMulti"] = True
2024-10-31 08:53:58 +01:00
if "auto_freeze" in props_no_requires:
schema[path]["autoFreeze"] = True
2024-10-31 08:53:58 +01:00
if web_type == "choice":
# values, values_params = childapi.value.callbacks()
# if values_params:
# for values_param in chain(values_params.args, values_params.kwargs.values()):
# if isinstance(values_param, ParamOption):
# self.set_remotable(path, form, childapi)
# return
2024-10-31 08:53:58 +01:00
schema[path]["enum"] = self.get_enum(
childapi, is_multi, path, props_no_requires
)
2019-07-06 07:18:32 +02:00
if value is not None and not self.is_remote(path, form):
2024-10-31 08:53:58 +01:00
schema[path]["value"] = value
2019-05-09 20:32:43 +02:00
2024-10-31 08:53:58 +01:00
def get_enum(
self,
childapi,
is_multi,
path,
props_no_requires,
):
values = childapi.value.list()
2023-06-19 17:19:07 +02:00
empty_is_required = not childapi.isfollower() and is_multi
2024-10-31 08:53:58 +01:00
if "" not in values and (
(empty_is_required and not "empty" in props_no_requires)
or (not empty_is_required and not "mandatory" in props_no_requires)
):
values = [""] + list(values)
2019-05-09 20:32:43 +02:00
return values
2024-10-31 08:53:58 +01:00
def gen_form(
self,
form,
web_type,
path,
child,
childapi,
childtype,
):
obj_form = {}
if path in form:
obj_form.update(form[path])
2023-06-19 17:19:07 +02:00
if not childapi.issymlinkoption():
2024-10-31 08:53:58 +01:00
# if childapi_option.validator() != (None, None):
# obj_form['remote'] = True
# params = childapi_option.validator()[1]
# if params is not None:
# for param in chain(params.args, params.kwargs.values()):
# if isinstance(param, ParamOption):
# self.set_remotable(param.option.impl_getpath(), form)
2024-10-31 08:53:58 +01:00
if self.clearable == "all":
obj_form["clearable"] = True
if self.clearable != "none":
obj_form["clearable"] = True
if self.remotable == "all" or childapi.has_dependency():
obj_form["remote"] = True
if childtype == "IPOption" and (
2025-02-10 08:45:21 +01:00
child.impl_get_extra("private_only")
or not child.impl_get_extra("allow_reserved")
or child.impl_get_extra("cidr")
2024-10-31 08:53:58 +01:00
):
obj_form["remote"] = True
if childtype == "DateOption":
obj_form["remote"] = True
if not obj_form.get("remote", False):
2023-06-19 17:19:07 +02:00
pattern = childapi.pattern()
2019-06-21 23:04:04 +02:00
if pattern is not None:
2024-10-31 08:53:58 +01:00
obj_form["pattern"] = pattern
if childtype == "PortOption":
obj_form["min"] = child.impl_get_extra("_min_value")
obj_form["max"] = child.impl_get_extra("_max_value")
if childtype == "FloatOption":
obj_form["step"] = "any"
if web_type == "choice":
obj_form["type"] = "choice"
elif web_type in INPUTS:
2024-10-31 08:53:58 +01:00
obj_form["type"] = "input"
if obj_form:
form[path] = obj_form
2024-10-31 08:53:58 +01:00
def calc_raises_properties(
self,
obj,
childapi,
):
2023-04-27 11:34:35 +02:00
old_properties = childapi._config_bag.properties
config = childapi._config_bag.context
settings = config.get_settings()
2024-10-31 08:53:58 +01:00
childapi._config_bag.properties = self.config.property.default(
"current"
) # settings.get_context_properties(config._impl_properties_cache)
childapi._config_bag.properties -= {"permissive"}
properties = childapi.property.get(
only_raises=True,
uncalculated=True,
)
properties -= childapi.permissive.get()
2019-06-21 23:04:04 +02:00
# 'hidden=True' means cannot access with or without permissive option
2019-12-24 15:24:20 +01:00
# 'display=False' means cannot access only without permissive option
2019-07-04 20:43:47 +02:00
if properties:
2024-10-31 08:53:58 +01:00
obj["display"] = False
properties -= self.config.permissive.get()
2019-12-24 15:24:20 +01:00
if properties:
2024-10-31 08:53:58 +01:00
obj["hidden"] = True
2023-04-27 11:34:35 +02:00
childapi._config_bag.properties = old_properties
2024-10-31 08:53:58 +01:00
def _gen_model_properties(
self,
childapi,
path,
index,
):
2023-06-19 17:19:07 +02:00
isfollower = childapi.isfollower()
props = set(childapi.property.get())
2024-10-31 08:53:58 +01:00
obj = self.gen_properties(props, isfollower, childapi.ismulti(), index)
self.calc_raises_properties(obj, childapi)
2019-06-21 23:04:04 +02:00
return obj
2024-10-31 08:53:58 +01:00
def gen_properties(self, properties, isfollower, ismulti, index):
2019-06-21 23:04:04 +02:00
obj = {}
if not isfollower and ismulti:
2024-10-31 08:53:58 +01:00
if "empty" in properties:
if index is None:
2024-10-31 08:53:58 +01:00
obj["required"] = True
properties.remove("empty")
if "mandatory" in properties:
if index is None:
2024-10-31 08:53:58 +01:00
obj["needs_len"] = True
properties.remove("mandatory")
elif "mandatory" in properties:
if index is None:
2024-10-31 08:53:58 +01:00
obj["required"] = True
properties.remove("mandatory")
if "frozen" in properties:
if index is None:
2024-10-31 08:53:58 +01:00
obj["readOnly"] = True
properties.remove("frozen")
if "hidden" in properties:
properties.remove("hidden")
if "disabled" in properties:
properties.remove("disabled")
2019-06-21 23:04:04 +02:00
if properties:
lprops = list(properties)
lprops.sort()
2024-10-31 08:53:58 +01:00
obj["properties"] = lprops
return obj
2024-10-31 08:53:58 +01:00
def gen_model(
self,
model,
childapi,
path,
leader_len,
updates_status,
):
2023-06-19 17:19:07 +02:00
if childapi.isoptiondescription():
props = set(childapi.property.get())
obj = {}
self.calc_raises_properties(obj, childapi)
if props:
lprops = list(props)
lprops.sort()
2024-10-31 08:53:58 +01:00
obj["properties"] = lprops
try:
2023-06-19 17:19:07 +02:00
self.config.option(path).get()
except PropertiesOptionError:
pass
else:
2024-10-31 08:53:58 +01:00
obj = self._gen_model_properties(
childapi,
path,
None,
)
2023-06-19 17:19:07 +02:00
if childapi.isfollower():
for index in range(leader_len):
follower_childapi = self.config.unrestraint.option(path, index)
2024-10-31 08:53:58 +01:00
sobj = self._gen_model_properties(
follower_childapi,
path,
index,
)
self._get_model_value(
follower_childapi,
path,
sobj,
index,
updates_status,
)
if sobj:
model.setdefault(path, {})[str(index)] = sobj
else:
2024-10-31 08:53:58 +01:00
self._get_model_value(
childapi,
path,
obj,
None,
updates_status,
)
if obj:
2023-06-19 17:19:07 +02:00
if not childapi.isoptiondescription() and childapi.isfollower():
2024-10-31 08:53:58 +01:00
model.setdefault(path, {})["null"] = obj
else:
model[path] = obj
2024-10-31 08:53:58 +01:00
def _get_model_value(
self,
childapi,
path,
obj,
index,
updates_status,
):
2019-06-21 23:04:04 +02:00
if path in updates_status and index in updates_status[path]:
value = childapi.value.get()
2024-10-31 08:53:58 +01:00
self._get_value_with_exception(obj, childapi, updates_status[path][index])
2019-06-21 23:04:04 +02:00
del updates_status[path][index]
else:
try:
with warnings.catch_warnings(record=True) as warns:
value = self.config.option(path, index=index).value.get()
2024-10-31 08:53:58 +01:00
self._get_value_with_exception(obj, childapi, warns)
2019-06-21 23:04:04 +02:00
except ValueError as err:
2024-10-31 08:53:58 +01:00
self._get_value_with_exception(obj, childapi, [err])
value = self.config.unrestraint.option(path, index=index).value.get()
2019-06-21 23:04:04 +02:00
except PropertiesOptionError as err:
2019-07-04 20:43:47 +02:00
config_bag = self.config._config_bag
settings = config_bag.context.get_settings()
2024-10-31 08:53:58 +01:00
if settings._calc_raises_properties(
childapi._subconfig,
set(err.proptype),
not_unrestraint=True,
):
obj["hidden"] = True
obj["display"] = False
value = childapi.value.get()
if value is not None and value != []:
2024-10-31 08:53:58 +01:00
obj["value"] = value
if not childapi.owner.isdefault():
2024-10-31 08:53:58 +01:00
obj["owner"] = childapi.owner.get()
2024-10-31 08:53:58 +01:00
def _get_value_with_exception(self, obj, childapi, values):
for value in values:
2019-06-21 23:04:04 +02:00
if isinstance(value, ValueError):
2024-10-31 08:53:58 +01:00
obj.setdefault("error", [])
msg = str(value)
2024-10-31 08:53:58 +01:00
if msg not in obj.get("error", []):
obj["error"].append(msg)
obj["invalid"] = True
2019-06-21 23:04:04 +02:00
elif isinstance(value.message, ValueErrorWarning):
2024-10-31 08:53:58 +01:00
value.message.prefix = ""
obj.setdefault("error", [])
msg = str(value.message)
2024-10-31 08:53:58 +01:00
if msg not in obj.get("error", []):
obj["error"].append(msg)
obj["invalid"] = True
else:
2024-10-31 08:53:58 +01:00
value.message.prefix = ""
obj.setdefault("warnings", [])
msg = str(value.message)
2024-10-31 08:53:58 +01:00
if msg not in obj.get("error", []):
obj["warnings"].append(msg)
obj["hasWarnings"] = True
def gen_global(self):
2019-06-21 23:04:04 +02:00
ret = {}
2024-10-31 08:53:58 +01:00
ret["owner"] = self.config.owner.get()
ret["properties"] = list(self.config.property.get())
ret["properties"].sort()
ret["permissives"] = list(self.config.permissive.get())
ret["permissives"].sort()
2019-06-21 23:04:04 +02:00
return ret
def get_form(self, form):
ret = []
buttons = []
2019-06-21 23:04:04 +02:00
dict_form = {}
for form_ in form:
2024-10-31 08:53:58 +01:00
if "key" in form_:
dict_form[form_["key"]] = form_
elif form_.get("type") == "submit":
if "cmd" not in form_:
form_["cmd"] = "submit"
buttons.append(form_)
else:
2024-10-31 08:53:58 +01:00
raise ValueError(_("unknown form {}").format(form_))
for key, form_ in self.form.items():
2024-10-31 08:53:58 +01:00
form_["key"] = key
if key in dict_form:
form_.update(dict_form[key])
ret.append(form_)
ret.extend(buttons)
return ret
def del_value(self, childapi, path, index):
2023-06-19 17:19:07 +02:00
if index is not None and childapi.isleader():
childapi.value.pop(index)
2023-06-19 17:19:07 +02:00
elif index is None or childapi.isfollower():
childapi.value.reset()
else:
multi = childapi.value.get()
multi.pop(index)
childapi.value.set(multi)
def add_value(self, childapi, path, value):
multi = childapi.value.get()
multi.append(value)
childapi.value.set(multi)
def mod_value(self, childapi, path, index, value):
2023-06-19 17:19:07 +02:00
if index is None or childapi.isfollower():
childapi.value.set(value)
else:
multi = childapi.value.get()
2019-06-21 23:04:04 +02:00
if len(multi) < index + 1:
multi.append(value)
else:
multi[index] = value
childapi.value.set(multi)
2024-10-31 08:53:58 +01:00
def apply_updates(
self,
oripath,
updates,
model_ori,
):
updates_status = {}
for update in updates:
2024-10-31 08:53:58 +01:00
path = update["name"]
index = update.get("index")
if oripath is not None and not path.startswith(oripath):
2024-10-31 08:53:58 +01:00
raise ValueError(_("not in current area"))
childapi = self.config.option(path)
2023-06-19 17:19:07 +02:00
if childapi.isfollower():
childapi = self.config.option(path, index)
with warnings.catch_warnings(record=True) as warns:
2019-06-21 23:04:04 +02:00
try:
2024-10-31 08:53:58 +01:00
if update["action"] == "modify":
self.mod_value(
childapi,
path,
index,
update.get("value", undefined),
)
elif update["action"] == "delete":
self.del_value(
childapi,
path,
index,
)
elif update["action"] == "add":
2023-06-19 17:19:07 +02:00
if childapi.ismulti():
2024-10-31 08:53:58 +01:00
self.add_value(childapi, path, update["value"])
2019-06-21 23:04:04 +02:00
else:
2024-10-31 08:53:58 +01:00
raise ValueError(
_(
'only multi option can have action "add", but "{}" is not a multi'
).format(path)
)
else:
2024-10-31 08:53:58 +01:00
raise ValueError(
_("unknown action {}").format(update["action"])
)
2019-06-21 23:04:04 +02:00
except ValueError as err:
updates_status.setdefault(path, {})[index] = [err]
if warns != []:
updates_status.setdefault(path, {}).setdefault(index, []).extend(warns)
return updates_status
2024-10-31 08:53:58 +01:00
def set_updates(
self,
body,
):
root_path = self.root
2024-10-31 08:53:58 +01:00
updates = body.get("updates", [])
updates_status = self.apply_updates(
root_path,
updates,
body.get("model"),
)
if "model" in body:
order = []
2024-10-31 08:53:58 +01:00
old_model = body["model"]
new_model = self.todict(
order=order,
build_schema=False,
build_form=False,
updates_status=updates_status,
)
values = {
"updates": list_keys(
old_model, new_model["model"], order, updates_status
),
"model": new_model["model"],
}
else:
2019-12-08 08:15:47 +01:00
values = updates_status
return values
2024-10-31 08:53:58 +01:00
def todict(
self,
custom_form=[],
build_schema=True,
build_model=True,
build_form=True,
order=None,
updates_status={},
):
rootpath = self.root
if build_schema:
2019-06-21 23:04:04 +02:00
schema = {}
else:
schema = None
if build_model:
model = {}
else:
model = None
if build_form:
form = {}
buttons = []
else:
form = None
2024-10-31 08:53:58 +01:00
self.walk(
rootpath,
None,
schema,
model,
form,
order,
updates_status,
init=True,
)
if build_form:
for form_ in custom_form:
2024-10-31 08:53:58 +01:00
if "key" in form_:
key = form_.pop("key")
form.setdefault(key, {}).update(form_)
2024-10-31 08:53:58 +01:00
elif form_.get("type") == "submit":
# FIXME if an Option has a key "null"?
form.setdefault(None, []).append(form_)
else:
2024-10-31 08:53:58 +01:00
raise ValueError(_("unknown form {}").format(form_))
ret = {}
if build_schema:
2024-10-31 08:53:58 +01:00
ret["schema"] = schema
if build_model:
2024-10-31 08:53:58 +01:00
ret["model"] = model
ret["global"] = self.gen_global()
if build_form:
2024-10-31 08:53:58 +01:00
ret["form"] = form
ret["version"] = "1.0"
return ret
def list_keys(model_a, model_b, ordered_key, updates_status):
model_a_dict = {}
model_b_dict = {}
keys_a = set(model_a.keys())
keys_b = set(model_b.keys())
keys = (keys_a ^ keys_b) | set(updates_status.keys())
for key in keys_a & keys_b:
keys_mod_a = set(model_a[key].keys())
keys_mod_b = set(model_b[key].keys())
if keys_mod_a != keys_mod_b:
keys.add(key)
else:
for skey in keys_mod_a:
if model_a[key][skey] != model_b[key][skey]:
keys.add(key)
break
2024-10-31 08:53:58 +01:00
def sort_key(key):
try:
return ordered_key.index(key)
except ValueError:
return -1
2024-10-31 08:53:58 +01:00
return sorted(list(keys), key=sort_key)