lyzr-cloudrift 0.1.0__tar.gz
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.
- lyzr_cloudrift-0.1.0/.claude/settings.local.json +9 -0
- lyzr_cloudrift-0.1.0/.github/workflows/develop.yml +34 -0
- lyzr_cloudrift-0.1.0/.github/workflows/publish.yml +42 -0
- lyzr_cloudrift-0.1.0/LICENSE +21 -0
- lyzr_cloudrift-0.1.0/PKG-INFO +362 -0
- lyzr_cloudrift-0.1.0/README.md +292 -0
- lyzr_cloudrift-0.1.0/cloudrift/__init__.py +16 -0
- lyzr_cloudrift-0.1.0/cloudrift/cache/__init__.py +43 -0
- lyzr_cloudrift-0.1.0/cloudrift/cache/base.py +270 -0
- lyzr_cloudrift-0.1.0/cloudrift/cache/redis_azure.py +162 -0
- lyzr_cloudrift-0.1.0/cloudrift/cache/redis_elasticache.py +229 -0
- lyzr_cloudrift-0.1.0/cloudrift/cache/redis_standalone.py +118 -0
- lyzr_cloudrift-0.1.0/cloudrift/core/__init__.py +0 -0
- lyzr_cloudrift-0.1.0/cloudrift/core/exceptions.py +84 -0
- lyzr_cloudrift-0.1.0/cloudrift/document/__init__.py +44 -0
- lyzr_cloudrift-0.1.0/cloudrift/document/base.py +86 -0
- lyzr_cloudrift-0.1.0/cloudrift/document/cosmos.py +371 -0
- lyzr_cloudrift-0.1.0/cloudrift/document/documentdb.py +242 -0
- lyzr_cloudrift-0.1.0/cloudrift/messaging/__init__.py +43 -0
- lyzr_cloudrift-0.1.0/cloudrift/messaging/azure_bus.py +263 -0
- lyzr_cloudrift-0.1.0/cloudrift/messaging/base.py +51 -0
- lyzr_cloudrift-0.1.0/cloudrift/messaging/sqs.py +206 -0
- lyzr_cloudrift-0.1.0/cloudrift/pubsub/__init__.py +46 -0
- lyzr_cloudrift-0.1.0/cloudrift/pubsub/azure_eventgrid.py +151 -0
- lyzr_cloudrift-0.1.0/cloudrift/pubsub/base.py +42 -0
- lyzr_cloudrift-0.1.0/cloudrift/pubsub/sns.py +185 -0
- lyzr_cloudrift-0.1.0/cloudrift/secrets/__init__.py +45 -0
- lyzr_cloudrift-0.1.0/cloudrift/secrets/aws_secrets_manager.py +180 -0
- lyzr_cloudrift-0.1.0/cloudrift/secrets/azure_keyvault.py +119 -0
- lyzr_cloudrift-0.1.0/cloudrift/secrets/base.py +46 -0
- lyzr_cloudrift-0.1.0/cloudrift/storage/__init__.py +48 -0
- lyzr_cloudrift-0.1.0/cloudrift/storage/azure_blob.py +255 -0
- lyzr_cloudrift-0.1.0/cloudrift/storage/base.py +76 -0
- lyzr_cloudrift-0.1.0/cloudrift/storage/s3.py +234 -0
- lyzr_cloudrift-0.1.0/pyproject.toml +81 -0
- lyzr_cloudrift-0.1.0/tests/__init__.py +0 -0
- lyzr_cloudrift-0.1.0/tests/test_cache.py +202 -0
- lyzr_cloudrift-0.1.0/tests/test_document.py +122 -0
- lyzr_cloudrift-0.1.0/tests/test_messaging.py +81 -0
- lyzr_cloudrift-0.1.0/tests/test_pubsub.py +101 -0
- lyzr_cloudrift-0.1.0/tests/test_secrets.py +111 -0
- lyzr_cloudrift-0.1.0/tests/test_storage.py +144 -0
- lyzr_cloudrift-0.1.0/uv.lock +2666 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Develop — Test & Publish to TestPyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["develop"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test-and-publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- uses: astral-sh/setup-uv@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.11"
|
|
16
|
+
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
run: uv sync --extra dev
|
|
19
|
+
|
|
20
|
+
- name: Run tests
|
|
21
|
+
run: uv run pytest tests/ -v
|
|
22
|
+
|
|
23
|
+
- name: Set dev version
|
|
24
|
+
run: |
|
|
25
|
+
VERSION=$(grep '^version' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
|
|
26
|
+
sed -i "s/version = \"${VERSION}\"/version = \"${VERSION}.dev${{ github.run_number }}\"/" pyproject.toml
|
|
27
|
+
|
|
28
|
+
- name: Build
|
|
29
|
+
run: uv build
|
|
30
|
+
|
|
31
|
+
- name: Publish to TestPyPI
|
|
32
|
+
run: uv publish --publish-url https://test.pypi.org/legacy/
|
|
33
|
+
env:
|
|
34
|
+
UV_PUBLISH_TOKEN: ${{ secrets.TEST_PYPI_TOKEN }}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Main — Test & Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- uses: astral-sh/setup-uv@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.11"
|
|
16
|
+
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
run: uv sync --extra dev
|
|
19
|
+
|
|
20
|
+
- name: Run tests
|
|
21
|
+
run: uv run pytest tests/ -v
|
|
22
|
+
|
|
23
|
+
publish:
|
|
24
|
+
needs: test
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
environment: pypi
|
|
27
|
+
permissions:
|
|
28
|
+
id-token: write
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
|
|
32
|
+
- uses: astral-sh/setup-uv@v5
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.11"
|
|
35
|
+
|
|
36
|
+
- name: Build
|
|
37
|
+
run: uv build
|
|
38
|
+
|
|
39
|
+
- name: Publish to PyPI
|
|
40
|
+
run: uv publish
|
|
41
|
+
env:
|
|
42
|
+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lyzr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lyzr-cloudrift
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cloud-agnostic abstraction for storage, messaging, document databases, cache, secrets, and pub/sub
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 Lyzr
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Keywords: aws,azure,blob,cache,cloud,cosmosdb,documentdb,elasticache,eventgrid,keyvault,pubsub,redis,s3,secrets,servicebus,sns,sqs
|
|
28
|
+
Classifier: Intended Audience :: Developers
|
|
29
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
30
|
+
Classifier: Programming Language :: Python :: 3
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
33
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
34
|
+
Requires-Python: >=3.11
|
|
35
|
+
Provides-Extra: all
|
|
36
|
+
Requires-Dist: aioboto3>=13.0.0; extra == 'all'
|
|
37
|
+
Requires-Dist: azure-cosmos>=4.7.0; extra == 'all'
|
|
38
|
+
Requires-Dist: azure-eventgrid>=4.9.0; extra == 'all'
|
|
39
|
+
Requires-Dist: azure-identity>=1.15.0; extra == 'all'
|
|
40
|
+
Requires-Dist: azure-keyvault-secrets>=4.7.0; extra == 'all'
|
|
41
|
+
Requires-Dist: azure-servicebus>=7.11.0; extra == 'all'
|
|
42
|
+
Requires-Dist: azure-storage-blob>=12.19.0; extra == 'all'
|
|
43
|
+
Requires-Dist: motor>=3.3.0; extra == 'all'
|
|
44
|
+
Requires-Dist: redis[hiredis]>=5.0.0; extra == 'all'
|
|
45
|
+
Provides-Extra: aws
|
|
46
|
+
Requires-Dist: aioboto3>=13.0.0; extra == 'aws'
|
|
47
|
+
Requires-Dist: motor>=3.3.0; extra == 'aws'
|
|
48
|
+
Requires-Dist: redis[hiredis]>=5.0.0; extra == 'aws'
|
|
49
|
+
Provides-Extra: azure
|
|
50
|
+
Requires-Dist: azure-cosmos>=4.7.0; extra == 'azure'
|
|
51
|
+
Requires-Dist: azure-eventgrid>=4.9.0; extra == 'azure'
|
|
52
|
+
Requires-Dist: azure-identity>=1.15.0; extra == 'azure'
|
|
53
|
+
Requires-Dist: azure-keyvault-secrets>=4.7.0; extra == 'azure'
|
|
54
|
+
Requires-Dist: azure-servicebus>=7.11.0; extra == 'azure'
|
|
55
|
+
Requires-Dist: azure-storage-blob>=12.19.0; extra == 'azure'
|
|
56
|
+
Requires-Dist: motor>=3.3.0; extra == 'azure'
|
|
57
|
+
Requires-Dist: redis[hiredis]>=5.0.0; extra == 'azure'
|
|
58
|
+
Provides-Extra: cache
|
|
59
|
+
Requires-Dist: redis[hiredis]>=5.0.0; extra == 'cache'
|
|
60
|
+
Provides-Extra: dev
|
|
61
|
+
Requires-Dist: aioboto3>=13.0.0; extra == 'dev'
|
|
62
|
+
Requires-Dist: fakeredis>=2.20.0; extra == 'dev'
|
|
63
|
+
Requires-Dist: httpx>=0.25.0; extra == 'dev'
|
|
64
|
+
Requires-Dist: mongomock-motor>=0.0.21; extra == 'dev'
|
|
65
|
+
Requires-Dist: moto[s3,secretsmanager,server,sns,sqs]>=5.0; extra == 'dev'
|
|
66
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
67
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
68
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
69
|
+
Description-Content-Type: text/markdown
|
|
70
|
+
|
|
71
|
+
# cloudrift
|
|
72
|
+
|
|
73
|
+
Cloud-agnostic abstraction for **storage**, **messaging**, **document databases**, and **cache** — built for Lyzr microservices.
|
|
74
|
+
|
|
75
|
+
- **Async-first.** Every public method is `async def`. All four categories use native-async SDK clients (`aioboto3`, `azure.*.aio`, `motor`, `redis.asyncio`) — no thread-pool wrapping.
|
|
76
|
+
- **Drop-in providers.** Same interface across AWS, Azure, and self-hosted backends. Swap `s3` ↔ `azure_blob` (or `sqs` ↔ `azure_bus`, `documentdb` ↔ `cosmos`, `redis` ↔ `elasticache` ↔ `azure_redis`) by changing one string.
|
|
77
|
+
- **Multiple auth methods per provider.** Static keys, IAM roles, profiles, managed identity, service principals, SAS tokens, mTLS, IAM auth — pick what your microservice already has.
|
|
78
|
+
|
|
79
|
+
| Category | AWS | Azure | Self-hosted |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| Storage | S3 | Blob Storage | — |
|
|
82
|
+
| Messaging | SQS | Service Bus | — |
|
|
83
|
+
| Document DB | DocumentDB | Cosmos DB (Core/SQL) | — |
|
|
84
|
+
| Cache | ElastiCache | Azure Cache for Redis | Redis |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Install
|
|
89
|
+
|
|
90
|
+
Pick the extras your service needs:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install "cloudrift[aws]" # S3 + SQS + DocumentDB + Redis client
|
|
94
|
+
pip install "cloudrift[azure]" # Blob + Service Bus + Cosmos + Redis client
|
|
95
|
+
pip install "cloudrift[cache]" # Just Redis (any flavour)
|
|
96
|
+
pip install "cloudrift[all]" # Everything
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Python 3.11+.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Quick start
|
|
104
|
+
|
|
105
|
+
Every backend is constructed via a factory function and held for the lifetime of the service. Reuse one instance per resource — the underlying client is connection-pooled.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from cloudrift.storage import get_storage
|
|
109
|
+
|
|
110
|
+
# Construct once at startup
|
|
111
|
+
storage = get_storage(
|
|
112
|
+
"s3",
|
|
113
|
+
bucket="my-bucket",
|
|
114
|
+
aws_access_key_id="AKIA...",
|
|
115
|
+
aws_secret_access_key="...",
|
|
116
|
+
region="us-east-1",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Use anywhere
|
|
120
|
+
await storage.upload("docs/hello.txt", b"hello world", content_type="text/plain")
|
|
121
|
+
data = await storage.download("docs/hello.txt")
|
|
122
|
+
url = await storage.presigned_url("docs/hello.txt", expires_in=3600)
|
|
123
|
+
|
|
124
|
+
# Release sockets at shutdown
|
|
125
|
+
await storage.close()
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Or as an async context manager (auto-close):
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
async with get_storage("s3", bucket="b", region="us-east-1") as storage:
|
|
132
|
+
await storage.upload("k", b"v")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Microservice integration
|
|
138
|
+
|
|
139
|
+
### Configuration via env vars
|
|
140
|
+
|
|
141
|
+
Pick the provider per environment with a single env var:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import os
|
|
145
|
+
from cloudrift.storage import get_storage
|
|
146
|
+
|
|
147
|
+
storage = get_storage(
|
|
148
|
+
os.environ["STORAGE_PROVIDER"], # "s3" in prod, "azure_blob" in dev
|
|
149
|
+
**{
|
|
150
|
+
k.lower().removeprefix("storage_"): v
|
|
151
|
+
for k, v in os.environ.items()
|
|
152
|
+
if k.startswith("STORAGE_") and k != "STORAGE_PROVIDER"
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Storage
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from cloudrift.storage import get_storage
|
|
163
|
+
|
|
164
|
+
# AWS S3
|
|
165
|
+
s3 = get_storage("s3", bucket="b", region="us-east-1") # IAM role
|
|
166
|
+
s3 = get_storage("s3", bucket="b", aws_access_key_id="...", # static keys
|
|
167
|
+
aws_secret_access_key="...", region="us-east-1")
|
|
168
|
+
s3 = get_storage("s3", bucket="b", profile_name="dev") # ~/.aws/credentials
|
|
169
|
+
|
|
170
|
+
# Azure Blob
|
|
171
|
+
blob = get_storage("azure_blob", connection_string="...", container="c")
|
|
172
|
+
blob = get_storage("azure_blob", account_url="https://acct.blob.core.windows.net",
|
|
173
|
+
account_key="...", container="c")
|
|
174
|
+
blob = get_storage("azure_blob", account_url="...", sas_token="...", container="c")
|
|
175
|
+
blob = get_storage("azure_blob", account_url="...", container="c") # managed identity
|
|
176
|
+
blob = get_storage("azure_blob", account_url="...", container="c",
|
|
177
|
+
tenant_id="...", client_id="...", client_secret="...") # service principal
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Operations** — same on every backend:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
await storage.upload(key, data, content_type="application/json")
|
|
184
|
+
data: bytes = await storage.download(key)
|
|
185
|
+
await storage.delete(key)
|
|
186
|
+
exists: bool = await storage.exists(key)
|
|
187
|
+
keys: list[str] = await storage.list(prefix="logs/")
|
|
188
|
+
url: str = await storage.presigned_url(key, expires_in=3600)
|
|
189
|
+
await storage.close()
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Messaging
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from cloudrift.messaging import get_queue
|
|
198
|
+
|
|
199
|
+
# AWS SQS
|
|
200
|
+
sqs = get_queue("sqs", queue_url="https://sqs.us-east-1.amazonaws.com/.../q",
|
|
201
|
+
region="us-east-1")
|
|
202
|
+
|
|
203
|
+
# Azure Service Bus
|
|
204
|
+
bus = get_queue("azure_bus", connection_string="...", queue_name="my-queue")
|
|
205
|
+
bus = get_queue("azure_bus", fully_qualified_namespace="ns.servicebus.windows.net",
|
|
206
|
+
queue_name="my-queue") # managed identity
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Operations**:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
msg_id = await queue.send({"action": "process", "id": 42}, delay=0)
|
|
213
|
+
ids = await queue.send_batch([{"n": 1}, {"n": 2}])
|
|
214
|
+
|
|
215
|
+
messages = await queue.receive(max_messages=10, wait_time=20) # long-poll
|
|
216
|
+
for m in messages:
|
|
217
|
+
handle_job(m.body)
|
|
218
|
+
await queue.delete(m.receipt_handle) # ack (SQS only — see below)
|
|
219
|
+
|
|
220
|
+
await queue.purge()
|
|
221
|
+
await queue.close()
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
> **Azure Service Bus note:** `delete(receipt_handle)` raises `NotImplementedError` because Service Bus completes messages via the receiver's lock token, not by handle. Until the abstraction is reworked, complete messages inside a custom receiver loop using `azure-servicebus` directly, or use the `purge()` helper.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Document Database
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from cloudrift.document import get_mongodb
|
|
232
|
+
|
|
233
|
+
# AWS DocumentDB (MongoDB-compatible)
|
|
234
|
+
db = get_mongodb(
|
|
235
|
+
"documentdb",
|
|
236
|
+
uri="mongodb://user:pass@cluster.docdb.amazonaws.com:27017/?tls=true",
|
|
237
|
+
database="lyzr",
|
|
238
|
+
tls_ca_file="/etc/ssl/rds-ca-bundle.pem",
|
|
239
|
+
max_pool_size=200,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Azure Cosmos DB (Core/SQL API)
|
|
243
|
+
cdb = get_mongodb("cosmos", connection_string="...", database="lyzr")
|
|
244
|
+
cdb = get_mongodb("cosmos", url="https://acct.documents.azure.com:443/",
|
|
245
|
+
account_key="...", database="lyzr")
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Operations** (MongoDB-style on both):
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
doc_id = await db.insert_one("users", {"name": "Alice", "age": 30})
|
|
252
|
+
ids = await db.insert_many("events", [{"v": 1}, {"v": 2}])
|
|
253
|
+
|
|
254
|
+
doc = await db.find_one("users", {"name": "Alice"})
|
|
255
|
+
docs = await db.find("events", {"v": {"$gte": 1}}, limit=100, skip=0)
|
|
256
|
+
|
|
257
|
+
modified = await db.update_one("users", {"_id": doc_id}, {"$set": {"age": 31}})
|
|
258
|
+
deleted = await db.delete_many("events", {"v": 1})
|
|
259
|
+
total = await db.count("users", {"age": {"$gte": 18}})
|
|
260
|
+
|
|
261
|
+
await db.close()
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Cache
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
from cloudrift.cache import get_cache
|
|
270
|
+
|
|
271
|
+
# Self-hosted Redis
|
|
272
|
+
cache = get_cache("redis", "from_url", url="redis://localhost:6379/0")
|
|
273
|
+
cache = get_cache("redis", "from_credentials",
|
|
274
|
+
host="redis.internal", port=6379, password="...", db=0)
|
|
275
|
+
|
|
276
|
+
# AWS ElastiCache
|
|
277
|
+
cache = get_cache("elasticache", "from_auth_token",
|
|
278
|
+
host="my-cluster.cache.amazonaws.com", auth_token="...")
|
|
279
|
+
cache = get_cache("elasticache", "from_iam_auth",
|
|
280
|
+
host="my-cluster.cache.amazonaws.com",
|
|
281
|
+
username="lyzr-app", region="us-east-1") # SigV4 + auto-refresh
|
|
282
|
+
|
|
283
|
+
# Azure Cache for Redis
|
|
284
|
+
cache = get_cache("azure_redis", "from_access_key",
|
|
285
|
+
host="my-cache.redis.cache.windows.net", access_key="...")
|
|
286
|
+
cache = get_cache("azure_redis", "from_managed_identity",
|
|
287
|
+
host="my-cache.redis.cache.windows.net", username="lyzr-app")
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Operations** — KV, hash, list, counters:
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
await cache.set("session:abc", b"data", ttl=3600)
|
|
294
|
+
value: bytes | None = await cache.get("session:abc")
|
|
295
|
+
await cache.delete("session:abc")
|
|
296
|
+
|
|
297
|
+
await cache.hset("user:1", "name", "Alice")
|
|
298
|
+
fields = await cache.hgetall("user:1")
|
|
299
|
+
|
|
300
|
+
await cache.lpush("jobs", "job-1", "job-2")
|
|
301
|
+
batch = await cache.lrange("jobs", 0, 99)
|
|
302
|
+
|
|
303
|
+
count = await cache.incr("hits:home")
|
|
304
|
+
ok = await cache.ping()
|
|
305
|
+
await cache.close()
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Connection pooling & lifecycle
|
|
311
|
+
|
|
312
|
+
Every backend holds **one long-lived async client** that is reused across all operations. This is the single biggest perf knob:
|
|
313
|
+
|
|
314
|
+
- **Don't** call `get_storage(...)` inside a request handler.
|
|
315
|
+
- **Do** construct it once at app startup and share it (e.g. `app.state.storage`, FastAPI dependency, or module-level singleton).
|
|
316
|
+
|
|
317
|
+
Pool sizes are configurable per backend:
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
get_storage("s3", bucket="b", region="us-east-1",
|
|
321
|
+
max_pool_connections=100, connect_timeout=5.0, read_timeout=30.0)
|
|
322
|
+
|
|
323
|
+
get_mongodb("documentdb", uri="...", database="db",
|
|
324
|
+
max_pool_size=200, min_pool_size=10)
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Always release sockets on shutdown with `await backend.close()` — or wrap the whole lifetime in `async with`.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Errors
|
|
332
|
+
|
|
333
|
+
All backends raise from a single hierarchy under `cloudrift.core.exceptions`:
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
from cloudrift.core.exceptions import (
|
|
337
|
+
ObjectNotFoundError, StoragePermissionError, StorageError,
|
|
338
|
+
QueueNotFoundError, MessageSendError, MessagingError,
|
|
339
|
+
DocumentNotFoundError, DocumentConnectionError, DocumentError,
|
|
340
|
+
CacheKeyNotFoundError, CacheConnectionError, CacheError,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
await storage.download("missing.txt")
|
|
345
|
+
except ObjectNotFoundError:
|
|
346
|
+
...
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Provider-specific exceptions (e.g. `botocore.ClientError`, `azure.core.exceptions.HttpResponseError`) are translated to the cloudrift hierarchy at the boundary.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Testing
|
|
354
|
+
|
|
355
|
+
The dev extra ships moto + a Motor mock so unit tests don't need real cloud credentials:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
pip install "cloudrift[dev]"
|
|
359
|
+
pytest
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
For local integration testing of the AWS backends, the suite uses `ThreadedMotoServer` (LocalStack-style in-process mock) — see `tests/test_storage.py` for the pattern. Azure backends are tested against Azurite / Service Bus / Cosmos emulators (configure endpoint via the relevant `*_url` kwarg).
|