diff --git a/docarray/array/any_array.py b/docarray/array/any_array.py index 612fba7f42e..c896d4e782e 100644 --- a/docarray/array/any_array.py +++ b/docarray/array/any_array.py @@ -21,6 +21,7 @@ from docarray.base_doc import BaseDoc from docarray.display.document_array_summary import DocArraySummary +from docarray.exceptions.exceptions import UnusableObjectError from docarray.typing.abstract_type import AbstractType from docarray.utils._internal._typing import change_cls_name @@ -32,6 +33,13 @@ T_doc = TypeVar('T_doc', bound=BaseDoc) IndexIterType = Union[slice, Iterable[int], Iterable[bool], None] +UNUSABLE_ERROR_MSG = ( + 'This {cls} instance is in an unusable state. \n' + 'The most common cause of this is converting a DocVec to a DocList. ' + 'After you call `doc_vec.to_doc_list()`, `doc_vec` cannot be used anymore. ' + 'Instead, you should do `doc_list = doc_vec.to_doc_list()` and only use `doc_list`.' +) + class AnyDocArray(Sequence[T_doc], Generic[T_doc], AbstractType): doc_type: Type[BaseDoc] @@ -64,9 +72,17 @@ class _DocArrayTyped(cls): # type: ignore def _property_generator(val: str): def _getter(self): + if getattr(self, '_is_unusable', False): + raise UnusableObjectError( + UNUSABLE_ERROR_MSG.format(cls=cls.__name__) + ) return self._get_data_column(val) def _setter(self, value): + if getattr(self, '_is_unusable', False): + raise UnusableObjectError( + UNUSABLE_ERROR_MSG.format(cls=cls.__name__) + ) self._set_data_column(val, value) # need docstring for the property diff --git a/docarray/array/doc_vec/doc_vec.py b/docarray/array/doc_vec/doc_vec.py index e1ba944fd10..3025c52a09b 100644 --- a/docarray/array/doc_vec/doc_vec.py +++ b/docarray/array/doc_vec/doc_vec.py @@ -170,6 +170,7 @@ def __init__( f'docs = DocVec[MyDoc](docs) instead of DocVec(docs)' ) self.tensor_type = tensor_type + self._is_unusable = False tensor_columns: Dict[str, Optional[AbstractTensor]] = dict() doc_columns: Dict[str, Optional['DocVec']] = dict() @@ -769,7 +770,14 @@ def to_doc_list(self: T) -> DocList[T_doc]: del self._storage - return DocList.__class_getitem__(self.doc_type).construct(docs) + doc_type = self.doc_type + + # Setting _is_unusable will raise an Exception if someone interacts with this instance from hereon out. + # I don't like relying on this state, but we can't override the getattr/setattr directly: + # https://stackoverflow.com/questions/10376604/overriding-special-methods-on-an-instance + self._is_unusable = True + + return DocList.__class_getitem__(doc_type).construct(docs) def traverse_flat( self, diff --git a/docarray/exceptions/__init__.py b/docarray/exceptions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docarray/exceptions/exceptions.py b/docarray/exceptions/exceptions.py new file mode 100644 index 00000000000..ef659901b36 --- /dev/null +++ b/docarray/exceptions/exceptions.py @@ -0,0 +1,2 @@ +class UnusableObjectError(NotImplementedError): + ... diff --git a/tests/units/array/stack/test_array_stacked.py b/tests/units/array/stack/test_array_stacked.py index 35509338068..e199a706b63 100644 --- a/tests/units/array/stack/test_array_stacked.py +++ b/tests/units/array/stack/test_array_stacked.py @@ -8,6 +8,7 @@ from docarray import BaseDoc, DocList from docarray.array import DocVec from docarray.documents import ImageDoc +from docarray.exceptions.exceptions import UnusableObjectError from docarray.typing import AnyEmbedding, AnyTensor, NdArray, TorchTensor @@ -657,3 +658,18 @@ class ImageDoc(BaseDoc): ) assert da != da2 + + +def teste_unusable_state_raises_exception(): + from docarray import DocVec + from docarray.documents import ImageDoc + + docs = DocVec[ImageDoc]([ImageDoc(url='http://url.com/foo.png') for _ in range(10)]) + + docs.to_doc_list() + + with pytest.raises(UnusableObjectError): + docs.url + + with pytest.raises(UnusableObjectError): + docs.url = 'hi'