From 241a81652b16d795cd9c129f1eb084e30038776d Mon Sep 17 00:00:00 2001 From: Mihail Date: Sun, 1 Nov 2026 17:21:32 +0300 Subject: [PATCH 01/11] feat: add names and constants --- src/part4/storage.py | 8 +++----- src/part4/utils.py | 8 ++------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/part4/storage.py b/src/part4/storage.py index 9acedf07..01afb14f 100644 --- a/src/part4/storage.py +++ b/src/part4/storage.py @@ -38,8 +38,6 @@ type Product = dict[str, Any] -# Name of each field inside a Product record. -# TODO: задайте имена полей записи -NAME_KEY: Final = "" -PRICE_KEY: Final = "" -QUANTITY_KEY: Final = "" +NAME_KEY: Final = "name" +PRICE_KEY: Final = "price" +QUANTITY_KEY: Final = "quantity" diff --git a/src/part4/utils.py b/src/part4/utils.py index d2a940ea..d2a6bae7 100644 --- a/src/part4/utils.py +++ b/src/part4/utils.py @@ -29,12 +29,8 @@ ) -# Number of fractional digits every stored price is rounded to. -# Quantisation step derived from PRICE_PRECISION, e.g. Decimal("0.01"). -# TODO: задайте число знаков после запятой и шаг квантования (используйте своё -# решение части 3) -PRICE_PRECISION: Final[int] = 0 -PRICE_STEP: Final = Decimal(0) +PRICE_PRECISION: Final[int] = 2 +PRICE_STEP: Final = Decimal("0.01") # Column headers of the table produced by get_storage_str_representation, # left to right. The width of each column is not fixed here -- it is From 3b04a3cf2b015f88a32be004a4380122516afda6 Mon Sep 17 00:00:00 2001 From: Mihail Date: Sun, 1 Nov 2026 17:39:56 +0300 Subject: [PATCH 02/11] feat: add functions in utils --- src/part4/utils.py | 81 ++++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 53 deletions(-) diff --git a/src/part4/utils.py b/src/part4/utils.py index d2a6bae7..651aa393 100644 --- a/src/part4/utils.py +++ b/src/part4/utils.py @@ -32,65 +32,40 @@ PRICE_PRECISION: Final[int] = 2 PRICE_STEP: Final = Decimal("0.01") -# Column headers of the table produced by get_storage_str_representation, -# left to right. The width of each column is not fixed here -- it is -# measured per call from the data (see the function). -# TODO: задайте заголовки столбцов таблицы (используйте своё решение части 3) -TABLE_HEADERS: Final[tuple[str, ...]] = () +TABLE_HEADERS: Final[tuple[str, ...]] = ("ID", "name", "price", "quantity") -def normalize_price(price: Decimal) -> Decimal: - """Round a price to the precision every stored record uses. - - Args: - price: The raw price amount. - Returns: - ``price`` quantised to :data:`PRICE_PRECISION` fractional digits, - with halves rounded up. - """ - # TODO: реализуйте функцию (используйте своё решение части 3) - return Decimal(0) +def normalize_price(price: Decimal) -> Decimal: + return price.quantize(PRICE_STEP, rounding=ROUND_HALF_UP) def normalize_product_name(name: str) -> str: - """Reduce a product name to the canonical form the store keeps. - - Args: - name: The raw product name. - - Returns: - ``name`` with every leading and trailing whitespace character - removed, every internal run of whitespace (spaces, tabs and the - like) collapsed to a single space, and the rest lower-cased. For - example ``"abc def\tghi\tjkl"`` becomes ``"abc def ghi jkl"``. - The result is an empty string when ``name`` holds nothing but - whitespace. - """ - # TODO: реализуйте функцию (используйте своё решение части 3) - return "" - - -# TODO: при необходимости добавьте свои вспомогательные функции + return " ".join(name.split()).lower() def get_storage_str_representation(storage: dict[str, Product]) -> str: - """Render the product store as a text table with data-sized columns. - - The table has the columns named by :data:`TABLE_HEADERS` -- id, name, - price and quantity. Each column is made exactly as wide as the longest - value it carries in this call (its header counted), so the columns - line up when the string is printed to a terminal and no value is ever - truncated. - - Args: - storage: The product store to render. - - Returns: - A multi-line string: a header row, a dashed separator row, then - one row per product in ``storage``, in insertion order. When - ``storage`` is empty only the header and separator rows are - returned, sized to the header labels. - """ - # TODO: реализуйте функцию (используйте своё решение части 3) - return "" + rows: list[tuple[str, str, str, str]] = [] + for product_id, product in storage.items(): + rows.append( + ( + product_id, + product[NAME_KEY], + str(product[PRICE_KEY]), + str(product[QUANTITY_KEY]), + ) + ) + + widths = [len(header) for header in TABLE_HEADERS] + for row in rows: + for i, value in enumerate(row): + if len(value) > widths[i]: + widths[i] = len(value) + + def format_row(row: tuple[str, str, str, str]) -> str: + return " | ".join(value.ljust(widths[i]) for i, value in enumerate(row)) + + separator = "-+-".join("-" * width for width in widths) + lines = [format_row(TABLE_HEADERS), separator] + lines.extend(format_row(row) for row in rows) + return "\n".join(lines) From b3cb590f99399938ce8492489967a700fb83225b Mon Sep 17 00:00:00 2001 From: Mihail Date: Sun, 1 Nov 2026 17:48:18 +0300 Subject: [PATCH 03/11] feat: add create_product --- src/part4/crud.py | 50 +++++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/src/part4/crud.py b/src/part4/crud.py index 471eff00..862e3b84 100644 --- a/src/part4/crud.py +++ b/src/part4/crud.py @@ -36,13 +36,7 @@ def generate_product_id() -> str: - """Generate an identifier for a new product. - - Returns: - A freshly generated UUID, stringified. - """ - # TODO: реализуйте функцию - return "" + return str(uuid.uuid4()) def create_product( @@ -50,31 +44,23 @@ def create_product( product_names: set[str], fields: dict[str, Any], ) -> str | None: - """Add a new product to ``storage`` and return its new identifier. - - Args: - storage: The product store to add to; modified in place on - success. - product_names: The normalised names already in ``storage``; the - new name is added to it in place on success. - fields: A dictionary with the ``name``, ``price`` and ``quantity`` - keys (see :data:`~src.part4.storage.NAME_KEY`, - :data:`~src.part4.storage.PRICE_KEY` and - :data:`~src.part4.storage.QUANTITY_KEY`) describing the - product. ``name`` is normalised (surrounding whitespace - stripped, lower-cased) before it is stored or compared. - ``price`` is a :class:`~decimal.Decimal` amount and is rounded - to the stored money precision before it is saved. - - Returns: - The identifier generated for the new product, or ``None`` when - ``name`` is blank once normalised or a product with the same - normalised name already exists. In the ``None`` case ``storage`` - and ``product_names`` are left unchanged and an explanatory - message is printed. - """ - # TODO: реализуйте функцию - return "" + 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: From 0d52d3199eaed0550c0049f680d5edd8f9d6a5b7 Mon Sep 17 00:00:00 2001 From: Mihail Date: Sun, 1 Nov 2026 17:55:34 +0300 Subject: [PATCH 04/11] feat: add read_product --- src/part4/crud.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/part4/crud.py b/src/part4/crud.py index 862e3b84..3349c76e 100644 --- a/src/part4/crud.py +++ b/src/part4/crud.py @@ -64,19 +64,10 @@ def create_product( def read_product(storage: dict[str, Product], product_id: str) -> Product | None: - """Return the product stored under ``product_id``. - - Args: - storage: The product store to search. - product_id: The identifier to look up. - - Returns: - The matching product record, or ``None`` when no product carries - that identifier (a message is printed in that case). - """ - # TODO: реализуйте функцию - return {} - + 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], From 06fea27ca03ee0f926fa7b239e2d2f44f3f2de36 Mon Sep 17 00:00:00 2001 From: Mihail Date: Sun, 1 Nov 2026 18:06:39 +0300 Subject: [PATCH 05/11] feat: add update_product --- src/part4/crud.py | 56 ++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/part4/crud.py b/src/part4/crud.py index 3349c76e..c7d43ae7 100644 --- a/src/part4/crud.py +++ b/src/part4/crud.py @@ -75,39 +75,35 @@ def update_product( product_id: str, fields: dict[str, Any], ) -> Product | None: - """Overwrite the fields of the product stored under ``product_id``. + if product_id not in storage: + print(f"no product with id {product_id}") + return None - The identifier itself is preserved. ``fields`` may carry any subset of - ``name``, ``price`` and ``quantity``; a key that is missing keeps the - value the product already has, so a caller can update a single field - without repeating the other two. + product = storage[product_id] + new_name: str | None = None - Args: - storage: The product store to modify; the matching record is - updated in place on success. - product_names: The normalised names already in ``storage``; on a - successful rename the old name is removed from it and the new - one added. - product_id: The identifier of the product to change. - fields: A dictionary with any of the ``name``, ``price`` and - ``quantity`` keys (see :data:`~src.part4.storage.NAME_KEY`, - :data:`~src.part4.storage.PRICE_KEY` and - :data:`~src.part4.storage.QUANTITY_KEY`) holding the new - values. ``price`` is a :class:`~decimal.Decimal` amount and is - rounded to the stored money precision before it is saved. - Mutated in place: a ``name`` or ``price`` value present in - ``fields`` is overwritten with its normalised form. + if NAME_KEY in fields: + new_name = normalize_product_name(fields[NAME_KEY]) + if not new_name: + print("product name must not be blank") + return None + if new_name != product[NAME_KEY] and new_name in product_names: + print(f"product name '{new_name}' is already taken") + return None + fields[NAME_KEY] = new_name - Returns: - The updated product record, or ``None`` when ``name`` is present - and blank once normalised, no product carries that identifier, or - the normalised name is already taken by another product. In every - ``None`` case ``storage`` and ``product_names`` are left unchanged - and an explanatory message is printed. Renaming a product to its - own current name is allowed. - """ - # TODO: реализуйте функцию - return {} + if PRICE_KEY in fields: + fields[PRICE_KEY] = normalize_price(fields[PRICE_KEY]) + + if new_name is not None and new_name != product[NAME_KEY]: + product_names.discard(product[NAME_KEY]) + product_names.add(new_name) + + for key in (NAME_KEY, PRICE_KEY, QUANTITY_KEY): + if key in fields: + product[key] = fields[key] + + return product def delete_product( From 512a64947efc6b99b829c6ab1622f122d286643b Mon Sep 17 00:00:00 2001 From: Mihail Date: Sun, 1 Nov 2026 18:14:34 +0300 Subject: [PATCH 06/11] feat: add delete_product --- src/part4/crud.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/part4/crud.py b/src/part4/crud.py index c7d43ae7..5fa4a468 100644 --- a/src/part4/crud.py +++ b/src/part4/crud.py @@ -111,20 +111,10 @@ def delete_product( product_names: set[str], product_id: str, ) -> str | None: - """Remove the product stored under ``product_id`` from ``storage``. - - Args: - storage: The product store to remove from; modified in place on - success. - product_names: The normalised names already in ``storage``; the - departing product's name is removed from it in place on - success. - product_id: The identifier of the product to remove. - - Returns: - ``product_id`` when a product was removed, or ``None`` when no - product carried that identifier (``storage`` and ``product_names`` - are left unchanged and a message is printed). - """ - # TODO: реализуйте функцию - return "" + if product_id not in storage: + print(f"no product with id {product_id}") + return None + + product = storage.pop(product_id) + product_names.discard(product[NAME_KEY]) + return product_id From 2fc32c01b6bd119a89c73ebec9fc096643e09da6 Mon Sep 17 00:00:00 2001 From: Mihail Date: Sun, 1 Nov 2026 18:51:47 +0300 Subject: [PATCH 07/11] feat: add add_to_cart --- src/part4/cart.py | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/src/part4/cart.py b/src/part4/cart.py index aed9bb72..09226119 100644 --- a/src/part4/cart.py +++ b/src/part4/cart.py @@ -9,7 +9,7 @@ an explanatory message to stdout and returns ``None``. """ -from .storage import Product +from .storage import Product, QUANTITY_KEY # TODO: при необходимости добавьте свои вспомогательные функции @@ -22,32 +22,21 @@ def add_to_cart( product_id: str, quantity: int, ) -> int | None: - """Move ``quantity`` units of ``product_id`` from the store into ``cart``. + if product_id not in storage: + print(f"no product with id {product_id}") + return None - The product is looked up in ``storage``. If it is missing, a message is - printed and nothing changes. If the store holds fewer units than - requested, an explanatory message is printed and nothing changes. - Otherwise the store record is decremented by ``quantity`` and the cart - gains ``quantity`` units of ``product_id`` -- the product is added when - the cart had none of it. + product = storage[product_id] + if product[QUANTITY_KEY] < quantity: + print( + f"not enough stock for product {product_id}: " + f"{product[QUANTITY_KEY]} available, {quantity} requested" + ) + return None - Args: - storage: The product store to draw stock from; modified in place on - success. - product_names: The normalised names in ``storage``; a - quantity-only change never renames a product, so it is left - unchanged here. - cart: The cart to add the units to; modified in place on success. - product_id: The identifier of the product to add. - quantity: Number of units to move into the cart. - - Returns: - The quantity of ``product_id`` in the cart after the addition, or - ``None`` when the product is unknown or the store cannot cover the - request. - """ - # TODO: реализуйте функцию - return 0 + product[QUANTITY_KEY] -= quantity + cart[product_id] = cart.get(product_id, 0) + quantity + return cart[product_id] def remove_from_cart( From ccb3e3acbb310cf55e173e29130c2df86fceebfb Mon Sep 17 00:00:00 2001 From: Mihail Date: Sun, 1 Nov 2026 19:00:18 +0300 Subject: [PATCH 08/11] feat: add remove_from_cart --- src/part4/cart.py | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/src/part4/cart.py b/src/part4/cart.py index 09226119..997aa913 100644 --- a/src/part4/cart.py +++ b/src/part4/cart.py @@ -46,31 +46,25 @@ def remove_from_cart( product_id: str, quantity: int, ) -> int | None: - """Move ``quantity`` units of ``product_id`` from ``cart`` back to the store. + if product_id not in cart: + print(f"product {product_id} is not in the cart") + return None - If the cart holds no units of ``product_id``, a message is printed and - nothing changes. If it holds fewer units than requested, an explanatory - message is printed and nothing changes. If the product is missing from - ``storage``, a message is printed and nothing changes. Otherwise the cart loses - ``quantity`` units of the product -- the product is dropped from the - cart when its quantity reaches zero -- and the store record gains - ``quantity`` units back. + if cart[product_id] < quantity: + print( + f"cart holds only {cart[product_id]} unit(s) of product " + f"{product_id}, cannot remove {quantity}" + ) + return None - Args: - storage: The product store to return stock to; modified in place on - success. - product_names: The normalised names in ``storage``; a - quantity-only change never renames a product, so it is left - unchanged here. - cart: The cart to take the units from; modified in place on - success. - product_id: The identifier of the product to remove. - quantity: Number of units to move back into the store. + if product_id not in storage: + print(f"no product with id {product_id}") + return None - Returns: - The quantity of ``product_id`` left in the cart after the removal - (zero means the product was dropped from the cart), or ``None`` when - the cart does not hold the product or holds too few units. - """ - # TODO: реализуйте функцию - return 0 + storage[product_id][QUANTITY_KEY] += quantity + remainig = cart[product_id] - quantity + if remainig == 0: + del cart[product_id] + else: + cart[product_id] = remainig + return remainig From 33bb7e3b7b8d7456bbcf9740d85758014c96a6b3 Mon Sep 17 00:00:00 2001 From: Mihail Date: Sun, 1 Nov 2026 19:02:56 +0300 Subject: [PATCH 09/11] feat: add help text --- src/part4/run_admin.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/part4/run_admin.py b/src/part4/run_admin.py index 2247b29b..a874811b 100644 --- a/src/part4/run_admin.py +++ b/src/part4/run_admin.py @@ -41,30 +41,27 @@ from .utils import get_storage_str_representation # noqa: F401 -# TODO: задайте приглашение и текст справки (используйте своё решение части 3) -PROMPT: Final[str] = "" -HELP_TEXT: Final[str] = "" +PROMPT: Final[str] = "> " +HELP_TEXT: Final[str] = ( + "Commands:\n" + " help -- show this help\n" + " exit -- quit\n" + " show -- show the store\n" + " create -- add a product\n" + " read -- show a product\n" + " update -- update a product\n" + " delete -- remove a product\n" + "Multi-word names need no quoting: the price and quantity are the last two words." +) def show_help() -> None: - """Print the command reference to stdout. - - The text is the module-level :data:`HELP_TEXT` constant, printed as - is: a heading, one line per command with a short description, and a - note on how multi-word names are parsed. - """ - # TODO: реализуйте функцию (используйте своё решение части 3) + print(HELP_TEXT) def print_result(result: object) -> None: - """Print a CRUD result to stdout unless it is ``None``. - - Args: - result: The value returned by a CRUD operation. ``None`` means the - operation already reported its own failure, so nothing is - printed in that case. - """ - # TODO: реализуйте функцию (используйте своё решение части 3) + if result is not None: + print(result) def run_command( From f83b51f290e0b7350280cb9f4f5fb277a482bba6 Mon Sep 17 00:00:00 2001 From: Mihail Date: Sun, 1 Nov 2026 19:22:42 +0300 Subject: [PATCH 10/11] feat: add run_command --- src/part4/run_admin.py | 65 +++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/src/part4/run_admin.py b/src/part4/run_admin.py index a874811b..81feb040 100644 --- a/src/part4/run_admin.py +++ b/src/part4/run_admin.py @@ -67,29 +67,48 @@ def print_result(result: object) -> None: def run_command( storage: dict[str, Product], product_names: set[str], line: str ) -> bool: - """Parse one console line and carry out the command it names. - - Args: - storage: The product store shared across the session; mutated in - place by the ``create``, ``update`` and ``delete`` commands. - product_names: The normalised names already in ``storage``; kept - in sync by the ``create``, ``update`` and ``delete`` commands. - line: One line of console input, already stripped of surrounding - whitespace. - - Returns: - ``True`` to keep the read-eval-print loop running, or ``False`` - once the ``exit`` command has been seen. A line that is not a - known command prints a notice and still returns ``True``. - - Raises: - ValueError: If the quantity argument of a CRUD command does not - parse as a base-10 integer. - decimal.InvalidOperation: If the price argument of ``create`` or - ``update`` does not parse as a decimal number. - """ - # TODO: реализуйте функцию - return False + parts = line.split() + if not parts: + print(f"{line!r} is not a command") + return True + + command = parts[0] + + match command: + case "help" if len(parts) == 1: + show_help() + case "exit" if len(parts) == 1: + return False + case "show" if len(parts) == 1: + print(get_storage_str_representation(storage)) + case "create" if len(parts) >= 4: + *name_parts, price_str, quantity_str = parts[1:] + fields = { + NAME_KEY: " ".join(name_parts), + PRICE_KEY: Decimal(price_str), + QUANTITY_KEY: int(quantity_str), + } + print_result(update_product(storage, product_names, fields)) + case "read" if len(parts) == 2: + print_result(read_product(storage, parts[1])) + case "update" if len(parts) >= 5: + product_id = parts[1] + *name_parts, price_str, quantity_str = parts[2:] + fields = { + NAME_KEY: " ".join(name_parts), + PRICE_KEY: Decimal(price_str), + QUANTITY_KEY: int(quantity_str), + } + print_result(update_product(storage, product_names, product_id, fields)) + case "delete" if len(parts) == 2: + print_result(delete_product(storage, product_names, parts[1])) + case _: + if command in ("help", "exit", "show", "create", "read", "update", "delete"): + print(f"{line!r} names a command but its arguments are invalid") + else: + print(f"{line!r} is not a command") + return True + def main() -> None: From 3a616878aa724d36608ffa3bdb9357433d81bab7 Mon Sep 17 00:00:00 2001 From: Mihail Date: Sun, 1 Nov 2026 19:46:37 +0300 Subject: [PATCH 11/11] =?UTF-8?q?fix(part4):=20=D0=BF=D1=80=D0=BE=D0=B9?= =?UTF-8?q?=D1=82=D0=B8=20ruff=20=D0=B8=20mypy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/part4/cart.py | 2 +- src/part4/crud.py | 6 +++--- src/part4/run_admin.py | 19 +++++-------------- src/part4/utils.py | 33 ++++++++++++++++----------------- 4 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/part4/cart.py b/src/part4/cart.py index 997aa913..a0ddb706 100644 --- a/src/part4/cart.py +++ b/src/part4/cart.py @@ -9,7 +9,7 @@ an explanatory message to stdout and returns ``None``. """ -from .storage import Product, QUANTITY_KEY +from .storage import QUANTITY_KEY, Product # TODO: при необходимости добавьте свои вспомогательные функции diff --git a/src/part4/crud.py b/src/part4/crud.py index 5fa4a468..2036fc10 100644 --- a/src/part4/crud.py +++ b/src/part4/crud.py @@ -23,16 +23,16 @@ than a scan over ``storage.values()``. """ -import uuid # noqa: F401 +import uuid from typing import Any -from .storage import ( # noqa: F401 +from .storage import ( NAME_KEY, PRICE_KEY, QUANTITY_KEY, Product, ) -from .utils import normalize_price, normalize_product_name # noqa: F401 +from .utils import normalize_price, normalize_product_name def generate_product_id() -> str: diff --git a/src/part4/run_admin.py b/src/part4/run_admin.py index 81feb040..a7e3bd73 100644 --- a/src/part4/run_admin.py +++ b/src/part4/run_admin.py @@ -28,17 +28,17 @@ is left untouched and the loop keeps running; only ``exit`` stops it. """ -from decimal import Decimal, InvalidOperation # noqa: F401 +from decimal import Decimal, InvalidOperation from typing import Final -from .crud import ( # noqa: F401 +from .crud import ( create_product, delete_product, read_product, update_product, ) -from .storage import NAME_KEY, PRICE_KEY, QUANTITY_KEY, Product # noqa: F401 -from .utils import get_storage_str_representation # noqa: F401 +from .storage import NAME_KEY, PRICE_KEY, QUANTITY_KEY, Product +from .utils import get_storage_str_representation PROMPT: Final[str] = "> " @@ -88,7 +88,7 @@ def run_command( PRICE_KEY: Decimal(price_str), QUANTITY_KEY: int(quantity_str), } - print_result(update_product(storage, product_names, fields)) + print_result(create_product(storage, product_names, fields)) case "read" if len(parts) == 2: print_result(read_product(storage, parts[1])) case "update" if len(parts) >= 5: @@ -112,15 +112,6 @@ def run_command( def main() -> None: - """Run the admin console until the ``exit`` command is entered. - - A single product store, and the set of normalised names it holds, are - created empty and kept in memory for the whole session. Each iteration - reads one line from stdin, strips its surrounding whitespace and hands - it to :func:`run_command`. A line that names a command but whose price - or quantity argument does not parse is caught here and reported - without stopping the loop; only ``exit`` ends it. - """ storage: dict[str, Product] = {} product_names: set[str] = set() running = True diff --git a/src/part4/utils.py b/src/part4/utils.py index 651aa393..ec8100b1 100644 --- a/src/part4/utils.py +++ b/src/part4/utils.py @@ -18,10 +18,10 @@ the longest value it holds in that particular call. """ -from decimal import ROUND_HALF_UP, Decimal # noqa: F401 +from decimal import ROUND_HALF_UP, Decimal from typing import Final -from .storage import ( # noqa: F401 +from .storage import ( NAME_KEY, PRICE_KEY, QUANTITY_KEY, @@ -45,25 +45,24 @@ def normalize_product_name(name: str) -> str: 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( - ( - product_id, - product[NAME_KEY], - str(product[PRICE_KEY]), - str(product[QUANTITY_KEY]), - ) - ) + rows: list[tuple[str, str, str, str]] = [] + for product_id, product in storage.items(): + rows.append( + ( + product_id, + product[NAME_KEY], + str(product[PRICE_KEY]), + str(product[QUANTITY_KEY]), + ) + ) widths = [len(header) for header in TABLE_HEADERS] for row in rows: - for i, value in enumerate(row): - if len(value) > widths[i]: - widths[i] = len(value) + for i, value in enumerate(row): + widths[i] = max(widths[i], len(value)) - def format_row(row: tuple[str, str, str, str]) -> str: - return " | ".join(value.ljust(widths[i]) for i, value in enumerate(row)) + def format_row(row: tuple[str, ...]) -> str: + return " | ".join(value.ljust(widths[i]) for i, value in enumerate(row)) separator = "-+-".join("-" * width for width in widths) lines = [format_row(TABLE_HEADERS), separator]