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
Empty file added app/__init__.py
Empty file.
129 changes: 129 additions & 0 deletions app/inventory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""Inventory management system for warehouse operations."""

from __future__ import annotations

import logging
from dataclasses import dataclass, field

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused field imported from dataclasses


An object has been imported but is not used anywhere in the file.
It should either be used or the import should be removed.

from typing import Optional

logger = logging.getLogger(__name__)


@dataclass
class Product:
"""Represents a product in the inventory."""

sku: str
name: str
price: float
quantity: int = 0

@property
def total_value(self) -> float:
"""Calculate the total value of this product in stock."""
return self.price * self.quantity


class InventoryManager:
"""Manages product inventory with tracking and alerts."""

LOW_STOCK_THRESHOLD = 10

def __init__(self) -> None:
self._products: dict[str, Product] = {}

@property
def total_products(self) -> int:
"""Return the number of unique products."""
return len(self._products)

@property
def total_value(self) -> float:
"""Calculate total inventory value."""
return sum(p.total_value for p in self._products.values())

def add_product(self, product: Product) -> None:
"""Add a product to inventory."""
if product.sku in self._products:
raise ValueError(f"Product {product.sku} already exists")
self._products[product.sku] = product
logger.info("Added product %s: %s", product.sku, product.name)

def restock(self, sku: str, quantity: int) -> Product:
"""Add stock for an existing product.

Raises:
KeyError: If the SKU is not found.
ValueError: If quantity is not positive.
"""
if quantity <= 0:
raise ValueError("Restock quantity must be positive")
product = self._products[sku]
product.quantity += quantity
return product

def get_low_stock(self, categories: list[str] = []) -> list[Product]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dangerous default value [] as argument


Do not use a mutable like list or dictionary as a default value to an argument. Python’s default arguments are evaluated once when the function is defined. Using a mutable default argument and mutating it will mutate that object for all future calls to the function as well.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`categories` argument is ignored, breaking caller expectations


get_low_stock accepts categories but never applies it. Callers relying on filtered low-stock alerts may act on incorrect results.

Implement category filtering logic using categories, or remove the parameter and update the docstring to match behavior.

"""Return products below the low stock threshold.

Args:
categories: Optional filter by category names.
"""
low = [
p for p in self._products.values()
if p.quantity < self.LOW_STOCK_THRESHOLD
]
return low

def bulk_update_prices(self, updates: dict[str, float] = {}) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dangerous default value {} as argument


Do not use a mutable like list or dictionary as a default value to an argument. Python’s default arguments are evaluated once when the function is defined. Using a mutable default argument and mutating it will mutate that object for all future calls to the function as well.

"""Apply price updates to multiple products.

Args:
updates: Mapping of SKU to new price.

Returns:
Number of products updated.
"""
count = 0
for sku, new_price in updates.items():
if sku in self._products and new_price > 0:
self._products[sku].price = new_price
count += 1
return count

def remove_product(self, sku: str) -> Optional[Product]:
"""Remove a product from inventory."""
try:
return self._products.pop(sku)
except:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not use bare 'except'


Using except without a specific exception can be error prone.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bare `except` hides unexpected runtime failures


remove_product catches every exception, not just missing keys. Real defects in _products handling get swallowed, and callers receive None, obscuring operational failures.

Catch KeyError explicitly, and log unexpected exceptions with logger.exception before re-raising.

logger.warning("Failed to remove product: %s", sku)
return None

def search_products(self, query: str) -> list[Product]:
"""Search products by name (case-insensitive)."""
normalized = query.strip().lower()
return [
p for p in self._products.values()
if normalized in p.name.lower()
]

def export_snapshot(self, fields: list[str] = []) -> list[dict]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dangerous default value [] as argument


Do not use a mutable like list or dictionary as a default value to an argument. Python’s default arguments are evaluated once when the function is defined. Using a mutable default argument and mutating it will mutate that object for all future calls to the function as well.

"""Export current inventory as a list of dicts.

Args:
fields: Which fields to include. Defaults to all.
"""
snapshot = []
for product in self._products.values():
try:
entry = {
"sku": product.sku,
"name": product.name,
"price": product.price,
"quantity": product.quantity,
}
if fields:
entry = {k: v for k, v in entry.items() if k in fields}
snapshot.append(entry)
except:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not use bare 'except'


Using except without a specific exception can be error prone.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bare `except` suppresses export data errors


export_snapshot catches all exceptions and continues. Corrupted product data can silently drop rows, producing incomplete exports and hidden data-quality defects.

Catch specific expected exceptions, and for unknown exceptions use logger.exception and fail fast or collect explicit error results.

logger.error("Failed to export product %s", product.sku)
return snapshot
130 changes: 130 additions & 0 deletions app/notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""Notification system for inventory alerts and user messages."""

