recursive yaml loading; readme text; minor refactoring

This commit is contained in:
2021-12-29 17:30:34 +01:00
parent 4b1536c19a
commit 987e83c110
3 changed files with 96 additions and 18 deletions

View File

@ -0,0 +1 @@
from yamlhttpforms.form import Form, yaml_overload, load_form

View File

@ -1,5 +1,5 @@
from pathlib import Path
from typing import Dict, Callable, Union, Optional, TYPE_CHECKING
from typing import Dict, Callable, Union, Optional, Any, TYPE_CHECKING
from yaml import safe_load
if TYPE_CHECKING:
@ -41,7 +41,7 @@ class FormField:
def valid_option(self, option: str) -> bool:
return self.options is None or option in self.options.keys() or option in self.options.values()
def clean_value(self, value: str) -> str:
def clean(self, value: str) -> str:
if self.options is None or value in self.options.keys():
return value
# Try to find an unambiguous match in the visible option texts:
@ -73,7 +73,7 @@ class Form:
url (optional):
Can be set in advance to the url that requests using this form's payload should be made to.
"""
self._fields: Dict[str, FormField] = {alias: FormField(**field_def) for alias, field_def in definition.items()}
self.fields: Dict[str, FormField] = {alias: FormField(**field_def) for alias, field_def in definition.items()}
self.full_payload_always: bool = full_payload
self.url: Optional[str] = url
@ -81,13 +81,9 @@ class Form:
fields = ', '.join(f"'{alias}': {field}" for alias, field in self.fields.items())
return f'Form({fields})'
@property
def fields(self) -> Dict[str, FormField]:
return self._fields.copy()
@property
def required_fields(self) -> Dict[str, FormField]:
return {alias: field for alias, field in self._fields.items() if field.required}
return {alias: field for alias, field in self.fields.items() if field.required}
def get_payload(self, **kwargs: str) -> Dict[str, str]:
"""
@ -115,7 +111,7 @@ class Form:
if value is None:
payload[field.name] = field.default
else:
payload[field.name] = field.clean_value(value)
payload[field.name] = field.clean(value)
elif field.required:
raise ValueError(f"`{key}` is a required field, but no argument was passed.")
elif self.full_payload_always:
@ -167,18 +163,55 @@ class Form:
return post(self.url, data=self.get_payload(**kwargs))
def load_form(*def_paths: PathT, full_payload: bool = True) -> Form:
def yaml_overload(*paths: PathT, dir_sort_key: Callable[[PathT], Any] = None, dir_sort_reverse: bool = False) -> dict:
"""
Loads YAML files from any number of paths, recursively going through any directories.
Args:
paths:
Each argument should be a path to either a YAML file or a directory containing YAML files;
only files with the extension `.yaml` are loaded.
dir_sort_key (optional):
If one of the paths is a directory, its contents are sorted, before recursively passing them into this
function; to apply a specific comparison key for each sub-path, a callable can be used here, which is
passed into the builtin `sorted` function.
dir_sort_reverse (optional):
Same as with the parameter above, this argument is also passed into the `sorted` function.
Returns:
Dictionary comprised of the contents of iteratively loaded YAML files.
NOTE: Since it is updated each time a file is loaded, their load order matters!
"""
output_dict = {}
for path in paths:
path = Path(path)
if path.is_dir():
output_dict.update(yaml_overload(*sorted(path.iterdir(), key=dir_sort_key, reverse=dir_sort_reverse)))
elif path.suffix == '.yaml':
with open(path, 'r') as f:
output_dict.update(safe_load(f))
return output_dict
def load_form(*def_paths: PathT, dir_sort_key: Callable[[PathT], Any] = None, dir_sort_reverse: bool = False,
full_payload: bool = True, url: str = None) -> Form:
"""
Creates a form instance from an arbitrary number of definition files in YAML format.
Args:
def_paths:
Each element must be a path to a readable YAML file containing a valid form definition.
Paths to the YAML files containing the form definitions; see the `paths` parameter in `yaml_overload`
dir_sort_key (optional):
See `yaml_overload`
dir_sort_reverse (optional):
See `yaml_overload`
full_payload (optional):
Passed directly into the Form constructor.
See the `Form` constructor
url (optional):
See the `Form` constructor
"""
definition = {}
for path in def_paths:
with open(path, 'r') as f:
definition.update(safe_load(f))
return Form(definition, full_payload=full_payload)
return Form(
definition=yaml_overload(*def_paths, dir_sort_key=dir_sort_key, dir_sort_reverse=dir_sort_reverse),
full_payload=full_payload,
url=url
)