Skip to content

Commit ed93434

Browse files
committed
feat: add file upload, download, and delete operations to DataverseClient
1 parent 8aa9945 commit ed93434

3 files changed

Lines changed: 124 additions & 6 deletions

File tree

src/PowerPlatform/Dataverse/client.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,80 @@ def upload_file(
653653
)
654654
return None
655655

656+
# File download
657+
def download_file(
658+
self,
659+
table_schema_name: str,
660+
record_id: str,
661+
file_name_attribute: str,
662+
) -> tuple[str, bytes]:
663+
"""
664+
Download a file from a Dataverse file column.
665+
:param table_schema_name: Schema name of the table, e.g. ``"account"`` or ``"new_MyTestTable"``.
666+
:type table_schema_name: str
667+
:param record_id: GUID of the record
668+
:type record_id: str
669+
:param file_name_attribute: Logical name of the file column attribute.
670+
:type file_name_attribute: str
671+
672+
:return: Tuple with file name and file content.
673+
674+
:raises ~PowerPlatform.Dataverse.core.errors.HttpError: If the download fails or the file column is empty
675+
676+
Example:
677+
Download a PDF file::
678+
679+
client.download_file(
680+
table_schema_name="account",
681+
record_id=account_id,
682+
file_name_attribute="new_contract"
683+
)
684+
685+
"""
686+
od = self._get_odata()
687+
entity_set = od._entity_set_from_schema_name(table_schema_name)
688+
return od._download_file(
689+
entity_set,
690+
record_id,
691+
file_name_attribute,
692+
)
693+
694+
# File delete
695+
def delete_file(
696+
self,
697+
table_schema_name: str,
698+
record_id: str,
699+
file_name_attribute: str,
700+
) -> None:
701+
"""
702+
Delete a file from a Dataverse file column.
703+
:param table_schema_name: Schema name of the table, e.g. ``"account"`` or ``"new_MyTestTable"``.
704+
:param record_id: GUID of the record
705+
:param file_name_attribute: Logical name of the file column attribute.
706+
707+
708+
:return: None
709+
:raises ~PowerPlatform.Dataverse.core.errors.HttpError: If the delete fails
710+
711+
Example:
712+
Delete a file::
713+
714+
client.delete_file(
715+
table_schema_name="account",
716+
record_id=account_id,
717+
file_name_attribute="new_contract"
718+
)
719+
720+
"""
721+
od = self._get_odata()
722+
entity_set = od._entity_set_from_schema_name(table_schema_name)
723+
od._delete_file(
724+
entity_set,
725+
record_id,
726+
file_name_attribute,
727+
)
728+
729+
656730
# Cache utilities
657731
def flush_cache(self, kind) -> int:
658732
"""

src/PowerPlatform/Dataverse/data/_upload.py renamed to src/PowerPlatform/Dataverse/data/_file_operations.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT license.
33

4-
"""File upload helpers."""
4+
"""File operations helpers."""
55

66
from __future__ import annotations
77

88
from typing import Optional
99

1010

11-
class _ODataFileUpload:
12-
"""File upload capabilities (small + chunk) with auto selection."""
11+
class _ODataFileOperations:
12+
"""Provides file management capabilities including upload, download, and delete operations."""
1313

1414
def _upload_file(
1515
self,
@@ -127,7 +127,8 @@ def _upload_file_chunk(
127127
None
128128
Returns nothing on success. Any failure raises an exception.
129129
"""
130-
import os, math
130+
import os
131+
import math
131132
from urllib.parse import quote
132133

133134
if not record_id:
@@ -176,3 +177,46 @@ def _upload_file_chunk(
176177
self._request("patch", location, headers=c_headers, data=chunk, expected=(206, 204))
177178
uploaded_bytes += len(chunk)
178179
return None
180+
181+
def _download_file(
182+
self,
183+
entity_set: str,
184+
record_id: str,
185+
file_name_attribute: str,
186+
) -> tuple[str, bytes]:
187+
"""
188+
Download a file from a Dataverse file column.
189+
:param entity_set: Source entity set (plural logical name), e.g. "accounts".
190+
:param record_id: GUID of the record
191+
:param file_name_attribute: Logical name of the file column attribute.
192+
193+
:return: Tuple with file name and file content.
194+
"""
195+
196+
key = self._format_key(record_id)
197+
url = f"{self.api}/{entity_set}{key}/{file_name_attribute}/$value"
198+
response = self._request("get", url)
199+
file_name = response.headers.get('x-ms-file-name')
200+
if file_name is None:
201+
raise ValueError("Response is missing the 'x-ms-file-name' header. The file column may be empty or the server did not return the expected header.")
202+
return file_name, response.content
203+
def _delete_file(
204+
self,
205+
entity_set: str,
206+
record_id: str,
207+
file_name_attribute: str,
208+
) -> None:
209+
"""
210+
Delete a file from a Dataverse file column.
211+
:param entity_set: Target entity set (plural logical name), e.g. "accounts".
212+
:param record_id: GUID of the record
213+
:param file_name_attribute: Logical name of the file column attribute.
214+
215+
:return: None
216+
"""
217+
218+
key = self._format_key(record_id)
219+
url = f"{self.api}/{entity_set}{key}/{file_name_attribute}"
220+
self._request("delete", url)
221+
222+
return None

src/PowerPlatform/Dataverse/data/_odata.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import importlib.resources as ir
1616

1717
from ..core._http import _HttpClient
18-
from ._upload import _ODataFileUpload
18+
from ._file_operations import _ODataFileOperations
1919
from ..core.errors import *
2020
from ..core._error_codes import (
2121
_http_subcode,
@@ -37,7 +37,7 @@
3737
_GUID_RE = re.compile(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")
3838

3939

40-
class _ODataClient(_ODataFileUpload):
40+
class _ODataClient(_ODataFileOperations):
4141
"""Dataverse Web API client: CRUD, SQL-over-API, and table metadata helpers."""
4242

4343
@staticmethod

0 commit comments

Comments
 (0)