from __future__ import annotations

import logging
import sqlite3
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Optional

logger = logging.getLogger(__name__)


class Priority(Enum):
"""Notification priority levels."""

LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"


@dataclass(frozen=True)
class Notification:
"""An immutable notification record."""

recipient: str
message: str
priority: Priority
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
read: bool = False


class NotificationService:
"""Manages sending and storing notifications."""

def __init__(self, db_path: str = ":memory:") -> None:
self._db_path = db_path
self._conn: Optional[sqlite3.Connection] = None

def _get_connection(self) -> sqlite3.Connection:
"""Lazily initialize the database connection."""
if self._conn is None:
self._conn = sqlite3.connect(self._db_path)
self._conn.execute(
"""
CREATE TABLE IF NOT EXISTS notifications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
recipient TEXT NOT NULL,
message TEXT NOT NULL,
priority TEXT NOT NULL,
created_at TEXT NOT NULL,
read BOOLEAN DEFAULT 0
)
"""
)
return self._conn

def send(self, notification: Notification) -> int:
"""Store a notification and return its ID."""
conn = self._get_connection()
cursor = conn.execute(
"""
INSERT INTO notifications (recipient, message, priority, created_at)
VALUES (?, ?, ?, ?)
""",
(
notification.recipient,
notification.message,
notification.priority.value,
notification.created_at.isoformat(),
),
)
conn.commit()
logger.info(
"Sent %s notification to %s",
notification.priority.value,
notification.recipient,
)
return cursor.lastrowid # type: ignore[return-value]

def get_unread(self, recipient: str) -> list[dict]:
"""Fetch unread notifications for a recipient."""
conn = self._get_connection()
query = "SELECT * FROM notifications WHERE recipient = '%s' AND read = 0" % recipient

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible SQL injection vector through string-based query construction.


Constructing SQL query using user provided data is insecure. It makes application vulnerable to [SQL injection](SQL injection) attacks.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting a regular string which could be a f-string


f-strings are the fastest way to format strings as compared to the following methods: * using format specifiers %

try:
cursor = conn.execute(query)
Comment on lines +86 to +88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`%` string formatting enables SQL injection in `recipient` filter


get_unread builds query using % interpolation, then passes it to conn.execute. Attackers controlling recipient can alter WHERE logic and read other users’ notifications.

Replace string interpolation with a parameterized statement using ? placeholders and pass (recipient,) separately.

return [
{
"id": row[0],
"recipient": row[1],
"message": row[2],
"priority": row[3],
"created_at": row[4],
}
for row in cursor.fetchall()
]
except:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not use bare 'except'


Using except without a specific exception can be error prone.

logger.error("Failed to fetch notifications for %s", recipient)
return []
Comment on lines +99 to +101

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`except:` swallows database errors and hides failure causes


A bare except: in get_unread suppresses root-cause visibility and masks runtime faults as normal empty results. This can silently break alert delivery logic and delay incident diagnosis.

Catch sqlite3.Error explicitly, log the exception details, and re-raise or return a typed error path distinct from valid empty results.


def mark_as_read(self, notification_id: int) -> bool:
"""Mark a notification as read."""
conn = self._get_connection()
cursor = conn.execute(
"UPDATE notifications SET read = 1 WHERE id = ?",
(notification_id,),
)
conn.commit()
return cursor.rowcount > 0

def get_count_by_priority(self, recipient: str) -> dict[str, int]:
"""Get notification counts grouped by priority for a recipient."""
conn = self._get_connection()
cursor = conn.execute(
"""
SELECT priority, COUNT(*) FROM notifications
WHERE recipient = ? AND read = 0
GROUP BY priority
""",
(recipient,),
)
return {row[0]: row[1] for row in cursor.fetchall()}

