pydantic + pytest for config.version, config.storage and config.network
This commit is contained in:
parent
243fee3f23
commit
b568e449f5
3 changed files with 248 additions and 18 deletions
|
@ -1,11 +1,10 @@
|
|||
# system
|
||||
import os
|
||||
|
||||
#############
|
||||
# REGEX PARTS
|
||||
|
||||
# regex part for a number with no leading zeroes
|
||||
_RE_NUMBER: str = r"[0-9]|[1-9][0-9]*"
|
||||
_RE_NUMBER: str = r"(?:0|[1-9][0-9]*)"
|
||||
|
||||
# regex for a semantic version string
|
||||
RE_SEMVER = rf"^{_RE_NUMBER}(?:\.{_RE_NUMBER}(?:\.{_RE_NUMBER})?)?$"
|
||||
|
|
|
@ -26,6 +26,19 @@ class _Storage(BaseModel):
|
|||
|
||||
return {"directory": str(self.directory)}
|
||||
|
||||
@root_validator(pre=True)
|
||||
@classmethod
|
||||
def unify_storage(cls, values) -> Dict[str, Any]:
|
||||
"""parse different storage notations"""
|
||||
|
||||
if "directory" in values:
|
||||
# default format
|
||||
return values
|
||||
|
||||
else:
|
||||
# undefined format
|
||||
raise ValueError("Invalid Storage Format")
|
||||
|
||||
|
||||
class _Project(BaseModel):
|
||||
"""a project subsection"""
|
||||
|
@ -46,6 +59,21 @@ class _Project(BaseModel):
|
|||
result["override_storage"] = self.override_storage.kiwi_dict
|
||||
return result
|
||||
|
||||
@validator("override_storage", pre=True)
|
||||
@classmethod
|
||||
def unify_storage(cls, value):
|
||||
if value is None or isinstance(value, dict):
|
||||
return value
|
||||
|
||||
elif isinstance(value, str):
|
||||
return {"directory": value}
|
||||
|
||||
elif isinstance(value, list) and len(value) == 1:
|
||||
return {"directory": value[0]}
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid Storage Format")
|
||||
|
||||
@root_validator(pre=True)
|
||||
@classmethod
|
||||
def unify_project(cls, values) -> Dict[str, Any]:
|
||||
|
@ -67,7 +95,7 @@ class _Project(BaseModel):
|
|||
|
||||
else:
|
||||
# undefined format
|
||||
raise ValueError
|
||||
raise ValueError("Invalid Project Format")
|
||||
|
||||
|
||||
class _Network(BaseModel):
|
||||
|
@ -225,10 +253,15 @@ class Config(BaseModel):
|
|||
def unify_environment(cls, value) -> Dict[str, Optional[str]]:
|
||||
"""parse different environment notations"""
|
||||
|
||||
def parse_str(var_val: str) -> (str, Optional[str]):
|
||||
def parse_str(var_val: Any) -> (str, Optional[str]):
|
||||
"""parse a "<variable>=<value>" string"""
|
||||
|
||||
idx = var_val.find("=")
|
||||
try:
|
||||
idx = str(var_val).find("=")
|
||||
except Exception:
|
||||
# undefined format
|
||||
raise ValueError("Invalid Environment Format")
|
||||
|
||||
if idx == -1:
|
||||
# don't split, just define the variable
|
||||
return var_val, None
|
||||
|
@ -249,24 +282,49 @@ class Config(BaseModel):
|
|||
|
||||
result: Dict[str, Optional[str]] = {}
|
||||
for item in value:
|
||||
try:
|
||||
key, value = parse_str(str(item))
|
||||
key, value = parse_str(item)
|
||||
result[key] = value
|
||||
|
||||
except Exception as e:
|
||||
# undefined format
|
||||
raise ValueError("Invalid Environment Format")
|
||||
|
||||
return result
|
||||
|
||||
else:
|
||||
# any other format (try to coerce to str first)
|
||||
# string format (single variable):
|
||||
# "<var>=<value>"
|
||||
try:
|
||||
key, value = parse_str(str(value))
|
||||
key, value = parse_str(value)
|
||||
return {key: value}
|
||||
|
||||
except Exception as e:
|
||||
# undefined format
|
||||
raise ValueError("Invalid Environment Format")
|
||||
@validator("storage", pre=True)
|
||||
@classmethod
|
||||
def unify_storage(cls, value):
|
||||
"""parse different storage notations"""
|
||||
|
||||
if value is None:
|
||||
raise ValueError("No Storage Given")
|
||||
|
||||
elif isinstance(value, dict):
|
||||
return value
|
||||
|
||||
elif isinstance(value, str):
|
||||
return {"directory": value}
|
||||
|
||||
elif isinstance(value, list) and len(value) == 1:
|
||||
return {"directory": value[0]}
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid Storage Format")
|
||||
|
||||
@validator("network", pre=True)
|
||||
@classmethod
|
||||
def unify_network(cls, value):
|
||||
"""parse different network notations"""
|
||||
|
||||
if value is None:
|
||||
raise ValueError("No Network Given")
|
||||
|
||||
elif isinstance(value, dict):
|
||||
return value
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid Network Format")
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ from kiwi_scp.config import Config
|
|||
|
||||
class UnCoercible:
|
||||
"""A class that doesn't have a string representation"""
|
||||
|
||||
def __str__(self):
|
||||
raise ValueError
|
||||
|
||||
|
@ -28,6 +29,53 @@ def test_default():
|
|||
assert c.network.cidr == IPv4Network("10.22.46.0/24")
|
||||
|
||||
|
||||
#########
|
||||
# VERSION
|
||||
#########
|
||||
|
||||
def test_version_valid():
|
||||
c = Config(version="0.0.0")
|
||||
|
||||
assert c.version == "0.0.0"
|
||||
|
||||
c = Config(version="0.0")
|
||||
|
||||
assert c.version == "0.0"
|
||||
|
||||
c = Config(version="0")
|
||||
|
||||
assert c.version == "0"
|
||||
|
||||
c = Config(version=1.0)
|
||||
|
||||
assert c.version == "1.0"
|
||||
|
||||
c = Config(version=1)
|
||||
|
||||
assert c.version == "1"
|
||||
|
||||
|
||||
def test_version_invalid():
|
||||
# definitely not a version
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
Config(version="dnaf")
|
||||
|
||||
assert len(exc_info.value.errors()) == 1
|
||||
error = exc_info.value.errors()[0]
|
||||
assert error["msg"].find("string does not match regex") != -1
|
||||
assert error["type"] == "value_error.str.regex"
|
||||
|
||||
# barely a version
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
c = Config(version="0.0.0alpha")
|
||||
print(c.version)
|
||||
|
||||
assert len(exc_info.value.errors()) == 1
|
||||
error = exc_info.value.errors()[0]
|
||||
assert error["msg"].find("string does not match regex") != -1
|
||||
assert error["type"] == "value_error.str.regex"
|
||||
|
||||
|
||||
########
|
||||
# SHELLS
|
||||
########
|
||||
|
@ -143,6 +191,19 @@ def test_proj_dict():
|
|||
assert p.override_storage is None
|
||||
|
||||
|
||||
def test_proj_invalid_dict():
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
Config(projects={
|
||||
"random key 1": "random value 1",
|
||||
"random key 2": "random value 2",
|
||||
})
|
||||
|
||||
assert len(exc_info.value.errors()) == 1
|
||||
error = exc_info.value.errors()[0]
|
||||
assert error["msg"] == "Invalid Project Format"
|
||||
assert error["type"] == "value_error"
|
||||
|
||||
|
||||
def test_proj_coercible():
|
||||
c = Config(projects="project")
|
||||
|
||||
|
@ -267,3 +328,115 @@ def test_env_uncoercible():
|
|||
error = exc_info.value.errors()[0]
|
||||
assert error["msg"] == "Invalid Environment Format"
|
||||
assert error["type"] == "value_error"
|
||||
|
||||
|
||||
#########
|
||||
# STORAGE
|
||||
#########
|
||||
|
||||
def test_storage_empty():
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
Config(storage=None)
|
||||
|
||||
assert len(exc_info.value.errors()) == 1
|
||||
error = exc_info.value.errors()[0]
|
||||
assert error["msg"] == "No Storage Given"
|
||||
assert error["type"] == "value_error"
|
||||
|
||||
|
||||
def test_storage_dict():
|
||||
c = Config(storage={"directory": "/test/directory"})
|
||||
|
||||
assert c.storage.directory == Path("/test/directory")
|
||||
|
||||
|
||||
def test_storage_invalid_dict():
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
Config(storage={"random key": "random value"})
|
||||
|
||||
assert len(exc_info.value.errors()) == 1
|
||||
error = exc_info.value.errors()[0]
|
||||
assert error["msg"] == "Invalid Storage Format"
|
||||
assert error["type"] == "value_error"
|
||||
|
||||
|
||||
def test_storage_str():
|
||||
c = Config(storage="/test/directory")
|
||||
|
||||
assert c.storage.directory == Path("/test/directory")
|
||||
|
||||
|
||||
def test_storage_list():
|
||||
c = Config(storage=["/test/directory"])
|
||||
|
||||
assert c.storage.directory == Path("/test/directory")
|
||||
|
||||
|
||||
def test_storage_invalid():
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
Config(storage=True)
|
||||
|
||||
assert len(exc_info.value.errors()) == 1
|
||||
error = exc_info.value.errors()[0]
|
||||
assert error["msg"] == "Invalid Storage Format"
|
||||
assert error["type"] == "value_error"
|
||||
|
||||
|
||||
#########
|
||||
# NETWORK
|
||||
#########
|
||||
|
||||
def test_network_empty():
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
Config(network=None)
|
||||
|
||||
assert len(exc_info.value.errors()) == 1
|
||||
error = exc_info.value.errors()[0]
|
||||
assert error["msg"] == "No Network Given"
|
||||
assert error["type"] == "value_error"
|
||||
|
||||
|
||||
def test_network_dict():
|
||||
c = Config(network={
|
||||
"name": "test_hub",
|
||||
"cidr": "1.2.3.4/32",
|
||||
})
|
||||
|
||||
assert c == Config(network={
|
||||
"name": "TEST_HUB",
|
||||
"cidr": "1.2.3.4/32",
|
||||
})
|
||||
|
||||
assert c.network.name == "test_hub"
|
||||
assert c.network.cidr == IPv4Network("1.2.3.4/32")
|
||||
|
||||
|
||||
def test_network_invalid_dict():
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
Config(network={"name": "test_hub"})
|
||||
|
||||
assert len(exc_info.value.errors()) == 1
|
||||
error = exc_info.value.errors()[0]
|
||||
assert error["msg"] == "field required"
|
||||
assert error["type"] == "value_error.missing"
|
||||
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
Config(network={
|
||||
"name": "test_hub",
|
||||
"cidr": "1.2.3.4/123",
|
||||
})
|
||||
|
||||
assert len(exc_info.value.errors()) == 1
|
||||
error = exc_info.value.errors()[0]
|
||||
assert error["msg"] == "value is not a valid IPv4 network"
|
||||
assert error["type"] == "value_error.ipv4network"
|
||||
|
||||
|
||||
def test_network_invalid():
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
Config(network=True)
|
||||
|
||||
assert len(exc_info.value.errors()) == 1
|
||||
error = exc_info.value.errors()[0]
|
||||
assert error["msg"] == "Invalid Network Format"
|
||||
assert error["type"] == "value_error"
|
||||
|
|
Loading…
Reference in a new issue