Skip to content

Commit 2c51ed2

Browse files
author
Saimon Michelson
committed
update to pass flake, pylint and ut
1 parent 2209200 commit 2c51ed2

5 files changed

Lines changed: 76 additions & 154 deletions

File tree

cterasdk/core/query.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from ..common import Object
55

66

7-
def database(core, path, param):
7+
def run(core, path, param):
88
return create_callback_function(core, path, callback_response=DefaultResponse)(param)
99

1010

cterasdk/core/servers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def system_database(self):
5252
:returns: Main database object.
5353
:rtype: cterasdk.common.object.Object
5454
"""
55-
response = query.database(self._core, '/servers', query.QueryParamBuilder().addFilter(
55+
response = query.run(self._core, '/servers', query.QueryParamBuilder().addFilter(
5656
query.FilterBuilder('mainDB').eq(True)
5757
).build())
5858
return response.objects[0]
@@ -118,7 +118,7 @@ class Backup(BaseCommand):
118118

119119
def connected(self):
120120
"""
121-
Verify connectivity to the backup S3 bucket.
121+
Verify connectivity to the backup S3 bucket
122122
"""
123123
return self._core.servers.system_database.backupToBucket.status == 'Connected'
124124

@@ -127,7 +127,7 @@ def enable(self, bucket, interval):
127127
Enable Main Database Backup to an S3 Bucket
128128
129129
:param cterasdk.core.types.Bucket bucket: Storage bucket
130-
:param int interval: Backup interval in minutes
130+
:param int interval: Backup interval in minutes
131131
"""
132132
database = self._core.servers.system_database
133133
database.backupToBucket = Object(enabled=True, exportSchedulePeriod=interval, details=bucket.database_backup_server_object())

cterasdk/direct/lib.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import logging
22
import asyncio
3-
import urllib.parse
43

