mirror of
https://github.com/yavook/kiwi-scp.git
synced 2024-11-24 13:43:01 +00:00
Custom Exception classes
This commit is contained in:
parent
33cb62bc3a
commit
a54d66b9e7
4 changed files with 127 additions and 44 deletions
|
@ -6,6 +6,27 @@ from typing import List, Optional
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
|
||||||
|
class MissingCMDObjectError(ValueError):
|
||||||
|
"""raised if command object can't be found in its module"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CMDObjectSubclassError(TypeError):
|
||||||
|
"""raised if a command object is not inheriting click.Command"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CMDUnregisteredError(ValueError):
|
||||||
|
"""raised if commands have not been assigned to a command group"""
|
||||||
|
|
||||||
|
unregistered: List[str]
|
||||||
|
|
||||||
|
def __init__(self, unregistered):
|
||||||
|
self.unregistered = unregistered
|
||||||
|
|
||||||
|
super().__init__(f"Some commands were not registered in a group above: {unregistered!r}")
|
||||||
|
|
||||||
|
|
||||||
class KiwiCLI(click.MultiCommand):
|
class KiwiCLI(click.MultiCommand):
|
||||||
"""Command Line Interface spread over multiple files in this directory"""
|
"""Command Line Interface spread over multiple files in this directory"""
|
||||||
|
|
||||||
|
@ -27,19 +48,19 @@ class KiwiCLI(click.MultiCommand):
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return
|
return
|
||||||
|
|
||||||
member_name = f"{cmd_name.capitalize()}Command"
|
cmd_object_name = f"{cmd_name.capitalize()}Command"
|
||||||
|
|
||||||
if member_name in dir(cmd_module):
|
if cmd_object_name in dir(cmd_module):
|
||||||
member = getattr(cmd_module, member_name)
|
cmd_object = getattr(cmd_module, cmd_object_name)
|
||||||
|
|
||||||
if isinstance(member, click.Command):
|
if isinstance(cmd_object, click.Command):
|
||||||
return member
|
return cmd_object
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("Fail class")
|
raise CMDObjectSubclassError()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("Fail member name")
|
raise MissingCMDObjectError()
|
||||||
|
|
||||||
def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
||||||
commands = {
|
commands = {
|
||||||
|
@ -71,4 +92,4 @@ class KiwiCLI(click.MultiCommand):
|
||||||
cmd_names -= set(cmd_list)
|
cmd_names -= set(cmd_list)
|
||||||
|
|
||||||
if len(cmd_names) > 0:
|
if len(cmd_names) > 0:
|
||||||
raise Exception(f"Some commands were not registered in a group above: {cmd_names}")
|
raise CMDUnregisteredError(cmd_names)
|
||||||
|
|
|
@ -23,6 +23,10 @@ class KiwiCommandType(Enum):
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
class KiwiCommandNotImplementedError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class KiwiCommand:
|
class KiwiCommand:
|
||||||
type: KiwiCommandType = KiwiCommandType.SERVICES
|
type: KiwiCommandType = KiwiCommandType.SERVICES
|
||||||
enabled_only: bool = False
|
enabled_only: bool = False
|
||||||
|
@ -177,4 +181,4 @@ class KiwiCommand:
|
||||||
@classmethod
|
@classmethod
|
||||||
def run_for_filtered_services(cls, instance: Instance, project: Project, services: Services,
|
def run_for_filtered_services(cls, instance: Instance, project: Project, services: Services,
|
||||||
new_service_names: List[str], **kwargs) -> None:
|
new_service_names: List[str], **kwargs) -> None:
|
||||||
raise Exception
|
raise KiwiCommandNotImplementedError()
|
||||||
|
|
|
@ -9,6 +9,25 @@ from ._constants import RE_SEMVER, RE_VARNAME, KIWI_CONF_NAME, RESERVED_PROJECT_
|
||||||
from .yaml import YAML
|
from .yaml import YAML
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidFormatError(ValueError):
|
||||||
|
"""raised if format recognition unsuccessful"""
|
||||||
|
|
||||||
|
cls: type
|
||||||
|
member: Optional[str]
|
||||||
|
data: str
|
||||||
|
|
||||||
|
def __init__(self, cls, data, member = None):
|
||||||
|
self.cls = cls
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
if member is not None:
|
||||||
|
self.member = member
|
||||||
|
super().__init__(f"Invalid {self.cls.__name__!r}.{self.member!r} Format: {self.data!r}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
super().__init__(f"Invalid {self.cls.__name__!r} Format: {self.data!r}")
|
||||||
|
|
||||||
|
|
||||||
class StorageConfig(BaseModel):
|
class StorageConfig(BaseModel):
|
||||||
"""a storage subsection"""
|
"""a storage subsection"""
|
||||||
|
|
||||||
|
@ -31,7 +50,17 @@ class StorageConfig(BaseModel):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# undefined format
|
# undefined format
|
||||||
raise ValueError("Invalid Storage Format")
|
raise InvalidFormatError(cls, str(values))
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectNameReservedError(ValueError):
|
||||||
|
"""raised if trying to create a project with a reserved name"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
super().__init__(f"Project name {self.name!r} is reserved!")
|
||||||
|
|
||||||
|
|
||||||
class ProjectConfig(BaseModel):
|
class ProjectConfig(BaseModel):
|
||||||
|
@ -58,7 +87,7 @@ class ProjectConfig(BaseModel):
|
||||||
"""check if project name is allowed"""
|
"""check if project name is allowed"""
|
||||||
|
|
||||||
if value in RESERVED_PROJECT_NAMES:
|
if value in RESERVED_PROJECT_NAMES:
|
||||||
raise ValueError(f"Project name '{value}' is reserved!")
|
raise ProjectNameReservedError(value)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -101,7 +130,7 @@ class ProjectConfig(BaseModel):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# undefined format
|
# undefined format
|
||||||
raise ValueError("Invalid Project Format")
|
raise InvalidFormatError(ProjectConfig, values)
|
||||||
|
|
||||||
|
|
||||||
class NetworkConfig(BaseModel):
|
class NetworkConfig(BaseModel):
|
||||||
|
@ -120,6 +149,18 @@ class NetworkConfig(BaseModel):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MissingMemberError(ValueError):
|
||||||
|
"""raised if class member is missing a definition"""
|
||||||
|
|
||||||
|
cls: type
|
||||||
|
member: str
|
||||||
|
|
||||||
|
def __init__(self, cls, member):
|
||||||
|
self.cls = cls
|
||||||
|
self.member = member
|
||||||
|
super().__init__(f"Member {self.cls.__name__!r}.{self.member!r} is required!")
|
||||||
|
|
||||||
|
|
||||||
class KiwiConfig(BaseModel):
|
class KiwiConfig(BaseModel):
|
||||||
"""represents a kiwi.yml"""
|
"""represents a kiwi.yml"""
|
||||||
|
|
||||||
|
@ -225,7 +266,7 @@ class KiwiConfig(BaseModel):
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# undefined format
|
# undefined format
|
||||||
raise ValueError("Invalid Shells Format")
|
raise InvalidFormatError(KiwiConfig, value, "shells")
|
||||||
|
|
||||||
@validator("projects", pre=True)
|
@validator("projects", pre=True)
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -254,7 +295,7 @@ class KiwiConfig(BaseModel):
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# undefined format
|
# undefined format
|
||||||
raise ValueError("Invalid Projects Format")
|
raise InvalidFormatError(KiwiConfig, value, "projects")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -270,7 +311,7 @@ class KiwiConfig(BaseModel):
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# undefined format
|
# undefined format
|
||||||
raise ValueError("Invalid Projects Format")
|
raise InvalidFormatError(KiwiConfig, value, "projects")
|
||||||
|
|
||||||
@validator("environment", pre=True)
|
@validator("environment", pre=True)
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -284,7 +325,7 @@ class KiwiConfig(BaseModel):
|
||||||
idx = str(var_val).find("=")
|
idx = str(var_val).find("=")
|
||||||
except Exception:
|
except Exception:
|
||||||
# undefined format
|
# undefined format
|
||||||
raise ValueError("Invalid Environment Format")
|
raise InvalidFormatError(KiwiConfig, value, "environment")
|
||||||
|
|
||||||
if idx == -1:
|
if idx == -1:
|
||||||
# don't split, just define the variable
|
# don't split, just define the variable
|
||||||
|
@ -325,7 +366,7 @@ class KiwiConfig(BaseModel):
|
||||||
|
|
||||||
if value is None:
|
if value is None:
|
||||||
# empty storage
|
# empty storage
|
||||||
raise ValueError("No Storage Given")
|
raise MissingMemberError(KiwiConfig, "storage")
|
||||||
|
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
# native dict format
|
# native dict format
|
||||||
|
@ -350,7 +391,7 @@ class KiwiConfig(BaseModel):
|
||||||
|
|
||||||
if value is None:
|
if value is None:
|
||||||
# empty network
|
# empty network
|
||||||
raise ValueError("No Network Given")
|
raise MissingMemberError(KiwiConfig, "network")
|
||||||
|
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
# native dict format
|
# native dict format
|
||||||
|
@ -358,4 +399,4 @@ class KiwiConfig(BaseModel):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# undefined format
|
# undefined format
|
||||||
raise ValueError("Invalid Network Format")
|
raise InvalidFormatError(KiwiConfig, value, "network")
|
||||||
|
|
|
@ -8,11 +8,18 @@ from kiwi_scp.config import KiwiConfig
|
||||||
from kiwi_scp.yaml import YAML
|
from kiwi_scp.yaml import YAML
|
||||||
|
|
||||||
|
|
||||||
|
class UnCoercibleError(ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UnCoercible:
|
class UnCoercible:
|
||||||
"""A class that doesn't have a string representation"""
|
"""A class that doesn't have a string representation"""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
raise ValueError
|
raise UnCoercibleError()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "UnCoercible()"
|
||||||
|
|
||||||
|
|
||||||
class TestDefault:
|
class TestDefault:
|
||||||
|
@ -130,8 +137,8 @@ class TestShells:
|
||||||
|
|
||||||
assert len(exc_info.value.errors()) == 1
|
assert len(exc_info.value.errors()) == 1
|
||||||
error = exc_info.value.errors()[0]
|
error = exc_info.value.errors()[0]
|
||||||
assert error["msg"] == "Invalid Shells Format"
|
assert error["msg"] == "Invalid 'KiwiConfig'.'shells' Format: UnCoercible()"
|
||||||
assert error["type"] == "value_error"
|
assert error["type"] == "value_error.invalidformat"
|
||||||
|
|
||||||
with pytest.raises(ValidationError) as exc_info:
|
with pytest.raises(ValidationError) as exc_info:
|
||||||
KiwiConfig(shells=["/bin/bash", UnCoercible()])
|
KiwiConfig(shells=["/bin/bash", UnCoercible()])
|
||||||
|
@ -207,8 +214,8 @@ class TestProject:
|
||||||
|
|
||||||
assert len(exc_info.value.errors()) == 1
|
assert len(exc_info.value.errors()) == 1
|
||||||
error = exc_info.value.errors()[0]
|
error = exc_info.value.errors()[0]
|
||||||
assert error["msg"] == "Invalid Storage Format"
|
assert error["msg"] == "Invalid 'StorageConfig' Format: '{}'"
|
||||||
assert error["type"] == "value_error"
|
assert error["type"] == "value_error.invalidformat"
|
||||||
|
|
||||||
def test_short(self):
|
def test_short(self):
|
||||||
kiwi_dict = {
|
kiwi_dict = {
|
||||||
|
@ -239,6 +246,15 @@ class TestProject:
|
||||||
assert p.enabled
|
assert p.enabled
|
||||||
assert p.override_storage is None
|
assert p.override_storage is None
|
||||||
|
|
||||||
|
def test_reserved_name(self):
|
||||||
|
with pytest.raises(ValidationError) as exc_info:
|
||||||
|
KiwiConfig(projects={"name": "config"})
|
||||||
|
|
||||||
|
assert len(exc_info.value.errors()) == 1
|
||||||
|
error = exc_info.value.errors()[0]
|
||||||
|
assert error["msg"] == "Project name 'config' is reserved!"
|
||||||
|
assert error["type"] == "value_error.projectnamereserved"
|
||||||
|
|
||||||
def test_invalid_dict(self):
|
def test_invalid_dict(self):
|
||||||
with pytest.raises(ValidationError) as exc_info:
|
with pytest.raises(ValidationError) as exc_info:
|
||||||
KiwiConfig(projects={
|
KiwiConfig(projects={
|
||||||
|
@ -248,8 +264,9 @@ class TestProject:
|
||||||
|
|
||||||
assert len(exc_info.value.errors()) == 1
|
assert len(exc_info.value.errors()) == 1
|
||||||
error = exc_info.value.errors()[0]
|
error = exc_info.value.errors()[0]
|
||||||
assert error["msg"] == "Invalid Project Format"
|
assert error["msg"] == "Invalid 'ProjectConfig' Format: " \
|
||||||
assert error["type"] == "value_error"
|
"{'random key 1': 'random value 1', 'random key 2': 'random value 2'}"
|
||||||
|
assert error["type"] == "value_error.invalidformat"
|
||||||
|
|
||||||
def test_coercible(self):
|
def test_coercible(self):
|
||||||
c = KiwiConfig(projects="project")
|
c = KiwiConfig(projects="project")
|
||||||
|
@ -268,16 +285,16 @@ class TestProject:
|
||||||
|
|
||||||
assert len(exc_info.value.errors()) == 1
|
assert len(exc_info.value.errors()) == 1
|
||||||
error = exc_info.value.errors()[0]
|
error = exc_info.value.errors()[0]
|
||||||
assert error["msg"] == "Invalid Projects Format"
|
assert error["msg"] == "Invalid 'KiwiConfig'.'projects' Format: ['valid', UnCoercible()]"
|
||||||
assert error["type"] == "value_error"
|
assert error["type"] == "value_error.invalidformat"
|
||||||
|
|
||||||
with pytest.raises(ValidationError) as exc_info:
|
with pytest.raises(ValidationError) as exc_info:
|
||||||
KiwiConfig(projects=UnCoercible())
|
KiwiConfig(projects=UnCoercible())
|
||||||
|
|
||||||
assert len(exc_info.value.errors()) == 1
|
assert len(exc_info.value.errors()) == 1
|
||||||
error = exc_info.value.errors()[0]
|
error = exc_info.value.errors()[0]
|
||||||
assert error["msg"] == "Invalid Projects Format"
|
assert error["msg"] == "Invalid 'KiwiConfig'.'projects' Format: UnCoercible()"
|
||||||
assert error["type"] == "value_error"
|
assert error["type"] == "value_error.invalidformat"
|
||||||
|
|
||||||
|
|
||||||
class TestEnvironment:
|
class TestEnvironment:
|
||||||
|
@ -360,16 +377,16 @@ class TestEnvironment:
|
||||||
|
|
||||||
assert len(exc_info.value.errors()) == 1
|
assert len(exc_info.value.errors()) == 1
|
||||||
error = exc_info.value.errors()[0]
|
error = exc_info.value.errors()[0]
|
||||||
assert error["msg"] == "Invalid Environment Format"
|
assert error["msg"] == "Invalid 'KiwiConfig'.'environment' Format: UnCoercible()"
|
||||||
assert error["type"] == "value_error"
|
assert error["type"] == "value_error.invalidformat"
|
||||||
|
|
||||||
with pytest.raises(ValidationError) as exc_info:
|
with pytest.raises(ValidationError) as exc_info:
|
||||||
KiwiConfig(environment=["valid", UnCoercible()])
|
KiwiConfig(environment=["valid", UnCoercible()])
|
||||||
|
|
||||||
assert len(exc_info.value.errors()) == 1
|
assert len(exc_info.value.errors()) == 1
|
||||||
error = exc_info.value.errors()[0]
|
error = exc_info.value.errors()[0]
|
||||||
assert error["msg"] == "Invalid Environment Format"
|
assert error["msg"] == "Invalid 'KiwiConfig'.'environment' Format: None"
|
||||||
assert error["type"] == "value_error"
|
assert error["type"] == "value_error.invalidformat"
|
||||||
|
|
||||||
|
|
||||||
class TestStorage:
|
class TestStorage:
|
||||||
|
@ -379,8 +396,8 @@ class TestStorage:
|
||||||
|
|
||||||
assert len(exc_info.value.errors()) == 1
|
assert len(exc_info.value.errors()) == 1
|
||||||
error = exc_info.value.errors()[0]
|
error = exc_info.value.errors()[0]
|
||||||
assert error["msg"] == "No Storage Given"
|
assert error["msg"] == "Member 'KiwiConfig'.'storage' is required!"
|
||||||
assert error["type"] == "value_error"
|
assert error["type"] == "value_error.missingmember"
|
||||||
|
|
||||||
def test_dict(self):
|
def test_dict(self):
|
||||||
kiwi_dict = {"directory": "/test/directory"}
|
kiwi_dict = {"directory": "/test/directory"}
|
||||||
|
@ -395,8 +412,8 @@ class TestStorage:
|
||||||
|
|
||||||
assert len(exc_info.value.errors()) == 1
|
assert len(exc_info.value.errors()) == 1
|
||||||
error = exc_info.value.errors()[0]
|
error = exc_info.value.errors()[0]
|
||||||
assert error["msg"] == "Invalid Storage Format"
|
assert error["msg"] == "Invalid 'StorageConfig' Format: \"{'random key': 'random value'}\""
|
||||||
assert error["type"] == "value_error"
|
assert error["type"] == "value_error.invalidformat"
|
||||||
|
|
||||||
def test_str(self):
|
def test_str(self):
|
||||||
c = KiwiConfig(storage="/test/directory")
|
c = KiwiConfig(storage="/test/directory")
|
||||||
|
@ -414,8 +431,8 @@ class TestStorage:
|
||||||
|
|
||||||
assert len(exc_info.value.errors()) == 1
|
assert len(exc_info.value.errors()) == 1
|
||||||
error = exc_info.value.errors()[0]
|
error = exc_info.value.errors()[0]
|
||||||
assert error["msg"] == "Invalid Storage Format"
|
assert error["msg"] == "Invalid 'StorageConfig' Format: '{}'"
|
||||||
assert error["type"] == "value_error"
|
assert error["type"] == "value_error.invalidformat"
|
||||||
|
|
||||||
|
|
||||||
class TestNetwork:
|
class TestNetwork:
|
||||||
|
@ -425,8 +442,8 @@ class TestNetwork:
|
||||||
|
|
||||||
assert len(exc_info.value.errors()) == 1
|
assert len(exc_info.value.errors()) == 1
|
||||||
error = exc_info.value.errors()[0]
|
error = exc_info.value.errors()[0]
|
||||||
assert error["msg"] == "No Network Given"
|
assert error["msg"] == "Member 'KiwiConfig'.'network' is required!"
|
||||||
assert error["type"] == "value_error"
|
assert error["type"] == "value_error.missingmember"
|
||||||
|
|
||||||
def test_dict(self):
|
def test_dict(self):
|
||||||
kiwi_dict = {
|
kiwi_dict = {
|
||||||
|
@ -470,5 +487,5 @@ class TestNetwork:
|
||||||
|
|
||||||
assert len(exc_info.value.errors()) == 1
|
assert len(exc_info.value.errors()) == 1
|
||||||
error = exc_info.value.errors()[0]
|
error = exc_info.value.errors()[0]
|
||||||
assert error["msg"] == "Invalid Network Format"
|
assert error["msg"] == "Invalid 'KiwiConfig'.'network' Format: True"
|
||||||
assert error["type"] == "value_error"
|
assert error["type"] == "value_error.invalidformat"
|
||||||
|
|
Loading…
Reference in a new issue