Skip to content

Commit 84c3409

Browse files
committed
updates
1 parent 42c61b2 commit 84c3409

2 files changed

Lines changed: 291 additions & 98 deletions

File tree

README.md

Lines changed: 140 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -239,81 +239,160 @@ Important
239239
Multiple files
240240
Rule: Type hints on all function parameters and return types
241241
242-
services/book_service.py:15 get_all_books() -- missing return type
243-
services/book_service.py:25 get_book(book_id) -- missing param + return
244-
services/order_service.py:9 create_order(...) -- missing all params + return
245-
services/order_service.py:80 update_status(...) -- missing all params + return
246-
utils/helpers.py:4 format_price(...) -- missing all params + return
247-
utils/helpers.py:15 get_bestsellers(limit) -- missing param + return
248-
... and 9 more functions
242+
File Function Missing
243+
------------------------------ ------------------------------- ---------------------
244+
services/book_service.py:15 get_all_books() return type
245+
services/book_service.py:25 get_book(book_id) param + return
246+
services/book_service.py:34 search_books(query) param + return
247+
services/order_service.py:9 create_order(self, ...) all params + return
248+
services/order_service.py:66 get_order(self, order_id) param + return
249+
services/order_service.py:80 update_status(self, ...) all params + return
250+
services/order_service.py:105 cancel_order(self, order_id) param + return
251+
services/order_service.py:133 get_order_history(self, ...) param + return
252+
services/order_service.py:139 calculate_shipping(self, ...) param + return
253+
services/order_service.py:154 _send_email(self, ...) all params + return
254+
utils/helpers.py:4 format_price(amount, currency) all params + return
255+
utils/helpers.py:15 get_bestsellers(limit) param + return
256+
utils/helpers.py:22 validate_isbn(isbn) param + return
257+
utils/helpers.py:30 generate_report() return
249258
250259
7. Error responses return 200 with error body instead of HTTPException
251260
routers/books.py:21-22, routers/orders.py:13-14,24-25
252261
262+
Returning {"error": "Book not found"} sends HTTP 200 with an error
263+
body. Clients cannot distinguish success from failure by status code.
264+
253265
# Before (routers/books.py:20-23) -- returns HTTP 200 with error!
266+
book = get_book(book_id)
254267
if not book:
255268
return {"error": "Book not found"}
269+
return book
256270
257271
# After -- proper HTTP semantics
272+
from fastapi import HTTPException
273+
274+
book = get_book(book_id)
258275
if not book:
259276
raise HTTPException(status_code=404, detail="Book not found")
277+
return book
278+
279+
8. Missing HTTP status codes on POST endpoints
280+
routers/books.py:8, routers/orders.py:10
281+
282+
POST endpoints default to 200 instead of 201 Created.
283+
284+
# Before
285+
@router.post("/")
286+
def add_book(data: BookCreate):
287+
288+
# After
289+
@router.post("/", status_code=201, response_model=Book)
290+
def add_book(data: BookCreate):
260291
261-
8. Returning raw dicts instead of Pydantic models
262-
services/book_service.py:20-22, services/order_service.py:58-64
292+
9. Returning raw dicts instead of Pydantic models
293+
services/book_service.py:20-22, services/order_service.py:58-64,71-77
263294
Principle: P6 Start with the Data
264295
265296
Functions return hand-built {"id": ..., "title": ...} dicts instead
266297
of using the Pydantic models already defined in models/. Loses type
267-
safety, validation, and IDE support.
298+
safety, validation, serialization control, and IDE support.
268299
269-
# Before
270-
result.append({"id": b.id, "title": b.title, "author": b.author, ...})
300+
# Before (services/book_service.py:19-22)
301+
result = []
302+
for b in books:
303+
result.append({"id": b.id, "title": b.title, "author": b.author,
304+
"isbn": b.isbn, "price": b.price})
305+
return result
271306
272307
# After -- use the model you already defined
273308
return [Book(id=b.id, title=b.title, author=b.author,
274309
isbn=b.isbn, price=b.price) for b in books]
275310
311+
10. Deprecated Pydantic v1 API
312+
services/book_service.py:7 -- data.dict()
313+
314+
Pydantic v2 renamed .dict() to .model_dump(). The old method
315+
still works but is deprecated and will be removed.
316+
317+
# Before (Pydantic v1)
318+
book = BookModel(**data.dict())
319+
320+
# After (Pydantic v2)
321+
book = BookModel(**data.model_dump())
322+
276323
Suggestions
277324
278-
9. if/elif chain for discount logic -- Strategy pattern
279-
services/order_service.py:19-31
280-
Pattern: Strategy -- Callable type alias + dict mapping
325+
11. if/elif chain for discount logic -- Strategy pattern
326+
services/order_service.py:19-31
327+
Pattern: Strategy -- Callable type alias + dict mapping
281328
282-
# Before
283-
if discount_type == "bulk":
284-
total = total * 0.8
285-
elif discount_type == "medium":
286-
total = total * 0.9
329+
# Before (services/order_service.py:19-31)
330+
if quantity >= 10:
331+
discount_type = "bulk"
332+
elif quantity >= 5:
333+
discount_type = "medium"
334+
else:
335+
discount_type = "none"
287336
288-
# After
289-
DiscountFn = Callable[[float, int], float]
337+
if discount_type == "bulk":
338+
total = total * 0.8
339+
elif discount_type == "medium":
340+
total = total * 0.9
341+
elif discount_type == "none":
342+
pass
343+
344+
# After -- discount strategies as functions
345+
from typing import Callable
346+
347+
DiscountFn = Callable[[float, int], float]
290348
291-
def bulk_discount(total: float, qty: int) -> float:
292-
return total * 0.8
349+
def bulk_discount(total: float, quantity: int) -> float:
350+
return total * 0.8
293351
294-
def get_discount(quantity: int) -> DiscountFn:
295-
if quantity >= 10: return bulk_discount
296-
if quantity >= 5: return medium_discount
297-
return no_discount
352+
def medium_discount(total: float, quantity: int) -> float:
353+
return total * 0.9
298354
299-
total = get_discount(quantity)(total, quantity)
355+
def no_discount(total: float, quantity: int) -> float:
356+
return total
300357
301-
10. if/elif chain for currency formatting -- dict mapping
358+
def get_discount(quantity: int) -> DiscountFn:
359+
if quantity >= 10:
360+
return bulk_discount
361+
elif quantity >= 5:
362+
return medium_discount
363+
return no_discount
364+
365+
# Usage
366+
discount = get_discount(quantity)
367+
total = discount(total, quantity)
368+
369+
12. if/elif chain for currency formatting -- dict mapping
302370
utils/helpers.py:4-12
303371
Pattern: Registry -- dict mapping replaces if/elif
304372
305373
# Before
306-
if currency == "USD": return f"${amount:.2f}"
307-
elif currency == "EUR": return f"E{amount:.2f}"
374+
def format_price(amount, currency):
375+
if currency == "USD":
376+
return f"${amount:.2f}"
377+
elif currency == "EUR":
378+
return f"E{amount:.2f}"
379+
elif currency == "GBP":
380+
return f"L{amount:.2f}"
381+
else:
382+
return f"{amount:.2f} {currency}"
308383
309384
# After
310-
SYMBOLS: dict[str, str] = {"USD": "$", "EUR": "E", "GBP": "L"}
385+
CURRENCY_SYMBOLS: dict[str, str] = {
386+
"USD": "$", "EUR": "E", "GBP": "L",
387+
}
311388
312389
def format_price(amount: float, currency: str) -> str:
313-
symbol = SYMBOLS.get(currency)
314-
return f"{symbol}{amount:.2f}" if symbol else f"{amount:.2f} {currency}"
390+
symbol = CURRENCY_SYMBOLS.get(currency)
391+
if symbol:
392+
return f"{symbol}{amount:.2f}"
393+
return f"{amount:.2f} {currency}"
315394
316-
11. Bare string constants for order status -- use StrEnum
395+
13. Bare string constants for order status -- use StrEnum
317396
services/order_service.py:38,86,89,95,111,114
318397
319398
# Before -- typo-prone strings scattered across the codebase
@@ -328,7 +407,7 @@ Suggestions
328407
DELIVERED = "delivered"
329408
CANCELLED = "cancelled"
330409
331-
12. Mixed concerns in utils/helpers.py -- split by domain
410+
14. Mixed concerns in utils/helpers.py -- split by domain
332411
utils/helpers.py:1-38
333412
Principle: P1 High Cohesion | Rule: #16 Avoid Generic Package Names
334413
@@ -339,23 +418,38 @@ Suggestions
339418
Fix: Move get_bestsellers/generate_report into services/.
340419
Move format_price into formatting.py. Move validate_isbn into models/.
341420
342-
13. Notification side effects interleaved with logic -- Pub/Sub pattern
421+
15. Notification side effects interleaved with business logic -- Pub/Sub
343422
services/order_service.py:44-49,89-100,121-125
344423
Pattern: Pub/Sub
345424
346-
_send_email calls scattered through create_order, update_status,
347-
and cancel_order. Each new side effect (Slack, analytics, audit)
348-
requires modifying every method.
425+
_send_email calls are scattered throughout create_order, update_status,
426+
and cancel_order. Each new side effect (Slack notification, analytics
427+
event, audit log) requires modifying every method.
428+
429+
# After -- event-based notification
430+
EventHandler = Callable[[dict], None]
431+
subscribers: dict[str, list[EventHandler]] = {}
432+
433+
def subscribe(event: str, handler: EventHandler) -> None:
434+
subscribers.setdefault(event, []).append(handler)
435+
436+
def post_event(event: str, data: dict) -> None:
437+
for handler in subscribers.get(event, []):
438+
handler(data)
349439
350-
# After -- event-based
351-
post_event("order.created", {"order_id": order.id, "email": email})
440+
# In create_order:
441+
post_event("order.created", {"order_id": order.id, "email": customer_email})
352442
353-
# Wire in composition root:
443+
# Wire handlers in composition root:
354444
subscribe("order.created", send_confirmation_email)
355445
subscribe("order.created", log_order)
356446
357-
Summary: 3 critical, 5 important, 5 suggestions. Biggest wins:
358-
introduce a DataInterface Protocol and split OrderService.
447+
---
448+
Summary: 3 critical (layer skip, no DI, no resource management),
449+
7 important (god class, broad catch, missing types, wrong HTTP responses,
450+
missing status codes, raw dicts, deprecated API), 5 suggestions (Strategy,
451+
Registry, StrEnum, split utils, Pub/Sub). The biggest wins would be
452+
introducing a DataInterface Protocol and splitting OrderService.
359453
```
360454

361455
### All Commands

0 commit comments

Comments
 (0)