/ test / core / test_serialization.py
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)