mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
typing: add missing type + misc typing fixes
This commit is contained in:
parent
54cc23c90c
commit
37b4eb956d
6 changed files with 72 additions and 59 deletions
13
src/app.py
13
src/app.py
|
@ -1814,26 +1814,29 @@ class AppConfigPanel(ConfigPanel):
|
||||||
form: "FormModel",
|
form: "FormModel",
|
||||||
previous_settings: dict[str, Any],
|
previous_settings: dict[str, Any],
|
||||||
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
||||||
):
|
) -> None:
|
||||||
env = {key: str(value) for key, value in form.dict().items()}
|
env = {key: str(value) for key, value in form.dict().items()}
|
||||||
return_content = self._call_config_script("apply", env=env)
|
return_content = self._call_config_script("apply", env=env)
|
||||||
|
|
||||||
# If the script returned validation error
|
# If the script returned validation error
|
||||||
# raise a ValidationError exception using
|
# raise a ValidationError exception using
|
||||||
# the first key
|
# the first key
|
||||||
if return_content:
|
errors = return_content.get("validation_errors")
|
||||||
for key, message in return_content.get("validation_errors").items():
|
if errors:
|
||||||
|
for key, message in errors.items():
|
||||||
raise YunohostValidationError(
|
raise YunohostValidationError(
|
||||||
"app_argument_invalid",
|
"app_argument_invalid",
|
||||||
name=key,
|
name=key,
|
||||||
error=message,
|
error=message,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _run_action(self, form: "FormModel", action_id: str):
|
def _run_action(self, form: "FormModel", action_id: str) -> None:
|
||||||
env = {key: str(value) for key, value in form.dict().items()}
|
env = {key: str(value) for key, value in form.dict().items()}
|
||||||
self._call_config_script(action_id, env=env)
|
self._call_config_script(action_id, env=env)
|
||||||
|
|
||||||
def _call_config_script(self, action, env=None):
|
def _call_config_script(
|
||||||
|
self, action: str, env: Union[dict[str, Any], None] = None
|
||||||
|
) -> dict[str, Any]:
|
||||||
from yunohost.hook import hook_exec
|
from yunohost.hook import hook_exec
|
||||||
|
|
||||||
if env is None:
|
if env is None:
|
||||||
|
|
|
@ -720,7 +720,7 @@ class DomainConfigPanel(ConfigPanel):
|
||||||
form: "FormModel",
|
form: "FormModel",
|
||||||
previous_settings: dict[str, Any],
|
previous_settings: dict[str, Any],
|
||||||
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
||||||
):
|
) -> None:
|
||||||
next_settings = {
|
next_settings = {
|
||||||
k: v for k, v in form.dict().items() if previous_settings.get(k) != v
|
k: v for k, v in form.dict().items() if previous_settings.get(k) != v
|
||||||
}
|
}
|
||||||
|
|
|
@ -469,7 +469,7 @@ class OperationLogger:
|
||||||
This class record logs and metadata like context or start time/end time.
|
This class record logs and metadata like context or start time/end time.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_instances: List[object] = []
|
_instances: List["OperationLogger"] = []
|
||||||
|
|
||||||
def __init__(self, operation, related_to=None, **kwargs):
|
def __init__(self, operation, related_to=None, **kwargs):
|
||||||
# TODO add a way to not save password on app installation
|
# TODO add a way to not save password on app installation
|
||||||
|
|
|
@ -136,7 +136,7 @@ class SettingsConfigPanel(ConfigPanel):
|
||||||
save_mode = "diff"
|
save_mode = "diff"
|
||||||
virtual_settings = {"root_password", "root_password_confirm", "passwordless_sudo"}
|
virtual_settings = {"root_password", "root_password_confirm", "passwordless_sudo"}
|
||||||
|
|
||||||
def __init__(self, config_path=None, save_path=None, creation=False):
|
def __init__(self, config_path=None, save_path=None, creation=False) -> None:
|
||||||
super().__init__("settings")
|
super().__init__("settings")
|
||||||
|
|
||||||
def get(
|
def get(
|
||||||
|
@ -150,7 +150,11 @@ class SettingsConfigPanel(ConfigPanel):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def reset(self, key: Union[str, None] = None, operation_logger: Union["OperationLogger", None] = None,):
|
def reset(
|
||||||
|
self,
|
||||||
|
key: Union[str, None] = None,
|
||||||
|
operation_logger: Union["OperationLogger", None] = None,
|
||||||
|
) -> None:
|
||||||
self.filter_key = parse_filter_key(key)
|
self.filter_key = parse_filter_key(key)
|
||||||
|
|
||||||
# Read config panel toml
|
# Read config panel toml
|
||||||
|
@ -160,8 +164,11 @@ class SettingsConfigPanel(ConfigPanel):
|
||||||
previous_settings = self.form.dict()
|
previous_settings = self.form.dict()
|
||||||
|
|
||||||
for option in self.config.options:
|
for option in self.config.options:
|
||||||
if not option.readonly and (option.optional or option.default not in {None, ""}):
|
if not option.readonly and (
|
||||||
self.form[option.id] = option.normalize(option.default, option)
|
option.optional or option.default not in {None, ""}
|
||||||
|
):
|
||||||
|
# FIXME Mypy complains about option.default not being a valid type for normalize but this should be ok
|
||||||
|
self.form[option.id] = option.normalize(option.default, option) # type: ignore
|
||||||
|
|
||||||
# FIXME Not sure if this is need (redact call to operation logger does it on all the instances)
|
# FIXME Not sure if this is need (redact call to operation logger does it on all the instances)
|
||||||
# BaseOption.operation_logger = operation_logger
|
# BaseOption.operation_logger = operation_logger
|
||||||
|
@ -230,7 +237,7 @@ class SettingsConfigPanel(ConfigPanel):
|
||||||
form: "FormModel",
|
form: "FormModel",
|
||||||
previous_settings: dict[str, Any],
|
previous_settings: dict[str, Any],
|
||||||
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
||||||
):
|
) -> None:
|
||||||
root_password = form.get("root_password", None)
|
root_password = form.get("root_password", None)
|
||||||
root_password_confirm = form.get("root_password_confirm", None)
|
root_password_confirm = form.get("root_password_confirm", None)
|
||||||
passwordless_sudo = form.get("passwordless_sudo", None)
|
passwordless_sudo = form.get("passwordless_sudo", None)
|
||||||
|
|
|
@ -21,7 +21,7 @@ import os
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from typing import TYPE_CHECKING, Any, Literal, Sequence, Type, Union
|
from typing import TYPE_CHECKING, Any, Iterator, Literal, Sequence, Type, Union
|
||||||
|
|
||||||
from pydantic import BaseModel, Extra, validator
|
from pydantic import BaseModel, Extra, validator
|
||||||
|
|
||||||
|
@ -32,7 +32,6 @@ from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||||
from yunohost.utils.form import (
|
from yunohost.utils.form import (
|
||||||
AnyOption,
|
AnyOption,
|
||||||
BaseInputOption,
|
BaseInputOption,
|
||||||
BaseOption,
|
|
||||||
BaseReadonlyOption,
|
BaseReadonlyOption,
|
||||||
FileOption,
|
FileOption,
|
||||||
OptionsModel,
|
OptionsModel,
|
||||||
|
@ -70,7 +69,7 @@ class ContainerModel(BaseModel):
|
||||||
services: list[str] = []
|
services: list[str] = []
|
||||||
help: Union[Translation, None] = None
|
help: Union[Translation, None] = None
|
||||||
|
|
||||||
def translate(self, i18n_key: Union[str, None] = None):
|
def translate(self, i18n_key: Union[str, None] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Translate `ask` and `name` attributes of panels and section.
|
Translate `ask` and `name` attributes of panels and section.
|
||||||
This is in place mutation.
|
This is in place mutation.
|
||||||
|
@ -111,16 +110,16 @@ class SectionModel(ContainerModel, OptionsModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_action_section(self):
|
def is_action_section(self) -> bool:
|
||||||
return any([option.type is OptionType.button for option in self.options])
|
return any([option.type is OptionType.button for option in self.options])
|
||||||
|
|
||||||
def is_visible(self, context: dict[str, Any]):
|
def is_visible(self, context: dict[str, Any]) -> bool:
|
||||||
if isinstance(self.visible, bool):
|
if isinstance(self.visible, bool):
|
||||||
return self.visible
|
return self.visible
|
||||||
|
|
||||||
return evaluate_simple_js_expression(self.visible, context=context)
|
return evaluate_simple_js_expression(self.visible, context=context)
|
||||||
|
|
||||||
def translate(self, i18n_key: Union[str, None] = None):
|
def translate(self, i18n_key: Union[str, None] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Call to `Container`'s `translate` for self translation
|
Call to `Container`'s `translate` for self translation
|
||||||
+ Call to `OptionsContainer`'s `translate_options` for options translation
|
+ Call to `OptionsContainer`'s `translate_options` for options translation
|
||||||
|
@ -151,7 +150,7 @@ class PanelModel(ContainerModel):
|
||||||
id=id, name=name, services=services, help=help, sections=sections
|
id=id, name=name, services=services, help=help, sections=sections
|
||||||
)
|
)
|
||||||
|
|
||||||
def translate(self, i18n_key: Union[str, None] = None):
|
def translate(self, i18n_key: Union[str, None] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Recursivly mutate translatable attributes to their translation
|
Recursivly mutate translatable attributes to their translation
|
||||||
"""
|
"""
|
||||||
|
@ -181,14 +180,14 @@ class ConfigPanelModel(BaseModel):
|
||||||
super().__init__(version=version, i18n=i18n, panels=panels)
|
super().__init__(version=version, i18n=i18n, panels=panels)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sections(self):
|
def sections(self) -> Iterator[SectionModel]:
|
||||||
"""Convinient prop to iter on all sections"""
|
"""Convinient prop to iter on all sections"""
|
||||||
for panel in self.panels:
|
for panel in self.panels:
|
||||||
for section in panel.sections:
|
for section in panel.sections:
|
||||||
yield section
|
yield section
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def options(self):
|
def options(self) -> Iterator[AnyOption]:
|
||||||
"""Convinient prop to iter on all options"""
|
"""Convinient prop to iter on all options"""
|
||||||
for section in self.sections:
|
for section in self.sections:
|
||||||
for option in section.options:
|
for option in section.options:
|
||||||
|
@ -231,7 +230,7 @@ class ConfigPanelModel(BaseModel):
|
||||||
for option in section.options:
|
for option in section.options:
|
||||||
yield (panel, section, option)
|
yield (panel, section, option)
|
||||||
|
|
||||||
def translate(self):
|
def translate(self) -> None:
|
||||||
"""
|
"""
|
||||||
Recursivly mutate translatable attributes to their translation
|
Recursivly mutate translatable attributes to their translation
|
||||||
"""
|
"""
|
||||||
|
@ -239,7 +238,7 @@ class ConfigPanelModel(BaseModel):
|
||||||
panel.translate(self.i18n)
|
panel.translate(self.i18n)
|
||||||
|
|
||||||
@validator("version", always=True)
|
@validator("version", always=True)
|
||||||
def check_version(cls, value, field: "ModelField"):
|
def check_version(cls, value: float, field: "ModelField") -> float:
|
||||||
if value < CONFIG_PANEL_VERSION_SUPPORTED:
|
if value < CONFIG_PANEL_VERSION_SUPPORTED:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Config panels version '{value}' are no longer supported."
|
f"Config panels version '{value}' are no longer supported."
|
||||||
|
@ -302,7 +301,9 @@ class ConfigPanel:
|
||||||
entities = []
|
entities = []
|
||||||
return entities
|
return entities
|
||||||
|
|
||||||
def __init__(self, entity, config_path=None, save_path=None, creation=False):
|
def __init__(
|
||||||
|
self, entity, config_path=None, save_path=None, creation=False
|
||||||
|
) -> None:
|
||||||
self.entity = entity
|
self.entity = entity
|
||||||
self.config_path = config_path
|
self.config_path = config_path
|
||||||
if not config_path:
|
if not config_path:
|
||||||
|
@ -350,7 +351,7 @@ class ConfigPanel:
|
||||||
if option is None:
|
if option is None:
|
||||||
# FIXME i18n
|
# FIXME i18n
|
||||||
raise YunohostValidationError(
|
raise YunohostValidationError(
|
||||||
f"Couldn't find any option with id {option_id}"
|
f"Couldn't find any option with id {option_id}", raw_msg=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(option, BaseReadonlyOption):
|
if isinstance(option, BaseReadonlyOption):
|
||||||
|
@ -398,7 +399,7 @@ class ConfigPanel:
|
||||||
args: Union[str, None] = None,
|
args: Union[str, None] = None,
|
||||||
args_file: Union[str, None] = None,
|
args_file: Union[str, None] = None,
|
||||||
operation_logger: Union["OperationLogger", None] = None,
|
operation_logger: Union["OperationLogger", None] = None,
|
||||||
):
|
) -> None:
|
||||||
self.filter_key = parse_filter_key(key)
|
self.filter_key = parse_filter_key(key)
|
||||||
panel_id, section_id, option_id = self.filter_key
|
panel_id, section_id, option_id = self.filter_key
|
||||||
|
|
||||||
|
@ -466,7 +467,7 @@ class ConfigPanel:
|
||||||
if operation_logger:
|
if operation_logger:
|
||||||
operation_logger.success()
|
operation_logger.success()
|
||||||
|
|
||||||
def list_actions(self):
|
def list_actions(self) -> dict[str, str]:
|
||||||
actions = {}
|
actions = {}
|
||||||
|
|
||||||
# FIXME : meh, loading the entire config panel is again going to cause
|
# FIXME : meh, loading the entire config panel is again going to cause
|
||||||
|
@ -486,7 +487,7 @@ class ConfigPanel:
|
||||||
args: Union[str, None] = None,
|
args: Union[str, None] = None,
|
||||||
args_file: Union[str, None] = None,
|
args_file: Union[str, None] = None,
|
||||||
operation_logger: Union["OperationLogger", None] = None,
|
operation_logger: Union["OperationLogger", None] = None,
|
||||||
):
|
) -> None:
|
||||||
#
|
#
|
||||||
# FIXME : this stuff looks a lot like set() ...
|
# FIXME : this stuff looks a lot like set() ...
|
||||||
#
|
#
|
||||||
|
@ -666,7 +667,7 @@ class ConfigPanel:
|
||||||
def _ask(
|
def _ask(
|
||||||
self,
|
self,
|
||||||
config: ConfigPanelModel,
|
config: ConfigPanelModel,
|
||||||
settings: "FormModel",
|
form: "FormModel",
|
||||||
prefilled_answers: dict[str, Any] = {},
|
prefilled_answers: dict[str, Any] = {},
|
||||||
action_id: Union[str, None] = None,
|
action_id: Union[str, None] = None,
|
||||||
hooks: "Hooks" = {},
|
hooks: "Hooks" = {},
|
||||||
|
@ -709,22 +710,22 @@ class ConfigPanel:
|
||||||
if option.type is not OptionType.button or option.id == action_id
|
if option.type is not OptionType.button or option.id == action_id
|
||||||
]
|
]
|
||||||
|
|
||||||
settings = prompt_or_validate_form(
|
form = prompt_or_validate_form(
|
||||||
options,
|
options,
|
||||||
settings,
|
form,
|
||||||
prefilled_answers=prefilled_answers,
|
prefilled_answers=prefilled_answers,
|
||||||
context=context,
|
context=context,
|
||||||
hooks=hooks,
|
hooks=hooks,
|
||||||
)
|
)
|
||||||
|
|
||||||
return settings
|
return form
|
||||||
|
|
||||||
def _apply(
|
def _apply(
|
||||||
self,
|
self,
|
||||||
form: "FormModel",
|
form: "FormModel",
|
||||||
previous_settings: dict[str, Any],
|
previous_settings: dict[str, Any],
|
||||||
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
||||||
) -> dict[str, Any]:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Save settings in yaml file.
|
Save settings in yaml file.
|
||||||
If `save_mode` is `"diff"` (which is the default), only values that are
|
If `save_mode` is `"diff"` (which is the default), only values that are
|
||||||
|
@ -764,15 +765,13 @@ class ConfigPanel:
|
||||||
# Save the settings to the .yaml file
|
# Save the settings to the .yaml file
|
||||||
write_to_yaml(self.save_path, current_settings)
|
write_to_yaml(self.save_path, current_settings)
|
||||||
|
|
||||||
return current_settings
|
def _run_action(self, form: "FormModel", action_id: str) -> None:
|
||||||
|
|
||||||
def _run_action(self, form: "FormModel", action_id: str):
|
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _reload_services(self):
|
def _reload_services(self) -> None:
|
||||||
from yunohost.service import service_reload_or_restart
|
from yunohost.service import service_reload_or_restart
|
||||||
|
|
||||||
services_to_reload = self.config.services
|
services_to_reload = self.config.services if self.config else []
|
||||||
|
|
||||||
if services_to_reload:
|
if services_to_reload:
|
||||||
logger.info("Reloading services...")
|
logger.info("Reloading services...")
|
||||||
|
|
|
@ -32,7 +32,7 @@ from typing import (
|
||||||
Annotated,
|
Annotated,
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
Callable,
|
||||||
List,
|
Iterable,
|
||||||
Literal,
|
Literal,
|
||||||
Mapping,
|
Mapping,
|
||||||
Type,
|
Type,
|
||||||
|
@ -207,7 +207,7 @@ def js_to_python(expr):
|
||||||
return py_expr
|
return py_expr
|
||||||
|
|
||||||
|
|
||||||
def evaluate_simple_js_expression(expr, context={}):
|
def evaluate_simple_js_expression(expr: str, context: dict[str, Any] = {}) -> bool:
|
||||||
if not expr.strip():
|
if not expr.strip():
|
||||||
return False
|
return False
|
||||||
node = ast.parse(js_to_python(expr), mode="eval").body
|
node = ast.parse(js_to_python(expr), mode="eval").body
|
||||||
|
@ -650,7 +650,7 @@ class NumberOption(BaseInputOption):
|
||||||
_none_as_empty_str = False
|
_none_as_empty_str = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def normalize(value, option={}):
|
def normalize(value, option={}) -> Union[int, None]:
|
||||||
if isinstance(value, int):
|
if isinstance(value, int):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -704,7 +704,7 @@ class BooleanOption(BaseInputOption):
|
||||||
_none_as_empty_str = False
|
_none_as_empty_str = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def humanize(value, option={}):
|
def humanize(value, option={}) -> str:
|
||||||
option = option.dict() if isinstance(option, BaseOption) else option
|
option = option.dict() if isinstance(option, BaseOption) else option
|
||||||
|
|
||||||
yes = option.get("yes", 1)
|
yes = option.get("yes", 1)
|
||||||
|
@ -727,7 +727,7 @@ class BooleanOption(BaseInputOption):
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def normalize(value, option={}):
|
def normalize(value, option={}) -> Any:
|
||||||
option = option.dict() if isinstance(option, BaseOption) else option
|
option = option.dict() if isinstance(option, BaseOption) else option
|
||||||
|
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
|
@ -844,7 +844,7 @@ class WebPathOption(BaseInputOption):
|
||||||
_annotation = str
|
_annotation = str
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def normalize(value, option={}):
|
def normalize(value, option={}) -> str:
|
||||||
option = option.dict() if isinstance(option, BaseOption) else option
|
option = option.dict() if isinstance(option, BaseOption) else option
|
||||||
|
|
||||||
if value is None:
|
if value is None:
|
||||||
|
@ -892,14 +892,14 @@ class FileOption(BaseInputOption):
|
||||||
_upload_dirs: set[str] = set()
|
_upload_dirs: set[str] = set()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clean_upload_dirs(cls):
|
def clean_upload_dirs(cls) -> None:
|
||||||
# Delete files uploaded from API
|
# Delete files uploaded from API
|
||||||
for upload_dir in cls._upload_dirs:
|
for upload_dir in cls._upload_dirs:
|
||||||
if os.path.exists(upload_dir):
|
if os.path.exists(upload_dir):
|
||||||
shutil.rmtree(upload_dir)
|
shutil.rmtree(upload_dir)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _value_post_validator(cls, value: Any, field: "ModelField") -> Any:
|
def _value_post_validator(cls, value: Any, field: "ModelField") -> str:
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
|
|
||||||
if not value:
|
if not value:
|
||||||
|
@ -967,7 +967,6 @@ class BaseChoicesOption(BaseInputOption):
|
||||||
choices = (
|
choices = (
|
||||||
self.choices if isinstance(self.choices, list) else self.choices.keys()
|
self.choices if isinstance(self.choices, list) else self.choices.keys()
|
||||||
)
|
)
|
||||||
# FIXME in case of dict, try to parse keys with `item_type` (at least number)
|
|
||||||
return Literal[tuple(choices)]
|
return Literal[tuple(choices)]
|
||||||
|
|
||||||
return self._annotation
|
return self._annotation
|
||||||
|
@ -1006,6 +1005,7 @@ class BaseChoicesOption(BaseInputOption):
|
||||||
|
|
||||||
class SelectOption(BaseChoicesOption):
|
class SelectOption(BaseChoicesOption):
|
||||||
type: Literal[OptionType.select] = OptionType.select
|
type: Literal[OptionType.select] = OptionType.select
|
||||||
|
filter: Literal[None] = None
|
||||||
choices: Union[dict[str, Any], list[Any]]
|
choices: Union[dict[str, Any], list[Any]]
|
||||||
default: Union[str, None]
|
default: Union[str, None]
|
||||||
_annotation = str
|
_annotation = str
|
||||||
|
@ -1013,13 +1013,14 @@ class SelectOption(BaseChoicesOption):
|
||||||
|
|
||||||
class TagsOption(BaseChoicesOption):
|
class TagsOption(BaseChoicesOption):
|
||||||
type: Literal[OptionType.tags] = OptionType.tags
|
type: Literal[OptionType.tags] = OptionType.tags
|
||||||
|
filter: Literal[None] = None
|
||||||
choices: Union[list[str], None] = None
|
choices: Union[list[str], None] = None
|
||||||
pattern: Union[Pattern, None] = None
|
pattern: Union[Pattern, None] = None
|
||||||
default: Union[str, list[str], None]
|
default: Union[str, list[str], None]
|
||||||
_annotation = str
|
_annotation = str
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def humanize(value, option={}):
|
def humanize(value, option={}) -> str:
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
return ",".join(str(v) for v in value)
|
return ",".join(str(v) for v in value)
|
||||||
if not value:
|
if not value:
|
||||||
|
@ -1027,7 +1028,7 @@ class TagsOption(BaseChoicesOption):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def normalize(value, option={}):
|
def normalize(value, option={}) -> str:
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
return ",".join(str(v) for v in value)
|
return ",".join(str(v) for v in value)
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
|
@ -1037,7 +1038,7 @@ class TagsOption(BaseChoicesOption):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _dynamic_annotation(self):
|
def _dynamic_annotation(self) -> Type[str]:
|
||||||
# TODO use Literal when serialization is seperated from validation
|
# TODO use Literal when serialization is seperated from validation
|
||||||
# if self.choices is not None:
|
# if self.choices is not None:
|
||||||
# return Literal[tuple(self.choices)]
|
# return Literal[tuple(self.choices)]
|
||||||
|
@ -1120,7 +1121,7 @@ class DomainOption(BaseChoicesOption):
|
||||||
return _get_maindomain()
|
return _get_maindomain()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def normalize(value, option={}):
|
def normalize(value, option={}) -> str:
|
||||||
if value.startswith("https://"):
|
if value.startswith("https://"):
|
||||||
value = value[len("https://") :]
|
value = value[len("https://") :]
|
||||||
elif value.startswith("http://"):
|
elif value.startswith("http://"):
|
||||||
|
@ -1314,7 +1315,9 @@ class OptionsModel(BaseModel):
|
||||||
options: list[Annotated[AnyOption, Field(discriminator="type")]]
|
options: list[Annotated[AnyOption, Field(discriminator="type")]]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def options_dict_to_list(options: dict[str, Any], optional: bool = False):
|
def options_dict_to_list(
|
||||||
|
options: dict[str, Any], optional: bool = False
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
return [
|
return [
|
||||||
option
|
option
|
||||||
| {
|
| {
|
||||||
|
@ -1329,7 +1332,7 @@ class OptionsModel(BaseModel):
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
super().__init__(options=self.options_dict_to_list(kwargs))
|
super().__init__(options=self.options_dict_to_list(kwargs))
|
||||||
|
|
||||||
def translate_options(self, i18n_key: Union[str, None] = None):
|
def translate_options(self, i18n_key: Union[str, None] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Mutate in place translatable attributes of options to their translations
|
Mutate in place translatable attributes of options to their translations
|
||||||
"""
|
"""
|
||||||
|
@ -1359,7 +1362,7 @@ class FormModel(BaseModel):
|
||||||
validate_assignment = True
|
validate_assignment = True
|
||||||
extra = Extra.ignore
|
extra = Extra.ignore
|
||||||
|
|
||||||
def __getitem__(self, name: str):
|
def __getitem__(self, name: str) -> Any:
|
||||||
# FIXME
|
# FIXME
|
||||||
# if a FormModel's required field is not instancied with a value, it is
|
# if a FormModel's required field is not instancied with a value, it is
|
||||||
# not available as an attr and therefor triggers an `AttributeError`
|
# not available as an attr and therefor triggers an `AttributeError`
|
||||||
|
@ -1372,7 +1375,7 @@ class FormModel(BaseModel):
|
||||||
|
|
||||||
return getattr(self, name)
|
return getattr(self, name)
|
||||||
|
|
||||||
def __setitem__(self, name: str, value: Any):
|
def __setitem__(self, name: str, value: Any) -> None:
|
||||||
setattr(self, name, value)
|
setattr(self, name, value)
|
||||||
|
|
||||||
def get(self, attr: str, default: Any = None) -> Any:
|
def get(self, attr: str, default: Any = None) -> Any:
|
||||||
|
@ -1382,7 +1385,9 @@ class FormModel(BaseModel):
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
def build_form(options: list[AnyOption], name: str = "DynamicForm") -> Type[FormModel]:
|
def build_form(
|
||||||
|
options: Iterable[AnyOption], name: str = "DynamicForm"
|
||||||
|
) -> Type[FormModel]:
|
||||||
"""
|
"""
|
||||||
Returns a dynamic pydantic model class that can be used as a form.
|
Returns a dynamic pydantic model class that can be used as a form.
|
||||||
Parsing/validation occurs at instanciation and assignements.
|
Parsing/validation occurs at instanciation and assignements.
|
||||||
|
@ -1468,7 +1473,7 @@ MAX_RETRIES = 4
|
||||||
|
|
||||||
|
|
||||||
def prompt_or_validate_form(
|
def prompt_or_validate_form(
|
||||||
options: list[AnyOption],
|
options: Iterable[AnyOption],
|
||||||
form: FormModel,
|
form: FormModel,
|
||||||
prefilled_answers: dict[str, Any] = {},
|
prefilled_answers: dict[str, Any] = {},
|
||||||
context: Context = {},
|
context: Context = {},
|
||||||
|
@ -1503,7 +1508,6 @@ def prompt_or_validate_form(
|
||||||
|
|
||||||
if isinstance(option, BaseReadonlyOption) or option.readonly:
|
if isinstance(option, BaseReadonlyOption) or option.readonly:
|
||||||
if isinstance(option, BaseInputOption):
|
if isinstance(option, BaseInputOption):
|
||||||
# FIXME normalized needed, form[option.id] should already be normalized
|
|
||||||
# only update the context with the value
|
# only update the context with the value
|
||||||
context[option.id] = option.normalize(form[option.id])
|
context[option.id] = option.normalize(form[option.id])
|
||||||
|
|
||||||
|
@ -1623,7 +1627,7 @@ def ask_questions_and_parse_answers(
|
||||||
return (model.options, form)
|
return (model.options, form)
|
||||||
|
|
||||||
|
|
||||||
def hydrate_questions_with_choices(raw_questions: List) -> List:
|
def hydrate_questions_with_choices(raw_questions: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
out = []
|
out = []
|
||||||
|
|
||||||
for raw_question in raw_questions:
|
for raw_question in raw_questions:
|
||||||
|
|
Loading…
Add table
Reference in a new issue