Skip to content

Commit 769cc44

Browse files
v0.0.4 :octocat:
1 parent 2a20f38 commit 769cc44

10 files changed

Lines changed: 383 additions & 84 deletions

File tree

.flake8

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[flake8]
2+
ignore = E203, E266, W503
3+
max-line-length = 88
4+
max-complexity = 18

.github/FUNDING.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# These are supported funding model platforms
2+
3+
github: RobertoPrevato

.github/workflows/build.yml

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
name: Build
2+
3+
on:
4+
release:
5+
types: [published]
6+
push:
7+
branches:
8+
- main
9+
- ci
10+
pull_request:
11+
branches:
12+
- "*"
13+
14+
env:
15+
PROJECT_NAME: galleristazurestorage
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-18.04
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
python-version: [3.6, 3.7, 3.8, 3.9]
24+
25+
steps:
26+
- uses: actions/checkout@v1
27+
with:
28+
fetch-depth: 9
29+
submodules: false
30+
31+
- name: Use Python ${{ matrix.python-version }}
32+
uses: actions/setup-python@v1
33+
with:
34+
python-version: ${{ matrix.python-version }}
35+
36+
- uses: actions/cache@v1
37+
id: depcache
38+
with:
39+
path: deps
40+
key: requirements-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}
41+
42+
- name: Download dependencies
43+
if: steps.depcache.outputs.cache-hit != 'true'
44+
run: |
45+
pip download --dest=deps -r requirements.txt
46+
47+
- name: Install dependencies
48+
run: |
49+
pip install -U --no-index --find-links=deps deps/*
50+
51+
- name: Run tests
52+
run: |
53+
flake8
54+
55+
- name: Install distribution dependencies
56+
run: pip install --upgrade twine setuptools wheel
57+
if: matrix.python-version == 3.8 || matrix.python-version == 3.9
58+
59+
- name: Create distribution package
60+
run: python setup.py sdist bdist_wheel
61+
if: matrix.python-version == 3.8 || matrix.python-version == 3.9
62+
63+
- name: Upload distribution package
64+
uses: actions/upload-artifact@master
65+
with:
66+
name: dist-package-${{ matrix.python-version }}
67+
path: dist
68+
if: matrix.python-version == 3.8 || matrix.python-version == 3.9
69+
70+
publish:
71+
runs-on: ubuntu-18.04
72+
needs: build
73+
if: github.event_name == 'release'
74+
steps:
75+
- name: Download a distribution artifact
76+
uses: actions/download-artifact@v2
77+
with:
78+
name: dist-package-3.9
79+
path: dist
80+
- name: Publish distribution 📦 to Test PyPI
81+
uses: pypa/gh-action-pypi-publish@master
82+
with:
83+
skip_existing: true
84+
user: __token__
85+
password: ${{ secrets.test_pypi_password }}
86+
repository_url: https://test.pypi.org/legacy/
87+
- name: Publish distribution 📦 to PyPI
88+
uses: pypa/gh-action-pypi-publish@master
89+
with:
90+
user: __token__
91+
password: ${{ secrets.pypi_password }}

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [0.0.4] - 2021-03-14 :octocat:
9+
- Completely migrates to GitHub Workflows
10+
- Adds a changelog
11+
- Improves badges
12+
- Upgrades to new version of `gallerist` (which includes upgraded `Pillow` and
13+
`aiofiles`), and to the new version of `azure-storage-blob`
14+
[(v12)](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-python)
15+
- Improves formatting

README.md

Lines changed: 175 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,182 @@
1-
[![Build Status](https://dev.azure.com/robertoprevato/Nest/_apis/build/status/RobertoPrevato.Gallerist-AzureStorage?branchName=master)](https://dev.azure.com/robertoprevato/Nest/_build/latest?definitionId=27&branchName=master) [![pypi](https://img.shields.io/pypi/v/gallerist-azurestorage.svg?color=blue)](https://pypi.org/project/gallerist-azurestorage/)
1+
[![Build](https://github.com/Neoteroi/Gallerist-AzureStorage/actions/workflows/build.yml/badge.svg)](https://github.com/Neoteroi/Gallerist-AzureStorage/actions/workflows/build.yml)
2+
[![pypi](https://img.shields.io/pypi/v/gallerist-azurestorage.svg)](https://pypi.python.org/pypi/gallerist-azurestorage)
3+
[![versions](https://img.shields.io/pypi/pyversions/gallerist-azurestorage.svg)](https://github.com/Neoteroi/Gallerist-AzureStorage)
4+
[![license](https://img.shields.io/github/license/Neoteroi/Gallerist-AzureStorage.svg)](https://github.com/Neoteroi/Gallerist-AzureStorage/blob/main/LICENSE)
25

36
# Gallerist-AzureStorage
4-
Gallerist classes for Azure Storage: implements reading image files from Azure Blob Service, and writing of resized pictures to the same.
7+
Gallerist classes for Azure Storage: implements reading image files from Azure
8+
Blob Service, and writing of resized pictures to the same.
59

610
```bash
711
$ pip install gallerist-azurestorage
812
```
913

10-
This library is used in [Gallerist-AzureFunctions](https://github.com/RobertoPrevato/Gallerist-AzureFunctions), an Azure Functions front-end that uses [`Gallerist`](https://github.com/RobertoPrevato/Gallerist) library, to resize pictures
11-
in Azure Storage Blob Service.
14+
This library is used in
15+
[Gallerist-AzureFunctions](https://github.com/Neoteroi/Gallerist-AzureFunctions),
16+
an Azure Functions front-end that uses
17+
[`Gallerist`](https://github.com/Neoteroi/Gallerist) library, to resize
18+
pictures in Azure Storage Blob Service.
19+
20+
# Example: synchronous code resizing pictures on Azure Storage
21+
22+
```python
23+
from gallerist import Gallerist, ImageSize
24+
from galleristazurestorage import AzureBlobFileStore
25+
26+
store = AzureBlobFileStore.from_connection_string(
27+
"<YOUR_CONNECTION_STRING>",
28+
"CONTAINER_NAME",
29+
)
30+
31+
gallerist = Gallerist(store)
32+
33+
# configuring sizes by mime (use '*' to match any other mime):
34+
gallerist = Gallerist(
35+
store,
36+
sizes={
37+
"image/jpeg": [ImageSize("a", 1200), ImageSize("b", 600), ImageSize("c", 300)],
38+
"image/png": [ImageSize("a", 350), ImageSize("b", 250), ImageSize("c", 150)],
39+
},
40+
)
41+
42+
# the following function call causes the creation of several versions of the
43+
# image in different sizes; note that this operation is IO bound
44+
metadata = gallerist.process_image("ORIGINAL_FILE_NAME_ALREADY_ON_STORAGE.png")
45+
46+
print(metadata)
47+
48+
```
49+
50+
# Asynchronous example using executors (recommended for async scenarios)
51+
52+
```python
53+
import asyncio
54+
import concurrent.futures
55+
56+
from gallerist import Gallerist, ImageSize
57+
58+
from galleristazurestorage import AzureBlobFileStore
59+
60+
store = AzureBlobFileStore.from_connection_string(
61+
"<YOUR_CONNECTION_STRING>",
62+
"CONTAINER_NAME",
63+
)
64+
65+
gallerist = Gallerist(store)
66+
67+
# configuring sizes by mime (use '*' to match any other mime):
68+
gallerist = Gallerist(
69+
store,
70+
sizes={
71+
"image/jpeg": [ImageSize("a", 1200), ImageSize("b", 600), ImageSize("c", 300)],
72+
"image/png": [ImageSize("a", 350), ImageSize("b", 250), ImageSize("c", 150)],
73+
},
74+
)
75+
76+
77+
async def main():
78+
loop = asyncio.get_event_loop()
79+
80+
with concurrent.futures.ProcessPoolExecutor() as pool:
81+
metadata = await loop.run_in_executor(
82+
pool, gallerist.process_image, "EXISTING_FILE_ON_STORAGE.jpg"
83+
)
84+
85+
print(metadata)
86+
87+
88+
asyncio.run(main())
89+
90+
```
91+
92+
Alternatively to using an executor explicitly, it is possible to use
93+
`loop.call_soon_threadsafe`:
94+
95+
```python
96+
from gallerist import Gallerist, ImageSize
97+
from galleristazurestorage import AzureBlobFileStore
98+
99+
store = AzureBlobFileStore.from_connection_string(
100+
"<YOUR_CONNECTION_STRING>",
101+
"CONTAINER_NAME",
102+
)
103+
104+
gallerist = Gallerist(store)
105+
106+
# configuring sizes by mime (use '*' to match any other mime):
107+
gallerist = Gallerist(
108+
store,
109+
sizes={
110+
"image/jpeg": [ImageSize("a", 1200), ImageSize("b", 600), ImageSize("c", 300)],
111+
"image/png": [ImageSize("a", 350), ImageSize("b", 250), ImageSize("c", 150)],
112+
},
113+
)
114+
115+
def process_image(image_path: str):
116+
# configuring sizes by mime (use '*' to match any other mime):
117+
gallerist = Gallerist(
118+
store,
119+
sizes={
120+
"image/jpeg": [
121+
ImageSize("a", 1200),
122+
ImageSize("b", 600),
123+
ImageSize("c", 300),
124+
],
125+
"image/png": [
126+
ImageSize("a", 350),
127+
ImageSize("b", 250),
128+
ImageSize("c", 150),
129+
],
130+
},
131+
)
132+
133+
metadata = gallerist.process_image(image_path)
134+
135+
print(metadata)
136+
137+
138+
async def main():
139+
loop = asyncio.get_event_loop()
140+
loop.call_soon_threadsafe(process_image, "EXISTING_FILE_ON_STORAGE.jpg")
141+
142+
143+
asyncio.run(main())
144+
```
145+
146+
# Asynchronous example using asynchronous methods from azure-storage-blob.aio
147+
148+
Note: `azure-storage-blob` requires `aiohttp`, and is not compatible with
149+
`concurrent.futures.ProcessPoolExecutor`.
150+
151+
```python
152+
import asyncio
153+
from gallerist import Gallerist, ImageSize
154+
from galleristazurestorage.aio import AzureBlobAsyncFileStore
155+
156+
store = AzureBlobFileStore.from_connection_string(
157+
"<YOUR_CONNECTION_STRING>",
158+
"CONTAINER_NAME",
159+
)
160+
161+
gallerist = Gallerist(store)
162+
163+
# configuring sizes by mime (use '*' to match any other mime):
164+
gallerist = Gallerist(
165+
store,
166+
sizes={
167+
"image/jpeg": [ImageSize("a", 1200), ImageSize("b", 600), ImageSize("c", 300)],
168+
"image/png": [ImageSize("a", 350), ImageSize("b", 250), ImageSize("c", 150)],
169+
},
170+
)
171+
172+
173+
async def main():
174+
metadata = await gallerist.process_image_async(
175+
"EXISTING_FILE_ON_STORAGE.jpg"
176+
)
177+
178+
print(metadata)
179+
180+
181+
asyncio.run(main())
182+
```

azure-pipelines.yml

Lines changed: 0 additions & 36 deletions
This file was deleted.

galleristazurestorage/__init__.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,38 @@
1-
from gallerist.abc import SyncFileStore, FileInfo
2-
from azure.storage.common import CloudStorageAccount
3-
from azure.storage.blob import ContentSettings
1+
from io import BytesIO
42

3+
from azure.storage.blob import BlobServiceClient, ContentSettings
4+
from gallerist.abc import FileInfo, SyncFileStore
55

6-
class AzureBlobFileStore(SyncFileStore):
76

8-
def __init__(self, account: CloudStorageAccount, container_name: str):
9-
self.account = account
10-
self.service = account.create_block_blob_service()
7+
class AzureBlobFileStore(SyncFileStore):
8+
def __init__(self, blob_client: BlobServiceClient, container_name: str):
9+
self.service = blob_client
1110
self.container_name = container_name
1211

1312
@classmethod
14-
def from_name_and_key(cls,
15-
account_name: str,
16-
account_key: str,
17-
container_name: str):
18-
return cls(CloudStorageAccount(account_name=account_name, account_key=account_key), container_name)
13+
def from_connection_string(
14+
cls, connection_string: str, container_name: str
15+
) -> "AzureBlobFileStore":
16+
return cls(
17+
BlobServiceClient.from_connection_string(connection_string),
18+
container_name,
19+
)
1920

2021
def read_file(self, file_path: str) -> bytes:
21-
blob = self.service.get_blob_to_bytes(self.container_name, file_path)
22-
return blob.content
22+
downloader = self.service.get_blob_client(
23+
self.container_name, file_path
24+
).download_blob()
25+
stream = BytesIO()
26+
downloader.readinto(stream)
27+
return stream.getvalue()
2328

2429
def write_file(self, file_path: str, data: bytes, info: FileInfo):
25-
self.service.create_blob_from_bytes(self.container_name,
26-
file_path,
27-
data,
28-
content_settings=ContentSettings(content_type=info.mime))
30+
blob_client = self.service.get_blob_client(self.container_name, file_path)
31+
blob_client.upload_blob(
32+
BytesIO(data),
33+
content_settings=ContentSettings(content_type=info.mime),
34+
)
2935

3036
def delete_file(self, file_path: str):
31-
self.service.delete_blob(self.container_name, file_path)
32-
37+
blob_client = self.service.get_blob_client(self.container_name, file_path)
38+
blob_client.delete_blob()

0 commit comments

Comments
 (0)