synapse-sdk 1.0.0a11__py3-none-any.whl → 2026.1.1b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of synapse-sdk might be problematic. Click here for more details.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +9 -8
- synapse_sdk/cli/agent/__init__.py +25 -0
- synapse_sdk/cli/agent/config.py +104 -0
- synapse_sdk/cli/agent/select.py +197 -0
- synapse_sdk/cli/auth.py +104 -0
- synapse_sdk/cli/main.py +1025 -0
- synapse_sdk/cli/plugin/__init__.py +58 -0
- synapse_sdk/cli/plugin/create.py +566 -0
- synapse_sdk/cli/plugin/job.py +196 -0
- synapse_sdk/cli/plugin/publish.py +322 -0
- synapse_sdk/cli/plugin/run.py +131 -0
- synapse_sdk/cli/plugin/test.py +200 -0
- synapse_sdk/clients/README.md +239 -0
- synapse_sdk/clients/__init__.py +5 -0
- synapse_sdk/clients/_template.py +266 -0
- synapse_sdk/clients/agent/__init__.py +84 -29
- synapse_sdk/clients/agent/async_ray.py +289 -0
- synapse_sdk/clients/agent/container.py +83 -0
- synapse_sdk/clients/agent/plugin.py +101 -0
- synapse_sdk/clients/agent/ray.py +296 -39
- synapse_sdk/clients/backend/__init__.py +152 -12
- synapse_sdk/clients/backend/annotation.py +164 -22
- synapse_sdk/clients/backend/core.py +101 -0
- synapse_sdk/clients/backend/data_collection.py +292 -0
- synapse_sdk/clients/backend/hitl.py +87 -0
- synapse_sdk/clients/backend/integration.py +374 -46
- synapse_sdk/clients/backend/ml.py +134 -22
- synapse_sdk/clients/backend/models.py +247 -0
- synapse_sdk/clients/base.py +538 -59
- synapse_sdk/clients/exceptions.py +35 -7
- synapse_sdk/clients/pipeline/__init__.py +5 -0
- synapse_sdk/clients/pipeline/client.py +636 -0
- synapse_sdk/clients/protocols.py +178 -0
- synapse_sdk/clients/utils.py +86 -8
- synapse_sdk/clients/validation.py +58 -0
- synapse_sdk/enums.py +76 -0
- synapse_sdk/exceptions.py +168 -0
- synapse_sdk/integrations/__init__.py +74 -0
- synapse_sdk/integrations/_base.py +119 -0
- synapse_sdk/integrations/_context.py +53 -0
- synapse_sdk/integrations/ultralytics/__init__.py +78 -0
- synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
- synapse_sdk/integrations/ultralytics/_patches.py +124 -0
- synapse_sdk/loggers.py +476 -95
- synapse_sdk/mcp/MCP.md +69 -0
- synapse_sdk/mcp/__init__.py +48 -0
- synapse_sdk/mcp/__main__.py +6 -0
- synapse_sdk/mcp/config.py +349 -0
- synapse_sdk/mcp/prompts/__init__.py +4 -0
- synapse_sdk/mcp/resources/__init__.py +4 -0
- synapse_sdk/mcp/server.py +1352 -0
- synapse_sdk/mcp/tools/__init__.py +6 -0
- synapse_sdk/plugins/__init__.py +133 -9
- synapse_sdk/plugins/action.py +229 -0
- synapse_sdk/plugins/actions/__init__.py +82 -0
- synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
- synapse_sdk/plugins/actions/dataset/action.py +471 -0
- synapse_sdk/plugins/actions/export/__init__.py +55 -0
- synapse_sdk/plugins/actions/export/action.py +183 -0
- synapse_sdk/plugins/actions/export/context.py +59 -0
- synapse_sdk/plugins/actions/inference/__init__.py +84 -0
- synapse_sdk/plugins/actions/inference/action.py +285 -0
- synapse_sdk/plugins/actions/inference/context.py +81 -0
- synapse_sdk/plugins/actions/inference/deployment.py +322 -0
- synapse_sdk/plugins/actions/inference/serve.py +252 -0
- synapse_sdk/plugins/actions/train/__init__.py +54 -0
- synapse_sdk/plugins/actions/train/action.py +326 -0
- synapse_sdk/plugins/actions/train/context.py +57 -0
- synapse_sdk/plugins/actions/upload/__init__.py +49 -0
- synapse_sdk/plugins/actions/upload/action.py +165 -0
- synapse_sdk/plugins/actions/upload/context.py +61 -0
- synapse_sdk/plugins/config.py +98 -0
- synapse_sdk/plugins/context/__init__.py +109 -0
- synapse_sdk/plugins/context/env.py +113 -0
- synapse_sdk/plugins/datasets/__init__.py +113 -0
- synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
- synapse_sdk/plugins/datasets/converters/base.py +347 -0
- synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
- synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
- synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
- synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
- synapse_sdk/plugins/datasets/formats/dm.py +351 -0
- synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
- synapse_sdk/plugins/decorators.py +83 -0
- synapse_sdk/plugins/discovery.py +790 -0
- synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
- synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
- synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
- synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
- synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
- synapse_sdk/plugins/docs/README.md +513 -0
- synapse_sdk/plugins/docs/STEP.md +656 -0
- synapse_sdk/plugins/enums.py +70 -10
- synapse_sdk/plugins/errors.py +92 -0
- synapse_sdk/plugins/executors/__init__.py +43 -0
- synapse_sdk/plugins/executors/local.py +99 -0
- synapse_sdk/plugins/executors/ray/__init__.py +18 -0
- synapse_sdk/plugins/executors/ray/base.py +282 -0
- synapse_sdk/plugins/executors/ray/job.py +298 -0
- synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
- synapse_sdk/plugins/executors/ray/packaging.py +137 -0
- synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
- synapse_sdk/plugins/executors/ray/task.py +257 -0
- synapse_sdk/plugins/models/__init__.py +26 -0
- synapse_sdk/plugins/models/logger.py +173 -0
- synapse_sdk/plugins/models/pipeline.py +25 -0
- synapse_sdk/plugins/pipelines/__init__.py +81 -0
- synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
- synapse_sdk/plugins/pipelines/context.py +107 -0
- synapse_sdk/plugins/pipelines/display.py +311 -0
- synapse_sdk/plugins/runner.py +114 -0
- synapse_sdk/plugins/schemas/__init__.py +19 -0
- synapse_sdk/plugins/schemas/results.py +152 -0
- synapse_sdk/plugins/steps/__init__.py +63 -0
- synapse_sdk/plugins/steps/base.py +128 -0
- synapse_sdk/plugins/steps/context.py +90 -0
- synapse_sdk/plugins/steps/orchestrator.py +128 -0
- synapse_sdk/plugins/steps/registry.py +103 -0
- synapse_sdk/plugins/steps/utils/__init__.py +20 -0
- synapse_sdk/plugins/steps/utils/logging.py +85 -0
- synapse_sdk/plugins/steps/utils/timing.py +71 -0
- synapse_sdk/plugins/steps/utils/validation.py +68 -0
- synapse_sdk/plugins/templates/__init__.py +50 -0
- synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
- synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
- synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
- synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
- synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
- synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
- synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
- synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
- synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
- synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
- synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
- synapse_sdk/plugins/testing/__init__.py +25 -0
- synapse_sdk/plugins/testing/sample_actions.py +98 -0
- synapse_sdk/plugins/types.py +206 -0
- synapse_sdk/plugins/upload.py +595 -64
- synapse_sdk/plugins/utils.py +325 -37
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/utils/__init__.py +1 -0
- synapse_sdk/utils/auth.py +74 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +449 -0
- synapse_sdk/utils/file/checksum.py +167 -0
- synapse_sdk/utils/file/download.py +286 -0
- synapse_sdk/utils/file/io.py +129 -0
- synapse_sdk/utils/file/requirements.py +36 -0
- synapse_sdk/utils/network.py +168 -0
- synapse_sdk/utils/storage/__init__.py +238 -0
- synapse_sdk/utils/storage/config.py +188 -0
- synapse_sdk/utils/storage/errors.py +52 -0
- synapse_sdk/utils/storage/providers/__init__.py +13 -0
- synapse_sdk/utils/storage/providers/base.py +76 -0
- synapse_sdk/utils/storage/providers/gcs.py +168 -0
- synapse_sdk/utils/storage/providers/http.py +250 -0
- synapse_sdk/utils/storage/providers/local.py +126 -0
- synapse_sdk/utils/storage/providers/s3.py +177 -0
- synapse_sdk/utils/storage/providers/sftp.py +208 -0
- synapse_sdk/utils/storage/registry.py +125 -0
- synapse_sdk/utils/websocket.py +99 -0
- synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
- synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
- synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
- locale/en/LC_MESSAGES/messages.mo +0 -0
- locale/en/LC_MESSAGES/messages.po +0 -39
- locale/ko/LC_MESSAGES/messages.mo +0 -0
- locale/ko/LC_MESSAGES/messages.po +0 -34
- synapse_sdk/cli/create_plugin.py +0 -10
- synapse_sdk/clients/agent/core.py +0 -7
- synapse_sdk/clients/agent/service.py +0 -15
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/clients/ray/__init__.py +0 -6
- synapse_sdk/clients/ray/core.py +0 -22
- synapse_sdk/clients/ray/serve.py +0 -20
- synapse_sdk/i18n.py +0 -35
- synapse_sdk/plugins/categories/__init__.py +0 -0
- synapse_sdk/plugins/categories/base.py +0 -235
- synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
- synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
- synapse_sdk/plugins/categories/decorators.py +0 -13
- synapse_sdk/plugins/categories/export/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/export.py +0 -10
- synapse_sdk/plugins/categories/import/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
- synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
- synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
- synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
- synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
- synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
- synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
- synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
- synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
- synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
- synapse_sdk/plugins/categories/registry.py +0 -16
- synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
- synapse_sdk/plugins/categories/templates.py +0 -32
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/cli/publish.py +0 -37
- synapse_sdk/plugins/cli/run.py +0 -67
- synapse_sdk/plugins/exceptions.py +0 -22
- synapse_sdk/plugins/models.py +0 -121
- synapse_sdk/plugins/templates/cookiecutter.json +0 -11
- synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
- synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
- synapse_sdk/shared/enums.py +0 -8
- synapse_sdk/utils/debug.py +0 -5
- synapse_sdk/utils/file.py +0 -87
- synapse_sdk/utils/module_loading.py +0 -29
- synapse_sdk/utils/pydantic/__init__.py +0 -0
- synapse_sdk/utils/pydantic/config.py +0 -4
- synapse_sdk/utils/pydantic/errors.py +0 -33
- synapse_sdk/utils/pydantic/validators.py +0 -7
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk/utils/string.py +0 -11
- synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
- synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
- synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Storage utilities module.
|
|
2
|
+
|
|
3
|
+
This module provides a unified interface for working with different storage
|
|
4
|
+
backends including local filesystem, S3, GCS, SFTP, and HTTP.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from synapse_sdk.utils.storage import get_storage, get_pathlib
|
|
8
|
+
>>>
|
|
9
|
+
>>> # Local filesystem
|
|
10
|
+
>>> storage = get_storage({
|
|
11
|
+
... 'provider': 'local',
|
|
12
|
+
... 'configuration': {'location': '/data'}
|
|
13
|
+
... })
|
|
14
|
+
>>>
|
|
15
|
+
>>> # S3-compatible storage
|
|
16
|
+
>>> storage = get_storage({
|
|
17
|
+
... 'provider': 's3',
|
|
18
|
+
... 'configuration': {
|
|
19
|
+
... 'bucket_name': 'my-bucket',
|
|
20
|
+
... 'access_key': 'AKIAIOSFODNN7EXAMPLE',
|
|
21
|
+
... 'secret_key': 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
|
22
|
+
... }
|
|
23
|
+
... })
|
|
24
|
+
>>>
|
|
25
|
+
>>> # Get pathlib object for path operations
|
|
26
|
+
>>> path = get_pathlib(storage_config, '/uploads')
|
|
27
|
+
>>> for file in path.rglob('*'):
|
|
28
|
+
... print(file)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from upath import UPath
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@runtime_checkable
|
|
41
|
+
class StorageProtocol(Protocol):
|
|
42
|
+
"""Protocol defining the storage provider interface.
|
|
43
|
+
|
|
44
|
+
All storage providers must implement these methods to be compatible
|
|
45
|
+
with the storage system. Uses structural typing (duck typing) rather
|
|
46
|
+
than inheritance, allowing third-party implementations.
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
>>> class CustomStorage:
|
|
50
|
+
... def upload(self, source: Path, target: str) -> str: ...
|
|
51
|
+
... def exists(self, target: str) -> bool: ...
|
|
52
|
+
... def get_url(self, target: str) -> str: ...
|
|
53
|
+
... def get_pathlib(self, path: str) -> Path: ...
|
|
54
|
+
... def get_path_file_count(self, pathlib_obj: Path) -> int: ...
|
|
55
|
+
... def get_path_total_size(self, pathlib_obj: Path) -> int: ...
|
|
56
|
+
>>>
|
|
57
|
+
>>> isinstance(CustomStorage(), StorageProtocol) # True
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def upload(self, source: Path, target: str) -> str:
|
|
61
|
+
"""Upload a file from local source to target path.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
source: Local file path to upload.
|
|
65
|
+
target: Target path in storage (relative to storage root).
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
URL or identifier of the uploaded file.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
StorageNotFoundError: If source file doesn't exist.
|
|
72
|
+
StorageUploadError: If upload fails.
|
|
73
|
+
"""
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
def exists(self, target: str) -> bool:
|
|
77
|
+
"""Check if a file or directory exists at target path.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
target: Path to check (relative to storage root).
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
True if path exists, False otherwise.
|
|
84
|
+
"""
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
def get_url(self, target: str) -> str:
|
|
88
|
+
"""Get the URL for accessing a file.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
target: Path to file (relative to storage root).
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
URL string for accessing the file.
|
|
95
|
+
"""
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
def get_pathlib(self, path: str) -> Path | UPath:
|
|
99
|
+
"""Get a pathlib-compatible object for the path.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
path: Path relative to storage root.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Path object (local) or UPath object (cloud/remote).
|
|
106
|
+
"""
|
|
107
|
+
...
|
|
108
|
+
|
|
109
|
+
def get_path_file_count(self, pathlib_obj: Path | UPath) -> int:
|
|
110
|
+
"""Count files recursively in the given path.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
pathlib_obj: Path object from get_pathlib().
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Number of files (excluding directories).
|
|
117
|
+
"""
|
|
118
|
+
...
|
|
119
|
+
|
|
120
|
+
def get_path_total_size(self, pathlib_obj: Path | UPath) -> int:
|
|
121
|
+
"""Calculate total size of files recursively.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
pathlib_obj: Path object from get_pathlib().
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Total size in bytes.
|
|
128
|
+
"""
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_storage(connection_param: dict[str, Any]) -> StorageProtocol:
|
|
133
|
+
"""Get a storage provider instance from configuration.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
connection_param: Dictionary with 'provider' and 'configuration' keys.
|
|
137
|
+
Example: {'provider': 's3', 'configuration': {'bucket_name': '...'}}
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Storage provider instance implementing StorageProtocol.
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
StorageConfigError: If configuration is invalid.
|
|
144
|
+
StorageProviderNotFoundError: If provider is not registered.
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
>>> config = {
|
|
148
|
+
... 'provider': 's3',
|
|
149
|
+
... 'configuration': {
|
|
150
|
+
... 'bucket_name': 'my-bucket',
|
|
151
|
+
... 'access_key': 'AKIAIOSFODNN7EXAMPLE',
|
|
152
|
+
... 'secret_key': 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
|
153
|
+
... }
|
|
154
|
+
... }
|
|
155
|
+
>>> storage = get_storage(config)
|
|
156
|
+
"""
|
|
157
|
+
from synapse_sdk.utils.storage.config import StorageConfig
|
|
158
|
+
from synapse_sdk.utils.storage.registry import get_provider_class
|
|
159
|
+
|
|
160
|
+
# Validate configuration with Pydantic
|
|
161
|
+
storage_config = StorageConfig.model_validate(connection_param)
|
|
162
|
+
|
|
163
|
+
# Get provider class and instantiate
|
|
164
|
+
provider_cls = get_provider_class(storage_config.provider)
|
|
165
|
+
return provider_cls(storage_config.configuration)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_pathlib(storage_config: dict[str, Any], path_root: str) -> Path | UPath:
|
|
169
|
+
"""Get pathlib object for a path in storage.
|
|
170
|
+
|
|
171
|
+
Convenience function that combines get_storage() and get_pathlib().
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
storage_config: Storage configuration dict.
|
|
175
|
+
path_root: Root path to get pathlib for.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Path or UPath object.
|
|
179
|
+
|
|
180
|
+
Example:
|
|
181
|
+
>>> config = {'provider': 'local', 'configuration': {'location': '/data'}}
|
|
182
|
+
>>> path = get_pathlib(config, '/uploads')
|
|
183
|
+
>>> path.exists()
|
|
184
|
+
True
|
|
185
|
+
"""
|
|
186
|
+
storage = get_storage(storage_config)
|
|
187
|
+
return storage.get_pathlib(path_root)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def get_path_file_count(storage_config: dict[str, Any], path_root: str) -> int:
|
|
191
|
+
"""Get file count in a storage path.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
storage_config: Storage configuration dict.
|
|
195
|
+
path_root: Root path to count files in.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Number of files.
|
|
199
|
+
|
|
200
|
+
Example:
|
|
201
|
+
>>> config = {'provider': 'local', 'configuration': {'location': '/data'}}
|
|
202
|
+
>>> count = get_path_file_count(config, '/uploads')
|
|
203
|
+
>>> print(f'Found {count} files')
|
|
204
|
+
"""
|
|
205
|
+
storage = get_storage(storage_config)
|
|
206
|
+
pathlib_obj = storage.get_pathlib(path_root)
|
|
207
|
+
return storage.get_path_file_count(pathlib_obj)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def get_path_total_size(storage_config: dict[str, Any], path_root: str) -> int:
|
|
211
|
+
"""Get total size of files in a storage path.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
storage_config: Storage configuration dict.
|
|
215
|
+
path_root: Root path to calculate size for.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Total size in bytes.
|
|
219
|
+
|
|
220
|
+
Example:
|
|
221
|
+
>>> config = {'provider': 'local', 'configuration': {'location': '/data'}}
|
|
222
|
+
>>> size = get_path_total_size(config, '/uploads')
|
|
223
|
+
>>> print(f'Total size: {size / 1024 / 1024:.2f} MB')
|
|
224
|
+
"""
|
|
225
|
+
storage = get_storage(storage_config)
|
|
226
|
+
pathlib_obj = storage.get_pathlib(path_root)
|
|
227
|
+
return storage.get_path_total_size(pathlib_obj)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
__all__ = [
|
|
231
|
+
# Protocol
|
|
232
|
+
'StorageProtocol',
|
|
233
|
+
# Public API functions
|
|
234
|
+
'get_storage',
|
|
235
|
+
'get_pathlib',
|
|
236
|
+
'get_path_file_count',
|
|
237
|
+
'get_path_total_size',
|
|
238
|
+
]
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Storage configuration models using Pydantic v2."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LocalStorageConfig(BaseModel):
|
|
11
|
+
"""Configuration for local filesystem storage.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
location: Base directory path for storage operations.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
location: str = Field(..., description='Base directory path')
|
|
18
|
+
|
|
19
|
+
@field_validator('location')
|
|
20
|
+
@classmethod
|
|
21
|
+
def validate_location(cls, v: str) -> str:
|
|
22
|
+
if not v:
|
|
23
|
+
raise ValueError('location cannot be empty')
|
|
24
|
+
return v
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class S3StorageConfig(BaseModel):
|
|
28
|
+
"""Configuration for S3-compatible storage (AWS S3, MinIO).
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
bucket_name: S3 bucket name.
|
|
32
|
+
access_key: AWS access key ID.
|
|
33
|
+
secret_key: AWS secret access key.
|
|
34
|
+
region_name: AWS region (default: us-east-1).
|
|
35
|
+
endpoint_url: Custom endpoint for S3-compatible services (MinIO).
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
bucket_name: str
|
|
39
|
+
access_key: str
|
|
40
|
+
secret_key: str
|
|
41
|
+
region_name: str = 'us-east-1'
|
|
42
|
+
endpoint_url: str | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class GCSStorageConfig(BaseModel):
|
|
46
|
+
"""Configuration for Google Cloud Storage.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
bucket_name: GCS bucket name.
|
|
50
|
+
credentials: Path to service account JSON or credentials dict.
|
|
51
|
+
project: GCP project ID (optional, inferred from credentials).
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
bucket_name: str
|
|
55
|
+
credentials: str | dict[str, Any]
|
|
56
|
+
project: str | None = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SFTPStorageConfig(BaseModel):
|
|
60
|
+
"""Configuration for SFTP storage.
|
|
61
|
+
|
|
62
|
+
Attributes:
|
|
63
|
+
host: SFTP server hostname.
|
|
64
|
+
username: SSH username.
|
|
65
|
+
password: SSH password (for password auth).
|
|
66
|
+
private_key: Path to private key file (for key auth).
|
|
67
|
+
private_key_passphrase: Passphrase for encrypted private key.
|
|
68
|
+
port: SSH port (default: 22).
|
|
69
|
+
root_path: Base path on remote server.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
host: str
|
|
73
|
+
username: str
|
|
74
|
+
password: str | None = None
|
|
75
|
+
private_key: str | None = None
|
|
76
|
+
private_key_passphrase: str | None = None
|
|
77
|
+
port: int = 22
|
|
78
|
+
root_path: str = '/'
|
|
79
|
+
|
|
80
|
+
@model_validator(mode='after')
|
|
81
|
+
def validate_auth(self) -> SFTPStorageConfig:
|
|
82
|
+
if not self.password and not self.private_key:
|
|
83
|
+
raise ValueError('Either password or private_key must be provided')
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class HTTPStorageConfig(BaseModel):
|
|
88
|
+
"""Configuration for HTTP storage.
|
|
89
|
+
|
|
90
|
+
Attributes:
|
|
91
|
+
base_url: Base URL of the HTTP file server.
|
|
92
|
+
timeout: Request timeout in seconds.
|
|
93
|
+
headers: Optional headers to include in requests.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
base_url: str
|
|
97
|
+
timeout: int = 30
|
|
98
|
+
headers: dict[str, str] = Field(default_factory=dict)
|
|
99
|
+
|
|
100
|
+
@field_validator('base_url')
|
|
101
|
+
@classmethod
|
|
102
|
+
def validate_base_url(cls, v: str) -> str:
|
|
103
|
+
if not v.startswith(('http://', 'https://')):
|
|
104
|
+
raise ValueError('base_url must start with http:// or https://')
|
|
105
|
+
# Ensure trailing slash
|
|
106
|
+
return v if v.endswith('/') else f'{v}/'
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Type alias for all provider configurations
|
|
110
|
+
ProviderConfig = LocalStorageConfig | S3StorageConfig | GCSStorageConfig | SFTPStorageConfig | HTTPStorageConfig
|
|
111
|
+
|
|
112
|
+
# Provider type literals
|
|
113
|
+
ProviderType = Literal[
|
|
114
|
+
'local',
|
|
115
|
+
'file_system', # alias for local (backward compat)
|
|
116
|
+
's3',
|
|
117
|
+
'amazon_s3', # alias for s3
|
|
118
|
+
'minio', # alias for s3
|
|
119
|
+
'gcs',
|
|
120
|
+
'gs', # alias for gcs
|
|
121
|
+
'gcp', # alias for gcs
|
|
122
|
+
'sftp',
|
|
123
|
+
'http',
|
|
124
|
+
'https', # alias for http
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class StorageConfig(BaseModel):
|
|
129
|
+
"""Top-level storage configuration model.
|
|
130
|
+
|
|
131
|
+
Attributes:
|
|
132
|
+
provider: Storage provider type.
|
|
133
|
+
configuration: Provider-specific configuration.
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
>>> config = StorageConfig(
|
|
137
|
+
... provider='s3',
|
|
138
|
+
... configuration={
|
|
139
|
+
... 'bucket_name': 'my-bucket',
|
|
140
|
+
... 'access_key': 'AKIAIOSFODNN7EXAMPLE',
|
|
141
|
+
... 'secret_key': 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
|
142
|
+
... }
|
|
143
|
+
... )
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
provider: ProviderType
|
|
147
|
+
configuration: dict[str, Any]
|
|
148
|
+
|
|
149
|
+
def get_typed_config(self) -> ProviderConfig:
|
|
150
|
+
"""Get configuration as the appropriate typed model.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Typed configuration model based on provider.
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
ValueError: If provider is unknown.
|
|
157
|
+
"""
|
|
158
|
+
config_map: dict[str, type[BaseModel]] = {
|
|
159
|
+
'local': LocalStorageConfig,
|
|
160
|
+
'file_system': LocalStorageConfig,
|
|
161
|
+
's3': S3StorageConfig,
|
|
162
|
+
'amazon_s3': S3StorageConfig,
|
|
163
|
+
'minio': S3StorageConfig,
|
|
164
|
+
'gcs': GCSStorageConfig,
|
|
165
|
+
'gs': GCSStorageConfig,
|
|
166
|
+
'gcp': GCSStorageConfig,
|
|
167
|
+
'sftp': SFTPStorageConfig,
|
|
168
|
+
'http': HTTPStorageConfig,
|
|
169
|
+
'https': HTTPStorageConfig,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
config_cls = config_map.get(self.provider)
|
|
173
|
+
if not config_cls:
|
|
174
|
+
raise ValueError(f'Unknown provider: {self.provider}')
|
|
175
|
+
|
|
176
|
+
return config_cls.model_validate(self.configuration)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
__all__ = [
|
|
180
|
+
'StorageConfig',
|
|
181
|
+
'LocalStorageConfig',
|
|
182
|
+
'S3StorageConfig',
|
|
183
|
+
'GCSStorageConfig',
|
|
184
|
+
'SFTPStorageConfig',
|
|
185
|
+
'HTTPStorageConfig',
|
|
186
|
+
'ProviderConfig',
|
|
187
|
+
'ProviderType',
|
|
188
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Storage-specific exceptions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StorageError(Exception):
|
|
9
|
+
"""Base exception for storage-related errors."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message: str, details: Any = None):
|
|
12
|
+
self.message = message
|
|
13
|
+
self.details = details
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
|
|
16
|
+
def __repr__(self) -> str:
|
|
17
|
+
return f'{self.__class__.__name__}(message={self.message!r}, details={self.details!r})'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StorageConfigError(StorageError):
|
|
21
|
+
"""Raised when storage configuration is invalid."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class StorageProviderNotFoundError(StorageError):
|
|
25
|
+
"""Raised when requested provider is not registered."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StorageConnectionError(StorageError):
|
|
29
|
+
"""Raised when connection to storage fails."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class StorageUploadError(StorageError):
|
|
33
|
+
"""Raised when file upload fails."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class StorageNotFoundError(StorageError):
|
|
37
|
+
"""Raised when requested path does not exist."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class StoragePermissionError(StorageError):
|
|
41
|
+
"""Raised when access to storage is denied."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
'StorageError',
|
|
46
|
+
'StorageConfigError',
|
|
47
|
+
'StorageProviderNotFoundError',
|
|
48
|
+
'StorageConnectionError',
|
|
49
|
+
'StorageUploadError',
|
|
50
|
+
'StorageNotFoundError',
|
|
51
|
+
'StoragePermissionError',
|
|
52
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Storage providers module.
|
|
2
|
+
|
|
3
|
+
This module exports all built-in storage providers. Import specific
|
|
4
|
+
providers directly for better control over dependencies.
|
|
5
|
+
|
|
6
|
+
Note: Providers are imported lazily via registry to avoid
|
|
7
|
+
loading optional dependencies (boto3, gcsfs, paramiko) at import time.
|
|
8
|
+
Direct imports are available for explicit usage.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
__all__: list[str] = []
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Shared implementation for storage providers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from upath import UPath
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _BaseStorageMixin:
|
|
13
|
+
"""Shared implementation for storage providers.
|
|
14
|
+
|
|
15
|
+
This mixin provides common implementations for file counting
|
|
16
|
+
and size calculation that work with both Path and UPath objects.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def _count_files(self, pathlib_obj: Path | UPath) -> int:
|
|
20
|
+
"""Count files recursively in the given path.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
pathlib_obj: Path object to count files in.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Number of files.
|
|
27
|
+
"""
|
|
28
|
+
if not pathlib_obj.exists():
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
if pathlib_obj.is_file():
|
|
32
|
+
return 1
|
|
33
|
+
|
|
34
|
+
count = 0
|
|
35
|
+
for item in pathlib_obj.rglob('*'):
|
|
36
|
+
if item.is_file():
|
|
37
|
+
count += 1
|
|
38
|
+
return count
|
|
39
|
+
|
|
40
|
+
def _calculate_total_size(self, pathlib_obj: Path | UPath) -> int:
|
|
41
|
+
"""Calculate total size of files recursively.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
pathlib_obj: Path object to calculate size for.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Total size in bytes.
|
|
48
|
+
"""
|
|
49
|
+
if not pathlib_obj.exists():
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
if pathlib_obj.is_file():
|
|
53
|
+
return pathlib_obj.stat().st_size
|
|
54
|
+
|
|
55
|
+
total_size = 0
|
|
56
|
+
for item in pathlib_obj.rglob('*'):
|
|
57
|
+
if item.is_file():
|
|
58
|
+
total_size += item.stat().st_size
|
|
59
|
+
return total_size
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def _normalize_path(path: str) -> str:
|
|
63
|
+
"""Normalize path by removing leading slashes.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
path: Path string to normalize.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Normalized path without leading slashes.
|
|
70
|
+
"""
|
|
71
|
+
if path in ('/', ''):
|
|
72
|
+
return ''
|
|
73
|
+
return path.lstrip('/')
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
__all__ = ['_BaseStorageMixin']
|