From c88729fbff42b8103c34de2e6185cf054053711c Mon Sep 17 00:00:00 2001 From: box-sdk-build Date: Thu, 3 Sep 2026 08:02:20 -0700 Subject: [PATCH 1/2] feat: support chunk upload avoidance API (box/box-openapi#616) --- .codegen.json | 2 +- box_sdk_gen/managers/chunked_uploads.py | 112 ++++++++++++++++++ box_sdk_gen/schemas/__init__.py | 8 ++ box_sdk_gen/schemas/upload_part_plan.py | 32 +++++ box_sdk_gen/schemas/upload_part_plan_hit.py | 35 ++++++ box_sdk_gen/schemas/upload_session.py | 29 +++-- .../schemas/upload_session_plan_request.py | 17 +++ .../schemas/upload_session_plan_response.py | 33 ++++++ docs/box_sdk_gen/chunked_uploads.md | 66 +++++++++++ 9 files changed, 321 insertions(+), 13 deletions(-) create mode 100644 box_sdk_gen/schemas/upload_part_plan.py create mode 100644 box_sdk_gen/schemas/upload_part_plan_hit.py create mode 100644 box_sdk_gen/schemas/upload_session_plan_request.py create mode 100644 box_sdk_gen/schemas/upload_session_plan_response.py diff --git a/.codegen.json b/.codegen.json index b1b9260b2..627cd29da 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "04310d4", "specHash": "be75fa1", "version": "4.14.0" } +{ "engineHash": "04310d4", "specHash": "88cd5aa", "version": "4.14.0" } diff --git a/box_sdk_gen/managers/chunked_uploads.py b/box_sdk_gen/managers/chunked_uploads.py index aed8f1fd8..3c6a52918 100644 --- a/box_sdk_gen/managers/chunked_uploads.py +++ b/box_sdk_gen/managers/chunked_uploads.py @@ -12,6 +12,8 @@ from box_sdk_gen.networking.fetch_options import ResponseFormat +from box_sdk_gen.schemas.upload_part_plan import UploadPartPlan + from box_sdk_gen.internal.utils import Buffer from box_sdk_gen.internal.utils import HashName @@ -26,6 +28,10 @@ from box_sdk_gen.schemas.upload_parts import UploadParts +from box_sdk_gen.schemas.upload_session_plan_response import UploadSessionPlanResponse + +from box_sdk_gen.schemas.upload_session_plan_request import UploadSessionPlanRequest + from box_sdk_gen.schemas.files import Files from box_sdk_gen.schemas.upload_part import UploadPart @@ -617,6 +623,112 @@ def get_file_upload_session_parts( ) return deserialize(response.data, UploadParts) + def create_file_upload_session_plan_by_url( + self, + url: str, + parts: List[UploadPartPlan], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UploadSessionPlanResponse: + """ + Using this method with urls provided in response when creating a new upload session is preferred to use over CreateFileUploadSessionPlan method. + + This allows to always upload your content to the closest Box data center and can significantly improve upload speed. + + + Plan an upload session by checking which parts already exist on the server. + + + This endpoint allows clients to optimize uploads by skipping parts that + + + have already been uploaded (cache hits) and only uploading missing parts. + + + The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + + + and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + + :param url: URL of createFileUploadSessionPlan method + :type url: str + :param parts: The list of parts to check for existence. + :type parts: List[UploadPartPlan] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'parts': parts} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=url, + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UploadSessionPlanResponse) + + def create_file_upload_session_plan( + self, + upload_session_id: str, + parts: List[UploadPartPlan], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UploadSessionPlanResponse: + """ + Plan an upload session by checking which parts already exist on the server. + + This endpoint allows clients to optimize uploads by skipping parts that + + + have already been uploaded (cache hits) and only uploading missing parts. + + + The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + + + and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + + :param upload_session_id: The ID of the upload session. + Example: "D5E3F7A" + :type upload_session_id: str + :param parts: The list of parts to check for existence. + :type parts: List[UploadPartPlan] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'parts': parts} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.upload_url, + '/2.0/files/upload_sessions/', + to_string(upload_session_id), + '/plan', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UploadSessionPlanResponse) + def create_file_upload_session_commit_by_url( self, url: str, diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index 7c4269727..f0b360d70 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -382,6 +382,14 @@ from box_sdk_gen.schemas.uploaded_part import * +from box_sdk_gen.schemas.upload_part_plan import * + +from box_sdk_gen.schemas.upload_session_plan_request import * + +from box_sdk_gen.schemas.upload_part_plan_hit import * + +from box_sdk_gen.schemas.upload_session_plan_response import * + from box_sdk_gen.schemas.upload_session import * from box_sdk_gen.schemas.upload_url import * diff --git a/box_sdk_gen/schemas/upload_part_plan.py b/box_sdk_gen/schemas/upload_part_plan.py new file mode 100644 index 000000000..18e88bb29 --- /dev/null +++ b/box_sdk_gen/schemas/upload_part_plan.py @@ -0,0 +1,32 @@ +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class UploadPartPlan(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'sha_512': 'sha512', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'sha512': 'sha_512', + **BaseObject._json_to_fields_mapping, + } + + def __init__(self, offset: int, size: int, sha_512: str, **kwargs): + """ + :param offset: The offset of the chunk within the file + in bytes. The lower bound of the position + of the chunk within the file. + :type offset: int + :param size: The size of the chunk in bytes. + :type size: int + :param sha_512: The `SHA-512` hash of the chunk. + :type sha_512: str + """ + super().__init__(**kwargs) + self.offset = offset + self.size = size + self.sha_512 = sha_512 diff --git a/box_sdk_gen/schemas/upload_part_plan_hit.py b/box_sdk_gen/schemas/upload_part_plan_hit.py new file mode 100644 index 000000000..83c28d78e --- /dev/null +++ b/box_sdk_gen/schemas/upload_part_plan_hit.py @@ -0,0 +1,35 @@ +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class UploadPartPlanHit(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'sha_512': 'sha512', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'sha512': 'sha_512', + **BaseObject._json_to_fields_mapping, + } + + def __init__(self, offset: int, size: int, sha_512: str, part_id: str, **kwargs): + """ + :param offset: The offset of the chunk within the file + in bytes. The lower bound of the position + of the chunk within the file. + :type offset: int + :param size: The size of the chunk in bytes. + :type size: int + :param sha_512: The `SHA-512` hash of the chunk. + :type sha_512: str + :param part_id: The unique ID of the chunk. + :type part_id: str + """ + super().__init__(**kwargs) + self.offset = offset + self.size = size + self.sha_512 = sha_512 + self.part_id = part_id diff --git a/box_sdk_gen/schemas/upload_session.py b/box_sdk_gen/schemas/upload_session.py index be0806203..495284b4d 100644 --- a/box_sdk_gen/schemas/upload_session.py +++ b/box_sdk_gen/schemas/upload_session.py @@ -17,6 +17,7 @@ class UploadSessionSessionEndpointsField(BaseObject): def __init__( self, *, + plan: Optional[str] = None, upload_part: Optional[str] = None, commit: Optional[str] = None, abort: Optional[str] = None, @@ -26,20 +27,24 @@ def __init__( **kwargs ): """ - :param upload_part: The URL to upload parts to., defaults to None - :type upload_part: Optional[str], optional - :param commit: The URL used to commit the file., defaults to None - :type commit: Optional[str], optional - :param abort: The URL for used to abort the session., defaults to None - :type abort: Optional[str], optional - :param list_parts: The URL users to list all parts., defaults to None - :type list_parts: Optional[str], optional - :param status: The URL used to get the status of the upload., defaults to None - :type status: Optional[str], optional - :param log_event: The URL used to get the upload log from., defaults to None - :type log_event: Optional[str], optional + :param plan: The URL used to plan the upload session by checking which parts + already exist on the server., defaults to None + :type plan: Optional[str], optional + :param upload_part: The URL to upload parts to., defaults to None + :type upload_part: Optional[str], optional + :param commit: The URL used to commit the file., defaults to None + :type commit: Optional[str], optional + :param abort: The URL for used to abort the session., defaults to None + :type abort: Optional[str], optional + :param list_parts: The URL users to list all parts., defaults to None + :type list_parts: Optional[str], optional + :param status: The URL used to get the status of the upload., defaults to None + :type status: Optional[str], optional + :param log_event: The URL used to get the upload log from., defaults to None + :type log_event: Optional[str], optional """ super().__init__(**kwargs) + self.plan = plan self.upload_part = upload_part self.commit = commit self.abort = abort diff --git a/box_sdk_gen/schemas/upload_session_plan_request.py b/box_sdk_gen/schemas/upload_session_plan_request.py new file mode 100644 index 000000000..dca724c62 --- /dev/null +++ b/box_sdk_gen/schemas/upload_session_plan_request.py @@ -0,0 +1,17 @@ +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.upload_part_plan import UploadPartPlan + +from box_sdk_gen.box.errors import BoxSDKError + + +class UploadSessionPlanRequest(BaseObject): + def __init__(self, parts: List[UploadPartPlan], **kwargs): + """ + :param parts: The list of parts to check for existence. + :type parts: List[UploadPartPlan] + """ + super().__init__(**kwargs) + self.parts = parts diff --git a/box_sdk_gen/schemas/upload_session_plan_response.py b/box_sdk_gen/schemas/upload_session_plan_response.py new file mode 100644 index 000000000..e35960bd2 --- /dev/null +++ b/box_sdk_gen/schemas/upload_session_plan_response.py @@ -0,0 +1,33 @@ +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.upload_part_plan_hit import UploadPartPlanHit + +from box_sdk_gen.schemas.upload_part_plan import UploadPartPlan + +from box_sdk_gen.box.errors import BoxSDKError + + +class UploadSessionPlanResponse(BaseObject): + def __init__( + self, + upload_session_id: str, + hits: List[UploadPartPlanHit], + misses: List[UploadPartPlan], + **kwargs + ): + """ + :param upload_session_id: The unique identifier for this upload session. + :type upload_session_id: str + :param hits: Parts that already exist on the server and + do not need to be uploaded again. + :type hits: List[UploadPartPlanHit] + :param misses: Parts that do not exist on the server and + need to be uploaded. + :type misses: List[UploadPartPlan] + """ + super().__init__(**kwargs) + self.upload_session_id = upload_session_id + self.hits = hits + self.misses = misses diff --git a/docs/box_sdk_gen/chunked_uploads.md b/docs/box_sdk_gen/chunked_uploads.md index d96f915eb..1a5587e8e 100644 --- a/docs/box_sdk_gen/chunked_uploads.md +++ b/docs/box_sdk_gen/chunked_uploads.md @@ -12,6 +12,8 @@ This is a manager for chunked uploads (allowed for files at least 20MB). - [Remove upload session](#remove-upload-session) - [List parts by URL](#list-parts-by-url) - [List parts](#list-parts) +- [Plan upload session by URL](#plan-upload-session-by-url) +- [Plan upload session](#plan-upload-session) - [Commit upload session by URL](#commit-upload-session-by-url) - [Commit upload session](#commit-upload-session) - [Upload big file](#upload-big-file) @@ -348,6 +350,70 @@ This function returns a value of type `UploadParts`. Returns a list of parts that have been uploaded. +## Plan upload session by URL + +Plan an upload session by checking which parts already exist on the server. +This endpoint allows clients to optimize uploads by skipping parts that +have already been uploaded (cache hits) and only uploading missing parts. + +The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) +and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + +This operation is performed by calling function `create_file_upload_session_plan_by_url`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-upload-sessions-id-plan/). + +_Currently we don't have an example for calling `create_file_upload_session_plan_by_url` in integration tests_ + +### Arguments + +- url `str` + - URL of createFileUploadSessionPlan method +- parts `List[UploadPartPlan]` + - The list of parts to check for existence. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UploadSessionPlanResponse`. + +Returns information about which parts already exist (hits) +and which parts need to be uploaded (misses). + +## Plan upload session + +Plan an upload session by checking which parts already exist on the server. +This endpoint allows clients to optimize uploads by skipping parts that +have already been uploaded (cache hits) and only uploading missing parts. + +The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) +and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + +This operation is performed by calling function `create_file_upload_session_plan`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-upload-sessions-id-plan/). + +_Currently we don't have an example for calling `create_file_upload_session_plan` in integration tests_ + +### Arguments + +- upload_session_id `str` + - The ID of the upload session. Example: "D5E3F7A" +- parts `List[UploadPartPlan]` + - The list of parts to check for existence. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UploadSessionPlanResponse`. + +Returns information about which parts already exist (hits) +and which parts need to be uploaded (misses). + ## Commit upload session by URL Close an upload session and create a file from the uploaded chunks. From 5f591b8235438f843a6873629ddd69c739750569 Mon Sep 17 00:00:00 2001 From: box-sdk-build Date: Thu, 3 Sep 2026 08:04:47 -0700 Subject: [PATCH 2/2] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 627cd29da..1fa6a7bb0 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "04310d4", "specHash": "88cd5aa", "version": "4.14.0" } +{ "engineHash": "db84f4c", "specHash": "88cd5aa", "version": "4.14.0" }