kiarina-lib-google-cloud-storage 1.6.0__py3-none-any.whl → 1.6.2__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.
@@ -9,20 +9,70 @@ from .settings import settings_manager
9
9
  def get_blob(
10
10
  blob_name: str | None = None,
11
11
  *,
12
+ placeholders: dict[str, Any] | None = None,
12
13
  config_key: str | None = None,
13
14
  auth_config_key: str | None = None,
14
15
  **kwargs: Any,
15
16
  ) -> storage.Blob:
17
+ """
18
+ Get a Google Cloud Storage blob.
19
+
20
+ Args:
21
+ blob_name: Full blob name (path). If provided, this takes precedence.
22
+ placeholders: Placeholders for blob_name_pattern formatting.
23
+ config_key: Configuration key for storage settings.
24
+ auth_config_key: Configuration key for authentication.
25
+ **kwargs: Additional arguments passed to get_bucket().
26
+
27
+ Returns:
28
+ Google Cloud Storage blob.
29
+
30
+ Raises:
31
+ ValueError: If blob_name cannot be determined or pattern formatting fails.
32
+
33
+ Examples:
34
+ # Direct blob name
35
+ blob = get_blob(blob_name="data/file.json")
36
+
37
+ # Using pattern with placeholders
38
+ blob = get_blob(placeholders={"user_id": "123", "basename": "profile.json"})
39
+ # With pattern "users/{user_id}/{basename}" -> "users/123/profile.json"
40
+
41
+ # Using default pattern from settings
42
+ blob = get_blob() # Uses settings.blob_name_pattern without placeholders
43
+ """
16
44
  settings = settings_manager.get_settings_by_key(config_key)
17
45
 
18
- if blob_name is None and settings.blob_name is None:
19
- raise ValueError("blob_name is not set in the settings and not provided")
46
+ # Priority 1: Explicit blob_name
47
+ if blob_name is not None:
48
+ final_blob_name = blob_name
49
+
50
+ # Priority 2: Pattern with placeholders
51
+ elif placeholders is not None:
52
+ if settings.blob_name_pattern is None:
53
+ raise ValueError(
54
+ "placeholders provided but blob_name_pattern is not set in settings"
55
+ )
56
+
57
+ try:
58
+ final_blob_name = settings.blob_name_pattern.format(**placeholders)
59
+ except KeyError as e:
60
+ raise ValueError(
61
+ f"Missing placeholder {e} in blob_name_pattern: "
62
+ f"{settings.blob_name_pattern}. "
63
+ f"Available placeholders: {list(placeholders.keys())}"
64
+ ) from e
20
65
 
21
- if blob_name is None:
22
- blob_name = settings.blob_name
66
+ # Priority 3: Default pattern from settings
67
+ elif settings.blob_name_pattern is not None:
68
+ # Pattern without placeholders (fixed name)
69
+ final_blob_name = settings.blob_name_pattern
23
70
 
24
- if settings.blob_name_prefix:
25
- blob_name = f"{settings.blob_name_prefix}/{blob_name}"
71
+ else:
72
+ raise ValueError(
73
+ "blob_name is not provided, placeholders are not provided, "
74
+ "and blob_name_pattern is not set in settings"
75
+ )
26
76
 
27
77
  bucket = get_bucket(config_key, auth_config_key=auth_config_key, **kwargs)
28
- return bucket.blob(blob_name)
78
+ return bucket.blob(final_blob_name)
@@ -5,9 +5,15 @@ from pydantic_settings_manager import SettingsManager
5
5
  class GoogleCloudStorageSettings(BaseSettings):
6
6
  bucket_name: str | None = None
7
7
 
8
- blob_name_prefix: str | None = None
9
-
10
- blob_name: str | None = None
8
+ blob_name_pattern: str | None = None
9
+ """
10
+ Blob name pattern with placeholders.
11
+
12
+ Examples:
13
+ - "data.json" (fixed name)
14
+ - "files/{basename}" (single placeholder)
15
+ - "web/{user_id}/{agent_id}/files/{basename}" (multiple placeholders)
16
+ """
11
17
 
12
18
 
