mirror of
https://github.com/Sea-Haven-Industries/procurement-ingest.git
synced 2026-09-30 14:13:13 +00:00
44 lines
1.5 KiB
Python
44 lines
1.5 KiB
Python
|
|
"""JSON response helpers for the procurement API.
|
||
|
|
|
||
|
|
DynamoDB items come back through boto3 with numeric attributes as
|
||
|
|
``decimal.Decimal`` (e.g. purchase-orders ``total_amount``/``line_items``
|
||
|
|
prices), which ``json.dumps`` rejects. The encoder below renders integral
|
||
|
|
Decimals as JSON integers (exact) and non-integral Decimals as floats. For the
|
||
|
|
values this API serves -- currency amounts at PO magnitudes, well under ~15
|
||
|
|
significant digits -- the shortest-float repr round-trips the original digits
|
||
|
|
exactly. A Decimal carrying more precision than a float can hold (DynamoDB
|
||
|
|
Number supports 38 digits) would be truncated silently; the pipeline never
|
||
|
|
writes such values, but if that ever changes, switch non-integral Decimals to
|
||
|
|
``str(o)`` and update the OpenAPI number types.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import decimal
|
||
|
|
import json
|
||
|
|
|
||
|
|
|
||
|
|
class _DecimalSafeEncoder(json.JSONEncoder):
|
||
|
|
def default(self, o):
|
||
|
|
if isinstance(o, decimal.Decimal):
|
||
|
|
if o == o.to_integral_value():
|
||
|
|
return int(o)
|
||
|
|
return float(o)
|
||
|
|
if isinstance(o, set):
|
||
|
|
return sorted(o)
|
||
|
|
return super().default(o)
|
||
|
|
|
||
|
|
|
||
|
|
def dumps(payload) -> str:
|
||
|
|
return json.dumps(payload, cls=_DecimalSafeEncoder)
|
||
|
|
|
||
|
|
|
||
|
|
def json_response(status_code: int, payload) -> dict:
|
||
|
|
return {
|
||
|
|
"statusCode": status_code,
|
||
|
|
"headers": {"Content-Type": "application/json"},
|
||
|
|
"body": dumps(payload),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def error_response(status_code: int, message: str) -> dict:
|
||
|
|
return json_response(status_code, {"error": message})
|