54
from ..lib.retries import execute_with_retries
65
from .types import Metadata, Block
76
from .crypto import decrypt_key, decrypt_block
87
from .decompressor import decompress
9-
from ..exceptions.transport import BadRequest, Unauthorized, Forbidden, Unprocessable, InternalServerError, HTTPError
8+
from ..exceptions.transport import BadRequest, Unauthorized, Unprocessable, InternalServerError, HTTPError
109
from ..exceptions.direct import (
1110
AuthorizationError, BlockListConnectionError, BlockListTimeout, BlockValidationException, BlocksNotFoundError,
1211
DecompressBlockError, DecryptBlockError, DecryptKeyError, DirectIOError, DownloadConnectionError,
@@ -27,7 +26,10 @@ async def get_object(client, file_id, chunk):
2726
:returns: Object
2827
:rtype: bytes
2928
"""
30-
message = f"Downloading block (offset={chunk.offset}, length={chunk.length})"
29+
message = (
30+
f"Downloading block #{chunk.number} "
31+
f"(offset={chunk.offset}, length={chunk.length})"
32+
)
3133

3234
if file_id:
3335
message += f" for file ID {file_id}"
@@ -58,8 +60,10 @@ async def get_object(client, file_id, chunk):
5860
"unknown": "Unknown error"
5961
}
6062

61-
message = f"Failed to download block (offset={chunk.offset}, length={chunk.length})"
62-
63+
message = (
64+
f"Failed to download block #{chunk.number} "
65+
f"(offset={chunk.offset}, length={chunk.length})"
66+
)
6367
if file_id:
6468
message = message + f" for file ID {file_id}"
6569

@@ -68,10 +72,6 @@ async def get_object(client, file_id, chunk):
6872
raise exception
6973

7074

71-
def is_azure_object_storage(chunk):
72-
return urllib.parse.urlparse(chunk.url).netloc.endswith('core.windows.net')
73-
74-
7575
async def decrypt_object(file_id, encrypted_object, encryption_key, chunk):
7676
"""
7777
Decrypt Encrypted Object.
@@ -83,7 +83,7 @@ async def decrypt_object(file_id, encrypted_object, encryption_key, chunk):
8383
:rtype: bytes
8484
"""
8585
try:
86-
return decrypt_block(encrypted_object[16:] if is_azure_object_storage(chunk) else encrypted_object, encryption_key)
86+
return decrypt_block(encrypted_object, encryption_key)
8787
except DirectIOError:
8888
logger.error('Failed to decrypt block.')
8989
raise DecryptBlockError(file_id, chunk)
@@ -123,14 +123,17 @@ async def process_chunk(client, file_id, chunk, encryption_key, semaphore):
123123
:rtype: cterasdk.direct.types.Block
124124
"""
125125
async def process(client, chunk, encryption_key):
126-
message = f"Processing block (offset={chunk.offset}, length={chunk.length}) "
126+
message = (
127+
f"Processing block #{chunk.number} "
128+
f"(offset={chunk.offset}, length={chunk.length})"
129+
)
127130
if file_id:
128131
message = message + f" for file ID {file_id}"
129132
logger.debug(message)
130133
encrypted_object = await get_object(client, file_id, chunk)
131134
decrypted_object = await decrypt_object(file_id, encrypted_object, encryption_key, chunk)
132135
decompressed_object = await decompress_object(file_id, decrypted_object, chunk)
133-
return Block(file_id, chunk.offset, decompressed_object, chunk.length)
136+
return Block(file_id, chunk.number, chunk.offset, decompressed_object, chunk.length)
134137

135138
if semaphore is not None:
136139
async with semaphore:
@@ -180,7 +183,7 @@ def decrypt_encryption_key(file_id, wrapped_key, secret_access_key):
180183

181184

182185
@execute_with_retries(retries=3, backoff=1, max_backoff=10)
183-
async def get_chunks(api, bearer, file_id, start=None, end=None):
186+
async def get_chunks(api, bearer, file_id):
184187
"""
185188
Get Chunks.
186189
@@ -192,15 +195,14 @@ async def get_chunks(api, bearer, file_id, start=None, end=None):
192195
"""
193196
logger.debug('Listing blocks for file ID: %s', file_id)
194197
try:
195-
params = {k: v for k, v in [('rangeStart', start), ('rangeEnd', end)] if v is not None}
196-
response = await api.get(f'{file_id}', params=params, headers={'Authorization': bearer})
198+
response = await api.get(f'{file_id}', headers={'Authorization': bearer})
197199
if not response.chunks:
198200
logger.error('Could not find blocks for file ID: %s.', file_id)
199201
raise BlocksNotFoundError(file_id)
200202
return Metadata(file_id, response)
201203
except BadRequest as error:
202204
raise ObjectNotFoundError(file_id) from error
203-
except (Unauthorized, Forbidden) as error:
205+
except Unauthorized as error:
204206
raise AuthorizationError(file_id) from error
205207
except Unprocessable as error:
206208
raise UnsupportedStorageError(file_id) from error

cterasdk/direct/types.py

Lines changed: 41 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -56,177 +56,89 @@ class CompressionLib:
5656

5757
class Chunk(Object):
5858

59-
def __init__(self, offset, url, length):
59+
def __init__(self, number, offset, url, length):
6060
"""
6161
Initialize a Chunk.
6262
63+
:param int number: Chunk number.
6364
:param int offset: Chunk offset.
6465
:param str url: Signed URL.
6566
:param int length: Object length.
6667
"""
6768
super().__init__(
69+
number=number,
6870
offset=offset,
6971
url=url,
7072
length=length
7173
)
7274

7375

74-
class MetadataPart(Object):
76+
class Metadata(Object):
7577
"""
76-
CTERA Direct I/O File Metadata Part
77-
78-
:ivar int start: Starting offset.
79-
:ivar int end: Ending offset.
80-
:ivar int length: Length of the range in bytes.
81-
:ivar bool encrypted: Indicates whether the range data is encrypted.
82-
:ivar str encryption_key: Encryption key used for the range data.
83-
:ivar bool compressed: Indicates whether the range data is compressed.
84-
:ivar str compression_alg: Compression algorithm used for the range data.
85-
:ivar list[cterasdk.common.object.Object] chunks: List of Chunk Objects.
78+
CTERA Direct IO File Metadata
8679
"""
87-
def __init__(self, offset, end, encrypted, encryption_key, compressed, compression_alg, chunks):
80+
81+
def __init__(self, file_id, server_object):
8882
"""
8983
Initialize a Direct IO metadata response object.
9084
91-
:param int offset: Starting offset.
92-
:param int end: Ending offset.
93-
:param bool encrypted: Indicates whether the file is encrypted.
94-
:param str encryption_key: Encryption key used for the file.
95-
:param bool compressed: Indicates whether the file is compressed.
96-
:param str compression_alg: Compression algorithm used for the file.
97-
:param list[cterasdk.common.object.Object] chunks: List of Chunk Objects.
85+
:param int file_id: File ID.
86+
:param cterasdk.common.object.Object server_object: Response Object.
9887
"""
9988
super().__init__(
100-
start=offset,
101-
end=end,
102-
length=(end - offset) + 1,
103-
encrypted=encrypted,
104-
encryption_key=encryption_key,
105-
compressed=compressed,
106-
compression_alg=compression_alg
89+
file_id=file_id,
90+
encrypted=server_object.encrypt_info.data_encrypted,
91+
compressed=server_object.compression_type != CompressionLib.Off,
92+
chunks=Metadata._format_chunks(server_object.chunks)
10793
)
108-
for chunk in chunks:
109-
self.chunks.append(Chunk(offset, chunk.url, chunk.len))
110-
offset = offset + chunk.len
94+
self.encryption_key = server_object.encrypt_info.wrapped_key if self.encrypted else None
95+
self.compression_library = server_object.compression_type if self.compressed else None
96+
last_chunk = self.chunks[-1]
97+
self.size = last_chunk.offset + last_chunk.length
11198

11299
@staticmethod
113-
def from_server_object(server_object):
114-
compressed = server_object.compression_type != CompressionLib.Off
115-
start, end = map(int, server_object.actual_blocks_range.range.split('-'))
116-
return MetadataPart(
117-
start,
118-
end,
119-
server_object.encrypt_info.data_encrypted,
120-
server_object.encrypt_info.wrapped_key if server_object.encrypt_info.data_encrypted else None,
121-
compressed,
122-
server_object.compression_type if compressed else None
123-
)
124-
125-
def __repr__(self):
126-
return str(self)
127-
128-
def __str__(self):
129-
return (
130-
f"{self.__class__.__name__}("
131-
f"{{'start': {self.start}, "
132-
f"'end': {self.end}, "
133-
f"'encrypted': {self.encrypted}, "
134-
f"'compressed': {self.compressed}, "
135-
f"'length': {self.length}, "
136-
f"'chunks': {len(self.chunks)}}})"
137-
)
138-
139-
140-
class Metadata(Object):
141-
"""
142-
CTERA Direct I/O File Metadata
143-
144-
:ivar int file_id: File ID.
145-
:ivar int file_size: File Size.
146-
:ivar list[cterasdk.direct.types.MetadataPart] parts: List of Metadata Parts.
147-
:ivar bool encrypted: Indicates whether the range data is encrypted.
148-
:ivar str encryption_key: Encryption key used for the range data.
149-
:ivar bool compressed: Indicates whether the range data is compressed.
150-
:ivar str compression_alg: Compression algorithm used for the range data.
151-
"""
152-
def __init__(self, file_id, *parts):
100+
def _format_chunks(server_object):
153101
"""
154-
Initialize a Direct IO metadata response object.
102+
Create Chunks.
155103
156104
:param int file_id: File ID.
157-
:param list[cterasdk.direct.types.MetadataPart] parts: List of Metadata Parts.
105+
:param cterasdk.common.object.Object server_object: Server response.
106+
:param list[int] blocks: List of block numbers to retrieve.
107+
:returns: Chunk objects
108+
:rtype: list[cterasdk.direct.types.Chunk]
158109
"""
159-
super().__init__(file_id=file_id, parts=parts)
160-
161-
@property
162-
def size(self):
163-
return self.parts[0].actual_blocks_range.file_size
164-
165-
@property
166-
def encrypted(self):
167-
return self.parts[0].encrypted
168-
169-
@property
170-
def encryption_key(self):
171-
return self.parts[0].encryption_key
172-
173-
@property
174-
def compressed(self):
175-
return self.parts[0].compressed
176-
177-
@property
178-
def compression_alg(self):
179-
return self.parts[0].compression_alg
180-
181-
@property
182-
def start(self):
183-
return self.parts[0].start
184-
185-
@property
186-
def end(self):
187-
return self.parts[-1].end
110+
offset = 0
111+
chunks = []
112+
for number, chunk in enumerate(server_object, 1):
113+
chunks.append(Chunk(number, offset, chunk.url, chunk.len))
114+
offset = offset + chunk.len
115+
return chunks
188116

189117
def serialize(self):
190118
"""
191-
Serialize Direct I/O metadata to a dictionary.
119+
Serialize Direct IO metadata to a dictionary.
192120
"""
193-
chunks = []
194-
for part in self.parts:
195-
chunks.extend(part.chunks)
196-
197-
return {
198-
'file_id': self.file_id,
199-
'size': self.size,
200-
'encrypted': self.encrypted,
201-
'encryption_key': utils.utf8_decode(base64.b64encode(self.encryption_key)) if self.encrypted else None,
202-
'compressed': self.compressed,
203-
'compression_alg': self.compression_alg,
204-
'chunks': chunks
205-
}
206-
207-
def __repr__(self):
208-
return str(self)
209-
210-
def __str__(self):
211-
d = self.serialize()
212-
d.pop('encryption_key')
213-
d['chunks'] = len(d['chunks'])
214-
return d
121+
x = copy.deepcopy(self)
122+
if self.encrypted:
123+
x.encryption_key = utils.utf8_decode(base64.b64encode(self.encryption_key))
124+
return x
215125

216126

217127
class Block:
218128
"""Block"""
219129

220-
def __init__(self, file_id, offset, data, length):
130+
def __init__(self, file_id, number, offset, data, length):
221131
"""
222132
Initialize a Block.
223133
224134
:param int file_id: File ID.
135+
:param int number: Block number.
225136
:param int offset: Block offset.
226137
:param bytes data: Bytes
227138
:param int length: Block length.
228139
"""
229140
self._file_id = file_id
141+
self._number = number
230142
self._offset = offset
231143
self._data = data
232144
self._length = length
@@ -235,6 +147,10 @@ def __init__(self, file_id, offset, data, length):
235147
def file_id(self):
236148
return self._file_id
237149

150+
@property
151+
def number(self):
152+
return self._number
153+
238154
@property
239155
def offset(self):
240156
return self._offset

0 commit comments

Comments
 (0)