Calling Campaign.from_json() with a JSON containing a CompositeSurrogate raises a TypeError buried in an ExceptionGroup:
TypeError: baybe.surrogates.composite._ReplicationMapping[baybe.surrogates.base.SurrogateProtocol]
is not a module, class, method, or function.
Minimal reproduction
import json
from baybe import Campaign
# Minimal JSON with a CompositeSurrogate (as exported by campaign.to_json())
json_str = json.dumps({
"version": "0.15.0",
"searchspace": {
"search_space_type": "DISCRETE",
"parameters": [{"type": "NumericalDiscreteParameter", "name": "x", "values": [1, 2, 3]}],
"constraints": [],
},
"objective": {
"type": "SingleTargetObjective",
"target": {"type": "NumericalTarget", "name": "y"},
},
"recommender": {
"type": "TwoPhaseMetaRecommender",
"recommender": {
"type": "BotorchRecommender",
"surrogate_model": {
"type": "CompositeSurrogate",
"surrogates": {
"type": "_ReplicationMapping",
"template": {"type": "GaussianProcessSurrogate"},
},
},
},
},
})
Campaign.from_json(json_str) # raises
Note: a same-process round-trip (campaign.to_json() followed by Campaign.from_json()) does not reproduce the bug, because to_json() accidentally resolves the type annotations as a side effect.
Root cause
composite.py uses from future import annotations, which stores all type annotations as lazy strings. _ReplicationMapping.template stores its type as the string "_T" instead of the resolved TypeVar.
When cattrs generates a structure hook for _ReplicationMapping[SurrogateProtocol] (a parameterized generic), it detects the unresolved string annotations and calls attrs.resolve_types(_ReplicationMapping[SurrogateProtocol]). Python's typing.get_type_hints() rejects parameterized generics — only plain classes are valid — and raises the TypeError.
The issue originates in _structure_surrogate_getter, which creates _ReplicationMapping[SurrogateProtocol] and hands it directly to the converter without a registered structure hook:
def _structure_surrogate_getter(obj: dict, _) -> _SurrogateGetter:
container_type = _get_surrogate_getter_type(obj.pop(_TYPE_FIELD))
return converter.structure(obj, container_type) # no hook registered for this type
Calling
Campaign.from_json()with a JSON containing a CompositeSurrogate raises aTypeErrorburied in anExceptionGroup:Minimal reproduction
Note: a same-process round-trip (campaign.to_json() followed by Campaign.from_json()) does not reproduce the bug, because to_json() accidentally resolves the type annotations as a side effect.
Root cause
composite.py uses from future import annotations, which stores all type annotations as lazy strings. _ReplicationMapping.template stores its type as the string "_T" instead of the resolved TypeVar.
When cattrs generates a structure hook for _ReplicationMapping[SurrogateProtocol] (a parameterized generic), it detects the unresolved string annotations and calls attrs.resolve_types(_ReplicationMapping[SurrogateProtocol]). Python's typing.get_type_hints() rejects parameterized generics — only plain classes are valid — and raises the TypeError.
The issue originates in _structure_surrogate_getter, which creates _ReplicationMapping[SurrogateProtocol] and hands it directly to the converter without a registered structure hook: