Custom Types#

Often, the behavior for a field needs to be customized to support a particular shape or validation method that ParamTools does not support out of the box. In this case, you may use the register_custom_type function to add your new type to the ParamTools type registry. Each type has a corresponding field that is used for serialization and deserialization. ParamTools will then use this field any time it is handling a value, label, or member that is of this type.

ParamTools is built on top of marshmallow, a general purpose validation library. This means that you must implement a custom marshmallow field to go along with your new type. Please refer to the marshmallow docs if you have questions about the use of marshmallow in the examples below.

32 Bit Integer Example#

ParamTools’s default integer field uses NumPy’s int64 type. This example shows you how to define an int32 type and reference it in your defaults.

First, let’s define the Marshmallow class:

import marshmallow as ma
import numpy as np

class Int32(ma.fields.Field):
    """
    A custom type for np.int32.
    https://numpy.org/devdocs/reference/arrays.dtypes.html
    """
    # minor detail that makes this play nice with array_first
    np_type = np.int32

    def _serialize(self, value, *args, **kwargs):
        """Convert np.int32 to basic, serializable Python int."""
        return value.tolist()

    def _deserialize(self, value, *args, **kwargs):
        """Cast value from JSON to NumPy Int32."""
        converted = np.int32(value)
        return converted

Now, reference it in our defaults JSON/dict object:

import paramtools as pt


# add int32 type to the paramtools type registry
pt.register_custom_type("int32", Int32())


class Params(pt.Parameters):
    defaults = {
        "small_int": {
            "title": "Small integer",
            "description": "Demonstrate how to define a custom type",
            "type": "int32",
            "value": 2
        }
    }


params = Params(array_first=True)


print(f"value: {params.small_int}, type: {type(params.small_int)}")
value: 2, type: <class 'numpy.int32'>

One problem with this is that we could run into some deserialization issues. Due to integer overflow, our deserialized result is not the number that we passed in–it’s negative!

params.adjust(dict(
    # this number wasn't chosen randomly.
    small_int=2147483647 + 1
))
---------------------------------------------------------------------------
OverflowError                             Traceback (most recent call last)
Cell In[3], line 1
----> 1 params.adjust(dict(
      2     # this number wasn't chosen randomly.
      3     small_int=2147483647 + 1
      4 ))

File ~/work/ParamTools/ParamTools/paramtools/parameters.py:257, in Parameters.adjust(self, params_or_path, ignore_warnings, raise_errors, extend_adj, clobber)
    210 def adjust(
    211     self,
    212     params_or_path: Union[str, Mapping[str, List[ValueObject]]],
   (...)    216     clobber: bool = True,
    217 ):
    218     """
    219     Deserialize and validate parameter adjustments. `params_or_path`
    220     can be a file path or a `dict` that has not been fully deserialized.
   (...)    255         least one existing value item's corresponding label values.
    256     """
--> 257     return self._adjust(
    258         params_or_path,
    259         ignore_warnings=ignore_warnings,
    260         raise_errors=raise_errors,
    261         extend_adj=extend_adj,
    262         clobber=clobber,
    263     )

File ~/work/ParamTools/ParamTools/paramtools/parameters.py:291, in Parameters._adjust(self, params_or_path, ignore_warnings, raise_errors, extend_adj, deserialized, validate, clobber)
    289 parsed_params = {}
    290 try:
--> 291     parsed_params = self._validator_schema.load(
    292         params, ignore_warnings
    293     )
    294 except MarshmallowValidationError as ve:
    295     self._parse_validation_messages(ve.messages, params)

File ~/work/ParamTools/ParamTools/paramtools/schema.py:234, in BaseValidatorSchema.load(self, data, ignore_warnings, deserialized)
    232         return self.validate_only(data)
    233     else:
--> 234         return super().load(data)
    235 finally:
    236     self.ignore_warnings = False

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:730, in Schema.load(self, data, many, partial, unknown)
    703 def load(
    704     self,
    705     data: Mapping[str, typing.Any] | Sequence[Mapping[str, typing.Any]],
   (...)    709     unknown: types.UnknownOption | None = None,
    710 ):
    711     """Deserialize a data structure to an object defined by this Schema's fields.
    712 
    713     :param data: The data to deserialize.
   (...)    728         if invalid data are passed.
    729     """
