diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 1fa7afb20..9ee74c75d 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -28,7 +28,7 @@ env: WEAVIATE_134: 1.34.5 WEAVIATE_135: 1.35.0 WEAVIATE_136: 1.36.0 - WEAVIATE_137: 1.37.0-dev-29d5c87.amd64 + WEAVIATE_137: 1.37.0-dev-efd28af.amd64 jobs: diff --git a/integration/test_export.py b/integration/test_export.py new file mode 100644 index 000000000..7748740c4 --- /dev/null +++ b/integration/test_export.py @@ -0,0 +1,220 @@ +import time +import uuid +from typing import Generator, List, Union + +import pytest +from _pytest.fixtures import SubRequest + +import weaviate +from weaviate.collections.classes.config import DataType, Property +from weaviate.exceptions import UnexpectedStatusCodeException +from weaviate.export.export import ( + ExportFileFormat, + ExportStatus, + ExportStorage, +) + +from .conftest import _sanitize_collection_name + +pytestmark = pytest.mark.xdist_group(name="export") + +BACKEND = ExportStorage.FILESYSTEM + +COLLECTION_NAME = "ExportTestCollection" + +OBJECT_PROPS = [{"title": f"object {i}", "count": i} for i in range(5)] + +OBJECT_IDS = [ + "fd34ccf4-1a2a-47ad-8446-231839366c3f", + "2653442b-05d8-4fa3-b46a-d4a152eb63bc", + "55374edb-17de-487f-86cb-9a9fbc30823f", + "124ff6aa-597f-44d0-8c13-62fbb1e66888", + "f787386e-7d1c-481f-b8c3-3dbfd8bbad85", +] + + +@pytest.fixture(scope="module") +def client() -> Generator[weaviate.WeaviateClient, None, None]: + client = weaviate.connect_to_local() + client.collections.delete(COLLECTION_NAME) + + col = client.collections.create( + name=COLLECTION_NAME, + properties=[ + Property(name="title", data_type=DataType.TEXT), + Property(name="count", data_type=DataType.INT), + ], + ) + for i, props in enumerate(OBJECT_PROPS): + col.data.insert(properties=props, uuid=OBJECT_IDS[i]) + + yield client + client.collections.delete(COLLECTION_NAME) + client.close() + + +def unique_export_id(name: str) -> str: + """Generate a unique export ID based on the test name.""" + name = _sanitize_collection_name(name) + random_part = str(uuid.uuid4()).replace("-", "")[:12] + return name + random_part + + +def test_create_export_with_waiting(client: weaviate.WeaviateClient, request: SubRequest) -> None: + """Create an export with wait_for_completion=True.""" + export_id = unique_export_id(request.node.name) + + resp = client.export.create( + export_id=export_id, + backend=BACKEND, + file_format=ExportFileFormat.PARQUET, + include_collections=[COLLECTION_NAME], + wait_for_completion=True, + ) + assert resp.status == ExportStatus.SUCCESS + assert COLLECTION_NAME in resp.collections + + +def test_create_export_without_waiting( + client: weaviate.WeaviateClient, request: SubRequest +) -> None: + """Create an export without waiting, then poll status.""" + export_id = unique_export_id(request.node.name) + + resp = client.export.create( + export_id=export_id, + backend=BACKEND, + file_format=ExportFileFormat.PARQUET, + include_collections=[COLLECTION_NAME], + ) + assert resp.status in [ExportStatus.STARTED, ExportStatus.TRANSFERRING, ExportStatus.SUCCESS] + + # poll until done + while True: + status = client.export.get_status(export_id=export_id, backend=BACKEND) + assert status.status in [ + ExportStatus.STARTED, + ExportStatus.TRANSFERRING, + ExportStatus.SUCCESS, + ] + if status.status == ExportStatus.SUCCESS: + break + time.sleep(0.1) + + assert status.export_id == export_id + + +def test_get_export_status(client: weaviate.WeaviateClient, request: SubRequest) -> None: + """Check status of a completed export.""" + export_id = unique_export_id(request.node.name) + + client.export.create( + export_id=export_id, + backend=BACKEND, + file_format=ExportFileFormat.PARQUET, + include_collections=[COLLECTION_NAME], + wait_for_completion=True, + ) + + status = client.export.get_status(export_id=export_id, backend=BACKEND) + assert status.status == ExportStatus.SUCCESS + assert status.export_id == export_id + assert status.backend == BACKEND.value + + +def test_create_export_with_parquet_format( + client: weaviate.WeaviateClient, request: SubRequest +) -> None: + """Create an export explicitly specifying parquet format.""" + export_id = unique_export_id(request.node.name) + + resp = client.export.create( + export_id=export_id, + backend=BACKEND, + file_format=ExportFileFormat.PARQUET, + include_collections=[COLLECTION_NAME], + wait_for_completion=True, + ) + assert resp.status == ExportStatus.SUCCESS + + +@pytest.mark.parametrize("include", [[COLLECTION_NAME], COLLECTION_NAME]) +def test_create_export_include_as_str_and_list( + client: weaviate.WeaviateClient, include: Union[str, List[str]], request: SubRequest +) -> None: + """Verify include_collections accepts both str and list.""" + export_id = unique_export_id(request.node.name) + + resp = client.export.create( + export_id=export_id, + backend=BACKEND, + file_format=ExportFileFormat.PARQUET, + include_collections=include, + wait_for_completion=True, + ) + assert resp.status == ExportStatus.SUCCESS + assert COLLECTION_NAME in resp.collections + + +def test_cancel_export(client: weaviate.WeaviateClient, request: SubRequest) -> None: + """Cancel a running export.""" + export_id = unique_export_id(request.node.name) + + resp = client.export.create( + export_id=export_id, + backend=BACKEND, + file_format=ExportFileFormat.PARQUET, + include_collections=[COLLECTION_NAME], + ) + assert resp.status in [ExportStatus.STARTED, ExportStatus.TRANSFERRING, ExportStatus.SUCCESS] + + result = client.export.cancel(export_id=export_id, backend=BACKEND) + assert result is True + + # verify it's cancelled or already completed (race condition) + start = time.time() + while time.time() - start < 5: + status = client.export.get_status(export_id=export_id, backend=BACKEND) + if status.status in [ExportStatus.CANCELED, ExportStatus.SUCCESS]: + break + time.sleep(0.1) + assert status.status in [ExportStatus.CANCELED, ExportStatus.SUCCESS] + + +def test_fail_on_non_existing_collection( + client: weaviate.WeaviateClient, request: SubRequest +) -> None: + """Fail export on non-existing collection.""" + export_id = unique_export_id(request.node.name) + with pytest.raises(UnexpectedStatusCodeException): + client.export.create( + export_id=export_id, + backend=BACKEND, + file_format=ExportFileFormat.PARQUET, + include_collections=["NonExistingCollection"], + wait_for_completion=True, + ) + + +def test_fail_on_both_include_and_exclude( + client: weaviate.WeaviateClient, request: SubRequest +) -> None: + """Fail when both include and exclude collections are set.""" + export_id = unique_export_id(request.node.name) + with pytest.raises(TypeError): + client.export.create( + export_id=export_id, + backend=BACKEND, + file_format=ExportFileFormat.PARQUET, + include_collections=COLLECTION_NAME, + exclude_collections="SomeOther", + ) + + +def test_fail_status_for_non_existing_export( + client: weaviate.WeaviateClient, request: SubRequest +) -> None: + """Fail checking status for non-existing export.""" + export_id = unique_export_id(request.node.name) + with pytest.raises(UnexpectedStatusCodeException): + client.export.get_status(export_id=export_id, backend=BACKEND) diff --git a/weaviate/classes/__init__.py b/weaviate/classes/__init__.py index 467a17f37..d495744ac 100644 --- a/weaviate/classes/__init__.py +++ b/weaviate/classes/__init__.py @@ -5,6 +5,7 @@ batch, config, data, + export, generate, generics, init, @@ -22,6 +23,7 @@ "config", "ConsistencyLevel", "data", + "export", "generate", "generics", "init", diff --git a/weaviate/classes/export.py b/weaviate/classes/export.py new file mode 100644 index 000000000..07e87a813 --- /dev/null +++ b/weaviate/classes/export.py @@ -0,0 +1,11 @@ +from weaviate.export.export import ( + ExportConfig, + ExportFileFormat, + ExportStorage, +) + +__all__ = [ + "ExportConfig", + "ExportFileFormat", + "ExportStorage", +] diff --git a/weaviate/client.py b/weaviate/client.py index d7f9080f4..f22389403 100644 --- a/weaviate/client.py +++ b/weaviate/client.py @@ -20,6 +20,7 @@ from .connect.v4 import ConnectionAsync, ConnectionSync from .debug import _Debug, _DebugAsync from .embedded import EmbeddedOptions +from .export import _Export, _ExportAsync from .groups import _Groups, _GroupsAsync from .rbac import _Roles, _RolesAsync from .types import NUMBER @@ -76,6 +77,7 @@ def __init__( ) self.alias = _AliasAsync(self._connection) self.backup = _BackupAsync(self._connection) + self.export = _ExportAsync(self._connection) self.batch = _BatchClientWrapperAsync(self._connection) self.cluster = _ClusterAsync(self._connection) self.collections = _CollectionsAsync(self._connection) @@ -152,6 +154,7 @@ def __init__( consistency_level=None, ) self.backup = _Backup(self._connection) + self.export = _Export(self._connection) self.cluster = _Cluster(self._connection) self.collections = collections self.debug = _Debug(self._connection) diff --git a/weaviate/exceptions.py b/weaviate/exceptions.py index 2a5b429d5..ce0fe6f7e 100644 --- a/weaviate/exceptions.py +++ b/weaviate/exceptions.py @@ -141,6 +141,14 @@ class BackupCanceledError(WeaviateBaseError): """Backup canceled Exception.""" +class ExportFailedError(WeaviateBaseError): + """Export Failed Exception.""" + + +class ExportCanceledError(WeaviateBaseError): + """Export Canceled Exception.""" + + class EmptyResponseError(WeaviateBaseError): """Occurs when an HTTP request unexpectedly returns an empty response.""" diff --git a/weaviate/export/__init__.py b/weaviate/export/__init__.py new file mode 100644 index 000000000..91de2d448 --- /dev/null +++ b/weaviate/export/__init__.py @@ -0,0 +1,7 @@ +"""Module for collection export operations.""" + +from .async_ import _ExportAsync +from .executor import ExportStorage +from .sync import _Export + +__all__ = ["ExportStorage", "_ExportAsync", "_Export"] diff --git a/weaviate/export/async_.py b/weaviate/export/async_.py new file mode 100644 index 000000000..8bd1e3c44 --- /dev/null +++ b/weaviate/export/async_.py @@ -0,0 +1,8 @@ +from weaviate.connect import executor +from weaviate.connect.v4 import ConnectionAsync +from weaviate.export.executor import _ExportExecutor + + +@executor.wrap("async") +class _ExportAsync(_ExportExecutor[ConnectionAsync]): + pass diff --git a/weaviate/export/async_.pyi b/weaviate/export/async_.pyi new file mode 100644 index 000000000..652c26bee --- /dev/null +++ b/weaviate/export/async_.pyi @@ -0,0 +1,38 @@ +from typing import List, Optional, Union + +from weaviate.connect.v4 import ConnectionAsync +from weaviate.export.export import ( + ExportConfig, + ExportCreateReturn, + ExportFileFormat, + ExportStatusReturn, + ExportStorage, +) + +from .executor import _ExportExecutor + +class _ExportAsync(_ExportExecutor[ConnectionAsync]): + async def create( + self, + export_id: str, + backend: ExportStorage, + file_format: ExportFileFormat, + include_collections: Union[List[str], str, None] = None, + exclude_collections: Union[List[str], str, None] = None, + wait_for_completion: bool = False, + config: Optional[ExportConfig] = None, + ) -> ExportCreateReturn: ... + async def get_status( + self, + export_id: str, + backend: ExportStorage, + bucket: Optional[str] = None, + path: Optional[str] = None, + ) -> ExportStatusReturn: ... + async def cancel( + self, + export_id: str, + backend: ExportStorage, + bucket: Optional[str] = None, + path: Optional[str] = None, + ) -> bool: ... diff --git a/weaviate/export/executor.py b/weaviate/export/executor.py new file mode 100644 index 000000000..0a54c741a --- /dev/null +++ b/weaviate/export/executor.py @@ -0,0 +1,331 @@ +"""Export class definition.""" + +import asyncio +import time +from typing import Dict, Generic, List, Optional, Tuple, Union + +from httpx import Response + +from weaviate.connect import executor +from weaviate.connect.v4 import ( + Connection, + ConnectionAsync, + ConnectionType, + _ExpectedStatusCodes, +) +from weaviate.exceptions import ( + EmptyResponseException, + ExportCanceledError, + ExportFailedError, +) +from weaviate.export.export import ( + STORAGE_NAMES, + ExportConfig, + ExportCreateReturn, + ExportFileFormat, + ExportStatus, + ExportStatusReturn, + ExportStorage, +) +from weaviate.util import ( + _capitalize_first_letter, + _decode_json_response_dict, +) + + +class _ExportExecutor(Generic[ConnectionType]): + def __init__(self, connection: Connection): + self._connection = connection + + def create( + self, + export_id: str, + backend: ExportStorage, + file_format: ExportFileFormat, + include_collections: Union[List[str], str, None] = None, + exclude_collections: Union[List[str], str, None] = None, + wait_for_completion: bool = False, + config: Optional[ExportConfig] = None, + ) -> executor.Result[ExportCreateReturn]: + """Create an export of all/per collection Weaviate objects. + + Args: + export_id: The identifier name of the export. + backend: The backend storage where to create the export. + file_format: The file format of the export (e.g. ExportFileFormat.PARQUET). + include_collections: The collection/list of collections to be included in the export. If not specified all + collections will be included. Either `include_collections` or `exclude_collections` can be set. + exclude_collections: The collection/list of collections to be excluded in the export. + Either `include_collections` or `exclude_collections` can be set. + wait_for_completion: Whether to wait until the export is done. By default False. + config: The configuration of the export (bucket, path). By default None. + + Returns: + An `ExportCreateReturn` object that contains the export creation response. + + Raises: + weaviate.exceptions.UnexpectedStatusCodeError: If weaviate reports a non-OK status. + TypeError: One of the arguments have a wrong type. + """ + ( + export_id, + backend, + include_collections, + exclude_collections, + ) = _get_and_validate_create_arguments( + export_id=export_id, + backend=backend, + include_classes=include_collections, + exclude_classes=exclude_collections, + wait_for_completion=wait_for_completion, + ) + + payload: dict = { + "id": export_id, + "file_format": file_format.value, + "include": include_collections, + "exclude": exclude_collections, + } + + if config is not None: + config_dict: Dict[str, str] = {} + if config.bucket is not None: + config_dict["bucket"] = config.bucket + if config.path is not None: + config_dict["path"] = config.path + if config_dict: + payload["config"] = config_dict + + path = f"/export/{backend.value}" + + if isinstance(self._connection, ConnectionAsync): + + async def _execute() -> ExportCreateReturn: + res = await executor.aresult( + self._connection.post( + path=path, + weaviate_object=payload, + error_msg="Export creation failed due to connection error.", + ) + ) + create_status = _decode_json_response_dict(res, "Export creation") + assert create_status is not None + if wait_for_completion: + while True: + status = await executor.aresult( + self.get_status( + export_id=export_id, + backend=backend, + bucket=config.bucket if config else None, + path=config.path if config else None, + ) + ) + create_status["status"] = status.status + if status.status == ExportStatus.SUCCESS: + break + if status.status == ExportStatus.FAILED: + raise ExportFailedError( + f"Export failed: {create_status} with error: {status.error}" + ) + if status.status == ExportStatus.CANCELED: + raise ExportCanceledError( + f"Export was canceled: {create_status} with error: {status.error}" + ) + await asyncio.sleep(1) + return ExportCreateReturn(**create_status) + + return _execute() + + res = executor.result( + self._connection.post( + path=path, + weaviate_object=payload, + error_msg="Export creation failed due to connection error.", + ) + ) + create_status = _decode_json_response_dict(res, "Export creation") + assert create_status is not None + if wait_for_completion: + while True: + status = executor.result( + self.get_status( + export_id=export_id, + backend=backend, + bucket=config.bucket if config else None, + path=config.path if config else None, + ) + ) + create_status["status"] = status.status + if status.status == ExportStatus.SUCCESS: + break + if status.status == ExportStatus.FAILED: + raise ExportFailedError( + f"Export failed: {create_status} with error: {status.error}" + ) + if status.status == ExportStatus.CANCELED: + raise ExportCanceledError( + f"Export was canceled: {create_status} with error: {status.error}" + ) + time.sleep(1) + return ExportCreateReturn(**create_status) + + def get_status( + self, + export_id: str, + backend: ExportStorage, + bucket: Optional[str] = None, + path: Optional[str] = None, + ) -> executor.Result[ExportStatusReturn]: + """Check the status of an export. + + Args: + export_id: The identifier name of the export. + backend: The backend storage where the export was created. + bucket: The bucket of the export location. By default None. + path: The path of the export location. By default None. + + Returns: + An `ExportStatusReturn` object that contains the export status response. + """ + export_id, backend = _get_and_validate_get_status( + export_id=export_id, + backend=backend, + ) + + url_path = f"/export/{backend.value}/{export_id}" + params: Dict[str, str] = {} + if bucket is not None: + params["bucket"] = bucket + if path is not None: + params["path"] = path + + def resp(res: Response) -> ExportStatusReturn: + typed_response = _decode_json_response_dict(res, "Export status check") + if typed_response is None: + raise EmptyResponseException() + return ExportStatusReturn(**typed_response) + + return executor.execute( + response_callback=resp, + method=self._connection.get, + path=url_path, + params=params, + error_msg="Export status check failed due to connection error.", + ) + + def cancel( + self, + export_id: str, + backend: ExportStorage, + bucket: Optional[str] = None, + path: Optional[str] = None, + ) -> executor.Result[bool]: + """Cancel a running export. + + Args: + export_id: The identifier name of the export. + backend: The backend storage where the export was created. + bucket: The bucket of the export location. By default None. + path: The path of the export location. By default None. + + Returns: + A bool indicating if the cancellation was successful. + """ + export_id, backend = _get_and_validate_get_status( + export_id=export_id, + backend=backend, + ) + url_path = f"/export/{backend.value}/{export_id}" + params: Dict[str, str] = {} + if bucket is not None: + params["bucket"] = bucket + if path is not None: + params["path"] = path + + def resp(res: Response) -> bool: + if res.status_code == 204: + return True + typed_response = _decode_json_response_dict(res, "Export cancel") + if typed_response is None: + raise EmptyResponseException() + return False + + return executor.execute( + response_callback=resp, + method=self._connection.delete, + path=url_path, + params=params, + error_msg="Export cancel failed due to connection error.", + status_codes=_ExpectedStatusCodes(ok_in=[204, 404], error="cancel export"), + ) + + +def _get_and_validate_create_arguments( + export_id: str, + backend: Union[str, ExportStorage], + include_classes: Union[List[str], str, None], + exclude_classes: Union[List[str], str, None], + wait_for_completion: bool, +) -> Tuple[str, ExportStorage, List[str], List[str]]: + if not isinstance(export_id, str): + raise TypeError(f"'export_id' must be of type str. Given type: {type(export_id)}.") + if isinstance(backend, str): + try: + backend = ExportStorage(backend.lower()) + except KeyError: + raise ValueError( + f"'backend' must have one of these values: {STORAGE_NAMES}. Given value: {backend}." + ) + + if not isinstance(wait_for_completion, bool): + raise TypeError( + f"'wait_for_completion' must be of type bool. Given type: {type(wait_for_completion)}." + ) + + if include_classes is not None: + if isinstance(include_classes, str): + include_classes = [include_classes] + elif not isinstance(include_classes, list): + raise TypeError( + "'include_collections' must be of type str, list of str or None. " + f"Given type: {type(include_classes)}." + ) + else: + include_classes = [] + + if exclude_classes is not None: + if isinstance(exclude_classes, str): + exclude_classes = [exclude_classes] + elif not isinstance(exclude_classes, list): + raise TypeError( + "'exclude_collections' must be of type str, list of str or None. " + f"Given type: {type(exclude_classes)}." + ) + else: + exclude_classes = [] + + if include_classes and exclude_classes: + raise TypeError( + "Either 'include_collections' OR 'exclude_collections' can be set, not both." + ) + + include_classes = [_capitalize_first_letter(cls) for cls in include_classes] + exclude_classes = [_capitalize_first_letter(cls) for cls in exclude_classes] + + return (export_id, backend, include_classes, exclude_classes) + + +def _get_and_validate_get_status( + export_id: str, backend: Union[str, ExportStorage] +) -> Tuple[str, ExportStorage]: + if not isinstance(export_id, str): + raise TypeError(f"'export_id' must be of type str. Given type: {type(export_id)}.") + if isinstance(backend, str): + try: + backend = ExportStorage(backend.lower()) + except KeyError: + raise ValueError( + f"'backend' must have one of these values: {STORAGE_NAMES}. Given value: {backend}." + ) + + return (export_id, backend) diff --git a/weaviate/export/export.py b/weaviate/export/export.py new file mode 100644 index 000000000..43ee08d99 --- /dev/null +++ b/weaviate/export/export.py @@ -0,0 +1,89 @@ +"""Export models and enums.""" + +from datetime import datetime +from enum import Enum +from typing import Dict, List, Optional + +from pydantic import BaseModel, Field + +STORAGE_NAMES = { + "filesystem", + "s3", + "gcs", + "azure", +} + + +class ExportStorage(str, Enum): + """Which backend should be used to write the export to.""" + + FILESYSTEM = "filesystem" + S3 = "s3" + GCS = "gcs" + AZURE = "azure" + + +class ExportFileFormat(str, Enum): + """Which file format should be used for the export.""" + + PARQUET = "parquet" + + +class ExportStatus(str, Enum): + """The status of an export.""" + + STARTED = "STARTED" + TRANSFERRING = "TRANSFERRING" + SUCCESS = "SUCCESS" + FAILED = "FAILED" + CANCELED = "CANCELED" + + +class ShardExportStatus(str, Enum): + """The status of an individual shard export.""" + + TRANSFERRING = "TRANSFERRING" + SUCCESS = "SUCCESS" + FAILED = "FAILED" + SKIPPED = "SKIPPED" + + +class ExportConfig(BaseModel): + """Configuration for where to write the export.""" + + bucket: Optional[str] = None + path: Optional[str] = None + + +class ShardProgress(BaseModel): + """Progress of a single shard export.""" + + status: ShardExportStatus + objects_exported: int = Field(alias="objectsExported", default=0) + error: Optional[str] = None + skip_reason: Optional[str] = Field(alias="skipReason", default=None) + + model_config = {"populate_by_name": True} + + +class ExportCreateReturn(BaseModel): + """Return type of the export creation method.""" + + export_id: str = Field(alias="id") + backend: str + path: str + status: ExportStatus + started_at: Optional[datetime] = Field(alias="startedAt", default=None) + collections: List[str] = Field(default_factory=list, alias="classes") + + model_config = {"populate_by_name": True} + + +class ExportStatusReturn(ExportCreateReturn): + """Return type of the export status method.""" + + shard_status: Optional[Dict[str, Dict[str, ShardProgress]]] = Field( + alias="shardStatus", default=None + ) + error: Optional[str] = None + took_in_ms: Optional[int] = Field(alias="tookInMs", default=None) diff --git a/weaviate/export/sync.py b/weaviate/export/sync.py new file mode 100644 index 000000000..0510601f8 --- /dev/null +++ b/weaviate/export/sync.py @@ -0,0 +1,8 @@ +from weaviate.connect import executor +from weaviate.connect.v4 import ConnectionSync +from weaviate.export.executor import _ExportExecutor + + +@executor.wrap("sync") +class _Export(_ExportExecutor[ConnectionSync]): + pass diff --git a/weaviate/export/sync.pyi b/weaviate/export/sync.pyi new file mode 100644 index 000000000..93ed5f4fa --- /dev/null +++ b/weaviate/export/sync.pyi @@ -0,0 +1,38 @@ +from typing import List, Optional, Union + +from weaviate.connect.v4 import ConnectionSync +from weaviate.export.export import ( + ExportConfig, + ExportCreateReturn, + ExportFileFormat, + ExportStatusReturn, + ExportStorage, +) + +from .executor import _ExportExecutor + +class _Export(_ExportExecutor[ConnectionSync]): + def create( + self, + export_id: str, + backend: ExportStorage, + file_format: ExportFileFormat, + include_collections: Union[List[str], str, None] = None, + exclude_collections: Union[List[str], str, None] = None, + wait_for_completion: bool = False, + config: Optional[ExportConfig] = None, + ) -> ExportCreateReturn: ... + def get_status( + self, + export_id: str, + backend: ExportStorage, + bucket: Optional[str] = None, + path: Optional[str] = None, + ) -> ExportStatusReturn: ... + def cancel( + self, + export_id: str, + backend: ExportStorage, + bucket: Optional[str] = None, + path: Optional[str] = None, + ) -> bool: ... diff --git a/weaviate/outputs/__init__.py b/weaviate/outputs/__init__.py index 62193fc35..cd6176d93 100644 --- a/weaviate/outputs/__init__.py +++ b/weaviate/outputs/__init__.py @@ -1,4 +1,16 @@ -from . import aggregate, backup, batch, cluster, config, data, query, replication, tenants, users +from . import ( + aggregate, + backup, + batch, + cluster, + config, + data, + export, + query, + replication, + tenants, + users, +) __all__ = [ "aggregate", @@ -7,6 +19,7 @@ "cluster", "config", "data", + "export", "query", "replication", "tenants", diff --git a/weaviate/outputs/export.py b/weaviate/outputs/export.py new file mode 100644 index 000000000..531b715c4 --- /dev/null +++ b/weaviate/outputs/export.py @@ -0,0 +1,15 @@ +from weaviate.export.export import ( + ExportCreateReturn, + ExportStatus, + ExportStatusReturn, + ShardExportStatus, + ShardProgress, +) + +__all__ = [ + "ExportCreateReturn", + "ExportStatus", + "ExportStatusReturn", + "ShardExportStatus", + "ShardProgress", +]