3
3
import typing
4
4
import urllib .parse
5
5
from datetime import datetime
6
+ from typing import ClassVar , List , Optional
6
7
7
8
import arrow
8
9
from azure .storage .blob import (
9
10
BlobServiceClient ,
10
11
BlobBlock ,
11
12
ContainerClient ,
12
13
generate_blob_sas ,
14
+ generate_container_sas ,
13
15
)
14
16
from azure .storage .blob ._models import BlobPrefix
15
17
@@ -47,19 +49,26 @@ class AzureWriter(AzureFile):
47
49
2. There is no separate call to instantiate the upload. The first call to put_block will
48
50
create the blob.
49
51
"""
50
- def __init__ (self , path : str , mode : base .FileMode , container_client : ContainerClient ,
51
- chunk_size : int = None ):
52
- max_block_size = 100 * 1024 * 1024
52
+
53
+ max_block_size : ClassVar [int ] = 100 * 1024 * 1024
54
+
55
+ def __init__ (
56
+ self ,
57
+ path : str ,
58
+ mode : base .FileMode ,
59
+ container_client : ContainerClient ,
60
+ chunk_size : int = max_block_size ,
61
+ ):
53
62
if chunk_size is not None :
54
63
# chunk_size cannot be larger than max_block_size due to API restrictions
55
- chunk_size = min (chunk_size , max_block_size )
64
+ chunk_size = min (chunk_size , self . max_block_size )
56
65
super ().__init__ (
57
66
path = path ,
58
67
mode = mode ,
59
68
container_client = container_client ,
60
69
chunk_size = chunk_size ,
61
70
)
62
- self .blocks = []
71
+ self .blocks : List [ BlobBlock ] = []
63
72
64
73
def _gen_block_id (self ) -> str :
65
74
"""
@@ -151,21 +160,43 @@ def read(self, size: int) -> bytes:
151
160
152
161
153
162
class AzureStorage (base .StorageBackend ):
154
- def __init__ (self , account : str , key : str , bucket : str , name : str = 'azure' ):
155
- super ().__init__ ()
156
- self .name = name
163
+ account_url : Optional [str ]
164
+
165
+ def __init__ (
166
+ self ,
167
+ account : Optional [str ] = None ,
168
+ key : Optional [str ] = None ,
169
+ bucket : Optional [str ] = None ,
170
+ sas_container_url : Optional [str ] = None ,
171
+ name : str = 'azure' ,
172
+ ):
173
+ super ().__init__ (name )
157
174
self .account = account
158
175
self .key = key
159
176
self .bucket = bucket
160
- self .account_url = 'https://{}.blob.core.windows.net' .format (self .account )
177
+
178
+ if account and key and bucket :
179
+ self .account_url = 'https://{}.blob.core.windows.net' .format (self .account )
180
+ self .container_url = None
181
+ elif sas_container_url :
182
+ self .account_url = None
183
+ self .container_url = sas_container_url
184
+ else :
185
+ raise ValueError ('Must provide either sas_container_url or account, key and bucket' )
161
186
162
187
def _create_service_client (self ):
188
+ if self .account_url is None :
189
+ raise ValueError ('Unable to construct a service client from a container SAS URL' )
163
190
return BlobServiceClient (
164
191
account_url = self .account_url ,
165
192
credential = self .key ,
166
193
)
167
194
168
195
def _create_container_client (self ):
196
+ if self .container_url :
197
+ # Constructed using an SAS URL
198
+ return ContainerClient .from_container_url (self .container_url )
199
+
169
200
service_client = self ._create_service_client ()
170
201
return service_client .get_container_client (self .bucket )
171
202
@@ -256,6 +287,8 @@ def create_download_url(self, path: str, expire: typing.Union[arrow.Arrow, datet
256
287
def _create_sas_url (self , path : str , sas_permissions : str ,
257
288
expire : typing .Union [arrow .Arrow , datetime ],
258
289
ip : typing .Optional [str ] = None ):
290
+ if not self .account_url :
291
+ raise ValueError ('Cannot create a SAS URL without account credentials' )
259
292
path = self ._clean_path (path )
260
293
expire = expire .datetime if isinstance (expire , arrow .Arrow ) else expire
261
294
token = generate_blob_sas (
@@ -269,3 +302,19 @@ def _create_sas_url(self, path: str, sas_permissions: str,
269
302
)
270
303
url = urllib .parse .urljoin (self .account_url , '{}/{}' .format (self .bucket , path ))
271
304
return '{}?{}' .format (url , token )
305
+
306
+ def create_container_url (self , expire : typing .Union [arrow .Arrow , datetime ],
307
+ ip : typing .Optional [str ] = None ):
308
+ if not self .account_url :
309
+ raise ValueError ('Cannot create a SAS URL without account credentials' )
310
+ expire = expire .datetime if isinstance (expire , arrow .Arrow ) else expire
311
+ token = generate_container_sas (
312
+ account_name = self .account ,
313
+ container_name = self .bucket ,
314
+ account_key = self .key ,
315
+ permission = 'rwdl' ,
316
+ expiry = expire ,
317
+ ip = ip
318
+ )
319
+ url = urllib .parse .urljoin (self .account_url , self .bucket )
320
+ return '{}?{}' .format (url , token )
0 commit comments