--> 730     return self._do_load(
    731         data, many=many, partial=partial, unknown=unknown, postprocess=True
    732     )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:887, in Schema._do_load(self, data, many, partial, unknown, postprocess)
    884     processed_data = data
    885 if not errors:
    886     # Deserialize data
--> 887     result = self._deserialize(
    888         processed_data,
    889         error_store=error_store,
    890         many=many,
    891         partial=partial,
    892         unknown=unknown,
    893     )
    894     # Run field-level validation
    895     self._invoke_field_validators(
    896         error_store=error_store, data=result, many=many
    897     )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:676, in Schema._deserialize(self, data, error_store, many, partial, unknown, index)
    666 def getter(
    667     val, field_obj=field_obj, field_name=field_name, d_kwargs=d_kwargs
    668 ):
    669     return field_obj.deserialize(
    670         val,
    671         field_name,
    672         data,
    673         **d_kwargs,
    674     )
--> 676 value = self._call_and_store(
    677     getter_func=getter,
    678     data=raw_value,
    679     field_name=field_name,
    680     error_store=error_store,
    681     index=index,
    682 )
    683 if value is not missing:
    684     key = field_obj.attribute or attr_name

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:517, in Schema._call_and_store(getter_func, data, field_name, error_store, index)
    507 """Call ``getter_func`` with ``data`` as its argument, and store any `ValidationErrors`.
    508 
    509 :param getter_func: Function for getting the serialized/deserialized
   (...)    514     otherwise `None`.
    515 """
    516 try:
--> 517     value = getter_func(data)
    518 except ValidationError as error:
    519     error_store.store_error(error.messages, field_name, index=index)

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:669, in Schema._deserialize.<locals>.getter(val, field_obj, field_name, d_kwargs)
    666 def getter(
    667     val, field_obj=field_obj, field_name=field_name, d_kwargs=d_kwargs
    668 ):
--> 669     return field_obj.deserialize(
    670         val,
    671         field_name,
    672         data,
    673         **d_kwargs,
    674     )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/fields.py:374, in Field.deserialize(self, value, attr, data, **kwargs)
    372 if self.allow_none and value is None:
    373     return None
--> 374 output = self._deserialize(value, attr, data, **kwargs)
    375 self._validate(output)
    376 return output

File ~/work/ParamTools/ParamTools/paramtools/schema.py:150, in ValueObject._deserialize(self, value, attr, data, partial, many, **kwargs)
    144 if not isinstance(value, list) or (
    145     isinstance(value, list)
    146     and value
    147     and not isinstance(value[0], dict)
    148 ):
    149     value = [{"value": value}]
--> 150 return super()._deserialize(
    151     value, attr, data, partial=partial, many=many, **kwargs
    152 )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/fields.py:621, in Nested._deserialize(self, value, attr, data, partial, **kwargs)
    612 """Same as :meth:`Field._deserialize` with additional ``partial`` argument.
    613 
    614 :param partial: For nested schemas, the ``partial``
   (...)    618     Add ``partial`` parameter.
    619 """
    620 self._test_collection(value)
--> 621 return self._load(value, partial=partial)

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/fields.py:597, in Nested._load(self, value, partial)
    593 def _load(
    594     self, value: typing.Any, partial: bool | types.StrSequenceOrSet | None = None
    595 ):
    596     try:
--> 597         valid_data = self.schema.load(value, unknown=self.unknown, partial=partial)
    598     except ValidationError as error:
    599         raise ValidationError(
    600             error.messages, valid_data=error.valid_data
    601         ) from error

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:730, in Schema.load(self, data, many, partial, unknown)
    703 def load(
    704     self,
    705     data: Mapping[str, typing.Any] | Sequence[Mapping[str, typing.Any]],
   (...)    709     unknown: types.UnknownOption | None = None,
    710 ):
    711     """Deserialize a data structure to an object defined by this Schema's fields.
    712 
    713     :param data: The data to deserialize.
   (...)    728         if invalid data are passed.
    729     """
