test_serialization.py
1 # SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai> 2 # 3 # SPDX-License-Identifier: Apache-2.0 4 5 import sys 6 from unittest.mock import Mock 7 8 import pytest 9 10 from haystack.core.component import component 11 from haystack.core.errors import DeserializationError, SerializationError 12 from haystack.core.pipeline import Pipeline 13 from haystack.core.serialization import ( 14 component_from_dict, 15 component_to_dict, 16 default_from_dict, 17 default_to_dict, 18 generate_qualified_class_name, 19 import_class_by_name, 20 ) 21 from haystack.document_stores.in_memory import InMemoryDocumentStore 22 from haystack.testing import factory 23 from haystack.utils import ComponentDevice, Secret 24 from haystack.utils.auth import EnvVarSecret 25 from haystack.utils.device import Device, DeviceMap 26 27 28 def test_default_component_to_dict(): 29 MyComponent = factory.component_class("MyComponent") 30 comp = MyComponent() 31 res = default_to_dict(comp) 32 assert res == {"type": "haystack.testing.factory.MyComponent", "init_parameters": {}} 33 34 35 def test_default_component_to_dict_with_init_parameters(): 36 MyComponent = factory.component_class("MyComponent") 37 comp = MyComponent() 38 res = default_to_dict(comp, some_key="some_value") 39 assert res == {"type": "haystack.testing.factory.MyComponent", "init_parameters": {"some_key": "some_value"}} 40 41 42 def test_default_component_from_dict(): 43 def custom_init(self, some_param): 44 self.some_param = some_param 45 46 extra_fields = {"__init__": custom_init} 47 MyComponent = factory.component_class("MyComponent", extra_fields=extra_fields) 48 comp = default_from_dict( 49 MyComponent, {"type": "haystack.testing.factory.MyComponent", "init_parameters": {"some_param": 10}} 50 ) 51 assert isinstance(comp, MyComponent) 52 assert comp.some_param == 10 # type: ignore[attr-defined] 53 54 55 def test_default_component_from_dict_without_type(): 56 with pytest.raises(DeserializationError, match="Missing 'type' in serialization data"): 57 default_from_dict(Mock, {}) 58 59 60 def test_default_component_from_dict_unregistered_component(request): 61 # We use the test function name as component name to make sure it's not registered. 62 # Since the registry is global we risk to have a component with the same name registered in another test. 63 component_name = request.node.name 64 65 with pytest.raises(DeserializationError, match=f"Class '{component_name}' can't be deserialized as 'Mock'"): 66 default_from_dict(Mock, {"type": component_name}) 67 68 69 def test_from_dict_import_type(): 70 pipeline_dict = { 71 "metadata": {}, 72 "components": { 73 "greeter": { 74 "type": "haystack.testing.sample_components.greet.Greet", 75 "init_parameters": { 76 "message": "\nGreeting component says: Hi! The value is {value}\n", 77 "log_level": "INFO", 78 }, 79 } 80 }, 81 "connections": [], 82 } 83 84 # remove the target component from the registry if already there 85 component.registry.pop("haystack.testing.sample_components.greet.Greet", None) 86 # remove the module from sys.modules if already there 87 sys.modules.pop("haystack.testing.sample_components.greet", None) 88 89 p = Pipeline.from_dict(pipeline_dict) 90 91 from haystack.testing.sample_components.greet import Greet 92 93 assert type(p.get_component("greeter")) == Greet 94 95 96 def test_get_qualified_class_name(): 97 MyComponent = factory.component_class("MyComponent") 98 comp = MyComponent() 99 res = generate_qualified_class_name(type(comp)) 100 assert res == "haystack.testing.factory.MyComponent" 101 102 103 def test_import_class_by_name(): 104 data = "haystack.core.pipeline.Pipeline" 105 class_object = import_class_by_name(data) 106 class_instance = class_object() 107 assert isinstance(class_instance, Pipeline) 108 109 110 def test_import_class_by_name_no_valid_class(): 111 data = "some.invalid.class" 112 with pytest.raises(ImportError): 113 import_class_by_name(data) 114 115 116 class CustomData: 117 def __init__(self, a: int, b: str) -> None: 118 self.a = a 119 self.b = b 120 121 122 @component() 123 class UnserializableClass: 124 def __init__(self, a: int, b: str, c: CustomData) -> None: 125 self.a = a 126 self.b = b 127 self.c = c 128 129 def run(self): 130 pass 131 132 133 def test_component_to_dict_invalid_type(): 134 with pytest.raises(SerializationError, match="unsupported value of type 'CustomData'"): 135 component_to_dict(UnserializableClass(1, "s", CustomData(99, "aa")), "invalid_component") 136 137 138 @component 139 class CustomComponentWithSecrets: 140 def __init__(self, api_key: Secret | None = None, token: Secret | None = None, regular_param: str | None = None): 141 self.api_key = api_key 142 self.token = token 143 self.regular_param = regular_param 144 145 @component.output_types(value=str) 146 def run(self, value: str) -> dict[str, str]: 147 return {"value": value} 148 149 150 def test_component_to_dict_with_secret(): 151 """Test that Secret instances are automatically serialized in component_to_dict.""" 152 # Test with EnvVarSecret (serializable) 153 env_secret = Secret.from_env_var("TEST_API_KEY") 154 comp = CustomComponentWithSecrets(api_key=env_secret) 155 res = component_to_dict(comp, "test_component") 156 assert res["init_parameters"]["api_key"] == env_secret.to_dict() 157 assert res["init_parameters"]["api_key"]["type"] == "env_var" 158 159 # Test with None 160 comp = CustomComponentWithSecrets(api_key=None) 161 res = component_to_dict(comp, "test_component") 162 assert res["init_parameters"]["api_key"] is None 163 164 # Test with regular value (not a Secret) 165 comp = CustomComponentWithSecrets(regular_param="regular_string") 166 res = component_to_dict(comp, "test_component") 167 assert res["init_parameters"]["regular_param"] == "regular_string" 168 169 # Test with multiple secrets 170 env_secret1 = Secret.from_env_var("TEST_API_KEY1") 171 env_secret2 = Secret.from_env_var("TEST_API_KEY2") 172 comp = CustomComponentWithSecrets(api_key=env_secret1, token=env_secret2, regular_param="test") 173 res = component_to_dict(comp, "test_component") 174 assert res["init_parameters"]["api_key"] == env_secret1.to_dict() 175 assert res["init_parameters"]["api_key"]["type"] == "env_var" 176 assert res["init_parameters"]["token"] == env_secret2.to_dict() 177 assert res["init_parameters"]["token"]["type"] == "env_var" 178 assert res["init_parameters"]["regular_param"] == "test" 179 180 181 def test_component_from_dict_with_secret(): 182 """Test that serialized Secret dictionaries are automatically deserialized in component_from_dict.""" 183 # Test with EnvVarSecret 184 env_secret = Secret.from_env_var("TEST_API_KEY") 185 serialized_secret = env_secret.to_dict() 186 data = { 187 "type": generate_qualified_class_name(CustomComponentWithSecrets), 188 "init_parameters": {"api_key": serialized_secret, "regular_param": "test"}, 189 } 190 comp = component_from_dict(CustomComponentWithSecrets, data, "test_component") 191 assert isinstance(comp, CustomComponentWithSecrets) 192 assert isinstance(comp.api_key, Secret) 193 assert comp.api_key.type.value == "env_var" 194 assert comp.regular_param == "test" 195 196 # Test with None 197 data = { 198 "type": generate_qualified_class_name(CustomComponentWithSecrets), 199 "init_parameters": {"api_key": None, "regular_param": "test"}, 200 } 201 comp = component_from_dict(CustomComponentWithSecrets, data, "test_component") 202 assert comp.api_key is None 203 assert comp.regular_param == "test" 204 205 # Test with regular dict (not a Secret) 206 data = { 207 "type": generate_qualified_class_name(CustomComponentWithSecrets), 208 "init_parameters": {"api_key": {"some": "dict"}, "regular_param": "test"}, 209 } 210 comp = component_from_dict(CustomComponentWithSecrets, data, "test_component") 211 assert comp.api_key == {"some": "dict"} 212 assert comp.regular_param == "test" 213 214 # Test with multiple secrets 215 env_secret1 = Secret.from_env_var("TEST_API_KEY1") 216 env_secret2 = Secret.from_env_var("TEST_API_KEY2") 217 data = { 218 "type": generate_qualified_class_name(CustomComponentWithSecrets), 219 "init_parameters": {"api_key": env_secret1.to_dict(), "token": env_secret2.to_dict(), "regular_param": "test"}, 220 } 221 comp = component_from_dict(CustomComponentWithSecrets, data, "test_component") 222 assert isinstance(comp.api_key, Secret) 223 assert isinstance(comp.token, Secret) 224 assert comp.regular_param == "test" 225 226 227 def test_component_to_dict_and_from_dict_roundtrip_with_secret(): 228 """Test that serialization and deserialization work together for Secrets.""" 229 # Test roundtrip with EnvVarSecret 230 original_secret = Secret.from_env_var("TEST_API_KEY") 231 comp = CustomComponentWithSecrets(api_key=original_secret) 232 233 serialized = component_to_dict(comp, "test_component") 234 assert serialized["init_parameters"]["api_key"]["type"] == "env_var" 235 236 deserialized_comp = component_from_dict(CustomComponentWithSecrets, serialized, "test_component") 237 assert isinstance(deserialized_comp.api_key, EnvVarSecret) 238 assert deserialized_comp.api_key.type.value == "env_var" 239 assert isinstance(original_secret, EnvVarSecret) 240 assert deserialized_comp.api_key._env_vars == original_secret._env_vars 241 242 # Test roundtrip with multiple secrets 243 env_secret1 = Secret.from_env_var("TEST_API_KEY1") 244 env_secret2 = Secret.from_env_var("TEST_API_KEY2") 245 comp = CustomComponentWithSecrets(api_key=env_secret1, token=env_secret2, regular_param="test") 246 247 serialized = component_to_dict(comp, "test_component") 248 assert serialized["init_parameters"]["api_key"]["type"] == "env_var" 249 assert serialized["init_parameters"]["token"]["type"] == "env_var" 250 assert serialized["init_parameters"]["regular_param"] == "test" 251 252 deserialized_comp = component_from_dict(CustomComponentWithSecrets, serialized, "test_component") 253 assert isinstance(deserialized_comp.api_key, EnvVarSecret) 254 assert isinstance(deserialized_comp.token, EnvVarSecret) 255 assert deserialized_comp.api_key.type.value == "env_var" 256 assert deserialized_comp.token.type.value == "env_var" 257 assert deserialized_comp.regular_param == "test" 258 assert isinstance(env_secret1, EnvVarSecret) 259 assert isinstance(env_secret2, EnvVarSecret) 260 assert deserialized_comp.api_key._env_vars == env_secret1._env_vars 261 assert deserialized_comp.token._env_vars == env_secret2._env_vars 262 263 264 @component 265 class CustomComponentWithDevice: 266 def __init__( 267 self, 268 device: ComponentDevice | None = None, 269 other_device: ComponentDevice | None = None, 270 name: str | None = None, 271 ): 272 self.device = device 273 self.other_device = other_device 274 self.name = name 275 276 @component.output_types(value=str) 277 def run(self, value: str) -> dict[str, str]: 278 return {"value": value} 279 280 281 def test_component_to_dict_with_component_device(): 282 """Test that ComponentDevice instances are automatically serialized in component_to_dict.""" 283 # Test with single device (CPU) 284 device = ComponentDevice.from_single(Device.cpu()) 285 comp = CustomComponentWithDevice(device=device) 286 res = component_to_dict(comp, "test_component") 287 assert res["init_parameters"]["device"] == {"type": "single", "device": "cpu"} 288 289 # Test with single device (GPU with id) 290 device = ComponentDevice.from_single(Device.gpu(1)) 291 comp = CustomComponentWithDevice(device=device) 292 res = component_to_dict(comp, "test_component") 293 assert res["init_parameters"]["device"] == {"type": "single", "device": "cuda:1"} 294 295 # Test with None 296 comp = CustomComponentWithDevice(device=None) 297 res = component_to_dict(comp, "test_component") 298 assert res["init_parameters"]["device"] is None 299 300 # Test with multiple devices (device map) 301 device_map = DeviceMap({"layer1": Device.gpu(0), "layer2": Device.gpu(1)}) 302 device = ComponentDevice.from_multiple(device_map) 303 comp = CustomComponentWithDevice(device=device) 304 res = component_to_dict(comp, "test_component") 305 assert res["init_parameters"]["device"] == { 306 "type": "multiple", 307 "device_map": {"layer1": "cuda:0", "layer2": "cuda:1"}, 308 } 309 310 # Test with multiple ComponentDevice params 311 device1 = ComponentDevice.from_single(Device.cpu()) 312 device2 = ComponentDevice.from_single(Device.gpu(0)) 313 comp = CustomComponentWithDevice(device=device1, other_device=device2, name="test") 314 res = component_to_dict(comp, "test_component") 315 assert res["init_parameters"]["device"] == {"type": "single", "device": "cpu"} 316 assert res["init_parameters"]["other_device"] == {"type": "single", "device": "cuda:0"} 317 assert res["init_parameters"]["name"] == "test" 318 319 320 def test_component_from_dict_with_component_device(): 321 """Test that serialized ComponentDevice dictionaries are automatically deserialized in component_from_dict.""" 322 # Test with single device (CPU) 323 data = { 324 "type": generate_qualified_class_name(CustomComponentWithDevice), 325 "init_parameters": {"device": {"type": "single", "device": "cpu"}, "name": "test"}, 326 } 327 comp = component_from_dict(CustomComponentWithDevice, data, "test_component") 328 assert isinstance(comp, CustomComponentWithDevice) 329 assert isinstance(comp.device, ComponentDevice) 330 assert comp.device.to_torch_str() == "cpu" 331 assert comp.name == "test" 332 333 # Test with single device (GPU with id) 334 data = { 335 "type": generate_qualified_class_name(CustomComponentWithDevice), 336 "init_parameters": {"device": {"type": "single", "device": "cuda:1"}, "name": "test"}, 337 } 338 comp = component_from_dict(CustomComponentWithDevice, data, "test_component") 339 assert isinstance(comp.device, ComponentDevice) 340 assert comp.device.to_torch_str() == "cuda:1" 341 342 # Test with None 343 data = { 344 "type": generate_qualified_class_name(CustomComponentWithDevice), 345 "init_parameters": {"device": None, "name": "test"}, 346 } 347 comp = component_from_dict(CustomComponentWithDevice, data, "test_component") 348 assert comp.device is None 349 assert comp.name == "test" 350 351 # Test with multiple devices (device map) 352 data = { 353 "type": generate_qualified_class_name(CustomComponentWithDevice), 354 "init_parameters": {"device": {"type": "multiple", "device_map": {"layer1": "cuda:0", "layer2": "cuda:1"}}}, 355 } 356 comp = component_from_dict(CustomComponentWithDevice, data, "test_component") 357 assert isinstance(comp.device, ComponentDevice) 358 assert comp.device.has_multiple_devices 359 360 # Test with regular dict (not a ComponentDevice - different structure) 361 data = { 362 "type": generate_qualified_class_name(CustomComponentWithDevice), 363 "init_parameters": {"device": {"some": "dict"}, "name": "test"}, 364 } 365 comp = component_from_dict(CustomComponentWithDevice, data, "test_component") 366 assert comp.device == {"some": "dict"} 367 assert comp.name == "test" 368 369 # Test with multiple ComponentDevice params 370 data = { 371 "type": generate_qualified_class_name(CustomComponentWithDevice), 372 "init_parameters": { 373 "device": {"type": "single", "device": "cpu"}, 374 "other_device": {"type": "single", "device": "cuda:0"}, 375 "name": "test", 376 }, 377 } 378 comp = component_from_dict(CustomComponentWithDevice, data, "test_component") 379 assert isinstance(comp.device, ComponentDevice) 380 assert isinstance(comp.other_device, ComponentDevice) 381 assert comp.device.to_torch_str() == "cpu" 382 assert comp.other_device.to_torch_str() == "cuda:0" 383 assert comp.name == "test" 384 385 386 def test_component_to_dict_and_from_dict_roundtrip_with_component_device(): 387 """Test that serialization and deserialization work together for ComponentDevice.""" 388 # Test roundtrip with single device 389 original_device = ComponentDevice.from_single(Device.cpu()) 390 comp = CustomComponentWithDevice(device=original_device) 391 392 serialized = component_to_dict(comp, "test_component") 393 assert serialized["init_parameters"]["device"]["type"] == "single" 394 395 deserialized_comp = component_from_dict(CustomComponentWithDevice, serialized, "test_component") 396 assert isinstance(deserialized_comp.device, ComponentDevice) 397 assert deserialized_comp.device.to_torch_str() == original_device.to_torch_str() 398 399 # Test roundtrip with GPU device 400 original_device = ComponentDevice.from_single(Device.gpu(2)) 401 comp = CustomComponentWithDevice(device=original_device) 402 403 serialized = component_to_dict(comp, "test_component") 404 deserialized_comp = component_from_dict(CustomComponentWithDevice, serialized, "test_component") 405 assert deserialized_comp.device.to_torch_str() == "cuda:2" 406 407 # Test roundtrip with device map 408 device_map = DeviceMap({"layer1": Device.gpu(0), "layer2": Device.cpu()}) 409 original_device = ComponentDevice.from_multiple(device_map) 410 comp = CustomComponentWithDevice(device=original_device) 411 412 serialized = component_to_dict(comp, "test_component") 413 assert serialized["init_parameters"]["device"]["type"] == "multiple" 414 415 deserialized_comp = component_from_dict(CustomComponentWithDevice, serialized, "test_component") 416 assert isinstance(deserialized_comp.device, ComponentDevice) 417 assert deserialized_comp.device.has_multiple_devices 418 419 # Test roundtrip with multiple ComponentDevice params 420 device1 = ComponentDevice.from_single(Device.cpu()) 421 device2 = ComponentDevice.from_single(Device.gpu(0)) 422 comp = CustomComponentWithDevice(device=device1, other_device=device2, name="test") 423 424 serialized = component_to_dict(comp, "test_component") 425 assert serialized["init_parameters"]["device"]["type"] == "single" 426 assert serialized["init_parameters"]["other_device"]["type"] == "single" 427 assert serialized["init_parameters"]["name"] == "test" 428 429 deserialized_comp = component_from_dict(CustomComponentWithDevice, serialized, "test_component") 430 assert isinstance(deserialized_comp.device, ComponentDevice) 431 assert isinstance(deserialized_comp.other_device, ComponentDevice) 432 assert deserialized_comp.device.to_torch_str() == "cpu" 433 assert deserialized_comp.other_device.to_torch_str() == "cuda:0" 434 assert deserialized_comp.name == "test" 435 436 437 @component 438 class CustomComponentWithDocumentStore: 439 def __init__(self, document_store: InMemoryDocumentStore | None = None, name: str | None = None): 440 self.document_store = document_store 441 self.name = name 442 443 @component.output_types(value=str) 444 def run(self, value: str) -> dict[str, str]: 445 return {"value": value} 446 447 448 def test_component_to_dict_with_document_store(in_memory_doc_store): 449 """Test that DocumentStore instances are automatically serialized in component_to_dict.""" 450 # Test with InMemoryDocumentStore 451 comp = CustomComponentWithDocumentStore(document_store=in_memory_doc_store) 452 res = component_to_dict(comp, "test_component") 453 assert "type" in res["init_parameters"]["document_store"] 454 assert "init_parameters" in res["init_parameters"]["document_store"] 455 assert ( 456 res["init_parameters"]["document_store"]["type"] 457 == "haystack.document_stores.in_memory.document_store.InMemoryDocumentStore" 458 ) 459 460 # Test with None 461 comp = CustomComponentWithDocumentStore(document_store=None) 462 res = component_to_dict(comp, "test_component") 463 assert res["init_parameters"]["document_store"] is None 464 465 466 def test_component_from_dict_with_document_store(in_memory_doc_store): 467 """Test that serialized DocumentStore dictionaries are automatically deserialized in component_from_dict.""" 468 # Test with InMemoryDocumentStore 469 serialized_doc_store = in_memory_doc_store.to_dict() 470 data = { 471 "type": generate_qualified_class_name(CustomComponentWithDocumentStore), 472 "init_parameters": {"document_store": serialized_doc_store, "name": "test"}, 473 } 474 comp = component_from_dict(CustomComponentWithDocumentStore, data, "test_component") 475 assert isinstance(comp, CustomComponentWithDocumentStore) 476 assert isinstance(comp.document_store, InMemoryDocumentStore) 477 assert comp.name == "test" 478 479 # Test with None 480 data = { 481 "type": generate_qualified_class_name(CustomComponentWithDocumentStore), 482 "init_parameters": {"document_store": None, "name": "test"}, 483 } 484 comp = component_from_dict(CustomComponentWithDocumentStore, data, "test_component") 485 assert comp.document_store is None 486 assert comp.name == "test" 487 488 489 def test_component_to_dict_and_from_dict_roundtrip_with_document_store(in_memory_doc_store): 490 """Test that serialization and deserialization work together for DocumentStore.""" 491 # Test roundtrip with InMemoryDocumentStore 492 comp = CustomComponentWithDocumentStore(document_store=in_memory_doc_store) 493 494 serialized = component_to_dict(comp, "test_component") 495 assert "type" in serialized["init_parameters"]["document_store"] 496 assert ( 497 serialized["init_parameters"]["document_store"]["type"] 498 == "haystack.document_stores.in_memory.document_store.InMemoryDocumentStore" 499 ) 500 501 deserialized_comp = component_from_dict(CustomComponentWithDocumentStore, serialized, "test_component") 502 assert isinstance(deserialized_comp.document_store, InMemoryDocumentStore) 503 assert deserialized_comp.document_store.bm25_algorithm == in_memory_doc_store.bm25_algorithm 504 assert ( 505 deserialized_comp.document_store.embedding_similarity_function 506 == in_memory_doc_store.embedding_similarity_function 507 ) 508 509 # Test roundtrip with custom parameters 510 in_memory_doc_store = InMemoryDocumentStore( 511 bm25_algorithm="BM25Okapi", embedding_similarity_function="cosine", return_embedding=False 512 ) 513 comp = CustomComponentWithDocumentStore(document_store=in_memory_doc_store) 514 515 serialized = component_to_dict(comp, "test_component") 516 deserialized_comp = component_from_dict(CustomComponentWithDocumentStore, serialized, "test_component") 517 assert isinstance(deserialized_comp.document_store, InMemoryDocumentStore) 518 assert deserialized_comp.document_store.bm25_algorithm == "BM25Okapi" 519 assert deserialized_comp.document_store.embedding_similarity_function == "cosine" 520 assert deserialized_comp.document_store.return_embedding is False 521 522 523 def test_default_to_dict_with_document_store(in_memory_doc_store): 524 """Test that DocumentStore instances are automatically serialized in default_to_dict.""" 525 res = default_to_dict(in_memory_doc_store) 526 assert res["type"] == "haystack.document_stores.in_memory.document_store.InMemoryDocumentStore" 527 assert "init_parameters" in res 528 529 # Test that DocumentStore is serialized when passed as a parameter 530 comp = CustomComponentWithDocumentStore(document_store=in_memory_doc_store) 531 res = default_to_dict(comp, document_store=in_memory_doc_store, name="test") 532 assert "type" in res["init_parameters"]["document_store"] 533 assert res["init_parameters"]["name"] == "test" 534 535 536 def test_default_from_dict_with_document_store(in_memory_doc_store): 537 """Test that serialized DocumentStore dictionaries are automatically deserialized in default_from_dict.""" 538 serialized = in_memory_doc_store.to_dict() 539 540 # Test direct deserialization 541 deserialized = default_from_dict(InMemoryDocumentStore, serialized) 542 assert isinstance(deserialized, InMemoryDocumentStore) 543 assert deserialized.bm25_algorithm == in_memory_doc_store.bm25_algorithm 544 545 # Test deserialization when DocumentStore is in init_parameters 546 data = { 547 "type": generate_qualified_class_name(CustomComponentWithDocumentStore), 548 "init_parameters": {"document_store": serialized, "name": "test"}, 549 } 550 comp = default_from_dict(CustomComponentWithDocumentStore, data) 551 assert isinstance(comp.document_store, InMemoryDocumentStore) 552 assert comp.name == "test" 553 554 555 def test_default_from_dict_with_invalid_class_name(): 556 """Test that deserialization raises ImportError with improved message when class cannot be imported.""" 557 data = { 558 "type": generate_qualified_class_name(CustomComponentWithDocumentStore), 559 "init_parameters": { 560 "document_store": {"type": "nonexistent.module.Class", "init_parameters": {}}, 561 "name": "test", 562 }, 563 } 564 # Verify the error message includes the parameter key and original error 565 with pytest.raises(ImportError, match=r"Failed to deserialize 'document_store':.*nonexistent\.module\.Class"): 566 default_from_dict(CustomComponentWithDocumentStore, data)