diff --git a/backend/app/schemas/chargepoint.py b/backend/app/schemas/chargepoint.py index bf95642..2493315 100644 --- a/backend/app/schemas/chargepoint.py +++ b/backend/app/schemas/chargepoint.py @@ -8,6 +8,8 @@ from app.schemas.connector import Connector from ocpp.v201.enums import ResetEnumType, ResetStatusEnumType +from app.util.encoders import decimal_encoder + class ChargePointBase(BaseModel): identity: str is_active: bool @@ -32,6 +34,7 @@ class ChargePoint(ChargePointBase): class Config: from_attributes = True + json_encoders = {Decimal: decimal_encoder} class ChargePointPassword(BaseModel): password: str diff --git a/backend/app/schemas/chargepoint_variable.py b/backend/app/schemas/chargepoint_variable.py index 19200eb..f0a3543 100644 --- a/backend/app/schemas/chargepoint_variable.py +++ b/backend/app/schemas/chargepoint_variable.py @@ -4,6 +4,8 @@ from uuid import UUID from pydantic import BaseModel import enum +from app.util.encoders import decimal_encoder + class AttributeType(enum.Enum): ACTUAL = "Actual" TARGET = "Target" @@ -52,6 +54,7 @@ class ChargepointVariable(BaseModel): class Config: from_attributes = True + json_encoders = {Decimal: decimal_encoder} class ChargepointVariableUpdate(BaseModel): value: str diff --git a/backend/app/schemas/meter_value.py b/backend/app/schemas/meter_value.py index 7a0343e..99f6945 100644 --- a/backend/app/schemas/meter_value.py +++ b/backend/app/schemas/meter_value.py @@ -5,6 +5,8 @@ from typing import Optional from uuid import UUID from pydantic import BaseModel +from app.util.encoders import decimal_encoder + class PhaseType(enum.Enum): L1 = "L1" L2 = "L2" @@ -55,3 +57,4 @@ class MeterValue(BaseModel): class Config: from_attributes = True + json_encoders = {Decimal: decimal_encoder} diff --git a/backend/app/schemas/session.py b/backend/app/schemas/session.py index b6a320b..07b98a1 100644 --- a/backend/app/schemas/session.py +++ b/backend/app/schemas/session.py @@ -8,4 +8,5 @@ class Session(BaseModel): name: str last_used: datetime - model_config = {"from_attributes": True} \ No newline at end of file + class Config: + from_attributes = True \ No newline at end of file diff --git a/backend/app/schemas/transaction.py b/backend/app/schemas/transaction.py index d50ccad..0188f91 100644 --- a/backend/app/schemas/transaction.py +++ b/backend/app/schemas/transaction.py @@ -5,6 +5,8 @@ from typing import Optional from uuid import UUID from pydantic import BaseModel +from app.util.encoders import decimal_encoder + class TransactionStatus(enum.Enum): ONGOING = "ongoing" ENDED = "ended" @@ -50,6 +52,7 @@ class Transaction(BaseModel): class Config: from_attributes = True + json_encoders = {Decimal: decimal_encoder} class RemoteTransactionStartStopResponse(BaseModel): status: RemoteTransactionStartStopStatus diff --git a/backend/app/util/encoders.py b/backend/app/util/encoders.py new file mode 100644 index 0000000..fbdec8d --- /dev/null +++ b/backend/app/util/encoders.py @@ -0,0 +1,22 @@ +from decimal import Decimal +from typing import Union + +def decimal_encoder(dec_value: Decimal) -> Union[int, float]: + """Encodes a Decimal as int of there's no exponent, otherwise float. + + This is useful when we use ConstrainedDecimal to represent Numeric(x,0) + where a integer (but not int typed) is used. Encoding this as a float + results in failed round-tripping between encode and parse. + Our Id type is a prime example of this. + + >>> decimal_encoder(Decimal("1.0")) + 1.0 + + >>> decimal_encoder(Decimal("1")) + 1 + """ + exponent = dec_value.as_tuple().exponent + if isinstance(exponent, int) and exponent >= 0: + return int(dec_value) + else: + return float(dec_value) \ No newline at end of file