--> 730     return self._do_load(
    731         data, many=many, partial=partial, unknown=unknown, postprocess=True
    732     )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:887, in Schema._do_load(self, data, many, partial, unknown, postprocess)
    884     processed_data = data
    885 if not errors:
    886     # Deserialize data
--> 887     result = self._deserialize(
    888         processed_data,
    889         error_store=error_store,
    890         many=many,
    891         partial=partial,
    892         unknown=unknown,
    893     )
    894     # Run field-level validation
    895     self._invoke_field_validators(
    896         error_store=error_store, data=result, many=many
    897     )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:626, in Schema._deserialize(self, data, error_store, many, partial, unknown, index)
    623         ret_l = []
    624     else:
    625         ret_l = [
--> 626             self._deserialize(
    627                 d,
    628                 error_store=error_store,
    629                 many=False,
    630                 partial=partial,
    631                 unknown=unknown,
    632                 index=idx,
    633             )
    634             for idx, d in enumerate(data)
    635         ]
    636     return ret_l
    637 ret_d = self.dict_class()

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:676, in Schema._deserialize(self, data, error_store, many, partial, unknown, index)
    666 def getter(
    667     val, field_obj=field_obj, field_name=field_name, d_kwargs=d_kwargs
    668 ):
    669     return field_obj.deserialize(
    670         val,
    671         field_name,
    672         data,
    673         **d_kwargs,
    674     )
--> 676 value = self._call_and_store(
    677     getter_func=getter,
    678     data=raw_value,
    679     field_name=field_name,
    680     error_store=error_store,
    681     index=index,
    682 )
    683 if value is not missing:
    684     key = field_obj.attribute or attr_name

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:517, in Schema._call_and_store(getter_func, data, field_name, error_store, index)
    507 """Call ``getter_func`` with ``data`` as its argument, and store any `ValidationErrors`.
    508 
    509 :param getter_func: Function for getting the serialized/deserialized
   (...)    514     otherwise `None`.
    515 """
    516 try:
--> 517     value = getter_func(data)
    518 except ValidationError as error:
    519     error_store.store_error(error.messages, field_name, index=index)

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:669, in Schema._deserialize.<locals>.getter(val, field_obj, field_name, d_kwargs)
    666 def getter(
    667     val, field_obj=field_obj, field_name=field_name, d_kwargs=d_kwargs
    668 ):
--> 669     return field_obj.deserialize(
    670         val,
    671         field_name,
    672         data,
    673         **d_kwargs,
    674     )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/fields.py:374, in Field.deserialize(self, value, attr, data, **kwargs)
    372 if self.allow_none and value is None:
    373     return None
--> 374 output = self._deserialize(value, attr, data, **kwargs)
    375 self._validate(output)
    376 return output

Cell In[1], line 18, in Int32._deserialize(self, value, *args, **kwargs)
     16 def _deserialize(self, value, *args, **kwargs):
     17     """Cast value from JSON to NumPy Int32."""
---> 18     converted = np.int32(value)
     19     return converted

OverflowError: Python integer 2147483648 out of bounds for int32

Marshmallow Validator#

Fortunately, you can specify a custom validator with marshmallow or ParamTools. Making this works requires modifying the _deserialize method to check for overflow like this:

class Int32(ma.fields.Field):
    """
    A custom type for np.int32.
    https://numpy.org/devdocs/reference/arrays.dtypes.html
    """
    # minor detail that makes this play nice with array_first
    np_type = np.int32

    def _serialize(self, value, *args, **kwargs):
        """Convert np.int32 to basic Python int."""
        return value.tolist()

    def _deserialize(self, value, *args, **kwargs):
        """Cast value from JSON to NumPy Int32."""
        converted = np.int32(value)

        # check for overflow and let range validator
        # display the error message.
        if converted != int(value):
            return int(value)

        return converted

Now, let’s see how to use marshmallow to fix this problem:

import marshmallow as ma
import paramtools as pt


# get the minimum and maxium values for 32 bit integers.
min_int32 = -2147483648  # = np.iinfo(np.int32).min
max_int32 = 2147483647  # = np.iinfo(np.int32).max

# add int32 type to the paramtools type registry
pt.register_custom_type(
    "int32",
    Int32(validate=[
        ma.validate.Range(min=min_int32, max=max_int32)
    ])
)


