Skip to content

Commit 73c1d3d

Browse files
📝 Add docstrings to feat/pricing-endpoint-cache (#617)
Docstrings generation was requested by @shayancoin. * #616 (comment) The following files were modified: * `backend/api/deps.py` * `backend/api/graphql.py` * `backend/api/routes_pricing.py` Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent e5acfaa commit 73c1d3d

File tree

3 files changed

+105
-4
lines changed

3 files changed

+105
-4
lines changed

backend/api/deps.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,48 @@
1717

1818
class RedisLike(Protocol):
1919
async def get(self, key: str) -> Optional[str]:
20+
"""
21+
Retrieve a value by key from the in-memory store, respecting expiry.
22+
23+
Returns:
24+
The stored string value for `key`, or `None` if the key is missing or has expired.
25+
"""
2026
...
2127

2228
async def set(self, key: str, value: str, ex: Optional[int] = None) -> None:
29+
"""
30+
Store a string value under the given key, optionally expiring after a number of seconds.
31+
32+
Parameters:
33+
key (str): Key under which to store the value.
34+
value (str): String value to store.
35+
ex (Optional[int]): Expiration time in seconds; if provided, the key will be removed after this many seconds. Existing values for the key are overwritten.
36+
"""
2337
...
2438

2539

2640
class _InMemoryRedis:
2741
"""Minimal in-memory Redis replacement used when redis-py asyncio is unavailable."""
2842

2943
def __init__(self) -> None:
44+
"""
45+
Initialize internal storage for the in-memory Redis replacement.
46+
47+
Creates the `_store` dictionary that maps keys (str) to tuples of `(value, expires_at)`,
48+
where `value` is the stored string and `expires_at` is a Unix timestamp (float) when the
49+
entry expires or `None` if it does not expire.
50+
"""
3051
self._store: dict[str, tuple[str, Optional[float]]] = {}
3152

3253
async def get(self, key: str) -> Optional[str]:
54+
"""
55+
Retrieve the string value stored for the given key if it exists and has not expired.
56+
57+
If the key is missing or its expiry time has passed, the key is removed from the in-memory store and `None` is returned.
58+
59+
Returns:
60+
The stored `str` value for `key`, or `None` if the key does not exist or is expired.
61+
"""
3362
payload = self._store.get(key)
3463
if payload is None:
3564
return None
@@ -40,6 +69,14 @@ async def get(self, key: str) -> Optional[str]:
4069
return value
4170

4271
async def set(self, key: str, value: str, ex: Optional[int] = None) -> None:
72+
"""
73+
Store a string value under a key with an optional TTL in seconds.
74+
75+
Parameters:
76+
key (str): The key to store the value under.
77+
value (str): The string value to store.
78+
ex (Optional[int]): Time-to-live in seconds; if provided, the key expires after this many seconds.
79+
"""
4380
expires_at = time.monotonic() + ex if ex is not None else None
4481
self._store[key] = (value, expires_at)
4582

@@ -49,7 +86,15 @@ async def set(self, key: str, value: str, ex: Optional[int] = None) -> None:
4986

5087

5188
async def get_redis(settings: Settings = Depends(get_settings)) -> RedisLike:
52-
"""Return a Redis client (asyncio) or in-memory fallback."""
89+
"""
90+
Provide a shared Redis-like client: use the asyncio Redis client when available, otherwise use the in-memory fallback.
91+
92+
Parameters:
93+
settings (Settings): Application settings used to obtain `redis_url` when lazily initializing the asyncio Redis client.
94+
95+
Returns:
96+
RedisLike: The module-level Redis-like client (an initialized asyncio Redis client if available, otherwise the in-memory fallback).
97+
"""
5398

5499
global _redis_client
55100

@@ -65,4 +110,4 @@ async def get_redis(settings: Settings = Depends(get_settings)) -> RedisLike:
65110
return _redis_client
66111

67112

68-
__all__ = ["get_redis", "RedisLike"]
113+
__all__ = ["get_redis", "RedisLike"]

backend/api/graphql.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ class PriceQuote:
2525

2626

2727
def _to_graphql(response: PriceResponse) -> PriceQuote:
28+
"""
29+
Convert a backend PriceResponse into a GraphQL PriceQuote.
30+
31+
Constructs a PriceQuote with total_cents from the response and a nested PriceBreakdown populated from the response.breakdown keys: "base", "finish", "hardware", and "countertop".
32+
33+
Returns:
34+
PriceQuote: GraphQL PriceQuote with mapped `total_cents` and `breakdown`.
35+
"""
2836
breakdown = response.breakdown
2937
return PriceQuote(
3038
total_cents=response.total_cents,
@@ -47,6 +55,20 @@ async def price_quote(
4755
hardware: str,
4856
countertop: str,
4957
) -> PriceQuote:
58+
"""
59+
Resolve a price quote for a product configuration specified by the provided attributes.
60+
61+
Builds a price request from the provided elevation, finish, hardware, and countertop identifiers, queries the pricing backend, and returns the result as a GraphQL PriceQuote.
62+
63+
Parameters:
64+
elevation (str): Identifier for the product elevation/profile.
65+
finish (str): Identifier for the surface finish option.
66+
hardware (str): Identifier for the hardware option.
67+
countertop (str): Identifier for the countertop option.
68+
69+
Returns:
70+
PriceQuote: GraphQL representation of the computed price, including `total_cents` and a `breakdown` of cost components.
71+
"""
5072
request = PriceRequest(
5173
elevation=elevation,
5274
finish=finish,
@@ -68,4 +90,4 @@ async def price_quote(
6890
)
6991

7092

71-
__all__ = ["graphql_app", "schema"]
93+
__all__ = ["graphql_app", "schema"]

backend/api/routes_pricing.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,38 @@ class PriceResponse(BaseModel):
4848

4949

5050
def _lookup(mapping: dict[str, int], key: str, field: str) -> int:
51+
"""
52+
Retrieve the integer value associated with `key` in `mapping`, or raise an HTTP 422 error if the key is not present.
53+
54+
Parameters:
55+
mapping (dict[str, int]): Lookup mapping from keys to integer values (e.g., price components in cents).
56+
key (str): Key to look up in the mapping.
57+
field (str): Human-readable field name used in the error detail when `key` is unsupported.
58+
59+
Returns:
60+
int: The value found for `key`.
61+
62+
Raises:
63+
HTTPException: With status code 422 and detail "Unsupported {field}: {key}" when `key` is not in `mapping`.
64+
"""
5165
try:
5266
return mapping[key]
5367
except KeyError as exc: # pragma: no cover - defensive guard
5468
raise HTTPException(status_code=422, detail=f"Unsupported {field}: {key}") from exc
5569

5670

5771
async def compute_price_quote(request: PriceRequest, redis: RedisLike) -> PriceResponse:
72+
"""
73+
Compute a price quote for the given selection and use a short-lived micro-cache.
74+
75+
Attempts to return a cached PriceResponse keyed by the request fields. On cache read errors the function treats the cache as a miss; after computing the quote it will try to write the result to the cache but will ignore cache write failures so they do not affect the response.
76+
77+
Returns:
78+
PriceResponse: The total price in cents and a breakdown of the component prices.
79+
80+
Raises:
81+
HTTPException: 422 if any provided option (elevation, finish, hardware, or countertop) is not supported.
82+
"""
5883
cache_key = f"price:{request.elevation}:{request.finish}:{request.hardware}:{request.countertop}"
5984

6085
cached = None
@@ -95,4 +120,13 @@ async def quote(
95120
request: PriceRequest,
96121
redis: RedisLike = Depends(get_redis),
97122
) -> PriceResponse:
98-
return await compute_price_quote(request, redis)
123+
"""
124+
Generate a price quote for the given PriceRequest, using Redis for micro-caching.
125+
126+
Parameters:
127+
request (PriceRequest): The requested configuration with fields `elevation`, `finish`, `hardware`, and `countertop`.
128+
129+
Returns:
130+
PriceResponse: The computed quote containing `total_cents` and a `breakdown` mapping of component prices.
131+
"""
132+
return await compute_price_quote(request, redis)

0 commit comments

Comments
 (0)