Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/justmars/cloudflare-images
Wrapper around Cloudflare Images API, usable custom Django storage class
https://github.com/justmars/cloudflare-images
cloudflare pydantic
Last synced: 17 days ago
JSON representation
Wrapper around Cloudflare Images API, usable custom Django storage class
- Host: GitHub
- URL: https://github.com/justmars/cloudflare-images
- Owner: justmars
- License: bsd-3-clause
- Created: 2023-04-23T22:12:18.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-08-05T11:58:51.000Z (3 months ago)
- Last Synced: 2024-10-25T21:58:49.900Z (23 days ago)
- Topics: cloudflare, pydantic
- Language: Python
- Homepage: https://justmars.github.io/cloudflare-images/
- Size: 1.71 MB
- Stars: 25
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# cloudflare-images
![Github CI](https://github.com/justmars/cloudflare-images/actions/workflows/main.yml/badge.svg)
Wrapper around Cloudflare Images API, with instructions to create a usable custom Django storage class such wrapper.
## Development
See [documentation](https://justmars.github.io/cloudflare-images).
1. Run `just start`
2. Run `just dumpenv`
3. Run `pytest`Note: `pytest` will work only if **no** `.env` file exists with the included values. See docstrings.
## Changes
### Dec. 2, 2023
- Compatibility: python 3.12
- Compatibility: pydantic 2.5### Initial
- Removed: _Django_ as a dependency
- Added: Instructions to create _Django_ custom storage class
- Added: `.enable_batch()`
- Added: `.list_images()`
- Added: `.get_batch_token()`
- Added: `.get_usage_statistics()`
- Added: `.update_image()`
- Added: `.v2`
- Renamed: `.base_api` to `v1`
- Renamed: `.get()` to `.get_image_details()`
- Renamed: `.post()` to `.upload_image()`
- Renamed: `.delete()` to `.delete_image()`
- Renamed: `.upsert()` to `.delete_then_upload_image()`
- Renamed: `CloudflareImagesAPIv1` to `CloudflareImagesAPI`## Django Instructions
Starting with `Django` 4.2, add a Custom `Storage` class to the `STORAGES` setting like so:
```py
STORAGES = { # django 4.2 and above
"default": { # default
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": { # default
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
"cloudflare_images": { # add location of custom storage class
"BACKEND": "path.to.storageclass",
},
}
```The path to the custom storage class should [resemble](https://docs.djangoproject.com/en/dev/howto/custom-file-storage/#django.core.files.storage._open) the following:
```py
from http import HTTPStatusimport httpx
from django.core.files.base import File
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructiblefrom cloudflare_images import CloudflareImagesAPI
@deconstructible
class LimitedStorageCloudflareImages(Storage):
def __init__(self):
super().__init__()
self.api = CloudflareImagesAPI()def __repr__(self):
return ""def _open(self, name: str, mode="rb") -> File:
return File(self.api.get(img_id=name), name=name)def _save(self, name: str, content: bytes) -> str:
res = self.api.delete_then_upload_image(name, content)
return self.api.url(img_id=res.json()["result"]["id"])def get_valid_name(self, name):
return namedef get_available_name(self, name, max_length=None):
return self.generate_filename(name)def generate_filename(self, filename):
return filenamedef delete(self, name) -> httpx.Response:
return self.api.delete(name)def exists(self, name: str) -> bool:
res = self.api.get(name)
if res.status_code == HTTPStatus.NOT_FOUND:
return False
elif res.status_code == HTTPStatus.OK:
return True
raise Exception("Image name found but http status code is not OK.")def listdir(self, path):
raise NotImplementedError(
"subclasses of Storage must provide a listdir() method"
)def size(self, name: str):
return len(self.api.get(name).content)def url(self, name: str):
return self.api.url(name)def url_variant(self, name: str, variant: str):
return self.api.url(name, variant)def get_accessed_time(self, name):
raise NotImplementedError(
"subclasses of Storage must provide a get_accessed_time() method"
)def get_created_time(self, name):
raise NotImplementedError(
"subclasses of Storage must provide a get_created_time() method"
)def get_modified_time(self, name):
raise NotImplementedError(
"subclasses of Storage must provide a get_modified_time() method"
)
```Can then define a [callable](https://docs.djangoproject.com/en/dev/topics/files/#using-a-callable) likeso:
```python title="For use in ImageField"
from django.core.files.storage import storagesdef select_storage(is_remote_env: bool):
return storages["cloudflare_images"] if is_remote_env else storages["default"]class MyModel(models.Model):
my_img = models.ImageField(storage=select_storage)
```Can also refer to it via:
```python title="Invocation"
from django.core.files.storage import storages
cf = storages["cloudflare_images"]# assume previous upload done
id =# get image url, defaults to 'public' variant
cf.url(id)# specified 'avatar' variant, assuming it was created in the Cloudflare Images dashboard / API
cf.url_variant(id, 'avatar')
```