class Params(pt.Parameters):
    defaults = {
        "small_int": {
            "title": "Small integer",
            "description": "Demonstrate how to define a custom type",
            "type": "int32",
            "value": 2
        }
    }


params = Params(array_first=True)

params.adjust(dict(
    small_int=np.int64(max_int32) + 1
))
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
Cell In[5], line 31
     19     defaults = {
     20         "small_int": {
     21             "title": "Small integer",
   (...)     25         }
     26     }
     29 params = Params(array_first=True)
---> 31 params.adjust(dict(
     32     small_int=np.int64(max_int32) + 1
     33 ))

File ~/work/ParamTools/ParamTools/paramtools/parameters.py:257, in Parameters.adjust(self, params_or_path, ignore_warnings, raise_errors, extend_adj, clobber)
    210 def adjust(
    211     self,
    212     params_or_path: Union[str, Mapping[str, List[ValueObject]]],
   (...)    216     clobber: bool = True,
    217 ):
    218     """
    219     Deserialize and validate parameter adjustments. `params_or_path`
    220     can be a file path or a `dict` that has not been fully deserialized.
   (...)    255         least one existing value item's corresponding label values.
    256     """
--> 257     return self._adjust(
    258         params_or_path,
    259         ignore_warnings=ignore_warnings,
    260         raise_errors=raise_errors,
    261         extend_adj=extend_adj,
    262         clobber=clobber,
    263     )

File ~/work/ParamTools/ParamTools/paramtools/parameters.py:375, in Parameters._adjust(self, params_or_path, ignore_warnings, raise_errors, extend_adj, deserialized, validate, clobber)
    371 # throw error if raise_errors is True or ignore_warnings is False
    372 if (raise_errors and has_errors) or (
    373     not ignore_warnings and has_warnings
    374 ):
--> 375     raise self.validation_error
    377 # Update attrs for params that were adjusted.
    378 self._set_state(params=parsed_params.keys())

ValidationError: {
    "errors": {
        "small_int": [
            "Must be greater than or equal to -2147483648 and less than or equal to 2147483647."
        ]
    }
}

ParamTools Validator#

Finally, we will use ParamTools to solve this problem. We need to modify how we create our custom marshmallow field so that it’s wrapped by ParamTools’s PartialField. This makes it clear that your field still needs to be initialized, and that your custom field is able to receive validation information from the defaults configuration:

import paramtools as pt


# add int32 type to the paramtools type registry
pt.register_custom_type(
    "int32",
    pt.PartialField(Int32)
)


class Params(pt.Parameters):
    defaults = {
        "small_int": {
            "title": "Small integer",
            "description": "Demonstrate how to define a custom type",
            "type": "int32",
            "value": 2,
            "validators": {
                "range": {"min": -2147483648, "max": 2147483647}
            }
        }
    }


params = Params(array_first=True)

params.adjust(dict(
    small_int=2147483647 + 1
))
---------------------------------------------------------------------------
OverflowError                             Traceback (most recent call last)
Cell In[6], line 27
     12     defaults = {
     13         "small_int": {
     14             "title": "Small integer",
   (...)     21         }
     22     }
     25 params = Params(array_first=True)
---> 27 params.adjust(dict(
     28     small_int=2147483647 + 1
     29 ))

File ~/work/ParamTools/ParamTools/paramtools/parameters.py:257, in Parameters.adjust(self, params_or_path, ignore_warnings, raise_errors, extend_adj, clobber)
    210 def adjust(
    211     self,
    212     params_or_path: Union[str, Mapping[str, List[ValueObject]]],
   (...)    216     clobber: bool = True,
    217 ):
    218     """
    219     Deserialize and validate parameter adjustments. `params_or_path`
    220     can be a file path or a `dict` that has not been fully deserialized.
   (...)    255         least one existing value item's corresponding label values.
    256     """
--> 257     return self._adjust(
    258         params_or_path,
    259         ignore_warnings=ignore_warnings,
    260         raise_errors=raise_errors,
    261         extend_adj=extend_adj,
    262         clobber=clobber,
    263     )

