kiarina-lib-google-cloud-storage 1.6.1__tar.gz → 1.6.3__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.
- kiarina_lib_google_cloud_storage-1.6.3/.env.sample +1 -0
- {kiarina_lib_google_cloud_storage-1.6.1 → kiarina_lib_google_cloud_storage-1.6.3}/.gitignore +1 -0
- {kiarina_lib_google_cloud_storage-1.6.1 → kiarina_lib_google_cloud_storage-1.6.3}/CHANGELOG.md +22 -0
- {kiarina_lib_google_cloud_storage-1.6.1 → kiarina_lib_google_cloud_storage-1.6.3}/PKG-INFO +178 -33
- {kiarina_lib_google_cloud_storage-1.6.1 → kiarina_lib_google_cloud_storage-1.6.3}/README.md +176 -31
- {kiarina_lib_google_cloud_storage-1.6.1 → kiarina_lib_google_cloud_storage-1.6.3}/pyproject.toml +2 -2
- kiarina_lib_google_cloud_storage-1.6.3/src/kiarina/lib/google/cloud_storage/_get_blob.py +90 -0
- {kiarina_lib_google_cloud_storage-1.6.1 → kiarina_lib_google_cloud_storage-1.6.3}/src/kiarina/lib/google/cloud_storage/_get_bucket.py +1 -5
- kiarina_lib_google_cloud_storage-1.6.3/src/kiarina/lib/google/cloud_storage/settings.py +19 -0
- kiarina_lib_google_cloud_storage-1.6.3/test_settings.sample.yaml +15 -0
- kiarina_lib_google_cloud_storage-1.6.3/tests/conftest.py +33 -0
- kiarina_lib_google_cloud_storage-1.6.3/tests/test_get_blob.py +82 -0
- kiarina_lib_google_cloud_storage-1.6.3/tests/test_get_bucket.py +18 -0
- kiarina_lib_google_cloud_storage-1.6.3/tests/test_get_storage_client.py +20 -0
- kiarina_lib_google_cloud_storage-1.6.1/src/kiarina/lib/google/cloud_storage/_get_blob.py +0 -28
- kiarina_lib_google_cloud_storage-1.6.1/src/kiarina/lib/google/cloud_storage/settings.py +0 -13
- kiarina_lib_google_cloud_storage-1.6.1/tests/conftest.py +0 -0
- kiarina_lib_google_cloud_storage-1.6.1/tests/test_get_blob.py +0 -175
- kiarina_lib_google_cloud_storage-1.6.1/tests/test_get_bucket.py +0 -91
- kiarina_lib_google_cloud_storage-1.6.1/tests/test_get_storage_client.py +0 -57
- {kiarina_lib_google_cloud_storage-1.6.1 → kiarina_lib_google_cloud_storage-1.6.3}/.vscode/settings.json +0 -0
- {kiarina_lib_google_cloud_storage-1.6.1 → kiarina_lib_google_cloud_storage-1.6.3}/src/kiarina/lib/google/cloud_storage/__init__.py +0 -0
- {kiarina_lib_google_cloud_storage-1.6.1 → kiarina_lib_google_cloud_storage-1.6.3}/src/kiarina/lib/google/cloud_storage/_get_storage_client.py +0 -0
- {kiarina_lib_google_cloud_storage-1.6.1 → kiarina_lib_google_cloud_storage-1.6.3}/src/kiarina/lib/google/cloud_storage/py.typed +0 -0
- {kiarina_lib_google_cloud_storage-1.6.1 → kiarina_lib_google_cloud_storage-1.6.3}/tests/__init__.py +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
KIARINA_LIB_GOOGLE_CLOUD_STORAGE_TEST_SETTINGS_FILE=packages/kiarina-lib-google-cloud-storage/test_settings.yaml
|
{kiarina_lib_google_cloud_storage-1.6.1 → kiarina_lib_google_cloud_storage-1.6.3}/CHANGELOG.md
RENAMED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [1.6.3] - 2025-10-13
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
- Converted tests from mock-based to real integration tests with multi-tenancy patterns
|
14
|
+
- Simplified test settings loading using `load_user_configs` from pydantic-settings-manager
|
15
|
+
- Updated `pydantic-settings-manager` dependency from `>=2.1.0` to `>=2.3.0`
|
16
|
+
- Refactored to use `settings_manager.get_settings` instead of deprecated `get_settings_by_key`
|
17
|
+
|
18
|
+
### Added
|
19
|
+
- Added `.env.sample` and `test_settings.sample.yaml` for easier test setup
|
20
|
+
|
21
|
+
## [1.6.2] - 2025-10-10
|
22
|
+
|
23
|
+
### Changed
|
24
|
+
- **Improved blob name handling**: Replaced `blob_name_prefix` and `blob_name` with `blob_name_pattern`
|
25
|
+
- `blob_name_pattern` supports both fixed names and template patterns with placeholders
|
26
|
+
- `get_blob()` now accepts `placeholders` parameter for pattern formatting
|
27
|
+
- `blob_name` parameter in `get_blob()` now always represents the full blob path
|
28
|
+
- Pattern examples: `"data.json"` (fixed), `"files/{basename}"` (single placeholder), `"my-service/{tenant_id}/users/{user_id}/files/{basename}"` (multiple placeholders)
|
29
|
+
- Priority: explicit `blob_name` → `blob_name_pattern` with `placeholders` → `blob_name_pattern` without placeholders
|
30
|
+
- Enhanced error messages for missing placeholders
|
31
|
+
|
10
32
|
## [1.6.1] - 2025-10-10
|
11
33
|
|
12
34
|
### Changed
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: kiarina-lib-google-cloud-storage
|
3
|
-
Version: 1.6.
|
3
|
+
Version: 1.6.3
|
4
4
|
Summary: Google Cloud Storage client library for kiarina namespace
|
5
5
|
Project-URL: Homepage, https://github.com/kiarina/kiarina-python
|
6
6
|
Project-URL: Repository, https://github.com/kiarina/kiarina-python
|
@@ -22,7 +22,7 @@ Classifier: Typing :: Typed
|
|
22
22
|
Requires-Python: >=3.12
|
23
23
|
Requires-Dist: google-cloud-storage>=2.19.0
|
24
24
|
Requires-Dist: kiarina-lib-google-auth>=1.4.0
|
25
|
-
Requires-Dist: pydantic-settings-manager>=2.
|
25
|
+
Requires-Dist: pydantic-settings-manager>=2.3.0
|
26
26
|
Requires-Dist: pydantic-settings>=2.10.1
|
27
27
|
Description-Content-Type: text/markdown
|
28
28
|
|
@@ -32,7 +32,7 @@ A Python client library for Google Cloud Storage that separates infrastructure c
|
|
32
32
|
|
33
33
|
## Design Philosophy
|
34
34
|
|
35
|
-
This library follows the principle of **separating infrastructure concerns from application logic**.
|
35
|
+
This library follows the principle of **separating infrastructure concerns from application logic**, with special emphasis on **security rule alignment** and **path structure management**.
|
36
36
|
|
37
37
|
### The Problem
|
38
38
|
|
@@ -58,17 +58,22 @@ blob = bucket.blob("v2/users/data.json") # Hard-coded path structure
|
|
58
58
|
- Hard to support multiple environments (dev, staging, production)
|
59
59
|
- Challenging to implement multi-tenancy
|
60
60
|
- Credentials management is error-prone
|
61
|
+
- **Path structures are coupled with application code, making security rule changes difficult**
|
61
62
|
|
62
|
-
### The Solution
|
63
|
+
### The Solution: Blob Name Patterns
|
63
64
|
|
64
|
-
This library externalizes all infrastructure configuration:
|
65
|
+
This library externalizes all infrastructure configuration, including path structures:
|
65
66
|
|
66
67
|
```python
|
67
|
-
# ✅ Application code only
|
68
|
+
# ✅ Application code only provides variables
|
68
69
|
from kiarina.lib.google.cloud_storage import get_blob
|
69
70
|
|
70
|
-
#
|
71
|
-
blob = get_blob(
|
71
|
+
# Path structure is managed in configuration
|
72
|
+
blob = get_blob(placeholders={
|
73
|
+
"user_id": user_id,
|
74
|
+
"agent_id": agent_id,
|
75
|
+
"basename": file_name
|
76
|
+
})
|
72
77
|
blob.upload_from_string(json.dumps(data))
|
73
78
|
```
|
74
79
|
|
@@ -78,6 +83,122 @@ blob.upload_from_string(json.dumps(data))
|
|
78
83
|
- **Multi-tenant ready**: Different configurations for different tenants
|
79
84
|
- **Secure**: Credentials managed through kiarina-lib-google-auth
|
80
85
|
- **Maintainable**: Infrastructure changes don't require code changes
|
86
|
+
- **Security-aligned**: Path structures match GCS security rules, managed together
|
87
|
+
|
88
|
+
### Why Blob Name Patterns Matter
|
89
|
+
|
90
|
+
**The Core Problem**: GCS security rules define path structures, and application code must align with them.
|
91
|
+
|
92
|
+
#### Without Blob Name Patterns (Tight Coupling)
|
93
|
+
|
94
|
+
```python
|
95
|
+
# Application code constructs paths
|
96
|
+
blob_name = f"users/{user_id}/files/{file_name}"
|
97
|
+
blob = get_blob(blob_name=blob_name)
|
98
|
+
```
|
99
|
+
|
100
|
+
**GCS Security Rules:**
|
101
|
+
```javascript
|
102
|
+
rules_version = '2';
|
103
|
+
service firebase.storage {
|
104
|
+
match /b/{bucket}/o {
|
105
|
+
match /users/{user_id}/files/{basename} {
|
106
|
+
allow read, write: if request.auth.uid == user_id;
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
```
|
111
|
+
|
112
|
+
**What happens when security requirements change?**
|
113
|
+
|
114
|
+
New requirement: Add agent-level isolation for multi-tenancy.
|
115
|
+
|
116
|
+
**Updated Security Rules:**
|
117
|
+
```javascript
|
118
|
+
match /my-service/{tenant_id}/users/{user_id}/files/{basename} {
|
119
|
+
allow read, write: if request.auth.uid == user_id
|
120
|
+
&& request.auth.token.tenant_id == tenant_id;
|
121
|
+
}
|
122
|
+
```
|
123
|
+
|
124
|
+
**Problem**: You must now update **every place** in your application code that constructs these paths:
|
125
|
+
```python
|
126
|
+
# Must change all of these
|
127
|
+
blob_name = f"my-service/{tenant_id}/users/{user_id}/files/{file_name}" # Changed!
|
128
|
+
blob_name = f"my-service/{tenant_id}/users/{user_id}/thumbnails/{file_name}" # Changed!
|
129
|
+
blob_name = f"my-service/{tenant_id}/users/{user_id}/exports/{file_name}" # Changed!
|
130
|
+
# ... and many more
|
131
|
+
```
|
132
|
+
|
133
|
+
#### With Blob Name Patterns (Loose Coupling)
|
134
|
+
|
135
|
+
**Configuration (Infrastructure Concern):**
|
136
|
+
```yaml
|
137
|
+
# config/production.yaml
|
138
|
+
google_cloud_storage:
|
139
|
+
default:
|
140
|
+
bucket_name: "my-app-data"
|
141
|
+
blob_name_pattern: "my-service/{tenant_id}/users/{user_id}/files/{basename}"
|
142
|
+
```
|
143
|
+
|
144
|
+
**GCS Security Rules (Infrastructure Concern):**
|
145
|
+
```javascript
|
146
|
+
match /my-service/{tenant_id}/users/{user_id}/files/{basename} {
|
147
|
+
allow read, write: if request.auth.uid == user_id
|
148
|
+
&& request.auth.token.tenant_id == tenant_id;
|
149
|
+
}
|
150
|
+
```
|
151
|
+
|
152
|
+
**Application Code (Business Logic):**
|
153
|
+
```python
|
154
|
+
# Application only provides variables - no path knowledge
|
155
|
+
blob = get_blob(placeholders={
|
156
|
+
"user_id": current_user.id,
|
157
|
+
"agent_id": current_agent.id,
|
158
|
+
"basename": uploaded_file.name
|
159
|
+
})
|
160
|
+
blob.upload_from_string(file_content)
|
161
|
+
```
|
162
|
+
|
163
|
+
**When security rules change**: Only update the configuration file. Application code remains unchanged.
|
164
|
+
|
165
|
+
#### Real-World Example: Multi-Environment Security
|
166
|
+
|
167
|
+
Different environments often have different security requirements:
|
168
|
+
|
169
|
+
**Production** (strict isolation):
|
170
|
+
```yaml
|
171
|
+
blob_name_pattern: "v2/production/{tenant_id}/{user_id}/{agent_id}/files/{basename}"
|
172
|
+
```
|
173
|
+
|
174
|
+
**Staging** (relaxed for testing):
|
175
|
+
```yaml
|
176
|
+
blob_name_pattern: "v2/staging/{user_id}/files/{basename}"
|
177
|
+
```
|
178
|
+
|
179
|
+
**Development** (flat structure):
|
180
|
+
```yaml
|
181
|
+
blob_name_pattern: "dev/{basename}"
|
182
|
+
```
|
183
|
+
|
184
|
+
**Application code** (same for all environments):
|
185
|
+
```python
|
186
|
+
blob = get_blob(placeholders={
|
187
|
+
"tenant_id": tenant.id,
|
188
|
+
"user_id": user.id,
|
189
|
+
"agent_id": agent.id,
|
190
|
+
"basename": file.name
|
191
|
+
})
|
192
|
+
```
|
193
|
+
|
194
|
+
Missing placeholders are simply ignored if not present in the pattern.
|
195
|
+
|
196
|
+
### Design Principles
|
197
|
+
|
198
|
+
1. **Infrastructure defines "where"**: Path structures, bucket names, security rules
|
199
|
+
2. **Application defines "what"**: Data content, business logic, variables
|
200
|
+
3. **Configuration is the contract**: Placeholders define the interface between infrastructure and application
|
201
|
+
4. **Security rules and path patterns are managed together**: Both are infrastructure concerns
|
81
202
|
|
82
203
|
## Features
|
83
204
|
|
@@ -106,14 +227,17 @@ from kiarina.lib.google.cloud_storage import get_blob, settings_manager
|
|
106
227
|
settings_manager.user_config = {
|
107
228
|
"default": {
|
108
229
|
"bucket_name": "my-app-data",
|
109
|
-
"
|
230
|
+
"blob_name_pattern": "production/v1/{basename}"
|
110
231
|
}
|
111
232
|
}
|
112
233
|
|
113
234
|
# Application code - clean and simple
|
114
|
-
blob = get_blob(
|
235
|
+
blob = get_blob(placeholders={"basename": "user_data.json"})
|
115
236
|
# Actual path: gs://my-app-data/production/v1/user_data.json
|
116
237
|
|
238
|
+
# Or use direct blob name (full path)
|
239
|
+
blob = get_blob(blob_name="production/v1/user_data.json")
|
240
|
+
|
117
241
|
# Use native google-cloud-storage API
|
118
242
|
blob.upload_from_string("Hello, World!")
|
119
243
|
content = blob.download_as_text()
|
@@ -244,14 +368,14 @@ def save_document(tenant_id: str, doc_id: str, content: bytes):
|
|
244
368
|
def list_documents(tenant_id: str) -> list[str]:
|
245
369
|
"""List documents for any tenant"""
|
246
370
|
from kiarina.lib.google.cloud_storage import get_bucket
|
247
|
-
|
371
|
+
|
248
372
|
config_key = f"tenant_{tenant_id}"
|
249
373
|
bucket = get_bucket(config_key=config_key, auth_config_key=config_key)
|
250
|
-
|
374
|
+
|
251
375
|
# Get prefix from settings
|
252
|
-
settings = settings_manager.
|
376
|
+
settings = settings_manager.get_settings(config_key)
|
253
377
|
prefix = f"{settings.blob_name_prefix}/documents/" if settings.blob_name_prefix else "documents/"
|
254
|
-
|
378
|
+
|
255
379
|
blobs = bucket.list_blobs(prefix=prefix)
|
256
380
|
return [blob.name for blob in blobs]
|
257
381
|
```
|
@@ -285,10 +409,10 @@ def mock_storage_config():
|
|
285
409
|
def test_save_user_profile(mock_storage_config):
|
286
410
|
"""Test user profile saving"""
|
287
411
|
from myapp.services import save_user_profile
|
288
|
-
|
412
|
+
|
289
413
|
# Application code uses test configuration automatically
|
290
414
|
save_user_profile("user123", {"name": "Alice"})
|
291
|
-
|
415
|
+
|
292
416
|
# Verify using the same configuration
|
293
417
|
from kiarina.lib.google.cloud_storage import get_blob
|
294
418
|
blob = get_blob(blob_name="users/user123/profile.json")
|
@@ -304,20 +428,20 @@ from kiarina.lib.google.cloud_storage import settings_manager
|
|
304
428
|
|
305
429
|
def debug_storage_config(config_key: str | None = None):
|
306
430
|
"""Show actual storage paths for debugging"""
|
307
|
-
settings = settings_manager.
|
308
|
-
|
431
|
+
settings = settings_manager.get_settings(config_key)
|
432
|
+
|
309
433
|
print(f"Configuration: {config_key or 'default'}")
|
310
434
|
print(f" Bucket: {settings.bucket_name}")
|
311
435
|
print(f" Prefix: {settings.blob_name_prefix or '(none)'}")
|
312
436
|
print(f" Auth: {settings.google_auth_config_key}")
|
313
|
-
|
437
|
+
|
314
438
|
# Example paths
|
315
439
|
example_blob = "users/123/profile.json"
|
316
440
|
if settings.blob_name_prefix:
|
317
441
|
full_path = f"{settings.blob_name_prefix}/{example_blob}"
|
318
442
|
else:
|
319
443
|
full_path = example_blob
|
320
|
-
|
444
|
+
|
321
445
|
print(f" Example: gs://{settings.bucket_name}/{full_path}")
|
322
446
|
|
323
447
|
# Usage
|
@@ -339,8 +463,7 @@ This library uses [pydantic-settings-manager](https://github.com/kiarina/pydanti
|
|
339
463
|
| Field | Type | Required | Description |
|
340
464
|
|-------|------|----------|-------------|
|
341
465
|
| `bucket_name` | `str \| None` | Yes* | Google Cloud Storage bucket name |
|
342
|
-
| `
|
343
|
-
| `blob_name` | `str \| None` | No | Default blob name (rarely used) |
|
466
|
+
| `blob_name_pattern` | `str \| None` | No | Blob name pattern with placeholders (e.g., "users/{user_id}/files/{basename}") |
|
344
467
|
|
345
468
|
*Required when using `get_bucket()` or `get_blob()`
|
346
469
|
|
@@ -501,6 +624,7 @@ Get a Google Cloud Storage blob.
|
|
501
624
|
def get_blob(
|
502
625
|
blob_name: str | None = None,
|
503
626
|
*,
|
627
|
+
placeholders: dict[str, Any] | None = None,
|
504
628
|
config_key: str | None = None,
|
505
629
|
auth_config_key: str | None = None,
|
506
630
|
**kwargs: Any
|
@@ -508,7 +632,8 @@ def get_blob(
|
|
508
632
|
```
|
509
633
|
|
510
634
|
**Parameters:**
|
511
|
-
- `blob_name`:
|
635
|
+
- `blob_name`: Full blob name (path). If provided, this takes precedence.
|
636
|
+
- `placeholders`: Placeholders for blob_name_pattern formatting.
|
512
637
|
- `config_key`: Configuration key for storage settings (default: None uses active key)
|
513
638
|
- `auth_config_key`: Configuration key for authentication (default: None uses active key)
|
514
639
|
- `**kwargs`: Additional arguments passed to `get_bucket()`
|
@@ -517,21 +642,41 @@ def get_blob(
|
|
517
642
|
- `storage.Blob`: Google Cloud Storage blob
|
518
643
|
|
519
644
|
**Raises:**
|
520
|
-
- `ValueError`: If
|
645
|
+
- `ValueError`: If blob_name cannot be determined or pattern formatting fails
|
646
|
+
|
647
|
+
**Priority:**
|
648
|
+
1. Explicit `blob_name` parameter (full path)
|
649
|
+
2. `blob_name_pattern` with `placeholders`
|
650
|
+
3. `blob_name_pattern` without placeholders (fixed name)
|
521
651
|
|
522
652
|
**Example:**
|
523
653
|
```python
|
524
|
-
#
|
525
|
-
blob = get_blob(blob_name="data.json")
|
526
|
-
|
527
|
-
#
|
528
|
-
# If
|
529
|
-
|
530
|
-
|
654
|
+
# Direct blob name (full path)
|
655
|
+
blob = get_blob(blob_name="production/v1/data.json")
|
656
|
+
|
657
|
+
# Using pattern with placeholders
|
658
|
+
# If blob_name_pattern="users/{user_id}/files/{basename}"
|
659
|
+
blob = get_blob(placeholders={"user_id": "123", "basename": "profile.json"})
|
660
|
+
# Actual: gs://bucket/users/123/files/profile.json
|
661
|
+
|
662
|
+
# Using fixed pattern from settings
|
663
|
+
# If blob_name_pattern="data/fixed.json"
|
664
|
+
blob = get_blob()
|
665
|
+
# Actual: gs://bucket/data/fixed.json
|
666
|
+
|
667
|
+
# Complex pattern
|
668
|
+
# If blob_name_pattern="my-service/{tenant_id}/users/{user_id}/files/{basename}"
|
669
|
+
blob = get_blob(placeholders={
|
670
|
+
"tenant_id": "tenant123",
|
671
|
+
"user_id": "user123",
|
672
|
+
"agent_id": "agent456",
|
673
|
+
"basename": "document.pdf"
|
674
|
+
})
|
675
|
+
# Actual: gs://bucket/my-service/tenant123/users/user123/files/document.pdf
|
531
676
|
|
532
677
|
# With custom configurations
|
533
678
|
blob = get_blob(
|
534
|
-
|
679
|
+
placeholders={"basename": "data.json"},
|
535
680
|
config_key="production",
|
536
681
|
auth_config_key="prod_auth"
|
537
682
|
)
|
@@ -556,7 +701,7 @@ settings_manager: SettingsManager[GoogleCloudStorageSettings]
|
|
556
701
|
- `active_key`: Get/set active configuration key
|
557
702
|
|
558
703
|
**Methods:**
|
559
|
-
- `
|
704
|
+
- `get_settings(key: str)`: Get settings by specific key
|
560
705
|
- `clear()`: Clear cached settings
|
561
706
|
|
562
707
|
## Common Operations
|