-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparser.py
More file actions
73 lines (53 loc) · 2.56 KB
/
Copy pathparser.py
File metadata and controls
73 lines (53 loc) · 2.56 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
"""
parser.py — row parsing and validation
Module D: Files + Error Handling
"""
# ── Custom exception ──────────────────────────────────────────────────────────
class ParseError(Exception):
"""Raised when a CSV row cannot be parsed into a valid log entry."""
def __init__(self, row: dict, reason: str):
self.row = row
self.reason = reason
super().__init__(f"Parse failed on {row}: {reason}")
# ── Constants ─────────────────────────────────────────────────────────────────
REQUIRED_FIELDS = {"path", "status", "duration_ms", "method", "timestamp"}
VALID_METHODS = {"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"}
# ── Parsing ───────────────────────────────────────────────────────────────────
def parse_row(row: dict) -> dict:
"""
Parse a raw CSV row dict into a typed log entry.
Args:
row: Raw dict from csv.DictReader.
Returns:
Dict with typed fields: endpoint, status, ms, method, timestamp.
Raises:
ParseError: If any required field is missing or has an invalid type.
"""
try:
return {
"endpoint": row["path"],
"status": int(row["status"]),
"ms": float(row["duration_ms"]),
"method": row["method"].upper(),
"timestamp": row["timestamp"],
}
except KeyError as e:
raise ParseError(row, f"Missing field: {e}") from e
except ValueError as e:
raise ParseError(row, f"Invalid value: {e}") from e
def validate_row(parsed: dict) -> None:
"""
Validate a parsed log entry for business rules.
Args:
parsed: Dict returned by parse_row().
Raises:
ParseError: If any field fails validation.
"""
if not (100 <= parsed["status"] <= 599):
raise ParseError(parsed, f"Invalid HTTP status: {parsed['status']}")
if parsed["ms"] < 0:
raise ParseError(parsed, f"Negative duration: {parsed['ms']}")
if parsed["method"] not in VALID_METHODS:
raise ParseError(parsed, f"Unknown HTTP method: {parsed['method']}")
if not parsed["endpoint"].startswith("/"):
raise ParseError(parsed, f"Endpoint must start with /: {parsed['endpoint']}")