File ~/work/ParamTools/ParamTools/paramtools/parameters.py:291, in Parameters._adjust(self, params_or_path, ignore_warnings, raise_errors, extend_adj, deserialized, validate, clobber)
    289 parsed_params = {}
    290 try:
--> 291     parsed_params = self._validator_schema.load(
    292         params, ignore_warnings
    293     )
    294 except MarshmallowValidationError as ve:
    295     self._parse_validation_messages(ve.messages, params)

File ~/work/ParamTools/ParamTools/paramtools/schema.py:234, in BaseValidatorSchema.load(self, data, ignore_warnings, deserialized)
    232         return self.validate_only(data)
    233     else:
--> 234         return super().load(data)
    235 finally:
    236     self.ignore_warnings = False

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:730, in Schema.load(self, data, many, partial, unknown)
    703 def load(
    704     self,
    705     data: Mapping[str, typing.Any] | Sequence[Mapping[str, typing.Any]],
   (...)    709     unknown: types.UnknownOption | None = None,
    710 ):
    711     """Deserialize a data structure to an object defined by this Schema's fields.
    712 
    713     :param data: The data to deserialize.
   (...)    728         if invalid data are passed.
    729     """
--> 730     return self._do_load(
    731         data, many=many, partial=partial, unknown=unknown, postprocess=True
    732     )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:887, in Schema._do_load(self, data, many, partial, unknown, postprocess)
    884     processed_data = data
    885 if not errors:
    886     # Deserialize data
--> 887     result = self._deserialize(
    888         processed_data,
    889         error_store=error_store,
    890         many=many,
    891         partial=partial,
    892         unknown=unknown,
    893     )
    894     # Run field-level validation
    895     self._invoke_field_validators(
    896         error_store=error_store, data=result, many=many
    897     )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:676, in Schema._deserialize(self, data, error_store, many, partial, unknown, index)
    666 def getter(
    667     val, field_obj=field_obj, field_name=field_name, d_kwargs=d_kwargs
    668 ):
    669     return field_obj.deserialize(
    670         val,
    671         field_name,
    672         data,
    673         **d_kwargs,
    674     )
--> 676 value = self._call_and_store(
    677     getter_func=getter,
    678     data=raw_value,
    679     field_name=field_name,
    680     error_store=error_store,
    681     index=index,
    682 )
    683 if value is not missing:
    684     key = field_obj.attribute or attr_name

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:517, in Schema._call_and_store(getter_func, data, field_name, error_store, index)
    507 """Call ``getter_func`` with ``data`` as its argument, and store any `ValidationErrors`.
    508 
    509 :param getter_func: Function for getting the serialized/deserialized
   (...)    514     otherwise `None`.
    515 """
    516 try:
--> 517     value = getter_func(data)
    518 except ValidationError as error:
    519     error_store.store_error(error.messages, field_name, index=index)

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:669, in Schema._deserialize.<locals>.getter(val, field_obj, field_name, d_kwargs)
    666 def getter(
    667     val, field_obj=field_obj, field_name=field_name, d_kwargs=d_kwargs
    668 ):
--> 669     return field_obj.deserialize(
    670         val,
    671         field_name,
    672         data,
    673         **d_kwargs,
    674     )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/fields.py:374, in Field.deserialize(self, value, attr, data, **kwargs)
    372 if self.allow_none and value is None:
    373     return None
--> 374 output = self._deserialize(value, attr, data, **kwargs)
    375 self._validate(output)
    376 return output

File ~/work/ParamTools/ParamTools/paramtools/schema.py:150, in ValueObject._deserialize(self, value, attr, data, partial, many, **kwargs)
    144 if not isinstance(value, list) or (
    145     isinstance(value, list)
    146     and value
    147     and not isinstance(value[0], dict)
    148 ):
    149     value = [{"value": value}]
--> 150 return super()._deserialize(
    151     value, attr, data, partial=partial, many=many, **kwargs
    152 )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/fields.py:621, in Nested._deserialize(self, value, attr, data, partial, **kwargs)
    612 """Same as :meth:`Field._deserialize` with additional ``partial`` argument.
    613 
    614 :param partial: For nested schemas, the ``partial``
   (...)    618     Add ``partial`` parameter.
    619 """
    620 self._test_collection(value)
