dyntastic
dyntastic copied to clipboard
Handle 'floats' when Dict is of type 'any'
I have a model of the following type for a workflow engine
Setup permalink - https://github.com/OrcaBus/service-icav2-wes-manager/blob/main/app/interface/icav2_wes_api/models/analysis.py#L29-L141
class Icav2WesAnalysisBase(BaseModel):
name: str
inputs: Dict[str, Any]
...
tags: Dict[str, Any]
class Icav2WesAnalysisOrcabusId(BaseModel):
# fqr.ABCDEFGHIJKLMNOP
# BCLConvert Metadata attributes
id: str = Field(default_factory=lambda: f"{ICAV2_WES_ANALYSIS_PREFIX}.{get_ulid()}")
class Icav2WesAnalysisWithId(Icav2WesAnalysisBase, Icav2WesAnalysisOrcabusId):
"""
Order class inheritance this way to ensure that the id field is set first
"""
# We also have the steps execution id as an attribute to add
status: AnalysisStatus = Field(default='PENDING')
submission_time: datetime = Field(default_factory=datetime.now)
steps_launch_execution_arn: Optional[str] = None
icav2_analysis_id: Optional[str] = None
start_time: Optional[datetime] = None
end_time: Optional[datetime] = None
class Icav2WesAnalysisData(Icav2WesAnalysisWithId, Dyntastic):
"""
The job data object
"""
__table_name__ = environ['DYNAMODB_ICAV2_WES_ANALYSIS_TABLE_NAME']
__table_host__ = environ['DYNAMODB_HOST']
__hash_key__ = "id"
# To Dictionary
def to_dict(self) -> 'Icav2WesAnalysisResponse':
"""
Alternative serialization path to return objects by camel case
:return:
"""
return jsonable_encoder(
Icav2WesAnalysisResponse(
**self.model_dump()
).model_dump(by_alias=True)
)
However I get the following error if any of the input values have a float in them, i.e
Icav2WesAnalysisData(
id='iwa.01JXVPNHYZ8Z3W7MM66J8C3Z46',
name='umccr--automated--dragen-wgts-dna--4-4-4--20250616efgh6789',
inputs=...,
engine_parameters=...,
tags={
...
'preLaunchCoverageEst': 44.41,
'preLaunchDupFracEst': 0.3,
...
},
status='PENDING',
submission_time=datetime.datetime(2025, 6, 16, 6, 26, 16, 159186),
steps_launch_execution_arn=None,
icav2_analysis_id=None,
start_time=None,
end_time=None
}
).save()
Click to expand!
Traceback (most recent call last):
File "/var/task/mangum/protocols/http.py", line 58, in run
await app(self.scope, self.receive, self.send)
File "/var/task/fastapi/applications.py", line 1054, in __call__
await super().__call__(scope, receive, send)
File "/var/task/starlette/applications.py", line 112, in __call__
await self.middleware_stack(scope, receive, send)
File "/var/task/starlette/middleware/errors.py", line 187, in __call__
raise exc
File "/var/task/starlette/middleware/errors.py", line 165, in __call__
await self.app(scope, receive, _send)
File "/var/task/starlette/middleware/exceptions.py", line 62, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "/var/task/starlette/_exception_handler.py", line 53, in wrapped_app
raise exc
File "/var/task/starlette/_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "/var/task/starlette/routing.py", line 714, in __call__
await self.middleware_stack(scope, receive, send)
File "/var/task/starlette/routing.py", line 734, in app
await route.handle(scope, receive, send)
File "/var/task/starlette/routing.py", line 288, in handle
await self.app(scope, receive, send)
File "/var/task/starlette/routing.py", line 76, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "/var/task/starlette/_exception_handler.py", line 53, in wrapped_app
raise exc
File "/var/task/starlette/_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "/var/task/starlette/routing.py", line 73, in app
response = await f(request)
^^^^^^^^^^^^^^^^
File "/var/task/fastapi/routing.py", line 301, in app
raw_response = await run_endpoint_function(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/task/fastapi/routing.py", line 212, in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/task/icav2_wes_api/api/analysis.py", line 176, in create_job
analysis_obj.save()
File "/var/task/dyntastic/main.py", line 342, in save
return self._dyntastic_call("put_item", Item=dynamo_serialized, ConditionExpression=condition)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/task/dyntastic/main.py", line 676, in _dyntastic_call
return method(**filtered_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/task/boto3/resources/factory.py", line 581, in do_action
response = action(self, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/task/boto3/resources/action.py", line 88, in __call__
response = getattr(parent.meta.client, operation_name)(*args, **params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/task/botocore/client.py", line 598, in _api_call
return self._make_api_call(operation_name, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/task/botocore/context.py", line 123, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/var/task/botocore/client.py", line 1002, in _make_api_call
api_params = self._emit_api_params(
^^^^^^^^^^^^^^^^^^^^^^
File "/var/task/botocore/client.py", line 1121, in _emit_api_params
self.meta.events.emit(
File "/var/task/botocore/hooks.py", line 412, in emit
return self._emitter.emit(aliased_event_name, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/task/botocore/hooks.py", line 256, in emit
return self._emit(event_name, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/task/botocore/hooks.py", line 239, in _emit
response = handler(**kwargs)
^^^^^^^^^^^^^^^^^
File "/var/task/boto3/dynamodb/transform.py", line 217, in inject_attribute_value_input
self._transformer.transform(
File "/var/task/boto3/dynamodb/transform.py", line 289, in transform
self._transform_parameters(model, params, transformation, target_shape)
File "/var/task/boto3/dynamodb/transform.py", line 296, in _transform_parameters
getattr(self, f'_transform_{type_name}')(
File "/var/task/boto3/dynamodb/transform.py", line 312, in _transform_structure
self._transform_parameters(
File "/var/task/boto3/dynamodb/transform.py", line 296, in _transform_parameters
getattr(self, f'_transform_{type_name}')(
File "/var/task/boto3/dynamodb/transform.py", line 326, in _transform_map
params[key] = transformation(value)
^^^^^^^^^^^^^^^^^^^^^
File "/var/task/boto3/dynamodb/types.py", line 116, in serialize
return {dynamodb_type: serializer(value)}
^^^^^^^^^^^^^^^^^
File "/var/task/boto3/dynamodb/types.py", line 240, in _serialize_m
return {k: self.serialize(v) for k, v in value.items()}
^^^^^^^^^^^^^^^^^
File "/var/task/boto3/dynamodb/types.py", line 114, in serialize
dynamodb_type = self._get_dynamodb_type(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/task/boto3/dynamodb/types.py", line 127, in _get_dynamodb_type
elif self._is_number(value):
^^^^^^^^^^^^^^^^^^^^^^
File "/var/task/boto3/dynamodb/types.py", line 171, in _is_number
raise TypeError(
TypeError: Float types are not supported. Use Decimal types instead.
Since it's not possible to 'schemafy' the inputs or tags for my use-case, as a workaround I json.dumps and then json.loads when generating an API response