Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

codegen output a7ba6f00842745a4a81cfd756b0a03af #36

Merged
merged 9 commits into from
Sep 12, 2023
Merged
134 changes: 132 additions & 2 deletions box_sdk_gen/managers/chunked_uploads.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
from typing import List

from typing import Optional

from typing import Dict

import json

from typing import List

from box_sdk_gen.base_object import BaseObject

from box_sdk_gen.utils import Buffer

from box_sdk_gen.utils import HashName

from box_sdk_gen.utils import Iterator

from box_sdk_gen.schemas import UploadSession

from box_sdk_gen.schemas import ClientError
Expand Down Expand Up @@ -36,6 +42,38 @@

from box_sdk_gen.fetch import FetchResponse

from box_sdk_gen.utils import generate_byte_stream_from_buffer

from box_sdk_gen.utils import hex_to_base_64

from box_sdk_gen.utils import iterate_chunks

from box_sdk_gen.utils import read_byte_stream

from box_sdk_gen.utils import reduce_iterator

from box_sdk_gen.utils import Hash

from box_sdk_gen.utils import list_concat

from box_sdk_gen.utils import buffer_length


class PartAccumulator:
def __init__(
self,
last_index: int,
parts: List[UploadPart],
file_size: int,
upload_session_id: str,
file_hash: Hash,
):
self.last_index = last_index
self.parts = parts
self.file_size = file_size
self.upload_session_id = upload_session_id
self.file_hash = file_hash


class ChunkedUploadsManager:
def __init__(
Expand Down Expand Up @@ -384,3 +422,95 @@ def create_file_upload_session_commit(
),
)
return Files.from_dict(json.loads(response.text))

def reducer(self, acc: PartAccumulator, chunk: ByteStream):
last_index: int = acc.last_index
parts: List[UploadPart] = acc.parts
chunk_buffer: Buffer = read_byte_stream(chunk)
hash: Hash = Hash(algorithm=HashName.SHA1.value)
hash.update_hash(chunk_buffer)
sha_1: str = hash.digest_hash('base64')
digest: str = ''.join(['sha=', sha_1])
chunk_size: int = buffer_length(chunk_buffer)
bytes_start: int = last_index + 1
bytes_end: int = last_index + chunk_size
content_range: str = ''.join(
[
'bytes ',
to_string(bytes_start),
'-',
to_string(bytes_end),
'/',
to_string(acc.file_size),
]
)
uploaded_part: UploadedPart = self.upload_file_part(
upload_session_id=acc.upload_session_id,
request_body=generate_byte_stream_from_buffer(chunk_buffer),
digest=digest,
content_range=content_range,
)
part: UploadPart = uploaded_part.part
part_sha_1: str = hex_to_base_64(part.sha_1)
assert part_sha_1 == sha_1
assert part.size == chunk_size
assert part.offset == bytes_start
acc.file_hash.update_hash(chunk_buffer)
return PartAccumulator(
last_index=bytes_end,
parts=list_concat(parts, [part]),
file_size=acc.file_size,
upload_session_id=acc.upload_session_id,
file_hash=acc.file_hash,
)

def upload_big_file(
self, file: ByteStream, file_name: str, file_size: int, parent_folder_id: str
):
"""
Starts the process of chunk uploading a big file. Should return a File object representing uploaded file.
:param file: The stream of the file to upload.
:type file: ByteStream
:param file_name: The name of the file, which will be used for storage in Box.
:type file_name: str
:param file_size: The total size of the file for the chunked upload in bytes.
:type file_size: int
:param parent_folder_id: The ID of the folder where the file should be uploaded.
:type parent_folder_id: str
"""
upload_session: UploadSession = self.create_file_upload_session(
folder_id=parent_folder_id, file_size=file_size, file_name=file_name
)
upload_session_id: str = upload_session.id
part_size: int = upload_session.part_size
total_parts: int = upload_session.total_parts
assert part_size * total_parts >= file_size
assert upload_session.num_parts_processed == 0
file_hash: Hash = Hash(algorithm=HashName.SHA1.value)
chunks_iterator: Iterator = iterate_chunks(file, part_size)
results: PartAccumulator = reduce_iterator(
chunks_iterator,
self.reducer,
PartAccumulator(
last_index=-1,
parts=[],
file_size=file_size,
upload_session_id=upload_session_id,
file_hash=file_hash,
),
)
parts: List[UploadPart] = results.parts
processed_session_parts: UploadParts = self.get_file_upload_session_parts(
upload_session_id=upload_session_id
)
assert processed_session_parts.total_count == total_parts
processed_session: UploadSession = self.get_file_upload_session_by_id(
upload_session_id=upload_session_id
)
assert processed_session.num_parts_processed == total_parts
sha_1: str = file_hash.digest_hash('base64')
digest: str = ''.join(['sha=', sha_1])
committed_session: Files = self.create_file_upload_session_commit(
upload_session_id=upload_session_id, parts=parts, digest=digest
)
return committed_session.entries[0]
6 changes: 4 additions & 2 deletions box_sdk_gen/managers/file_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import json

from box_sdk_gen.base_object import BaseObject

from box_sdk_gen.schemas import FileRequest

