Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions .gitignore

This file was deleted.

1 change: 0 additions & 1 deletion .python-version

This file was deleted.

57 changes: 57 additions & 0 deletions 01.11/cart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from .crud import read_product
from .storage import QUANTITY_KEY, Product


def add_to_cart(
storage: dict[str, Product],
product_names: set[str],
cart: dict[str, int],
product_id: str,
quantity: int,
) -> int | None:
product = read_product(storage, product_id)
if product is None:
return None

available = product[QUANTITY_KEY]
if available < quantity:
print(
f"not enough stock for product {product_id}: "
f"{available} available, {quantity} requested"
)
return None

product[QUANTITY_KEY] = available - quantity
cart[product_id] = cart.get(product_id, 0) + quantity
return cart[product_id]


def remove_from_cart(
storage: dict[str, Product],
product_names: set[str],
cart: dict[str, int],
product_id: str,
quantity: int,
) -> int | None:
if product_id not in cart:
print(f"product {product_id} is not in the cart")
return None

in_cart = cart[product_id]
if in_cart < quantity:
print(
f"cart holds only {in_cart} unit(s) of product "
f"{product_id}, cannot remove {quantity}"
)
return None

product = read_product(storage, product_id)
if product is None:
return None

product[QUANTITY_KEY] += quantity
cart[product_id] = in_cart - quantity
if cart[product_id] == 0:
del cart[product_id]
return 0
return cart[product_id]
90 changes: 90 additions & 0 deletions 01.11/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import uuid
from typing import Any

from .storage import NAME_KEY, PRICE_KEY, QUANTITY_KEY, Product
from .utils import normalize_price, normalize_product_name


def generate_product_id() -> str:
return str(uuid.uuid4())


def create_product(
storage: dict[str, Product],
product_names: set[str],
fields: dict[str, Any],
) -> str | None:
name = normalize_product_name(fields[NAME_KEY])

if not name:
print("product name must not be blank")
return None

if name in product_names:
print(f"product name '{name}' is already taken")
return None

product_id = generate_product_id()
storage[product_id] = {
NAME_KEY: name,
PRICE_KEY: normalize_price(fields[PRICE_KEY]),
QUANTITY_KEY: fields[QUANTITY_KEY],
}
product_names.add(name)
return product_id


def read_product(storage: dict[str, Product], product_id: str) -> Product | None:
if product_id not in storage:
print(f"no product with id {product_id}")
return None
return storage[product_id]


def update_product(
storage: dict[str, Product],
product_names: set[str],
product_id: str,
fields: dict[str, Any],
) -> Product | None:
if product_id not in storage:
print(f"no product with id {product_id}")
return None

product = storage[product_id]
old_name = product[NAME_KEY]

if NAME_KEY in fields:
name = normalize_product_name(fields[NAME_KEY])
if not name:
print("product name must not be blank")
return None
if name != old_name and name in product_names:
print(f"product name '{name}' is already taken")
return None
fields[NAME_KEY] = name

if PRICE_KEY in fields:
fields[PRICE_KEY] = normalize_price(fields[PRICE_KEY])

product.update(fields)

if NAME_KEY in fields and fields[NAME_KEY] != old_name:
product_names.discard(old_name)
product_names.add(fields[NAME_KEY])

return product


def delete_product(
storage: dict[str, Product],
product_names: set[str],
product_id: str,
) -> str | None:
if product_id not in storage:
print(f"no product with id {product_id}")
return None

product_names.discard(storage[product_id][NAME_KEY])
del storage[product_id]
return product_id
92 changes: 92 additions & 0 deletions 01.11/run_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from decimal import Decimal, InvalidOperation
from typing import Final

from .crud import create_product, delete_product, read_product, update_product
from .storage import NAME_KEY, PRICE_KEY, QUANTITY_KEY, Product
from .utils import get_storage_str_representation

PROMPT: Final[str] = "admin> "

