-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript_exceptions.py
More file actions
145 lines (96 loc) · 3.78 KB
/
Copy pathscript_exceptions.py
File metadata and controls
145 lines (96 loc) · 3.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#!/usr/bin/env python3
"""Demo: Paasify-style CLI error handling with Clak.
The whole program is wrapped in try/except inside ``Parser.dispatch()``.
Errors flow through ``clean_terminate()``:
1. ``Meta.known_exceptions`` — your app exception tree (with ``rc``)
2. ``Meta.exception_handlers`` — third-party libs (YAML, shell, …)
3. Built-in Clak exceptions
4. OS errors
5. No match → full traceback + "report to developer"
Try:
./script_exceptions.py deploy myapp
./script_exceptions.py deploy missing # rc 44
./script_exceptions.py load bad.yaml # YAML handler, rc 42
./script_exceptions.py broken # unexpected bug (traceback)
./script_exceptions.py --trace broken # traceback before handler chain
CLAK_DEBUG=1 ./script_exceptions.py broken # same as --trace
"""
from __future__ import annotations
import logging
import sys
import yaml
from clak import Argument, Command, LoggingOptMixin, Parser
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# App exception tree (see paasify/errors.py, paasify_v4/exception.py)
# ---------------------------------------------------------------------------
class AppError(Exception):
"Base application error."
rc = 1
advice = None
def __init__(self, message, rc=None, advice=None):
self.advice = advice
if rc is not None:
self.rc = rc
super().__init__(message)
class AppNotFound(AppError):
"Unknown application."
rc = 44
class InvalidConfig(AppError):
"Invalid configuration file."
rc = 36
# ---------------------------------------------------------------------------
# Third-party handlers (see paasify/cli.py clean_terminate)
# ---------------------------------------------------------------------------
def handle_yaml_parser_error(_app, err):
logger.critical(err)
logger.critical("Invalid YAML file (syntax or structure)")
sys.exit(42)
APPS = {"myapp": {"name": "myapp", "config": "app.yml"}}
# ---------------------------------------------------------------------------
# Commands — raise domain errors; no manual sys.exit()
# ---------------------------------------------------------------------------
class DeployCmd(Parser):
"Deploy one application."
name = Argument("NAME", help="Application name")
def cli_run(self, name: str, **_):
if name not in APPS:
raise AppNotFound(
f"application {name!r} not found",
advice="Run: script_exceptions.py catalog list",
)
print(f"Deploying {name}")
class LoadCmd(Parser):
"Load a YAML config (demo third-party handler)."
path = Argument("PATH", help="YAML file path")
def cli_run(self, path: str, **_):
with open(path, encoding="utf-8") as handle:
yaml.safe_load(handle)
print(f"Loaded {path}")
class BrokenCmd(Parser):
"Trigger an unexpected bug."
def cli_run(self, **_):
raise RuntimeError("unexpected failure in demo command")
class CatalogGroup(Parser):
"Catalog operations."
class ListCmd(Parser):
"List known applications."
def cli_run(self, **_):
for name in APPS:
print(name)
list = Command(ListCmd)
class AppMain(LoggingOptMixin, Parser):
"""Exception-handling demo."""
class Meta:
log_prefix = __name__
known_exceptions = [AppError]
exception_handlers = [
(yaml.parser.ParserError, handle_yaml_parser_error),
(yaml.scanner.ScannerError, handle_yaml_parser_error),
]
deploy = Command(DeployCmd)
load = Command(LoadCmd)
broken = Command(BrokenCmd)
catalog = Command(CatalogGroup)
if __name__ == "__main__":
AppMain()