from box_sdk_gen.schemas import ClientError
Expand Down Expand Up @@ -178,7 +180,7 @@ def update_file_request_by_id(
"""
if extra_headers is None:
extra_headers = {}
request_body = FileRequestUpdateRequest(
request_body = BaseObject(
title=title,
description=description,
status=status,
Expand Down Expand Up @@ -301,7 +303,7 @@ def create_file_request_copy(
"""
if extra_headers is None:
extra_headers = {}
request_body = FileRequestCopyRequest(
request_body = BaseObject(
folder=folder,
title=title,
description=description,
Expand Down
2 changes: 1 addition & 1 deletion box_sdk_gen/managers/integration_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def create_integration_mapping_slack(
"""
if extra_headers is None:
extra_headers = {}
request_body = IntegrationMappingSlackCreateRequest(
request_body = BaseObject(
partner_item=partner_item, box_item=box_item, options=options
)
headers_map: Dict[str, str] = prepare_params({**extra_headers})
Expand Down
4 changes: 3 additions & 1 deletion box_sdk_gen/managers/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import json

from box_sdk_gen.base_object import BaseObject

from box_sdk_gen.schemas import MetadataQueryResults

from box_sdk_gen.schemas import ClientError
Expand Down Expand Up @@ -189,7 +191,7 @@ def create_metadata_query_execute_read(
"""
if extra_headers is None:
extra_headers = {}
request_body = MetadataQuery(
request_body = BaseObject(
from_=from_,
query=query,
query_params=query_params,
Expand Down
6 changes: 3 additions & 3 deletions box_sdk_gen/managers/shield_information_barrier_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from box_sdk_gen.schemas import ShieldInformationBarrierBase

from box_sdk_gen.base_object import BaseObject

from box_sdk_gen.schemas import ShieldInformationBarrierReport

from box_sdk_gen.schemas import ClientError
Expand Down Expand Up @@ -95,9 +97,7 @@ def create_shield_information_barrier_report(
"""
if extra_headers is None:
extra_headers = {}
request_body = ShieldInformationBarrierReference(
shield_information_barrier=shield_information_barrier
)
request_body = BaseObject(shield_information_barrier=shield_information_barrier)
headers_map: Dict[str, str] = prepare_params({**extra_headers})
response: FetchResponse = fetch(
''.join(['https://api.box.com/2.0/shield_information_barrier_reports']),
Expand Down
2 changes: 1 addition & 1 deletion box_sdk_gen/managers/shield_information_barriers.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def create_shield_information_barrier(
"""
if extra_headers is None:
extra_headers = {}
request_body = ShieldInformationBarrier(
request_body = BaseObject(
id=id,
type=type,
enterprise=enterprise,
Expand Down
4 changes: 3 additions & 1 deletion box_sdk_gen/managers/sign_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

from box_sdk_gen.schemas import SignRequestPrefillTag

from box_sdk_gen.base_object import BaseObject

from box_sdk_gen.schemas import SignRequest

from box_sdk_gen.schemas import ClientError
Expand Down Expand Up @@ -236,7 +238,7 @@ def create_sign_request(
"""
if extra_headers is None:
extra_headers = {}
request_body = SignRequestCreateRequest(
request_body = BaseObject(
source_files=source_files,
signers=signers,
is_document_preparation_needed=is_document_preparation_needed,
Expand Down
6 changes: 3 additions & 3 deletions box_sdk_gen/managers/zip_downloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import json

from box_sdk_gen.base_object import BaseObject

from box_sdk_gen.schemas import ZipDownload

from box_sdk_gen.schemas import ClientError
Expand Down Expand Up @@ -121,9 +123,7 @@ def create_zip_download(
"""
if extra_headers is None:
extra_headers = {}
request_body = ZipDownloadRequest(
items=items, download_file_name=download_file_name
)
request_body = BaseObject(items=items, download_file_name=download_file_name)
headers_map: Dict[str, str] = prepare_params({**extra_headers})
response: FetchResponse = fetch(
''.join(['https://api.box.com/2.0/zip_downloads']),
Expand Down
70 changes: 69 additions & 1 deletion box_sdk_gen/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import base64
import hashlib
from enum import Enum
from io import BytesIO, SEEK_SET, SEEK_END, BufferedIOBase
import os
import uuid
from typing import Dict, Optional
from typing import Dict, Optional, Iterable, Callable, TypeVar

ByteStream = BufferedIOBase
Buffer = bytes
Expand Down Expand Up @@ -75,6 +77,10 @@ def buffer_equals(buffer1: Buffer, buffer2: Buffer) -> bool:
return buffer1 == buffer2


def buffer_length(buffer: Buffer) -> int:
return len(buffer)


def decode_base_64_byte_stream(value: str) -> ByteStream:
return BytesIO(base64.b64decode(value))

Expand All @@ -91,3 +97,65 @@ def to_string(value: any) -> Optional[str]:
if value is None:
return value
return str(value)


class HashName(str, Enum):
SHA1 = 'sha1'


class Hash:
def __init__(self, algorithm: HashName):
self.algorithm = algorithm
self.hash = hashlib.sha1()

def update_hash(self, data: Buffer):
self.hash.update(data)

def digest_hash(self, encoding):
return base64.b64encode(self.hash.digest()).decode("utf-8")


def hex_to_base_64(data: hex):
return base64.b64encode(bytes.fromhex(data)).decode()


def list_concat(a: list, b: list):
return [*a, *b]


T = TypeVar('T')
Iterator = Iterable[T]
Accumulator = TypeVar('Accumulator')


def iterate_chunks(stream: ByteStream, chunk_size: int) -> Iterable[ByteStream]:
stream_is_finished = False
while not stream_is_finished:
copied_length = 0
chunk = b''
while copied_length < chunk_size:
bytes_read = stream.read(chunk_size - copied_length)
if bytes_read is None:
# stream returns none when no bytes are ready currently but there are
# potentially more bytes in the stream to be read.
continue
if not bytes_read:
# stream is exhausted.
stream_is_finished = True
break
chunk += bytes_read
copied_length += len(bytes_read)
yield BytesIO(chunk)


def reduce_iterator(
iterator: Iterator,
reducer: Callable[[Accumulator, T], Accumulator],
initial_value: Accumulator,
) -> Accumulator:
result = initial_value

for item in iterator:
result = reducer(result, item)

return result
Loading
Loading