--> 621 return self._load(value, partial=partial)

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/fields.py:597, in Nested._load(self, value, partial)
    593 def _load(
    594     self, value: typing.Any, partial: bool | types.StrSequenceOrSet | None = None
    595 ):
    596     try:
--> 597         valid_data = self.schema.load(value, unknown=self.unknown, partial=partial)
    598     except ValidationError as error:
    599         raise ValidationError(
    600             error.messages, valid_data=error.valid_data
    601         ) from error

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:730, in Schema.load(self, data, many, partial, unknown)
    703 def load(
    704     self,
    705     data: Mapping[str, typing.Any] | Sequence[Mapping[str, typing.Any]],
   (...)    709     unknown: types.UnknownOption | None = None,
    710 ):
    711     """Deserialize a data structure to an object defined by this Schema's fields.
    712 
    713     :param data: The data to deserialize.
   (...)    728         if invalid data are passed.
    729     """
--> 730     return self._do_load(
    731         data, many=many, partial=partial, unknown=unknown, postprocess=True
    732     )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:887, in Schema._do_load(self, data, many, partial, unknown, postprocess)
    884     processed_data = data
    885 if not errors:
    886     # Deserialize data
--> 887     result = self._deserialize(
    888         processed_data,
    889         error_store=error_store,
    890         many=many,
    891         partial=partial,
    892         unknown=unknown,
    893     )
    894     # Run field-level validation
    895     self._invoke_field_validators(
    896         error_store=error_store, data=result, many=many
    897     )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:626, in Schema._deserialize(self, data, error_store, many, partial, unknown, index)
    623         ret_l = []
    624     else:
    625         ret_l = [
--> 626             self._deserialize(
    627                 d,
    628                 error_store=error_store,
    629                 many=False,
    630                 partial=partial,
    631                 unknown=unknown,
    632                 index=idx,
    633             )
    634             for idx, d in enumerate(data)
    635         ]
    636     return ret_l
    637 ret_d = self.dict_class()

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:676, in Schema._deserialize(self, data, error_store, many, partial, unknown, index)
    666 def getter(
    667     val, field_obj=field_obj, field_name=field_name, d_kwargs=d_kwargs
    668 ):
    669     return field_obj.deserialize(
    670         val,
    671         field_name,
    672         data,
    673         **d_kwargs,
    674     )
--> 676 value = self._call_and_store(
    677     getter_func=getter,
    678     data=raw_value,
    679     field_name=field_name,
    680     error_store=error_store,
    681     index=index,
    682 )
    683 if value is not missing:
    684     key = field_obj.attribute or attr_name

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:517, in Schema._call_and_store(getter_func, data, field_name, error_store, index)
    507 """Call ``getter_func`` with ``data`` as its argument, and store any `ValidationErrors`.
    508 
    509 :param getter_func: Function for getting the serialized/deserialized
   (...)    514     otherwise `None`.
    515 """
    516 try:
--> 517     value = getter_func(data)
    518 except ValidationError as error:
    519     error_store.store_error(error.messages, field_name, index=index)

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/schema.py:669, in Schema._deserialize.<locals>.getter(val, field_obj, field_name, d_kwargs)
    666 def getter(
    667     val, field_obj=field_obj, field_name=field_name, d_kwargs=d_kwargs
    668 ):
--> 669     return field_obj.deserialize(
    670         val,
    671         field_name,
    672         data,
    673         **d_kwargs,
    674     )

File /usr/share/miniconda/envs/paramtools-dev/lib/python3.12/site-packages/marshmallow/fields.py:374, in Field.deserialize(self, value, attr, data, **kwargs)
    372 if self.allow_none and value is None:
    373     return None
--> 374 output = self._deserialize(value, attr, data, **kwargs)
    375 self._validate(output)
    376 return output

Cell In[4], line 15, in Int32._deserialize(self, value, *args, **kwargs)
     13 def _deserialize(self, value, *args, **kwargs):
     14     """Cast value from JSON to NumPy Int32."""
---> 15     converted = np.int32(value)
     17     # check for overflow and let range validator
     18     # display the error message.
     19     if converted != int(value):

OverflowError: Python integer 2147483648 out of bounds for int32