HELP_TEXT: Final[str] = """Available commands:
help show this message
exit leave the console
show print the whole store as a table
create <name...> <price> <quantity> add a product, print its new id
read <id> print the product with that id
update <id> <name...> <price> <quantity> overwrite that product's fields
delete <id> remove the product with that id

For create and update the price and quantity are the last two words of the
line; everything before them is the product name, so it may contain spaces
(for example "Gibson SG Junior") and needs no quoting."""


def show_help() -> None:
print(HELP_TEXT)


def print_result(result: object) -> None:
if result is not None:
print(result)


def run_command(
storage: dict[str, Product], product_names: set[str], line: str
) -> bool:
parts = line.split()
cmd = parts[0] if parts else ""

if cmd == "help" and len(parts) == 1:
show_help()
elif cmd == "exit" and len(parts) == 1:
return False
elif cmd == "show" and len(parts) == 1:
print(get_storage_str_representation(storage))
elif cmd == "create" and len(parts) >= 4:
result = create_product(
storage,
product_names,
{
NAME_KEY: " ".join(parts[1:-2]),
PRICE_KEY: Decimal(parts[-2]),
QUANTITY_KEY: int(parts[-1]),
},
)
print_result(result)
elif cmd == "read" and len(parts) == 2:
print_result(read_product(storage, parts[1]))
elif cmd == "update" and len(parts) >= 5:
result = update_product(
storage,
product_names,
parts[1],
{
NAME_KEY: " ".join(parts[2:-2]),
PRICE_KEY: Decimal(parts[-2]),
QUANTITY_KEY: int(parts[-1]),
},
)
print_result(result)
elif cmd == "delete" and len(parts) == 2:
print_result(delete_product(storage, product_names, parts[1]))
else:
print(f"'{line}' is not a command")

return True


def main() -> None:
storage: dict[str, Product] = {}
product_names: set[str] = set()
running = True

while running:
line = input(PROMPT).strip()
try:
running = run_command(storage, product_names, line)
except (ValueError, InvalidOperation):
print(f"'{line}' names a command but its arguments are invalid")


if __name__ == "__main__":
main()
7 changes: 7 additions & 0 deletions 01.11/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from typing import Any, Final

type Product = dict[str, Any]

NAME_KEY: Final = "name"
PRICE_KEY: Final = "price"
QUANTITY_KEY: Final = "quantity"
45 changes: 45 additions & 0 deletions 01.11/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from decimal import ROUND_HALF_UP, Decimal
from typing import Final

from .storage import NAME_KEY, PRICE_KEY, QUANTITY_KEY, Product

PRICE_PRECISION: Final[int] = 2
PRICE_STEP: Final[Decimal] = Decimal(1).scaleb(-PRICE_PRECISION)

TABLE_HEADERS: Final[tuple[str, ...]] = ("ID", "name", "price", "quantity")


def normalize_price(price: Decimal) -> Decimal:
return price.quantize(PRICE_STEP, rounding=ROUND_HALF_UP)


def normalize_product_name(name: str) -> str:
return " ".join(name.split()).lower()


def get_storage_str_representation(storage: dict[str, Product]) -> str:
rows: list[tuple[str, str, str, str]] = []
for product_id, product in storage.items():
rows.append((
str(product_id),
str(product[NAME_KEY]),
str(product[PRICE_KEY]),
str(product[QUANTITY_KEY]),
))

widths: list[int] = [len(header) for header in TABLE_HEADERS]
for row in rows:
for i, cell in enumerate(row):
if len(cell) > widths[i]:
widths[i] = len(cell)

def format_row(cells: tuple[str, ...]) -> str:
parts = [f" {cell.ljust(widths[i])} " for i, cell in enumerate(cells)]
return "|" + "|".join(parts) + "|"

lines = [format_row(TABLE_HEADERS)]
lines.append("|" + "|".join("-" * (width + 2) for width in widths) + "|")
for row in rows:
lines.append(format_row(row))

return "\n".join(lines)
Loading
Loading