13
19
  settings_manager = SettingsManager(GoogleCloudStorageSettings, multi=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kiarina-lib-google-cloud-storage
3
- Version: 1.6.0
3
+ Version: 1.6.2
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
@@ -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 knows logical names
68
+ # ✅ Application code only provides variables
68
69
  from kiarina.lib.google.cloud_storage import get_blob
69
70
 
70
- # All infrastructure details are managed externally
71
- blob = get_blob(blob_name="user_data.json")
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 /web/{user_id}/{agent_id}/files/{basename} {
119
+ allow read, write: if request.auth.uid == user_id
120
+ && request.auth.token.agent_id == agent_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"web/{user_id}/{agent_id}/files/{file_name}" # Changed!
128
+ blob_name = f"web/{user_id}/{agent_id}/thumbnails/{file_name}" # Changed!
129
+ blob_name = f"web/{user_id}/{agent_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: "web/{user_id}/{agent_id}/files/{basename}"
142
+ ```
143
+
144
+ **GCS Security Rules (Infrastructure Concern):**
145
+ ```javascript
146
+ match /web/{user_id}/{agent_id}/files/{basename} {
147
+ allow read, write: if request.auth.uid == user_id
148
+ && request.auth.token.agent_id == agent_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
- "blob_name_prefix": "production/v1"
230
+ "blob_name_pattern": "production/v1/{basename}"
110
231
  }
111
232
  }
112
233
 
113
234
  # Application code - clean and simple
114
- blob = get_blob(blob_name="user_data.json")
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()
@@ -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
- | `blob_name_prefix` | `str \| None` | No | Prefix for blob names (e.g., "production/v1") |
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`: Blob name (default: None uses settings.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,40 @@ def get_blob(
517
642
  - `storage.Blob`: Google Cloud Storage blob
518
643
 
519
644
  **Raises:**
520
- - `ValueError`: If `blob_name` is not provided and not set in settings
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
- # Basic usage
525
- blob = get_blob(blob_name="data.json")
526
-
527
- # With blob_name_prefix in settings
528
- # If blob_name_prefix="production/v1" and blob_name="data.json"
529
- # Actual blob name will be "production/v1/data.json"
530
- blob = get_blob(blob_name="data.json")
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="web/{user_id}/{agent_id}/files/{basename}"
669
+ blob = get_blob(placeholders={
670
+ "user_id": "user123",
671
+ "agent_id": "agent456",
672
+ "basename": "document.pdf"
673
+ })
674
+ # Actual: gs://bucket/web/user123/agent456/files/document.pdf
531
675
 
532
676
  # With custom configurations
533
677
  blob = get_blob(
534
- blob_name="data.json",
678
+ placeholders={"basename": "data.json"},
535
679
  config_key="production",
536
680
  auth_config_key="prod_auth"
537
681
  )
@@ -1,9 +1,9 @@
1
1
  kiarina/lib/google/cloud_storage/__init__.py,sha256=q2AdOOOHUj_D-eot8_1-atrIaCKsCg0YatUOdq3kVVE,1184
2
- kiarina/lib/google/cloud_storage/_get_blob.py,sha256=ymvAwPZmPYC407jmLgc0uCOoo3qGh8B2zssAVkLHX_I,817
2
+ kiarina/lib/google/cloud_storage/_get_blob.py,sha256=J8GFQIx_xQWgqvd2LscI83u6xmzS7uPBIshyUDgBZEI,2644
3
3
  kiarina/lib/google/cloud_storage/_get_bucket.py,sha256=SaGIQqRb9UOJaosGf2pXduAEYscE_T21AHCNTaZtRGo,597
4
4
  kiarina/lib/google/cloud_storage/_get_storage_client.py,sha256=lmBR2I9_vcyb0PfsiSz0BPFYBwQ6zVci75cmk9GUN5U,359
5
5
  kiarina/lib/google/cloud_storage/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- kiarina/lib/google/cloud_storage/settings.py,sha256=q47sa7CjegzXT2jhIDFbAc1huESQzZ3N1tlcmtKnLGU,334
7
- kiarina_lib_google_cloud_storage-1.6.0.dist-info/METADATA,sha256=h26xRaiJeWJ_EQqAQQkt4DkruqVtb9pXVUJnT5LbRUk,22959
8
- kiarina_lib_google_cloud_storage-1.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
- kiarina_lib_google_cloud_storage-1.6.0.dist-info/RECORD,,
6
+ kiarina/lib/google/cloud_storage/settings.py,sha256=2ZG2AmWe6mM8wq-OHTqSneTIvoraIxh2Q1W7umLOFvI,540
7
+ kiarina_lib_google_cloud_storage-1.6.2.dist-info/METADATA,sha256=y16n-HD2RzseJezOZHujIS4xhzrds78eWEXKJK-p3-E,27462
8
+ kiarina_lib_google_cloud_storage-1.6.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
+ kiarina_lib_google_cloud_storage-1.6.2.dist-info/RECORD,,