-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinventory_system.py
More file actions
156 lines (125 loc) · 5.04 KB
/
inventory_system.py
File metadata and controls
156 lines (125 loc) · 5.04 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
153
154
155
156
"""Simple in-memory inventory utility.
This module provides a tiny API to add/remove items, persist the inventory
to JSON, and produce a small text report. It is intentionally minimal and
uses a module-level dict as the backing store.
"""
from typing import Dict, List, Optional
import json
import logging
# Global variable holding item -> quantity
stock_data: Dict[str, int] = {}
logger = logging.getLogger(__name__)
LOG_FORMAT = "%(asctime)s %(levelname)s: %(message)s"
logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
def add_item(item: str, qty: int, logs: Optional[List[str]] = None) -> None:
"""Add ``qty`` of ``item`` to the inventory.
Args:
item: Item name.
qty: Quantity to add (can be negative to subtract).
logs: Optional list to append a human-readable action log.
Raises:
TypeError: if input types are incorrect.
"""
if logs is None:
logs = []
if not isinstance(item, str):
raise TypeError("item must be a string")
if not isinstance(qty, int):
raise TypeError("qty must be an integer")
if qty == 0:
return
stock_data[item] = stock_data.get(item, 0) + qty
log_msg = f"Added {qty} of {item}"
logs.append(log_msg)
logger.info("Added %d of %s", qty, item)
def remove_item(item: str, qty: int) -> None:
"""Remove ``qty`` of ``item`` from the inventory.
Raises:
TypeError: if types are incorrect.
ValueError: if qty is not positive or not enough stock.
KeyError: if the item is not present.
"""
if not isinstance(item, str):
raise TypeError("item must be a string")
if not isinstance(qty, int):
raise TypeError("qty must be an integer")
if qty <= 0:
raise ValueError("qty must be a positive integer")
if item not in stock_data:
raise KeyError(f"{item} not in stock")
if stock_data[item] < qty:
raise ValueError(f"not enough {item} in stock")
stock_data[item] -= qty
if stock_data[item] <= 0:
del stock_data[item]
logger.info("Removed %d of %s", qty, item)
def get_qty(item: str) -> int:
"""Return quantity for ``item`` or 0 if not present."""
if not isinstance(item, str):
raise TypeError("item must be a string")
return stock_data.get(item, 0)
def load_data(file: str = "inventory.json") -> None:
"""Load inventory from a JSON file.
If the file is missing or invalid the inventory is cleared to an empty
state and a warning is logged.
"""
try:
with open(file, "r", encoding="utf-8") as handle:
data = json.load(handle)
# normalize data to str->int mapping and mutate the global dict
normalized = {str(k): int(v) for k, v in data.items()}
stock_data.clear()
stock_data.update(normalized)
logger.info("Loaded inventory from %s", file)
except FileNotFoundError:
logger.warning("%s not found, starting with empty inventory", file)
stock_data.clear()
except (json.JSONDecodeError, ValueError, TypeError) as exc:
logger.error("Failed to load %s: %s", file, exc)
stock_data.clear()
def save_data(file: str = "inventory.json") -> None:
"""Save the current inventory to a JSON file."""
try:
with open(file, "w", encoding="utf-8") as handle:
json.dump(stock_data, handle)
logger.info("Saved inventory to %s", file)
except Exception as exc: # pragma: no cover - bubble up in caller
logger.error("Failed to save %s: %s", file, exc)
raise
def print_data() -> None:
"""Print a tiny items report to stdout."""
print("Items Report")
for item_, qty in stock_data.items():
print(item_, "->", qty)
def check_low_items(threshold: int = 5) -> List[str]:
"""Return list of item names whose quantity is below ``threshold``.
Args:
threshold: integer threshold to compare quantities against.
"""
if not isinstance(threshold, int):
raise TypeError("threshold must be an integer")
return [item for item, qty in stock_data.items() if qty < threshold]
def main() -> None:
"""Run a short example of the inventory utilities.
This function demonstrates typical usage and is not executed on import.
"""
# Example usage (keeps invalid calls guarded)
add_item("apple", 10)
add_item("banana", 2)
try:
# example of invalid usage — will raise and be logged
add_item(123, 10) # type: ignore[arg-type]
except TypeError as exc: # pragma: no cover - demonstration path
logger.warning(str(exc))
remove_item("apple", 3)
try:
remove_item("orange", 1)
except KeyError: # pragma: no cover - demonstration path
logger.warning("orange not found in inventory")
print("Apple stock:", get_qty("apple"))
print("Low items:", check_low_items())
save_data()
load_data()
print_data()
if __name__ == "__main__":
main()