config.py constraints and doc

This commit is contained in:
Jörn-Michael Miehe 2021-10-12 19:06:49 +02:00
parent c0edd3bcc5
commit 370097e5c3
3 changed files with 86 additions and 34 deletions

View file

@ -1,6 +1,18 @@
# system # system
import os import os
#############
# REGEX PARTS
# regex part for a number with no leading zeroes
_RE_NUMBER: str = r"[0-9]|[1-9][0-9]*"
# regex for a semantic version string
RE_SEMVER = rf"^{_RE_NUMBER}(?:\.{_RE_NUMBER}(?:\.{_RE_NUMBER})?)?$"
# regex for a lowercase variable name
RE_VARNAME = r"^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9])$"
############# #############
# ENVIRONMENT # ENVIRONMENT

View file

@ -1,48 +1,67 @@
import re from ipaddress import IPv4Network
from pathlib import Path
from typing import Optional, Dict, List from typing import Optional, Dict, List
import pydantic import pydantic
from ._constants import RE_SEMVER, RE_VARNAME
class _Storage(pydantic.BaseModel): class _Storage(pydantic.BaseModel):
"""a storage subsection""" """a storage subsection"""
directory: str directory: Path
class _Project(pydantic.BaseModel): class _Project(pydantic.BaseModel):
"""a project subsection""" """a project subsection"""
name: str name: pydantic.constr(
regex=RE_VARNAME
)
enabled: bool = True enabled: bool = True
storage: Optional[_Storage] override_storage: Optional[_Storage]
@pydantic.root_validator(pre=True) @pydantic.root_validator(pre=True)
@classmethod @classmethod
def check_grammar(cls, values): def unify_project(cls, values):
if isinstance(values, dict): """parse different project notations"""
if "name" in values:
return values
elif len(values) == 1: if "name" in values:
name, enabled = list(values.items())[0] # default format
return {"name": name, "enabled": True if enabled is None else enabled} return values
elif isinstance(values, str): elif len(values) == 1:
return {"name": values} # short format:
# - <name>: <enabled>
name, enabled = list(values.items())[0]
return {
"name": name,
"enabled": True if enabled is None else enabled
}
else:
# undefined format
raise ValueError
class _Network(pydantic.BaseModel): class _Network(pydantic.BaseModel):
"""a network subsection""" """a network subsection"""
name: str name: pydantic.constr(
cidr: str to_lower=True,
regex=RE_VARNAME
)
cidr: IPv4Network
class Config(pydantic.BaseModel): class Config(pydantic.BaseModel):
"""represents a kiwi.yml""" """represents a kiwi.yml"""
version: str version: pydantic.constr(
regex=RE_SEMVER
)
shells: Optional[List[str]] shells: Optional[List[str]]
environment: Optional[Dict[str, Optional[str]]] environment: Optional[Dict[str, Optional[str]]]
@ -50,30 +69,51 @@ class Config(pydantic.BaseModel):
storage: _Storage storage: _Storage
network: _Network network: _Network
@pydantic.validator("version")
@classmethod
def check_version(cls, value: str) -> str:
if not re.match(r"^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$", value):
raise ValueError
return value
@pydantic.validator("environment", pre=True) @pydantic.validator("environment", pre=True)
@classmethod @classmethod
def unify_env(cls, value) -> Optional[Dict[str, Optional[str]]]: def unify_environment(cls, value) -> Optional[Dict[str, Optional[str]]]:
if isinstance(value, dict): """parse different environment notations"""
def parse_str(var_val: str) -> (str, Optional[str]):
"""parse a "<variable>=<value>" string"""
idx = var_val.find("=")
if idx == -1:
# don't split, just define the variable
return var_val, None
else:
# split string, set variable to value
return var_val[:idx], var_val[idx + 1:]
if value is None:
# empty environment
return None
elif isinstance(value, dict):
# native dict format
return value return value
elif isinstance(value, list): elif isinstance(value, list):
# list format (multiple strings)
result: Dict[str, Optional[str]] = {} result: Dict[str, Optional[str]] = {}
for item in value: for item in value:
idx = item.find("=") key, value = parse_str(item)
if idx == -1:
key, value = item, None
else:
key, value = item[:idx], item[idx + 1:]
result[key] = value result[key] = value
return result return result
elif isinstance(value, str):
# string format (single variable):
# "<var>=<value>"
key, value = parse_str(value)
return {key: value}
elif isinstance(value, int):
# integer format (just define single oddly named variable)
return {str(value): None}
else: else:
return None # undefined format
raise ValueError

View file

@ -4,7 +4,7 @@ shells:
projects: projects:
- name: admin - name: admin
enabled: true enabled: true
- test: - Test:
- test2: false - test2: false
storage: storage:
directory: /var/local/kiwi directory: /var/local/kiwi