cledar-sdk 2.0.2__py3-none-any.whl → 2.0.3__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.
- cledar/__init__.py +0 -0
- cledar/kafka/README.md +239 -0
- cledar/kafka/__init__.py +40 -0
- cledar/kafka/clients/base.py +98 -0
- cledar/kafka/clients/consumer.py +110 -0
- cledar/kafka/clients/producer.py +80 -0
- cledar/kafka/config/schemas.py +178 -0
- cledar/kafka/exceptions.py +22 -0
- cledar/kafka/handlers/dead_letter.py +82 -0
- cledar/kafka/handlers/parser.py +49 -0
- cledar/kafka/logger.py +3 -0
- cledar/kafka/models/input.py +13 -0
- cledar/kafka/models/message.py +10 -0
- cledar/kafka/models/output.py +8 -0
- cledar/kafka/tests/.env.test.kafka +3 -0
- cledar/kafka/tests/README.md +216 -0
- cledar/kafka/tests/conftest.py +104 -0
- cledar/kafka/tests/integration/__init__.py +1 -0
- cledar/kafka/tests/integration/conftest.py +78 -0
- cledar/kafka/tests/integration/helpers.py +47 -0
- cledar/kafka/tests/integration/test_consumer_integration.py +375 -0
- cledar/kafka/tests/integration/test_integration.py +394 -0
- cledar/kafka/tests/integration/test_producer_consumer_interaction.py +388 -0
- cledar/kafka/tests/integration/test_producer_integration.py +217 -0
- cledar/kafka/tests/unit/__init__.py +1 -0
- cledar/kafka/tests/unit/test_base_kafka_client.py +391 -0
- cledar/kafka/tests/unit/test_config_validation.py +609 -0
- cledar/kafka/tests/unit/test_dead_letter_handler.py +443 -0
- cledar/kafka/tests/unit/test_error_handling.py +674 -0
- cledar/kafka/tests/unit/test_input_parser.py +310 -0
- cledar/kafka/tests/unit/test_input_parser_comprehensive.py +489 -0
- cledar/kafka/tests/unit/test_utils.py +25 -0
- cledar/kafka/tests/unit/test_utils_comprehensive.py +408 -0
- cledar/kafka/utils/callbacks.py +19 -0
- cledar/kafka/utils/messages.py +28 -0
- cledar/kafka/utils/topics.py +2 -0
- cledar/kserve/README.md +352 -0
- cledar/kserve/__init__.py +3 -0
- cledar/kserve/tests/__init__.py +0 -0
- cledar/kserve/tests/test_utils.py +64 -0
- cledar/kserve/utils.py +27 -0
- cledar/logging/README.md +53 -0
- cledar/logging/__init__.py +3 -0
- cledar/logging/tests/test_universal_plaintext_formatter.py +249 -0
- cledar/logging/universal_plaintext_formatter.py +94 -0
- cledar/monitoring/README.md +71 -0
- cledar/monitoring/__init__.py +3 -0
- cledar/monitoring/monitoring_server.py +112 -0
- cledar/monitoring/tests/integration/test_monitoring_server_int.py +162 -0
- cledar/monitoring/tests/test_monitoring_server.py +59 -0
- cledar/nonce/README.md +99 -0
- cledar/nonce/__init__.py +3 -0
- cledar/nonce/nonce_service.py +36 -0
- cledar/nonce/tests/__init__.py +0 -0
- cledar/nonce/tests/test_nonce_service.py +136 -0
- cledar/redis/README.md +536 -0
- cledar/redis/__init__.py +15 -0
- cledar/redis/async_example.py +111 -0
- cledar/redis/example.py +37 -0
- cledar/redis/exceptions.py +22 -0
- cledar/redis/logger.py +3 -0
- cledar/redis/model.py +10 -0
- cledar/redis/redis.py +525 -0
- cledar/redis/redis_config_store.py +252 -0
- cledar/redis/tests/test_async_integration_redis.py +158 -0
- cledar/redis/tests/test_async_redis_service.py +380 -0
- cledar/redis/tests/test_integration_redis.py +119 -0
- cledar/redis/tests/test_redis_service.py +319 -0
- cledar/storage/README.md +529 -0
- cledar/storage/__init__.py +4 -0
- cledar/storage/constants.py +3 -0
- cledar/storage/exceptions.py +50 -0
- cledar/storage/models.py +19 -0
- cledar/storage/object_storage.py +955 -0
- cledar/storage/tests/conftest.py +18 -0
- cledar/storage/tests/test_abfs.py +164 -0
- cledar/storage/tests/test_integration_filesystem.py +359 -0
- cledar/storage/tests/test_integration_s3.py +453 -0
- cledar/storage/tests/test_local.py +384 -0
- cledar/storage/tests/test_s3.py +521 -0
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.0.3.dist-info}/METADATA +1 -1
- cledar_sdk-2.0.3.dist-info/RECORD +84 -0
- cledar_sdk-2.0.2.dist-info/RECORD +0 -4
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.0.3.dist-info}/WHEEL +0 -0
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from faker import Faker
|
|
3
|
+
|
|
4
|
+
from cledar.storage import ObjectStorageServiceConfig
|
|
5
|
+
|
|
6
|
+
fake = Faker()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def object_storage_config() -> ObjectStorageServiceConfig:
|
|
11
|
+
return ObjectStorageServiceConfig(
|
|
12
|
+
s3_access_key=fake.password(),
|
|
13
|
+
s3_endpoint_url=fake.url(),
|
|
14
|
+
s3_secret_key=fake.password(),
|
|
15
|
+
s3_max_concurrency=10,
|
|
16
|
+
azure_account_name=fake.word(),
|
|
17
|
+
azure_account_key=fake.password(),
|
|
18
|
+
)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# mypy: disable-error-code=no-untyped-def
|
|
2
|
+
# pylint: disable=import-error
|
|
3
|
+
import io
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from unittest.mock import MagicMock, patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from faker import Faker
|
|
9
|
+
|
|
10
|
+
from cledar.storage import ObjectStorageService, ObjectStorageServiceConfig
|
|
11
|
+
|
|
12
|
+
fake = Faker()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture(name="object_storage_service")
|
|
16
|
+
@patch("fsspec.filesystem")
|
|
17
|
+
def fixture_object_storage_service(
|
|
18
|
+
fsspec_client: MagicMock, object_storage_config: ObjectStorageServiceConfig
|
|
19
|
+
) -> ObjectStorageService:
|
|
20
|
+
# first call returns s3, second file, third abfs
|
|
21
|
+
fsspec_client.side_effect = [MagicMock(), MagicMock(), MagicMock()]
|
|
22
|
+
return ObjectStorageService(object_storage_config)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_upload_file_to_abfs_path(object_storage_service: ObjectStorageService) -> None:
|
|
26
|
+
file_path = fake.file_path()
|
|
27
|
+
dest_path = "abfs://container/path/to/file.txt"
|
|
28
|
+
object_storage_service.azure_client.put = MagicMock()
|
|
29
|
+
|
|
30
|
+
object_storage_service.upload_file(file_path=file_path, destination_path=dest_path)
|
|
31
|
+
|
|
32
|
+
object_storage_service.azure_client.put.assert_called_once_with(
|
|
33
|
+
lpath=file_path, rpath=dest_path
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_upload_buffer_to_abfs_path(
|
|
38
|
+
object_storage_service: ObjectStorageService,
|
|
39
|
+
) -> None:
|
|
40
|
+
buffer_bytes = io.BytesIO(fake.text().encode())
|
|
41
|
+
dest_path = "abfss://container/path/to/file.txt"
|
|
42
|
+
|
|
43
|
+
mock_file = MagicMock()
|
|
44
|
+
mock_file.write = MagicMock()
|
|
45
|
+
|
|
46
|
+
@contextmanager
|
|
47
|
+
def open_cm(*_args: object, **_kwargs: object):
|
|
48
|
+
yield mock_file
|
|
49
|
+
|
|
50
|
+
object_storage_service.azure_client.open = MagicMock(
|
|
51
|
+
side_effect=lambda *a, **k: open_cm()
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
object_storage_service.upload_buffer(
|
|
55
|
+
buffer=buffer_bytes, destination_path=dest_path
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
object_storage_service.azure_client.open.assert_called_once_with(
|
|
59
|
+
path=dest_path, mode="wb"
|
|
60
|
+
)
|
|
61
|
+
mock_file.write.assert_called_once()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_read_file_from_abfs_path(object_storage_service: ObjectStorageService) -> None:
|
|
65
|
+
path = "abfs://container/path/to/file.txt"
|
|
66
|
+
expected_content = fake.text().encode()
|
|
67
|
+
|
|
68
|
+
mock_file = MagicMock()
|
|
69
|
+
mock_file.read.return_value = expected_content
|
|
70
|
+
|
|
71
|
+
@contextmanager
|
|
72
|
+
def open_cm(*_args: object, **_kwargs: object):
|
|
73
|
+
yield mock_file
|
|
74
|
+
|
|
75
|
+
object_storage_service.azure_client.open = MagicMock(
|
|
76
|
+
side_effect=lambda *a, **k: open_cm()
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
result = object_storage_service.read_file(path=path)
|
|
80
|
+
assert result == expected_content
|
|
81
|
+
object_storage_service.azure_client.open.assert_called_once_with(
|
|
82
|
+
path=path, mode="rb"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_list_objects_abfs_recursive(
|
|
87
|
+
object_storage_service: ObjectStorageService,
|
|
88
|
+
) -> None:
|
|
89
|
+
path = "abfs://container/prefix"
|
|
90
|
+
mock_objects = [
|
|
91
|
+
"abfs://container/prefix/file1",
|
|
92
|
+
"abfs://container/prefix/file2",
|
|
93
|
+
]
|
|
94
|
+
object_storage_service.azure_client.find.return_value = mock_objects
|
|
95
|
+
|
|
96
|
+
result = object_storage_service.list_objects(path=path, recursive=True)
|
|
97
|
+
assert result == mock_objects
|
|
98
|
+
object_storage_service.azure_client.find.assert_called_once_with(path)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_list_objects_abfs_non_recursive(
|
|
102
|
+
object_storage_service: ObjectStorageService,
|
|
103
|
+
) -> None:
|
|
104
|
+
path = "abfs://container/prefix"
|
|
105
|
+
mock_objects = ["abfs://container/prefix/file1"]
|
|
106
|
+
object_storage_service.azure_client.ls.return_value = mock_objects
|
|
107
|
+
|
|
108
|
+
result = object_storage_service.list_objects(path=path, recursive=False)
|
|
109
|
+
assert result == mock_objects
|
|
110
|
+
object_storage_service.azure_client.ls.assert_called_once_with(path, detail=False)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_delete_file_abfs(object_storage_service: ObjectStorageService) -> None:
|
|
114
|
+
path = "abfs://container/file.txt"
|
|
115
|
+
object_storage_service.delete_file(path=path)
|
|
116
|
+
object_storage_service.azure_client.rm.assert_called_once_with(path)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_file_exists_abfs(object_storage_service: ObjectStorageService) -> None:
|
|
120
|
+
path = "abfs://container/file.txt"
|
|
121
|
+
object_storage_service.azure_client.exists.return_value = True
|
|
122
|
+
assert object_storage_service.file_exists(path=path) is True
|
|
123
|
+
object_storage_service.azure_client.exists.assert_called_once_with(path)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_download_file_from_abfs(object_storage_service: ObjectStorageService) -> None:
|
|
127
|
+
source_path = "abfs://container/file.txt"
|
|
128
|
+
dest_path = "/tmp/dest.txt"
|
|
129
|
+
object_storage_service.azure_client.get = MagicMock()
|
|
130
|
+
object_storage_service.download_file(dest_path, source_path=source_path)
|
|
131
|
+
object_storage_service.azure_client.get.assert_called_once_with(
|
|
132
|
+
source_path, dest_path
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_get_file_size_abfs(object_storage_service: ObjectStorageService) -> None:
|
|
137
|
+
path = "abfs://container/file.txt"
|
|
138
|
+
object_storage_service.azure_client.info.return_value = {"size": 123}
|
|
139
|
+
result = object_storage_service.get_file_size(path=path)
|
|
140
|
+
assert result == 123
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_get_file_info_abfs(object_storage_service: ObjectStorageService) -> None:
|
|
144
|
+
path = "abfs://container/file.txt"
|
|
145
|
+
info = {"size": 1}
|
|
146
|
+
object_storage_service.azure_client.info.return_value = info
|
|
147
|
+
result = object_storage_service.get_file_info(path=path)
|
|
148
|
+
assert result == info
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_copy_file_abfs_to_abfs(object_storage_service: ObjectStorageService) -> None:
|
|
152
|
+
src = "abfs://container/src.txt"
|
|
153
|
+
dst = "abfs://container/dst.txt"
|
|
154
|
+
object_storage_service.azure_client.copy = MagicMock()
|
|
155
|
+
object_storage_service.copy_file(source_path=src, dest_path=dst)
|
|
156
|
+
object_storage_service.azure_client.copy.assert_called_once_with(src, dst)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_move_file_abfs_to_abfs(object_storage_service: ObjectStorageService) -> None:
|
|
160
|
+
src = "abfs://container/src.txt"
|
|
161
|
+
dst = "abfs://container/dst.txt"
|
|
162
|
+
object_storage_service.azure_client.move = MagicMock()
|
|
163
|
+
object_storage_service.move_file(source_path=src, dest_path=dst)
|
|
164
|
+
object_storage_service.azure_client.move.assert_called_once_with(src, dst)
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# mypy: disable-error-code=no-untyped-def
|
|
2
|
+
import io
|
|
3
|
+
import tempfile
|
|
4
|
+
from collections.abc import Generator
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from faker import Faker
|
|
9
|
+
|
|
10
|
+
from cledar.storage.exceptions import ReadFileError
|
|
11
|
+
from cledar.storage.models import ObjectStorageServiceConfig
|
|
12
|
+
from cledar.storage.object_storage import ObjectStorageService
|
|
13
|
+
|
|
14
|
+
fake = Faker()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture(scope="module")
|
|
18
|
+
def object_storage_service() -> ObjectStorageService:
|
|
19
|
+
"""
|
|
20
|
+
Create an ObjectStorageService with minimal S3 config (only for local operations).
|
|
21
|
+
"""
|
|
22
|
+
# we still need to provide S3 config, but it won't be used for local operations
|
|
23
|
+
config = ObjectStorageServiceConfig(
|
|
24
|
+
s3_endpoint_url="http://localhost:9000",
|
|
25
|
+
s3_access_key="dummy",
|
|
26
|
+
s3_secret_key="dummy",
|
|
27
|
+
s3_max_concurrency=10,
|
|
28
|
+
)
|
|
29
|
+
return ObjectStorageService(config)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.fixture
|
|
33
|
+
def test_dir() -> Generator[str, None, None]:
|
|
34
|
+
"""
|
|
35
|
+
Create a temporary directory and clean it up after test.
|
|
36
|
+
"""
|
|
37
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
38
|
+
yield temp_dir
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_upload_and_read_buffer(
|
|
42
|
+
object_storage_service: ObjectStorageService, test_dir: str
|
|
43
|
+
) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Test uploading a buffer and reading it back.
|
|
46
|
+
"""
|
|
47
|
+
test_content = fake.text().encode()
|
|
48
|
+
buffer = io.BytesIO(test_content)
|
|
49
|
+
file_path = f"{test_dir}/test_buffer_{fake.file_name()}"
|
|
50
|
+
|
|
51
|
+
object_storage_service.upload_buffer(buffer=buffer, destination_path=file_path)
|
|
52
|
+
|
|
53
|
+
result = object_storage_service.read_file(path=file_path)
|
|
54
|
+
assert result == test_content
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_upload_buffer_invalid_params(
|
|
58
|
+
object_storage_service: ObjectStorageService,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Test that upload_buffer raises error with invalid params.
|
|
62
|
+
"""
|
|
63
|
+
buffer = io.BytesIO(b"test")
|
|
64
|
+
with pytest.raises(
|
|
65
|
+
ValueError,
|
|
66
|
+
match="Either destination_path or bucket and key must be provided",
|
|
67
|
+
):
|
|
68
|
+
object_storage_service.upload_buffer(buffer=buffer)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_upload_and_download_file(
|
|
72
|
+
object_storage_service: ObjectStorageService, test_dir: str
|
|
73
|
+
) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Test uploading a file and downloading it.
|
|
76
|
+
"""
|
|
77
|
+
test_content = fake.text().encode()
|
|
78
|
+
source_path = f"{test_dir}/source_{fake.file_name()}"
|
|
79
|
+
dest_path = f"{test_dir}/dest_{fake.file_name()}"
|
|
80
|
+
|
|
81
|
+
with open(source_path, "wb") as f:
|
|
82
|
+
f.write(test_content)
|
|
83
|
+
upload_path = f"{test_dir}/uploaded_{fake.file_name()}"
|
|
84
|
+
object_storage_service.upload_file(
|
|
85
|
+
file_path=source_path, destination_path=upload_path
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
object_storage_service.download_file(dest_path=dest_path, source_path=upload_path)
|
|
89
|
+
|
|
90
|
+
with open(dest_path, "rb") as f:
|
|
91
|
+
downloaded_content = f.read()
|
|
92
|
+
assert downloaded_content == test_content
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_read_file_retry_mechanism(
|
|
96
|
+
object_storage_service: ObjectStorageService, test_dir: str
|
|
97
|
+
) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Test that read_file succeeds with valid file.
|
|
100
|
+
"""
|
|
101
|
+
test_content = fake.text().encode()
|
|
102
|
+
buffer = io.BytesIO(test_content)
|
|
103
|
+
file_path = f"{test_dir}/retry_{fake.file_name()}"
|
|
104
|
+
|
|
105
|
+
object_storage_service.upload_buffer(buffer=buffer, destination_path=file_path)
|
|
106
|
+
|
|
107
|
+
result = object_storage_service.read_file(path=file_path, max_tries=3)
|
|
108
|
+
assert result == test_content
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_list_objects_recursive(
|
|
112
|
+
object_storage_service: ObjectStorageService, test_dir: str
|
|
113
|
+
) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Test listing objects recursively.
|
|
116
|
+
"""
|
|
117
|
+
files = [
|
|
118
|
+
"folder1/file1.txt",
|
|
119
|
+
"folder1/file2.txt",
|
|
120
|
+
"folder1/subfolder/file3.txt",
|
|
121
|
+
"folder2/file4.txt",
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
for file_path in files:
|
|
125
|
+
full_path = f"{test_dir}/{file_path}"
|
|
126
|
+
Path(full_path).parent.mkdir(parents=True, exist_ok=True)
|
|
127
|
+
buffer = io.BytesIO(fake.text().encode())
|
|
128
|
+
object_storage_service.upload_buffer(buffer=buffer, destination_path=full_path)
|
|
129
|
+
|
|
130
|
+
result = object_storage_service.list_objects(path=test_dir, recursive=True)
|
|
131
|
+
|
|
132
|
+
result_basenames = [Path(r).relative_to(test_dir).as_posix() for r in result]
|
|
133
|
+
|
|
134
|
+
assert len(result_basenames) >= 4
|
|
135
|
+
for file_path in files:
|
|
136
|
+
assert file_path in result_basenames
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_list_objects_with_prefix(
|
|
140
|
+
object_storage_service: ObjectStorageService, test_dir: str
|
|
141
|
+
) -> None:
|
|
142
|
+
"""
|
|
143
|
+
Test listing objects with prefix filter.
|
|
144
|
+
"""
|
|
145
|
+
files = [
|
|
146
|
+
"prefix1/file1.txt",
|
|
147
|
+
"prefix1/file2.txt",
|
|
148
|
+
"prefix2/file3.txt",
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
for file_path in files:
|
|
152
|
+
full_path = f"{test_dir}/{file_path}"
|
|
153
|
+
Path(full_path).parent.mkdir(parents=True, exist_ok=True)
|
|
154
|
+
buffer = io.BytesIO(fake.text().encode())
|
|
155
|
+
object_storage_service.upload_buffer(buffer=buffer, destination_path=full_path)
|
|
156
|
+
|
|
157
|
+
prefix1_dir = f"{test_dir}/prefix1"
|
|
158
|
+
result = object_storage_service.list_objects(path=prefix1_dir, recursive=True)
|
|
159
|
+
|
|
160
|
+
result_basenames = [Path(r).name for r in result]
|
|
161
|
+
|
|
162
|
+
assert len(result_basenames) >= 2
|
|
163
|
+
assert "file1.txt" in result_basenames
|
|
164
|
+
assert "file2.txt" in result_basenames
|
|
165
|
+
assert "file3.txt" not in result_basenames
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_list_objects_non_recursive(
|
|
169
|
+
object_storage_service: ObjectStorageService, test_dir: str
|
|
170
|
+
) -> None:
|
|
171
|
+
"""
|
|
172
|
+
Test listing objects non-recursively.
|
|
173
|
+
"""
|
|
174
|
+
files = [
|
|
175
|
+
"root1.txt",
|
|
176
|
+
"root2.txt",
|
|
177
|
+
"folder/nested.txt",
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
for file_path in files:
|
|
181
|
+
full_path = f"{test_dir}/{file_path}"
|
|
182
|
+
Path(full_path).parent.mkdir(parents=True, exist_ok=True)
|
|
183
|
+
buffer = io.BytesIO(fake.text().encode())
|
|
184
|
+
object_storage_service.upload_buffer(buffer=buffer, destination_path=full_path)
|
|
185
|
+
|
|
186
|
+
result = object_storage_service.list_objects(path=test_dir, recursive=False)
|
|
187
|
+
|
|
188
|
+
result_basenames = [Path(r).name for r in result]
|
|
189
|
+
|
|
190
|
+
assert len(result) >= 2
|
|
191
|
+
has_root_file = any("root" in r for r in result_basenames)
|
|
192
|
+
has_folder = any("folder" in r for r in result_basenames)
|
|
193
|
+
assert has_root_file or has_folder
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_file_exists(
|
|
197
|
+
object_storage_service: ObjectStorageService, test_dir: str
|
|
198
|
+
) -> None:
|
|
199
|
+
"""
|
|
200
|
+
Test checking if file exists.
|
|
201
|
+
"""
|
|
202
|
+
file_path = f"{test_dir}/exists_{fake.file_name()}"
|
|
203
|
+
|
|
204
|
+
assert object_storage_service.file_exists(path=file_path) is False
|
|
205
|
+
|
|
206
|
+
buffer = io.BytesIO(fake.text().encode())
|
|
207
|
+
object_storage_service.upload_buffer(buffer=buffer, destination_path=file_path)
|
|
208
|
+
|
|
209
|
+
assert object_storage_service.file_exists(path=file_path) is True
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_delete_file(
|
|
213
|
+
object_storage_service: ObjectStorageService, test_dir: str
|
|
214
|
+
) -> None:
|
|
215
|
+
"""
|
|
216
|
+
Test deleting a file.
|
|
217
|
+
"""
|
|
218
|
+
file_path = f"{test_dir}/delete_{fake.file_name()}"
|
|
219
|
+
|
|
220
|
+
buffer = io.BytesIO(fake.text().encode())
|
|
221
|
+
object_storage_service.upload_buffer(buffer=buffer, destination_path=file_path)
|
|
222
|
+
|
|
223
|
+
assert object_storage_service.file_exists(path=file_path) is True
|
|
224
|
+
|
|
225
|
+
object_storage_service.delete_file(path=file_path)
|
|
226
|
+
|
|
227
|
+
assert object_storage_service.file_exists(path=file_path) is False
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_get_file_size(
|
|
231
|
+
object_storage_service: ObjectStorageService, test_dir: str
|
|
232
|
+
) -> None:
|
|
233
|
+
"""
|
|
234
|
+
Test getting file size.
|
|
235
|
+
"""
|
|
236
|
+
test_content = fake.text().encode()
|
|
237
|
+
file_path = f"{test_dir}/size_{fake.file_name()}"
|
|
238
|
+
|
|
239
|
+
buffer = io.BytesIO(test_content)
|
|
240
|
+
object_storage_service.upload_buffer(buffer=buffer, destination_path=file_path)
|
|
241
|
+
|
|
242
|
+
size = object_storage_service.get_file_size(path=file_path)
|
|
243
|
+
assert size == len(test_content)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def test_get_file_info(
|
|
247
|
+
object_storage_service: ObjectStorageService, test_dir: str
|
|
248
|
+
) -> None:
|
|
249
|
+
"""
|
|
250
|
+
Test getting file metadata.
|
|
251
|
+
"""
|
|
252
|
+
test_content = fake.text().encode()
|
|
253
|
+
file_path = f"{test_dir}/info_{fake.file_name()}"
|
|
254
|
+
|
|
255
|
+
buffer = io.BytesIO(test_content)
|
|
256
|
+
object_storage_service.upload_buffer(buffer=buffer, destination_path=file_path)
|
|
257
|
+
|
|
258
|
+
info = object_storage_service.get_file_info(path=file_path)
|
|
259
|
+
|
|
260
|
+
assert "size" in info
|
|
261
|
+
assert info["size"] == len(test_content)
|
|
262
|
+
assert info is not None
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def test_copy_file(object_storage_service: ObjectStorageService, test_dir: str) -> None:
|
|
266
|
+
"""
|
|
267
|
+
Test copying a file.
|
|
268
|
+
"""
|
|
269
|
+
test_content = fake.text().encode()
|
|
270
|
+
source_path = f"{test_dir}/copy_source_{fake.file_name()}"
|
|
271
|
+
dest_path = f"{test_dir}/copy_dest_{fake.file_name()}"
|
|
272
|
+
|
|
273
|
+
buffer = io.BytesIO(test_content)
|
|
274
|
+
object_storage_service.upload_buffer(buffer=buffer, destination_path=source_path)
|
|
275
|
+
|
|
276
|
+
object_storage_service.copy_file(source_path=source_path, dest_path=dest_path)
|
|
277
|
+
|
|
278
|
+
assert object_storage_service.file_exists(path=source_path) is True
|
|
279
|
+
assert object_storage_service.file_exists(path=dest_path) is True
|
|
280
|
+
|
|
281
|
+
dest_content = object_storage_service.read_file(path=dest_path)
|
|
282
|
+
assert dest_content == test_content
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def test_move_file(object_storage_service: ObjectStorageService, test_dir: str) -> None:
|
|
286
|
+
"""
|
|
287
|
+
Test moving a file.
|
|
288
|
+
"""
|
|
289
|
+
test_content = fake.text().encode()
|
|
290
|
+
source_path = f"{test_dir}/move_source_{fake.file_name()}"
|
|
291
|
+
dest_path = f"{test_dir}/move_dest_{fake.file_name()}"
|
|
292
|
+
|
|
293
|
+
buffer = io.BytesIO(test_content)
|
|
294
|
+
object_storage_service.upload_buffer(buffer=buffer, destination_path=source_path)
|
|
295
|
+
|
|
296
|
+
object_storage_service.move_file(source_path=source_path, dest_path=dest_path)
|
|
297
|
+
|
|
298
|
+
assert object_storage_service.file_exists(path=source_path) is False
|
|
299
|
+
assert object_storage_service.file_exists(path=dest_path) is True
|
|
300
|
+
|
|
301
|
+
dest_content = object_storage_service.read_file(path=dest_path)
|
|
302
|
+
assert dest_content == test_content
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def test_read_nonexistent_file(
|
|
306
|
+
object_storage_service: ObjectStorageService, test_dir: str
|
|
307
|
+
) -> None:
|
|
308
|
+
"""
|
|
309
|
+
Test reading a file that doesn't exist.
|
|
310
|
+
"""
|
|
311
|
+
non_existent_path = f"{test_dir}/nonexistent_{str(fake.uuid4())}.txt"
|
|
312
|
+
|
|
313
|
+
with pytest.raises(ReadFileError):
|
|
314
|
+
object_storage_service.read_file(path=non_existent_path, max_tries=1)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def test_invalid_parameters(
|
|
318
|
+
object_storage_service: ObjectStorageService,
|
|
319
|
+
) -> None:
|
|
320
|
+
"""
|
|
321
|
+
Test operations with invalid parameters.
|
|
322
|
+
"""
|
|
323
|
+
with pytest.raises(
|
|
324
|
+
ValueError, match="Either path or bucket and key must be provided"
|
|
325
|
+
):
|
|
326
|
+
object_storage_service.read_file()
|
|
327
|
+
|
|
328
|
+
with pytest.raises(
|
|
329
|
+
ValueError,
|
|
330
|
+
match="Either destination_path or bucket and key must be provided",
|
|
331
|
+
):
|
|
332
|
+
object_storage_service.upload_buffer(buffer=io.BytesIO(b"test"))
|
|
333
|
+
|
|
334
|
+
with pytest.raises(
|
|
335
|
+
ValueError,
|
|
336
|
+
match="Either source_path or source_bucket and source_key must be provided",
|
|
337
|
+
):
|
|
338
|
+
object_storage_service.copy_file(dest_path="/tmp/test")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def test_large_file_upload_download(
|
|
342
|
+
object_storage_service: ObjectStorageService, test_dir: str
|
|
343
|
+
) -> None:
|
|
344
|
+
"""
|
|
345
|
+
Test uploading and downloading a larger file (10MB).
|
|
346
|
+
"""
|
|
347
|
+
size_mb = 10
|
|
348
|
+
test_content = fake.binary(length=size_mb * 1024 * 1024)
|
|
349
|
+
file_path = f"{test_dir}/large_{fake.file_name()}"
|
|
350
|
+
|
|
351
|
+
buffer = io.BytesIO(test_content)
|
|
352
|
+
object_storage_service.upload_buffer(buffer=buffer, destination_path=file_path)
|
|
353
|
+
|
|
354
|
+
size = object_storage_service.get_file_size(path=file_path)
|
|
355
|
+
assert size == len(test_content)
|
|
356
|
+
|
|
357
|
+
result = object_storage_service.read_file(path=file_path)
|
|
358
|
+
assert result == test_content
|
|
359
|
+
assert len(result) == size_mb * 1024 * 1024
|