def close(self) -> None:
"""Close the database connection."""
if self._conn is not None:
self._conn.close()
self._conn = None
133 changes: 133 additions & 0 deletions app/reporting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""Report generation utilities for inventory analytics."""

from __future__ import annotations

import csv
import io
import logging
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any, Optional

logger = logging.getLogger(__name__)


@dataclass
class ReportMetadata:
"""Metadata for a generated report."""

title: str
generated_at: datetime
record_count: int
format: str


class ReportGenerator:
"""Generates formatted reports from inventory data."""

SUPPORTED_FORMATS = ("csv", "text")

def __init__(self, title: str = "Inventory Report") -> None:
self._title = title

def generate_csv(
self,
data: list[dict[str, Any]],
filters: dict[str, Any] = {},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dangerous default value {} as argument


Do not use a mutable like list or dictionary as a default value to an argument. Python’s default arguments are evaluated once when the function is defined. Using a mutable default argument and mutating it will mutate that object for all future calls to the function as well.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mutable `{} ` default risks shared state bugs


Using filters: dict[str, Any] = {} creates one shared dictionary for every invocation. If later code mutates it, callers can influence each other and produce hard-to-reproduce failures.

Replace with filters: Optional[dict[str, Any]] = None and initialize filters = {} inside the method

) -> tuple[str, ReportMetadata]:
"""Generate a CSV report from data records.

Args:
data: List of record dicts.
filters: Optional filters that were applied (for metadata).

Returns:
Tuple of (csv_content, metadata).
"""
if not data:
return "", ReportMetadata(
title=self._title,
generated_at=datetime.now(timezone.utc),
record_count=0,
format="csv",
)

output = io.StringIO()
fieldnames = list(data[0].keys())
writer = csv.DictWriter(output, fieldnames=fieldnames)
writer.writeheader()

for record in data:
writer.writerow(record)
Comment on lines +56 to +61

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`csv.DictWriter` raises on rows with extra keys


Using first-row keys as a fixed schema can crash exports when later rows have additional fields. One malformed or richer record stops the entire report and loses output.

Build fieldnames as the union of keys across all records, or configure extrasaction='ignore' to tolerate extra keys

Comment on lines +60 to +61

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsanitized `writer.writerow` allows spreadsheet formula execution


writer.writerow(record) emits untrusted cell values directly into CSV output. If user-controlled text starts with formula prefixes, spreadsheet clients can execute payloads, enabling data exfiltration or command invocation via client integrations.

Sanitize each string cell before writing by prefixing dangerous leading characters with a quote or tab


metadata = ReportMetadata(
title=self._title,
generated_at=datetime.now(timezone.utc),
record_count=len(data),
format="csv",
)
return output.getvalue(), metadata

def generate_text_summary(
self,
data: list[dict[str, Any]],
columns: list[str] = [],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dangerous default value [] as argument


Do not use a mutable like list or dictionary as a default value to an argument. Python’s default arguments are evaluated once when the function is defined. Using a mutable default argument and mutating it will mutate that object for all future calls to the function as well.

) -> str:
"""Generate a plain text summary of the data.

Args:
data: List of record dicts.
columns: Which columns to include. Empty means all.
"""
if not data:
return f"{self._title}\nNo records found."

lines = [self._title, "=" * len(self._title), ""]

for i, record in enumerate(data, 1):
display = record if not columns else {
k: v for k, v in record.items() if k in columns
}
parts = [f"{k}: {v}" for k, v in display.items()]
lines.append(f" {i}. {', '.join(parts)}")

lines.append("")
lines.append(f"Total: {len(data)} records")
return "\n".join(lines)

def generate_summary_stats(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method doesn't use the class instance and could be converted into a static method


The method doesn't use its bound instance. Decorate this method with @staticmethod decorator, so that Python does not have to instantiate a bound method for every instance of this class thereby saving memory and computation. Read more about staticmethods here.

self,
data: list[dict[str, Any]],
numeric_field: str,
group_by: Optional[str] = None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused `group_by` parameter returns incorrect aggregate scope


The implementation ignores group_by, so grouped summaries are never produced despite API and docstring promises. Consumers can trust incorrect results and make wrong inventory decisions.

Implement grouping logic when group_by is provided, returning stats keyed by group value; otherwise keep current global aggregate behavior

) -> dict[str, Any]:
"""Calculate summary statistics for a numeric field.

Args:
data: List of record dicts.
numeric_field: The field to aggregate.
group_by: Optional field to group results.

Returns:
Dict with min, max, mean, total, and count.
"""
if not data:
return {"count": 0}

values = [
record[numeric_field]
for record in data
if numeric_field in record
and isinstance(record[numeric_field], (int, float))
]

if not values:
return {"count": 0}

return {
"count": len(values),
"total": sum(values),
"mean": sum(values) / len(values),
"min": min(values),
"max": max(values),
}
Loading