From 4aadc95d59efef52610cffeb16d55d68bc75ecd7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:47:04 +0100 Subject: [PATCH 01/14] added helper upload methods for project files --- ayon_api/server_api.py | 68 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 65b6faa15..03086e050 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1517,6 +1517,74 @@ def download_file( return progress + def upload_project_file( + self, + project_name: str, + filepath: str, + *, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = None, + ) -> requests.Response: + """Upload project file from a filepath. + + Project files are usually binary files, such as images, videos, + or other media files that can be accessed via api endpoint + '{server url}/api/projects/{project_name}/files/{file_id}'. + + Args: + project_name (str): Project name. + filepath (str): Path where file will be downloaded. + chunk_size (Optional[int]): Size of chunks that are received + in single loop. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. + + Returns: + requests.Response: Requests response. + + """ + return self.upload_file( + f"api/projects/{project_name}/files", + filepath, + chunk_size=chunk_size, + progress=progress, + request_type=RequestTypes.post, + ) + + def upload_project_file_from_stream( + self, + project_name: str, + stream: StreamType, + *, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = None, + ) -> requests.Response: + """Upload project file from a filepath. + + Project files are usually binary files, such as images, videos, + or other media files that can be accessed via api endpoint + '{server url}/api/projects/{project_name}/files/{file_id}'. + + Args: + project_name (str): Project name. + stream (StreamType): Stream used as source for upload. + chunk_size (Optional[int]): Size of chunks that are received + in single loop. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. + + Returns: + requests.Response: Requests response. + + """ + return self.upload_file_from_stream( + f"api/projects/{project_name}/files", + stream, + chunk_size=chunk_size, + progress=progress, + request_type=RequestTypes.post, + ) + def download_project_file( self, project_name: str, From a090ba4855c3b5234745aaaef393cb73ed9a878e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:47:30 +0100 Subject: [PATCH 02/14] add new functions to public api --- ayon_api/__init__.py | 4 +++ ayon_api/_api.py | 68 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index c43bfcdb1..ae73d2090 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -70,6 +70,8 @@ delete, download_file_to_stream, download_file, + upload_project_file, + upload_project_file_from_stream, download_project_file, download_project_file_to_stream, upload_file_from_stream, @@ -351,6 +353,8 @@ "delete", "download_file_to_stream", "download_file", + "upload_project_file", + "upload_project_file_from_stream", "download_project_file", "download_project_file_to_stream", "upload_file_from_stream", diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 06ece19b0..6c1ba45bc 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -993,6 +993,74 @@ def download_file( ) +def upload_project_file( + project_name: str, + filepath: str, + *, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = None, +) -> requests.Response: + """Upload project file from a filepath. + + Project files are usually binary files, such as images, videos, + or other media files that can be accessed via api endpoint + '{server url}/api/projects/{project_name}/files/{file_id}'. + + Args: + project_name (str): Project name. + filepath (str): Path where file will be downloaded. + chunk_size (Optional[int]): Size of chunks that are received + in single loop. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. + + Returns: + requests.Response: Requests response. + + """ + con = get_server_api_connection() + return con.upload_project_file( + project_name=project_name, + filepath=filepath, + chunk_size=chunk_size, + progress=progress, + ) + + +def upload_project_file_from_stream( + project_name: str, + stream: StreamType, + *, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = None, +) -> requests.Response: + """Upload project file from a filepath. + + Project files are usually binary files, such as images, videos, + or other media files that can be accessed via api endpoint + '{server url}/api/projects/{project_name}/files/{file_id}'. + + Args: + project_name (str): Project name. + stream (StreamType): Stream used as source for upload. + chunk_size (Optional[int]): Size of chunks that are received + in single loop. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. + + Returns: + requests.Response: Requests response. + + """ + con = get_server_api_connection() + return con.upload_project_file_from_stream( + project_name=project_name, + stream=stream, + chunk_size=chunk_size, + progress=progress, + ) + + def download_project_file( project_name: str, file_id: str, From 9d5ddd3cd40e1d4d582ff91f20e06dfe3000dc3f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:22:59 +0100 Subject: [PATCH 03/14] added support for pdf --- ayon_api/utils.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/ayon_api/utils.py b/ayon_api/utils.py index 7554c60fa..7fb636352 100644 --- a/ayon_api/utils.py +++ b/ayon_api/utils.py @@ -1118,12 +1118,12 @@ def _get_media_mime_type_for_content_base(content: bytes) -> Optional[str]: content_len = len(content) # Pre-validation (largest definition check) # - hopefully there cannot be media defined in less than 12 bytes - if content_len < 12: + if content_len < 4: return None - # FTYP - if content[4:8] == b"ftyp": - return _get_media_mime_type_from_ftyp(content) + # PDF + if content[0:4] == b"%PDF": + return "application/pdf" # BMP if content[0:2] == b"BM": @@ -1162,6 +1162,14 @@ def _get_media_mime_type_for_content_base(content: bytes) -> Optional[str]: # with this header if content[0:4] == b"\x00\x00\x01\x00": return "image/x-icon" + + if content_len < 8: + return None + + # FTYP + if content[4:8] == b"ftyp": + return _get_media_mime_type_from_ftyp(content) + return None From 3e4ce9af3515c1f33c106ef770f3e509a6289eb0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:23:11 +0100 Subject: [PATCH 04/14] added support for json file --- ayon_api/utils.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/ayon_api/utils.py b/ayon_api/utils.py index 7fb636352..5f7bf07c4 100644 --- a/ayon_api/utils.py +++ b/ayon_api/utils.py @@ -1180,23 +1180,32 @@ def _get_svg_mime_type(content: bytes) -> Optional[str]: return None +def _get_json_mime_type(content: bytes) -> Optional[str]: + # json + try: + json.loads(content.decode("utf-8")) + return "application/json" + except (UnicodeDecodeError, ValueError): + pass + return None + + def get_media_mime_type_for_content(content: bytes) -> Optional[str]: mime_type = _get_media_mime_type_for_content_base(content) if mime_type is not None: return mime_type - return _get_svg_mime_type(content) + return _get_svg_mime_type(content) or _get_json_mime_type(content) def get_media_mime_type_for_stream(stream: StreamType) -> Optional[str]: # Read only 12 bytes to determine mime type content = stream.read(12) - if len(content) < 12: - return None mime_type = _get_media_mime_type_for_content_base(content) - if mime_type is None: - content += stream.read() - mime_type = _get_svg_mime_type(content) - return mime_type + if mime_type is not None: + return mime_type + + content += stream.read() + return _get_svg_mime_type(content) or _get_json_mime_type(content) def get_media_mime_type(filepath: str) -> Optional[str]: From 9b14d6c2f87d325d0f75057fd16e83098efb08fb Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:30:32 +0100 Subject: [PATCH 05/14] move handling of content type and xfilename to _upload_file --- ayon_api/server_api.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 03086e050..4ce59687b 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1687,6 +1687,9 @@ def _upload_file( progress: TransferProgress, request_type: Optional[RequestType] = None, chunk_size: Optional[int] = None, + *, + content_type: Optional[str] = None, + filename: Optional[str] = None, **kwargs ) -> requests.Response: """Upload file to server. @@ -1714,11 +1717,14 @@ def _upload_file( url = self._endpoint_to_url(endpoint, use_rest=False) progress.set_destination_url(url) + headers = kwargs.setdefault("headers", {}) + headers_keys_by_low_key = {key.lower(): key for key in headers} if self._session is None: - headers = kwargs.setdefault("headers", {}) for key, value in self.get_headers().items(): - if key not in headers: + orig_key = headers_keys_by_low_key.get(key) + if not orig_key: headers[key] = value + post_func = self._base_functions_mapping[request_type] else: post_func = self._session_functions_mapping[request_type] @@ -1726,6 +1732,18 @@ def _upload_file( if not chunk_size: chunk_size = self.default_upload_chunk_size + for key, value in ( + ("x-file-name", filename), + ("Content-Type", content_type), + ): + if not value: + continue + + orig_key = headers_keys_by_low_key.get(key) + if orig_key: + headers.pop(orig_key) + headers[key] = filename + retries = self.get_default_max_retries() response = None @@ -1905,24 +1923,9 @@ def upload_reviewable( f"Could not determine MIME type of file '{filepath}'" ) - if headers is None: - headers = self.get_headers(content_type) - else: - # Make sure content-type is filled with file content type - content_type_key = next( - ( - key - for key in headers - if key.lower() == "content-type" - ), - "Content-Type" - ) - headers[content_type_key] = content_type - # Fill original filename if not explicitly defined if not filename: filename = os.path.basename(filepath) - headers["x-file-name"] = filename query = prepare_query_string({"label": label or None}) endpoint = ( From a1f6ed975766f701a35e4a60c11120350117ddc2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:31:12 +0100 Subject: [PATCH 06/14] handle mime type and filename --- ayon_api/server_api.py | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 4ce59687b..80fa5fbdc 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -62,6 +62,7 @@ get_default_site_id, NOT_SET, get_media_mime_type, + get_media_mime_type_for_stream, get_machine_name, fill_own_attribs, ) @@ -1522,6 +1523,8 @@ def upload_project_file( project_name: str, filepath: str, *, + content_type: Optional[str] = None, + filename: Optional[str] = None, chunk_size: Optional[int] = None, progress: Optional[TransferProgress] = None, ) -> requests.Response: @@ -1534,6 +1537,9 @@ def upload_project_file( Args: project_name (str): Project name. filepath (str): Path where file will be downloaded. + content_type (Optional[str]): MIME type of file. + filename (Optional[str]): Server filename, filename from filepath + is used if not passed. chunk_size (Optional[int]): Size of chunks that are received in single loop. progress (Optional[TransferProgress]): Object that gives ability @@ -1543,9 +1549,18 @@ def upload_project_file( requests.Response: Requests response. """ + if not filename: + filename = os.path.basename(filepath) + + if not content_type: + content_type = get_media_mime_type(filepath) + if not content_type: + content_type = "application/octet-stream" + return self.upload_file( f"api/projects/{project_name}/files", filepath, + filename=filename, chunk_size=chunk_size, progress=progress, request_type=RequestTypes.post, @@ -1555,7 +1570,9 @@ def upload_project_file_from_stream( self, project_name: str, stream: StreamType, + filename: str, *, + content_type: Optional[str] = None, chunk_size: Optional[int] = None, progress: Optional[TransferProgress] = None, ) -> requests.Response: @@ -1568,6 +1585,8 @@ def upload_project_file_from_stream( Args: project_name (str): Project name. stream (StreamType): Stream used as source for upload. + filename (str): Name of file on server. + content_type (Optional[str]): MIME type of file. chunk_size (Optional[int]): Size of chunks that are received in single loop. progress (Optional[TransferProgress]): Object that gives ability @@ -1577,11 +1596,19 @@ def upload_project_file_from_stream( requests.Response: Requests response. """ + if not content_type: + stream.seek(0) + content_type = get_media_mime_type_for_stream(stream) + if not content_type: + content_type = "application/octet-stream" + return self.upload_file_from_stream( f"api/projects/{project_name}/files", stream, + filename=filename, chunk_size=chunk_size, progress=progress, + content_type=content_type, request_type=RequestTypes.post, ) @@ -1761,6 +1788,7 @@ def _upload_file( data=self._upload_chunks_iter( stream, progress, chunk_size ), + headers=headers, **kwargs ) # Auto-fix missing 'api/' @@ -1798,6 +1826,9 @@ def upload_file_from_stream( stream: StreamType, progress: Optional[TransferProgress] = None, request_type: Optional[RequestType] = None, + *, + filename: Optional[str] = None, + content_type: Optional[str] = None, **kwargs ) -> requests.Response: """Upload file to server from bytes. @@ -1813,6 +1844,8 @@ def upload_file_from_stream( to track upload progress. request_type (Optional[RequestType]): Type of request that will be used to upload file. + filename (Optional[str]): Filename of file on server. + content_type (Optional[str]): MIME type of the file. **kwargs (Any): Additional arguments that will be passed to request function. @@ -1833,6 +1866,8 @@ def upload_file_from_stream( stream, progress, request_type, + filename=filename, + content_type=content_type, **kwargs ) @@ -1849,6 +1884,9 @@ def upload_file( filepath: str, progress: Optional[TransferProgress] = None, request_type: Optional[RequestType] = None, + *, + filename: Optional[str] = None, + content_type: Optional[str] = None, **kwargs ) -> requests.Response: """Upload file to server. @@ -1864,6 +1902,8 @@ def upload_file( to track upload progress. request_type (Optional[RequestType]): Type of request that will be used to upload file. + content_type (Optional[str]): MIME type of the file. + filename (Optional[str]): Filename of file on server. **kwargs (Any): Additional arguments that will be passed to request function. @@ -1882,6 +1922,8 @@ def upload_file( stream, progress, request_type, + filename=filename, + content_type=content_type, **kwargs ) @@ -1936,6 +1978,8 @@ def upload_reviewable( endpoint, filepath, progress=progress, + content_type=content_type, + filename=filename, headers=headers, request_type=RequestTypes.post, **kwargs From 304b7fbef2028e029b384272edcf2998c6073d5f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:31:47 +0100 Subject: [PATCH 07/14] update public api --- ayon_api/_api.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 6c1ba45bc..92946f43c 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -997,6 +997,8 @@ def upload_project_file( project_name: str, filepath: str, *, + content_type: Optional[str] = None, + filename: Optional[str] = None, chunk_size: Optional[int] = None, progress: Optional[TransferProgress] = None, ) -> requests.Response: @@ -1009,6 +1011,9 @@ def upload_project_file( Args: project_name (str): Project name. filepath (str): Path where file will be downloaded. + content_type (Optional[str]): MIME type of file. + filename (Optional[str]): Server filename, filename from filepath + is used if not passed. chunk_size (Optional[int]): Size of chunks that are received in single loop. progress (Optional[TransferProgress]): Object that gives ability @@ -1022,6 +1027,8 @@ def upload_project_file( return con.upload_project_file( project_name=project_name, filepath=filepath, + content_type=content_type, + filename=filename, chunk_size=chunk_size, progress=progress, ) @@ -1030,7 +1037,9 @@ def upload_project_file( def upload_project_file_from_stream( project_name: str, stream: StreamType, + filename: str, *, + content_type: Optional[str] = None, chunk_size: Optional[int] = None, progress: Optional[TransferProgress] = None, ) -> requests.Response: @@ -1043,6 +1052,8 @@ def upload_project_file_from_stream( Args: project_name (str): Project name. stream (StreamType): Stream used as source for upload. + filename (str): Name of file on server. + content_type (Optional[str]): MIME type of file. chunk_size (Optional[int]): Size of chunks that are received in single loop. progress (Optional[TransferProgress]): Object that gives ability @@ -1056,6 +1067,8 @@ def upload_project_file_from_stream( return con.upload_project_file_from_stream( project_name=project_name, stream=stream, + filename=filename, + content_type=content_type, chunk_size=chunk_size, progress=progress, ) @@ -1140,6 +1153,9 @@ def upload_file_from_stream( stream: StreamType, progress: Optional[TransferProgress] = None, request_type: Optional[RequestType] = None, + *, + filename: Optional[str] = None, + content_type: Optional[str] = None, **kwargs, ) -> requests.Response: """Upload file to server from bytes. @@ -1155,6 +1171,8 @@ def upload_file_from_stream( to track upload progress. request_type (Optional[RequestType]): Type of request that will be used to upload file. + filename (Optional[str]): Filename of file on server. + content_type (Optional[str]): MIME type of the file. **kwargs (Any): Additional arguments that will be passed to request function. @@ -1168,6 +1186,8 @@ def upload_file_from_stream( stream=stream, progress=progress, request_type=request_type, + filename=filename, + content_type=content_type, **kwargs, ) @@ -1177,6 +1197,9 @@ def upload_file( filepath: str, progress: Optional[TransferProgress] = None, request_type: Optional[RequestType] = None, + *, + filename: Optional[str] = None, + content_type: Optional[str] = None, **kwargs, ) -> requests.Response: """Upload file to server. @@ -1192,6 +1215,8 @@ def upload_file( to track upload progress. request_type (Optional[RequestType]): Type of request that will be used to upload file. + content_type (Optional[str]): MIME type of the file. + filename (Optional[str]): Filename of file on server. **kwargs (Any): Additional arguments that will be passed to request function. @@ -1205,6 +1230,8 @@ def upload_file( filepath=filepath, progress=progress, request_type=request_type, + filename=filename, + content_type=content_type, **kwargs, ) @@ -6955,6 +6982,7 @@ def create_link( output_id: str, output_type: str, link_name: Optional[str] = None, + data: Optional[dict[str, Any]] = None, ) -> CreateLinkData: """Create link between 2 entities. @@ -6974,7 +7002,8 @@ def create_link( output_id (str): Output entity id. output_type (str): Entity type of output entity. link_name (Optional[str]): Name of link. - Available from server version '1.0.0-rc.6'. + data (Optional[dict[str, Any]]): Additional data to be stored + with the link. Returns: CreateLinkData: Information about link. @@ -6992,6 +7021,7 @@ def create_link( output_id=output_id, output_type=output_type, link_name=link_name, + data=data, ) From d18b03951cf9a6de34b4c367dae35c0b18db110e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:45:56 +0100 Subject: [PATCH 08/14] unify arguments order --- ayon_api/server_api.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 80fa5fbdc..f2c6de0ce 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1827,8 +1827,8 @@ def upload_file_from_stream( progress: Optional[TransferProgress] = None, request_type: Optional[RequestType] = None, *, - filename: Optional[str] = None, content_type: Optional[str] = None, + filename: Optional[str] = None, **kwargs ) -> requests.Response: """Upload file to server from bytes. @@ -1844,8 +1844,8 @@ def upload_file_from_stream( to track upload progress. request_type (Optional[RequestType]): Type of request that will be used to upload file. - filename (Optional[str]): Filename of file on server. content_type (Optional[str]): MIME type of the file. + filename (Optional[str]): Filename of file on server. **kwargs (Any): Additional arguments that will be passed to request function. @@ -1866,8 +1866,8 @@ def upload_file_from_stream( stream, progress, request_type, - filename=filename, content_type=content_type, + filename=filename, **kwargs ) @@ -1885,8 +1885,8 @@ def upload_file( progress: Optional[TransferProgress] = None, request_type: Optional[RequestType] = None, *, - filename: Optional[str] = None, content_type: Optional[str] = None, + filename: Optional[str] = None, **kwargs ) -> requests.Response: """Upload file to server. @@ -1922,8 +1922,8 @@ def upload_file( stream, progress, request_type, - filename=filename, content_type=content_type, + filename=filename, **kwargs ) From c6673cd3b6c8f533bbb104ede885c590e21448c9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:46:14 +0100 Subject: [PATCH 09/14] added delete project file helper --- ayon_api/server_api.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index f2c6de0ce..de7ec832d 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1682,6 +1682,11 @@ def download_project_file_to_stream( progress=progress, ) + def delete_project_file(self, project_name: str, file_id: str) -> None: + """Delete project file.""" + response = self.delete(f"projects/{project_name}/files/{file_id}") + response.raise_for_status() + @staticmethod def _upload_chunks_iter( file_stream: StreamType, From 8b96a92d9ba0f29166d1621d0479c1ef8e9e63a9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:46:57 +0100 Subject: [PATCH 10/14] fix arguments order passed to upload --- ayon_api/server_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index de7ec832d..6b90435be 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1560,6 +1560,7 @@ def upload_project_file( return self.upload_file( f"api/projects/{project_name}/files", filepath, + content_type=content_type, filename=filename, chunk_size=chunk_size, progress=progress, @@ -1605,10 +1606,10 @@ def upload_project_file_from_stream( return self.upload_file_from_stream( f"api/projects/{project_name}/files", stream, + content_type=content_type, filename=filename, chunk_size=chunk_size, progress=progress, - content_type=content_type, request_type=RequestTypes.post, ) From 44aa7ee0bc32764127716168c0ddd9285d322719 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:47:09 +0100 Subject: [PATCH 11/14] fix headers preparation --- ayon_api/server_api.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 6b90435be..6c13983ec 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1771,11 +1771,10 @@ def _upload_file( ): if not value: continue - - orig_key = headers_keys_by_low_key.get(key) + orig_key = headers_keys_by_low_key.get(key.lower()) if orig_key: headers.pop(orig_key) - headers[key] = filename + headers[key] = value retries = self.get_default_max_retries() response = None From 5ca0a669402d5345d65ee6fe7a8a5875608bde6d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:47:16 +0100 Subject: [PATCH 12/14] pass headers only once --- ayon_api/server_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 6c13983ec..a318738b6 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1793,7 +1793,6 @@ def _upload_file( data=self._upload_chunks_iter( stream, progress, chunk_size ), - headers=headers, **kwargs ) # Auto-fix missing 'api/' From 67c41d2f28bdfb5390476fbab1d05ec9c8f1e6c1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:47:29 +0100 Subject: [PATCH 13/14] support query parameters --- ayon_api/server_api.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index a318738b6..b97e39f18 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1525,6 +1525,8 @@ def upload_project_file( *, content_type: Optional[str] = None, filename: Optional[str] = None, + file_id: Optional[str] = None, + activity_id: Optional[str] = None, chunk_size: Optional[int] = None, progress: Optional[TransferProgress] = None, ) -> requests.Response: @@ -1540,6 +1542,8 @@ def upload_project_file( content_type (Optional[str]): MIME type of file. filename (Optional[str]): Server filename, filename from filepath is used if not passed. + file_id (Optional[str]): File id. + activity_id (Optional[str]): To which activity is file related. chunk_size (Optional[int]): Size of chunks that are received in single loop. progress (Optional[TransferProgress]): Object that gives ability @@ -1557,8 +1561,12 @@ def upload_project_file( if not content_type: content_type = "application/octet-stream" + query = prepare_query_string({ + "x_file_id": file_id, + "x_activity_id": activity_id, + }) return self.upload_file( - f"api/projects/{project_name}/files", + f"api/projects/{project_name}/files{query}", filepath, content_type=content_type, filename=filename, @@ -1574,6 +1582,8 @@ def upload_project_file_from_stream( filename: str, *, content_type: Optional[str] = None, + file_id: Optional[str] = None, + activity_id: Optional[str] = None, chunk_size: Optional[int] = None, progress: Optional[TransferProgress] = None, ) -> requests.Response: @@ -1588,6 +1598,8 @@ def upload_project_file_from_stream( stream (StreamType): Stream used as source for upload. filename (str): Name of file on server. content_type (Optional[str]): MIME type of file. + file_id (Optional[str]): File id. + activity_id (Optional[str]): To which activity is file related. chunk_size (Optional[int]): Size of chunks that are received in single loop. progress (Optional[TransferProgress]): Object that gives ability @@ -1603,8 +1615,12 @@ def upload_project_file_from_stream( if not content_type: content_type = "application/octet-stream" + query = prepare_query_string({ + "x_file_id": file_id, + "x_activity_id": activity_id, + }) return self.upload_file_from_stream( - f"api/projects/{project_name}/files", + f"api/projects/{project_name}/files{query}", stream, content_type=content_type, filename=filename, From 842b620a82f4172320ebc8f51707f08021c4769c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:48:31 +0100 Subject: [PATCH 14/14] update public api --- ayon_api/__init__.py | 2 ++ ayon_api/_api.py | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index ae73d2090..0c52eb8ba 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -74,6 +74,7 @@ upload_project_file_from_stream, download_project_file, download_project_file_to_stream, + delete_project_file, upload_file_from_stream, upload_file, upload_reviewable, @@ -357,6 +358,7 @@ "upload_project_file_from_stream", "download_project_file", "download_project_file_to_stream", + "delete_project_file", "upload_file_from_stream", "upload_file", "upload_reviewable", diff --git a/ayon_api/_api.py b/ayon_api/_api.py index cb6becb4c..97833c8c8 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -999,6 +999,8 @@ def upload_project_file( *, content_type: Optional[str] = None, filename: Optional[str] = None, + file_id: Optional[str] = None, + activity_id: Optional[str] = None, chunk_size: Optional[int] = None, progress: Optional[TransferProgress] = None, ) -> requests.Response: @@ -1014,6 +1016,8 @@ def upload_project_file( content_type (Optional[str]): MIME type of file. filename (Optional[str]): Server filename, filename from filepath is used if not passed. + file_id (Optional[str]): File id. + activity_id (Optional[str]): To which activity is file related. chunk_size (Optional[int]): Size of chunks that are received in single loop. progress (Optional[TransferProgress]): Object that gives ability @@ -1029,6 +1033,8 @@ def upload_project_file( filepath=filepath, content_type=content_type, filename=filename, + file_id=file_id, + activity_id=activity_id, chunk_size=chunk_size, progress=progress, ) @@ -1040,6 +1046,8 @@ def upload_project_file_from_stream( filename: str, *, content_type: Optional[str] = None, + file_id: Optional[str] = None, + activity_id: Optional[str] = None, chunk_size: Optional[int] = None, progress: Optional[TransferProgress] = None, ) -> requests.Response: @@ -1054,6 +1062,8 @@ def upload_project_file_from_stream( stream (StreamType): Stream used as source for upload. filename (str): Name of file on server. content_type (Optional[str]): MIME type of file. + file_id (Optional[str]): File id. + activity_id (Optional[str]): To which activity is file related. chunk_size (Optional[int]): Size of chunks that are received in single loop. progress (Optional[TransferProgress]): Object that gives ability @@ -1069,6 +1079,8 @@ def upload_project_file_from_stream( stream=stream, filename=filename, content_type=content_type, + file_id=file_id, + activity_id=activity_id, chunk_size=chunk_size, progress=progress, ) @@ -1148,14 +1160,27 @@ def download_project_file_to_stream( ) +def delete_project_file( + project_name: str, + file_id: str, +) -> None: + """Delete project file. + """ + con = get_server_api_connection() + return con.delete_project_file( + project_name=project_name, + file_id=file_id, + ) + + def upload_file_from_stream( endpoint: str, stream: StreamType, progress: Optional[TransferProgress] = None, request_type: Optional[RequestType] = None, *, - filename: Optional[str] = None, content_type: Optional[str] = None, + filename: Optional[str] = None, **kwargs, ) -> requests.Response: """Upload file to server from bytes. @@ -1171,8 +1196,8 @@ def upload_file_from_stream( to track upload progress. request_type (Optional[RequestType]): Type of request that will be used to upload file. - filename (Optional[str]): Filename of file on server. content_type (Optional[str]): MIME type of the file. + filename (Optional[str]): Filename of file on server. **kwargs (Any): Additional arguments that will be passed to request function. @@ -1186,8 +1211,8 @@ def upload_file_from_stream( stream=stream, progress=progress, request_type=request_type, - filename=filename, content_type=content_type, + filename=filename, **kwargs, ) @@ -1198,8 +1223,8 @@ def upload_file( progress: Optional[TransferProgress] = None, request_type: Optional[RequestType] = None, *, - filename: Optional[str] = None, content_type: Optional[str] = None, + filename: Optional[str] = None, **kwargs, ) -> requests.Response: """Upload file to server. @@ -1230,8 +1255,8 @@ def upload_file( filepath=filepath, progress=progress, request_type=request_type, - filename=filename, content_type=content_type, + filename=filename, **kwargs, )