-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhelp_desk.py
More file actions
152 lines (118 loc) · 5.34 KB
/
Copy pathhelp_desk.py
File metadata and controls
152 lines (118 loc) · 5.34 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
146
147
148
149
150
151
152
"""Domain and storage functions for the community help-desk starter."""
import json
from pathlib import Path
PRIORITIES = {"low", "normal", "high"}
STATUSES = {"open", "closed"}
class HelpDeskError(ValueError):
"""Raised when ticket data does not match the help-desk contract."""
def _as_path(path):
try:
return Path(path)
except TypeError as error:
raise TypeError("path must be text or a path-like value") from error
def _required_text(value, name):
if not isinstance(value, str) or not value.strip():
raise HelpDeskError(f"{name} must be non-empty text")
return value.strip()
def _validate_tickets(tickets):
if not isinstance(tickets, list):
raise HelpDeskError("ticket JSON must contain a list")
seen_ids = set()
for index, ticket in enumerate(tickets):
prefix = f"ticket[{index}]"
if not isinstance(ticket, dict):
raise HelpDeskError(f"{prefix} must be a dictionary")
ticket_id = ticket.get("id")
if isinstance(ticket_id, bool) or not isinstance(ticket_id, int) or ticket_id < 1:
raise HelpDeskError(f"{prefix}.id must be a positive whole number")
if ticket_id in seen_ids:
raise HelpDeskError(f"{prefix}.id must be unique")
seen_ids.add(ticket_id)
_required_text(ticket.get("requester"), f"{prefix}.requester")
_required_text(ticket.get("issue"), f"{prefix}.issue")
_required_text(ticket.get("category"), f"{prefix}.category")
if ticket.get("priority") not in PRIORITIES:
raise HelpDeskError(f"{prefix}.priority must be low, normal, or high")
if ticket.get("status") not in STATUSES:
raise HelpDeskError(f"{prefix}.status must be open or closed")
extra_fields = set(ticket) - {
"id",
"requester",
"issue",
"category",
"priority",
"status",
}
if extra_fields:
names = ", ".join(sorted(str(field) for field in extra_fields))
raise HelpDeskError(f"{prefix} has unsupported fields: {names}")
def _normalised_words(text):
words = []
current_word = []
for character in text:
if character.isalpha():
current_word.append(character)
elif current_word:
words.append("".join(current_word).casefold())
current_word = []
if current_word:
words.append("".join(current_word).casefold())
return words
def _copy_tickets(tickets):
return [ticket.copy() for ticket in tickets]
def open_ticket(tickets, requester, issue, category, priority="normal"):
"""Return copied tickets plus one validated open ticket with the next ID."""
_validate_tickets(tickets)
_required_text(requester, "requester")
_required_text(issue, "issue")
_required_text(category, "category")
if not isinstance(priority, str) or priority.strip().casefold() not in PRIORITIES:
raise HelpDeskError("priority must be low, normal, or high")
raise NotImplementedError("TODO: create and append the next ticket in open_ticket()")
def search_tickets(tickets, query):
"""Return copied tickets containing every normalised query word."""
_validate_tickets(tickets)
query = _required_text(query, "query")
if not tickets:
return []
raise NotImplementedError("TODO: match all normalised query words in search_tickets()")
def close_ticket(tickets, ticket_id):
"""Return copied tickets with one matching status changed to closed."""
_validate_tickets(tickets)
if isinstance(ticket_id, bool) or not isinstance(ticket_id, int) or ticket_id < 1:
raise HelpDeskError("ticket_id must be a positive whole number")
if not any(ticket["id"] == ticket_id for ticket in tickets):
raise LookupError(f"Ticket #{ticket_id} was not found")
raise NotImplementedError("TODO: copy and close the matching ticket in close_ticket()")
def summarize_tickets(tickets):
"""Return status, category, and deterministic issue-word insights."""
_validate_tickets(tickets)
if not tickets:
return {"open": 0, "closed": 0, "by_category": {}, "common_issue_words": []}
raise NotImplementedError("TODO: count tickets and issue words in summarize_tickets()")
def load_tickets(path):
"""Load validated tickets, returning an empty list for a missing file."""
path = _as_path(path)
try:
with path.open("r", encoding="utf-8") as handle:
tickets = json.load(handle)
except FileNotFoundError:
return []
except json.JSONDecodeError as error:
raise HelpDeskError("ticket file contains invalid JSON") from error
except UnicodeDecodeError as error:
raise HelpDeskError("ticket file is not valid UTF-8") from error
_validate_tickets(tickets)
raise NotImplementedError("TODO: return copied records from load_tickets()")
def save_tickets(path, tickets):
"""Validate and save tickets as readable UTF-8 JSON."""
_as_path(path)
_validate_tickets(tickets)
raise NotImplementedError("TODO: write JSON safely in save_tickets()")
def format_ticket(ticket):
"""Return one readable terminal line for a validated ticket."""
_validate_tickets([ticket])
return (
f'#{ticket["id"]} [{ticket["status"]}/{ticket["priority"]}] '
f'{ticket["category"]}: {ticket["issue"]} — {ticket["requester"]}'
)