gcshttpx 0.1.4__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.
@@ -0,0 +1,73 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ node_modules
5
+ .pnp
6
+ .pnp.js
7
+
8
+ # testing
9
+ coverage
10
+ .trivyignore
11
+ .pytest_cache
12
+
13
+ # temporary
14
+ temp/
15
+ logs/
16
+
17
+ # next.js
18
+ .next/
19
+ out/
20
+ build
21
+
22
+ # misc
23
+ .DS_Store
24
+ *.pem
25
+
26
+ # debug
27
+ npm-debug.log*
28
+ yarn-debug.log*
29
+ yarn-error.log*
30
+ .pnpm-debug.log*
31
+
32
+ # local env files
33
+ .env.local
34
+ .env.development.local
35
+ .env.test.local
36
+ .env.production.local
37
+
38
+ # turbo
39
+ .turbo
40
+
41
+ .env
42
+ .vercel
43
+ .vscode
44
+ .env*.local
45
+
46
+ venv
47
+
48
+ .vercel
49
+ .env*.local
50
+ .idea
51
+
52
+ __pycache__
53
+
54
+ # Python virtual environments
55
+ .venv
56
+ venv/
57
+ *.exe
58
+
59
+ # uv
60
+ .uv/
61
+
62
+ # Build artifacts
63
+ dist/
64
+ *.egg-info/
65
+
66
+ .env*.local
67
+ *.log
68
+ secret
69
+ *.code-workspace
70
+ .claude/settings.local.json
71
+ profile.html
72
+ docker-compose.override.yml
73
+ .coverage
gcshttpx-0.1.4/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Piotr Penar
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,421 @@
1
+ Metadata-Version: 2.4
2
+ Name: gcshttpx
3
+ Version: 0.1.4
4
+ Summary: Minimal async Google Cloud Storage + Auth client built on httpx (HTTP/2).
5
+ Project-URL: Homepage, https://github.com/piotrpenar/gcshttpx
6
+ Project-URL: Source, https://github.com/piotrpenar/gcshttpx
7
+ Project-URL: Issues, https://github.com/piotrpenar/gcshttpx/issues
8
+ Author: Piotr Penar
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: asyncio,gcs,google-cloud-storage,http2,httpx
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Internet :: WWW/HTTP
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: cryptography>=43.0.0
24
+ Requires-Dist: httpx[http2]>=0.27.0
25
+ Requires-Dist: orjson>=3.10.0
26
+ Requires-Dist: pyjwt>=2.9.0
27
+ Provides-Extra: test
28
+ Requires-Dist: coverage>=7.6.0; extra == 'test'
29
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
30
+ Requires-Dist: pytest>=8.0.0; extra == 'test'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # gcshttpx
34
+
35
+ **Minimal, secure async Google Cloud Storage client built on httpx with native HTTP/2 support.**
36
+
37
+ [![CI](https://github.com/piotrpenar/gcshttpx/workflows/CI/badge.svg)](https://github.com/piotrpenar/gcshttpx/actions)
38
+ [![PyPI](https://img.shields.io/pypi/v/gcshttpx.svg)](https://pypi.org/project/gcshttpx/)
39
+ [![Python](https://img.shields.io/pypi/pyversions/gcshttpx.svg)](https://pypi.org/project/gcshttpx/)
40
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
41
+
42
+ ## Why gcshttpx?
43
+
44
+ - **🚀 Fast**: Built on httpx with HTTP/2 support for connection multiplexing
45
+ - **🔒 Secure**: Explicit credential handling, no automatic filesystem searches
46
+ - **🪶 Lightweight**: Minimal dependencies (httpx, orjson, PyJWT, cryptography)
47
+ - **⚡ Async-first**: Full async/await support with modern Python
48
+ - **🎯 Simple**: Clean, intuitive API for common operations
49
+ - **📦 Type-safe**: Fully typed with py.typed marker
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ # Using uv (recommended)
55
+ uv add gcshttpx
56
+
57
+ # Using pip
58
+ pip install gcshttpx
59
+ ```
60
+
61
+ ## Quick Start
62
+
63
+ ```python
64
+ import asyncio
65
+ from gcshttpx import Storage, Token
66
+
67
+ async def main():
68
+ # Create authenticated token
69
+ token = Token(
70
+ service_file="path/to/service-account.json",
71
+ scopes=["https://www.googleapis.com/auth/cloud-platform"]
72
+ )
73
+
74
+ # Create storage client
75
+ storage = Storage(token=token)
76
+
77
+ # Upload a file
78
+ await storage.upload("my-bucket", "hello.txt", b"Hello, World!")
79
+
80
+ # Download a file
81
+ data = await storage.download("my-bucket", "hello.txt")
82
+ print(data) # b"Hello, World!"
83
+
84
+ # List objects
85
+ items = await storage.list_objects("my-bucket", params={"prefix": "logs/"})
86
+ for item in items["items"]:
87
+ print(item["name"])
88
+
89
+ # Clean up
90
+ await storage.close()
91
+
92
+ asyncio.run(main())
93
+ ```
94
+
95
+ ## Authentication
96
+
97
+ ### Service Account (Recommended for Production)
98
+
99
+ ```python
100
+ from gcshttpx import Token
101
+
102
+ # From file path
103
+ token = Token(
104
+ service_file="/path/to/service-account.json",
105
+ scopes=["https://www.googleapis.com/auth/devstorage.read_write"]
106
+ )
107
+
108
+ # From environment variable
109
+ # Set GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
110
+ token = Token(scopes=["https://www.googleapis.com/auth/cloud-platform"])
111
+
112
+ # From file-like object
113
+ import io
114
+ import json
115
+
116
+ # Do not use this pattern in production
117
+ credentials = {
118
+ "type": "service_account",
119
+ "project_id": "my-project",
120
+ "private_key_id": "key-id",
121
+ "private_key": "-----BEGIN PRIVATE KEY-----\n...",
122
+ "client_email": "service@my-project.iam.gserviceaccount.com",
123
+ "client_id": "123456789",
124
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
125
+ "token_uri": "https://oauth2.googleapis.com/token",
126
+ }
127
+
128
+ token = Token(
129
+ service_file=io.StringIO(json.dumps(credentials)),
130
+ scopes=["https://www.googleapis.com/auth/cloud-platform"]
131
+ )
132
+ ```
133
+
134
+ ### GCE Metadata Server (For GCP Environments)
135
+
136
+ ```python
137
+ from gcshttpx import Token
138
+
139
+ # Automatically uses GCE metadata service if no credentials provided
140
+ token = Token(scopes=["https://www.googleapis.com/auth/cloud-platform"])
141
+ ```
142
+
143
+ ### Security Notes
144
+
145
+ ⚠️ **gcshttpx takes security seriously:**
146
+
147
+ - **HTTPS-only token endpoints**: Prevents credential leakage
148
+ - **Input validation**: All credentials are validated before use
149
+ - **No sensitive data in logs**: Errors don't expose credentials
150
+
151
+ ## Storage Operations
152
+
153
+ ### Upload
154
+
155
+ ```python
156
+ # Simple upload (bytes or str)
157
+ await storage.upload("bucket", "file.txt", b"content")
158
+
159
+ # With metadata
160
+ await storage.upload(
161
+ "bucket",
162
+ "file.json",
163
+ b'{"key": "value"}',
164
+ content_type="application/json",
165
+ metadata={"cacheControl": "no-cache", "contentLanguage": "en"}
166
+ )
167
+
168
+ # From file
169
+ await storage.upload_from_filename("bucket", "remote.txt", "local.txt")
170
+
171
+ # Gzip compression
172
+ await storage.upload("bucket", "file.txt", b"content", zipped=True)
173
+
174
+ # Force resumable upload (for large files)
175
+ await storage.upload(
176
+ "bucket",
177
+ "large-file.bin",
178
+ large_data,
179
+ force_resumable_upload=True
180
+ )
181
+ ```
182
+
183
+ ### Download
184
+
185
+ ```python
186
+ # Download to bytes
187
+ data = await storage.download("bucket", "file.txt")
188
+
189
+ # Download to file
190
+ await storage.download_to_filename("bucket", "remote.txt", "local.txt")
191
+
192
+ # Download metadata only
193
+ metadata = await storage.download_metadata("bucket", "file.txt")
194
+ print(metadata["size"], metadata["contentType"])
195
+
196
+ # Stream large files
197
+ stream = await storage.download_stream("bucket", "large-file.bin")
198
+ while chunk := await stream.read(8192):
199
+ process(chunk)
200
+ ```
201
+
202
+ ### List & Search
203
+
204
+ ```python
205
+ # List all objects
206
+ result = await storage.list_objects("bucket")
207
+ for item in result["items"]:
208
+ print(item["name"], item["size"])
209
+
210
+ # With prefix filter
211
+ result = await storage.list_objects("bucket", params={"prefix": "logs/2024/"})
212
+
213
+ # List with delimiter (folder-like)
214
+ result = await storage.list_objects("bucket", params={"delimiter": "/"})
215
+ print("Prefixes:", result.get("prefixes")) # 'folders'
216
+ print("Items:", result.get("items")) # files in current 'folder'
217
+
218
+ # List buckets
219
+ buckets = await storage.list_buckets("my-project-id")
220
+ for bucket in buckets:
221
+ print(bucket.name)
222
+ ```
223
+
224
+ ### Delete
225
+
226
+ ```python
227
+ # Delete object
228
+ await storage.delete("bucket", "file.txt")
229
+
230
+ # Delete returns status text
231
+ status = await storage.delete("bucket", "file.txt")
232
+ print(status) # "OK" or error message
233
+ ```
234
+
235
+ ### Object Operations
236
+
237
+ ```python
238
+ # Check if object exists
239
+ exists = await storage.blob_exists("bucket", "file.txt")
240
+
241
+ # Compose multiple objects into one
242
+ await storage.compose(
243
+ "bucket",
244
+ "merged.txt",
245
+ ["part1.txt", "part2.txt", "part3.txt"],
246
+ content_type="text/plain"
247
+ )
248
+
249
+ # Update metadata
250
+ await storage.patch_metadata(
251
+ "bucket",
252
+ "file.txt",
253
+ {"cacheControl": "public, max-age=3600"}
254
+ )
255
+
256
+ # Get bucket metadata
257
+ metadata = await storage.get_bucket_metadata("bucket")
258
+ print(metadata["location"], metadata["storageClass"])
259
+ ```
260
+
261
+ ### Signed URLs
262
+
263
+ ```python
264
+ from gcshttpx import IamClient
265
+
266
+ # Create IAM client for signing
267
+ iam = IamClient(token=token)
268
+
269
+ # Generate signed URL (valid for 1 hour)
270
+ bucket = storage.get_bucket("my-bucket")
271
+ blob = await bucket.get_blob("file.txt")
272
+ signed_url = await blob.get_signed_url(
273
+ expiration=3600,
274
+ iam_client=iam
275
+ )
276
+
277
+ # Share the URL
278
+ print(f"Download link: {signed_url}")
279
+ ```
280
+
281
+ ### Bucket and Blob Objects
282
+
283
+ ```python
284
+ # Get bucket object
285
+ bucket = storage.get_bucket("my-bucket")
286
+ metadata = await bucket.get_metadata()
287
+ exists = await bucket.blob_exists("file.txt")
288
+
289
+ # Get blob object
290
+ blob = await bucket.get_blob("file.txt")
291
+ await blob.upload(b"new content", content_type="text/plain")
292
+ data = await blob.download()
293
+ await blob.delete()
294
+ ```
295
+
296
+ ## Advanced Usage
297
+
298
+ ### Custom HTTP Client
299
+
300
+ ```python
301
+ import httpx
302
+ from gcshttpx import Storage, Token
303
+
304
+ # Use custom httpx client with specific settings
305
+ async with httpx.AsyncClient(
306
+ http2=True,
307
+ timeout=httpx.Timeout(30.0),
308
+ limits=httpx.Limits(max_keepalive_connections=5)
309
+ ) as client:
310
+ token = Token(service_file="credentials.json")
311
+ storage = Storage(session=client, token=token)
312
+
313
+ await storage.upload("bucket", "file.txt", b"content")
314
+ ```
315
+
316
+ ### Token Management
317
+
318
+ ```python
319
+ from gcshttpx import Token
320
+
321
+ token = Token(
322
+ service_file="credentials.json",
323
+ scopes=["https://www.googleapis.com/auth/cloud-platform"],
324
+ # Refresh token when 50% of lifetime has passed
325
+ background_refresh_after=0.5,
326
+ # Force refresh when 95% of lifetime has passed
327
+ force_refresh_after=0.95
328
+ )
329
+
330
+ # Manually get current token
331
+ access_token = await token.get()
332
+
333
+ # Get project ID
334
+ project_id = await token.get_project()
335
+
336
+ # Close token session
337
+ await token.close()
338
+ ```
339
+
340
+ ### Error Handling
341
+
342
+ ```python
343
+ import httpx
344
+ from gcshttpx import Storage
345
+
346
+ try:
347
+ data = await storage.download("bucket", "nonexistent.txt")
348
+ except httpx.HTTPStatusError as e:
349
+ print(f"HTTP {e.response.status_code}: {e.response.text}")
350
+ except httpx.TimeoutException:
351
+ print("Request timed out")
352
+ except ValueError as e:
353
+ print(f"Validation error: {e}")
354
+ ```
355
+
356
+ ## Common OAuth Scopes
357
+
358
+ ```python
359
+ # Full control
360
+ "https://www.googleapis.com/auth/cloud-platform"
361
+
362
+ # Read/write access to storage
363
+ "https://www.googleapis.com/auth/devstorage.read_write"
364
+
365
+ # Read-only access
366
+ "https://www.googleapis.com/auth/devstorage.read_only"
367
+
368
+ # Write-only access
369
+ "https://www.googleapis.com/auth/devstorage.write_only"
370
+ ```
371
+
372
+ ## Development
373
+
374
+ ```bash
375
+ # Clone repository
376
+ git clone https://github.com/piotrpenar/gcshttpx.git
377
+ cd gcshttpx
378
+
379
+ # Install with uv
380
+ uv sync
381
+
382
+ # Run tests
383
+ uv run pytest
384
+
385
+ # Run tests with coverage
386
+ uv run pytest --cov=gcshttpx
387
+
388
+ # Lint code
389
+ uv run ruff check .
390
+
391
+ # Format code
392
+ uv run ruff format .
393
+
394
+ # Build package
395
+ uv build
396
+ ```
397
+
398
+ ## Requirements
399
+
400
+ - Python 3.10+
401
+ - httpx[http2] >= 0.27.0
402
+ - orjson >= 3.10.0
403
+ - PyJWT >= 2.9.0
404
+ - cryptography >= 43.0.0
405
+
406
+ ## License
407
+
408
+ MIT License - see [LICENSE](LICENSE) file for details.
409
+
410
+ ## Contributing
411
+
412
+ Contributions are welcome! Please feel free to submit a Pull Request.
413
+
414
+ ## Links
415
+
416
+ - **Documentation**: [README.md](https://github.com/piotrpenar/gcshttpx)
417
+ - **Source Code**: [GitHub](https://github.com/piotrpenar/gcshttpx)
418
+ - **Issue Tracker**: [GitHub Issues](https://github.com/piotrpenar/gcshttpx/issues)
419
+ - **PyPI**: [gcshttpx](https://pypi.org/project/gcshttpx/)
420
+
421
+