Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/PowerPlatform/Dataverse/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,79 @@ def upload_file(
)
return None

# File download
def download_file(
self,
table_schema_name: str,
record_id: str,
file_name_attribute: str,
) -> tuple[str, bytes]:
"""
Download a file from a Dataverse file column.
:param table_schema_name: Schema name of the table, e.g. ``"account"`` or ``"new_MyTestTable"``.
:type table_schema_name: str
:param record_id: GUID of the record
:type record_id: str
:param file_name_attribute: Logical name of the file column attribute.
:type file_name_attribute: str

:return: Tuple with file name and file content.

:raises ~PowerPlatform.Dataverse.core.errors.HttpError: If the download fails or the file column is empty

Example:
Download a PDF file::

client.download_file(
table_schema_name="account",
record_id=account_id,
file_name_attribute="new_contract"
)

"""
od = self._get_odata()
entity_set = od._entity_set_from_schema_name(table_schema_name)
return od._download_file(
entity_set,
record_id,
file_name_attribute,
)

# File delete
def delete_file(
self,
table_schema_name: str,
record_id: str,
file_name_attribute: str,
) -> None:
"""
Delete a file from a Dataverse file column.
:param table_schema_name: Schema name of the table, e.g. ``"account"`` or ``"new_MyTestTable"``.
:param record_id: GUID of the record
:param file_name_attribute: Logical name of the file column attribute.


:return: None
:raises ~PowerPlatform.Dataverse.core.errors.HttpError: If the delete fails

Example:
Delete a file::

client.delete_file(
table_schema_name="account",
record_id=account_id,
file_name_attribute="new_contract"
)

"""
od = self._get_odata()
entity_set = od._entity_set_from_schema_name(table_schema_name)
od._delete_file(
entity_set,
record_id,
file_name_attribute,
)

# Cache utilities
def flush_cache(self, kind) -> int:
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

"""File upload helpers."""
"""File operations helpers."""

from __future__ import annotations

from typing import Optional


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

def _upload_file(
self,
Expand Down Expand Up @@ -127,7 +127,8 @@ def _upload_file_chunk(
None
Returns nothing on success. Any failure raises an exception.
"""
import os, math
import os
import math
from urllib.parse import quote

if not record_id:
Expand Down Expand Up @@ -176,3 +177,49 @@ def _upload_file_chunk(
self._request("patch", location, headers=c_headers, data=chunk, expected=(206, 204))
uploaded_bytes += len(chunk)
return None

def _download_file(
self,
entity_set: str,
record_id: str,
file_name_attribute: str,
) -> tuple[str, bytes]:
"""
Download a file from a Dataverse file column.
:param entity_set: Source entity set (plural logical name), e.g. "accounts".
:param record_id: GUID of the record
:param file_name_attribute: Logical name of the file column attribute.

:return: Tuple with file name and file content.
"""

key = self._format_key(record_id)
url = f"{self.api}/{entity_set}{key}/{file_name_attribute}/$value"
response = self._request("get", url, expected=(200,))
file_name = response.headers.get("x-ms-file-name")
if file_name is None:
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."
)
return file_name, response.content

def _delete_file(
self,
entity_set: str,
record_id: str,
file_name_attribute: str,
) -> None:
"""
Delete a file from a Dataverse file column.
:param entity_set: Target entity set (plural logical name), e.g. "accounts".
:param record_id: GUID of the record
:param file_name_attribute: Logical name of the file column attribute.

:return: None
"""

key = self._format_key(record_id)
url = f"{self.api}/{entity_set}{key}/{file_name_attribute}"
self._request("delete", url, expected=(204,))

return None
4 changes: 2 additions & 2 deletions src/PowerPlatform/Dataverse/data/_odata.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import importlib.resources as ir

from ..core._http import _HttpClient
from ._upload import _ODataFileUpload
from ._file_operations import _ODataFileOperations
from ..core.errors import *
from ..core._error_codes import (
_http_subcode,
Expand All @@ -37,7 +37,7 @@
_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}")


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

@staticmethod
Expand Down