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
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ a PATCH request; multiple items use the `UpsertMultiple` bulk action.
> upsert requests will be rejected by Dataverse with a 400 error.

```python
from PowerPlatform.Dataverse.models.upsert import UpsertItem
from PowerPlatform.Dataverse.models import UpsertItem

# Upsert a single record
client.records.upsert("account", [
Expand Down Expand Up @@ -346,7 +346,7 @@ query = (client.query.builder("contact")
For complex logic (OR, NOT, grouping), compose expressions with `&`, `|`, `~`:

```python
from PowerPlatform.Dataverse.models.filters import col
from PowerPlatform.Dataverse.models import col

# OR conditions: (statecode = 0 OR statecode = 1) AND revenue > 100k
for record in (client.query.builder("account")
Expand Down Expand Up @@ -397,7 +397,7 @@ if record:
**Nested expand with options** -- expand navigation properties with `$select`, `$filter`, `$orderby`, and `$top`:

```python
from PowerPlatform.Dataverse.models.query_builder import ExpandOption
from PowerPlatform.Dataverse.models import ExpandOption

# Expand related tasks with filtering and sorting
for record in (client.query.builder("account")
Expand Down Expand Up @@ -614,12 +614,14 @@ client.tables.delete("new_Product")
Create relationships between tables using the relationship API. For a complete working example, see [examples/advanced/relationships.py](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/blob/main/examples/advanced/relationships.py).

```python
from PowerPlatform.Dataverse.models.relationship import (
from PowerPlatform.Dataverse.models import (
CascadeConfiguration,
Label,
LocalizedLabel,
LookupAttributeMetadata,
OneToManyRelationshipMetadata,
ManyToManyRelationshipMetadata,
OneToManyRelationshipMetadata,
)
from PowerPlatform.Dataverse.models.labels import Label, LocalizedLabel

# Create a one-to-many relationship: Department (1) -> Employee (N)
# This adds a "Department" lookup field to the Employee table
Expand Down Expand Up @@ -821,7 +823,7 @@ The client raises structured exceptions for different error scenarios:

```python
from PowerPlatform.Dataverse.client import DataverseClient
from PowerPlatform.Dataverse.core.errors import HttpError, ValidationError
from PowerPlatform.Dataverse.core import HttpError, ValidationError

try:
client.records.retrieve("account", "invalid-id")
Expand Down Expand Up @@ -862,8 +864,7 @@ Enable file-based HTTP logging to capture all requests and responses for debuggi

```python
from PowerPlatform.Dataverse.client import DataverseClient
from PowerPlatform.Dataverse.core.config import DataverseConfig
from PowerPlatform.Dataverse.core.log_config import LogConfig
from PowerPlatform.Dataverse.core import DataverseConfig, LogConfig

log_cfg = LogConfig(
log_folder="./my_logs", # Directory for log files (created if missing)
Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/alternate_keys_upsert.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import time

from PowerPlatform.Dataverse.client import DataverseClient
from PowerPlatform.Dataverse.models.upsert import UpsertItem
from PowerPlatform.Dataverse.models import UpsertItem
from azure.identity import InteractiveBrowserCredential # type: ignore

# --- Config ---
Expand Down
9 changes: 5 additions & 4 deletions examples/advanced/relationships.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
import time
from azure.identity import InteractiveBrowserCredential
from PowerPlatform.Dataverse.client import DataverseClient
from PowerPlatform.Dataverse.models.relationship import (
from PowerPlatform.Dataverse.models import (
CascadeConfiguration,
Label,
LocalizedLabel,
LookupAttributeMetadata,
OneToManyRelationshipMetadata,
ManyToManyRelationshipMetadata,
CascadeConfiguration,
OneToManyRelationshipMetadata,
)
from PowerPlatform.Dataverse.models.labels import Label, LocalizedLabel
from PowerPlatform.Dataverse.common.constants import (
CASCADE_BEHAVIOR_NO_CASCADE,
CASCADE_BEHAVIOR_REMOVE_LINK,
Expand Down
5 changes: 2 additions & 3 deletions examples/advanced/walkthrough.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@
from enum import IntEnum
from azure.identity import InteractiveBrowserCredential
from PowerPlatform.Dataverse.client import DataverseClient
from PowerPlatform.Dataverse.core.errors import MetadataError
from PowerPlatform.Dataverse.models.filters import col
from PowerPlatform.Dataverse.models.query_builder import ExpandOption
from PowerPlatform.Dataverse.core import MetadataError
from PowerPlatform.Dataverse.models import ExpandOption, col
import requests


Expand Down
13 changes: 7 additions & 6 deletions examples/basic/functional_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,20 @@

# Import SDK components (assumes installation is already validated)
from PowerPlatform.Dataverse.client import DataverseClient
from PowerPlatform.Dataverse.core.errors import HttpError, MetadataError
from PowerPlatform.Dataverse.models.relationship import (
from PowerPlatform.Dataverse.core import HttpError, MetadataError
from PowerPlatform.Dataverse.models import (
CascadeConfiguration,
Label,
LocalizedLabel,
LookupAttributeMetadata,
OneToManyRelationshipMetadata,
ManyToManyRelationshipMetadata,
CascadeConfiguration,
OneToManyRelationshipMetadata,
UpsertItem,
)
from PowerPlatform.Dataverse.models.labels import Label, LocalizedLabel
from PowerPlatform.Dataverse.common.constants import (
CASCADE_BEHAVIOR_NO_CASCADE,
CASCADE_BEHAVIOR_REMOVE_LINK,
)
from PowerPlatform.Dataverse.models.upsert import UpsertItem
from azure.identity import InteractiveBrowserCredential


Expand Down
9 changes: 3 additions & 6 deletions examples/basic/installation_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,7 @@
from typing import Optional
from datetime import datetime

from PowerPlatform.Dataverse.operations.records import RecordOperations
from PowerPlatform.Dataverse.operations.query import QueryOperations
from PowerPlatform.Dataverse.operations.tables import TableOperations
from PowerPlatform.Dataverse.operations.files import FileOperations
from PowerPlatform.Dataverse.operations import FileOperations, QueryOperations, RecordOperations, TableOperations


def validate_imports():
Expand All @@ -81,11 +78,11 @@ def validate_imports():
print(f" [OK] Client class: PowerPlatform.Dataverse.client.DataverseClient")

# Test submodule imports
from PowerPlatform.Dataverse.core.errors import HttpError, MetadataError
from PowerPlatform.Dataverse.core import HttpError, MetadataError

print(f" [OK] Core errors: HttpError, MetadataError")

from PowerPlatform.Dataverse.core.config import DataverseConfig
from PowerPlatform.Dataverse.core import DataverseConfig

print(f" [OK] Core config: DataverseConfig")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ client.records.update("account", [id1, id2, id3], {"industry": "Technology"})
Creates or updates records identified by alternate keys. Single item -> PATCH; multiple items -> `UpsertMultiple` bulk action.
> **Prerequisite**: The table must have an alternate key configured in Dataverse for the columns used in `alternate_key`. Without it, Dataverse will reject the request with a 400 error.
```python
from PowerPlatform.Dataverse.models.upsert import UpsertItem
from PowerPlatform.Dataverse.models import UpsertItem

# Single upsert
client.records.upsert("account", [
Expand Down Expand Up @@ -403,12 +403,12 @@ client.tables.delete("new_Product")

#### Create One-to-Many Relationship
```python
from PowerPlatform.Dataverse.models.relationship import (
LookupAttributeMetadata,
OneToManyRelationshipMetadata,
from PowerPlatform.Dataverse.models import (
CascadeConfiguration,
Label,
LocalizedLabel,
CascadeConfiguration,
LookupAttributeMetadata,
OneToManyRelationshipMetadata,
)
from PowerPlatform.Dataverse.common.constants import CASCADE_BEHAVIOR_REMOVE_LINK

Expand All @@ -435,7 +435,7 @@ print(f"Created lookup field: {result['lookup_schema_name']}")

#### Create Many-to-Many Relationship
```python
from PowerPlatform.Dataverse.models.relationship import ManyToManyRelationshipMetadata
from PowerPlatform.Dataverse.models import ManyToManyRelationshipMetadata

relationship = ManyToManyRelationshipMetadata(
schema_name="new_employee_project",
Expand Down Expand Up @@ -532,12 +532,12 @@ print(f"Succeeded: {len(result.succeeded)}, Failed: {len(result.failed)}")
The SDK provides structured exceptions with detailed error information:

```python
from PowerPlatform.Dataverse.core.errors import (
from PowerPlatform.Dataverse.core import (
DataverseError,
HttpError,
ValidationError,
MetadataError,
SQLParseError
SQLParseError,
ValidationError,
)
from PowerPlatform.Dataverse.client import DataverseClient

Expand Down
2 changes: 1 addition & 1 deletion src/PowerPlatform/Dataverse/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class DataverseClient:
This client provides a simple, stable interface for interacting with Dataverse environments
through the Web API. It handles authentication via Azure Identity and delegates HTTP operations
to an internal :class:`~PowerPlatform.Dataverse.data._odata._ODataClient`.
to an internal OData client.
Key capabilities:
- OData CRUD operations: create, read, update, delete records
Expand Down
6 changes: 5 additions & 1 deletion src/PowerPlatform/Dataverse/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@
configuration, HTTP client, and error handling.
"""

__all__ = []
from .config import DataverseConfig, OperationContext
from .errors import DataverseError, HttpError, MetadataError, SQLParseError, ValidationError
from .log_config import LogConfig

__all__: list[str] = []
2 changes: 2 additions & 0 deletions src/PowerPlatform/Dataverse/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
if TYPE_CHECKING:
from .log_config import LogConfig

__all__ = ["DataverseConfig", "OperationContext"]

# key=value pairs separated by semicolons.
# Keys: alphanumeric, hyphens, underscores.
# Values: alphanumeric, hyphens, underscores, dots, slashes.
Expand Down
2 changes: 1 addition & 1 deletion src/PowerPlatform/Dataverse/core/log_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""
Local file logging configuration for Dataverse SDK HTTP diagnostics.

Provides :class:`LogConfig`, an opt-in configuration for writing request/response
Provides :class:`~PowerPlatform.Dataverse.core.log_config.LogConfig`, an opt-in configuration for writing request/response
traces to ``.log`` files with automatic header redaction and timestamped filenames.
"""

Expand Down
32 changes: 22 additions & 10 deletions src/PowerPlatform/Dataverse/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,31 @@
Provides dataclasses and helpers for Dataverse entities:

- :class:`~PowerPlatform.Dataverse.models.query_builder.QueryBuilder`: Fluent query builder.
- :mod:`~PowerPlatform.Dataverse.models.filters`: Composable OData filter expressions.
- :mod:`~PowerPlatform.Dataverse.models.filters`: Composable OData filter expressions
via :func:`~PowerPlatform.Dataverse.models.filters.col` and
:func:`~PowerPlatform.Dataverse.models.filters.raw`.
- :class:`~PowerPlatform.Dataverse.models.record.QueryResult`: Iterable result wrapper.
- :class:`~PowerPlatform.Dataverse.models.record.Record`: Dataverse entity record.
- :class:`~PowerPlatform.Dataverse.models.upsert.UpsertItem`: Upsert operation item.

Import directly from the specific module, e.g.::

from PowerPlatform.Dataverse.models.query_builder import QueryBuilder
from PowerPlatform.Dataverse.models.filters import col, raw
from PowerPlatform.Dataverse.models.record import QueryResult
- :class:`~PowerPlatform.Dataverse.models.fetchxml_query.FetchXmlQuery`: FetchXML query object.
- :class:`~PowerPlatform.Dataverse.models.protocol.DataverseModel`: Typed-model protocol.
"""

from .filters import col, raw
from .batch import BatchItemResponse, BatchResult
from .fetchxml_query import FetchXmlQuery
from .filters import ColumnProxy, FilterExpression, col, raw
from .labels import Label, LocalizedLabel
from .protocol import DataverseModel
from .record import QueryResult
from .query_builder import ExpandOption, QueryBuilder, QueryParams
from .record import QueryResult, Record
from .relationship import (
CascadeConfiguration,
LookupAttributeMetadata,
ManyToManyRelationshipMetadata,
OneToManyRelationshipMetadata,
RelationshipInfo,
)
from .table_info import AlternateKeyInfo, ColumnInfo, TableInfo
from .upsert import UpsertItem

__all__ = ["col", "raw", "DataverseModel", "QueryResult"]
__all__: list[str] = []
6 changes: 3 additions & 3 deletions src/PowerPlatform/Dataverse/models/fetchxml_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def __init__(self, xml: str, entity_name: str, client: "DataverseClient") -> Non
self._client = client

def execute(self) -> QueryResult:
"""Execute the FetchXML query and return all results as a :class:`QueryResult`.
"""Execute the FetchXML query and return all results as a :class:`~PowerPlatform.Dataverse.models.record.QueryResult`.

Blocking — fetches all pages upfront and holds every record in memory before
returning. Simple for small-to-medium result sets; use :meth:`execute_pages`
Expand All @@ -72,7 +72,7 @@ def execute(self) -> QueryResult:
return QueryResult(all_records)

def execute_pages(self) -> Iterator[QueryResult]:
"""Lazily yield one :class:`QueryResult` per HTTP page.
"""Lazily yield one :class:`~PowerPlatform.Dataverse.models.record.QueryResult` per HTTP page.

Streaming — each iteration fires one HTTP request and yields one page.
Prefer over :meth:`execute` when:
Expand All @@ -84,7 +84,7 @@ def execute_pages(self) -> Iterator[QueryResult]:

One-shot — do not iterate more than once.

:return: Iterator of per-page :class:`QueryResult` objects.
:return: Iterator of per-page :class:`~PowerPlatform.Dataverse.models.record.QueryResult` objects.
:rtype: Iterator[:class:`~PowerPlatform.Dataverse.models.record.QueryResult`]

Example::
Expand Down
18 changes: 16 additions & 2 deletions src/PowerPlatform/Dataverse/operations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@
SDK operations into logical groups: records, query, and tables.
"""

from typing import List
from .batch import (
BatchDataFrameOperations,
BatchOperations,
BatchQueryOperations,
BatchRecordOperations,
BatchRequest,
BatchTableOperations,
ChangeSet,
ChangeSetRecordOperations,
)
from .dataframe import DataFrameOperations
from .files import FileOperations
from .query import QueryOperations
from .records import RecordOperations
from .tables import TableOperations
Comment thread
abelmilash-msft marked this conversation as resolved.

__all__: List[str] = []
__all__: list[str] = []
12 changes: 6 additions & 6 deletions src/PowerPlatform/Dataverse/operations/batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class ChangeSetRecordOperations:
create/update/delete). Only write operations are allowed — GET is not
permitted inside a changeset.

Do not instantiate directly; use :attr:`ChangeSet.records`.
Do not instantiate directly; use ``ChangeSet.records``.
"""

def __init__(self, cs_internal: _ChangeSet) -> None:
Expand Down Expand Up @@ -159,7 +159,7 @@ class ChangeSet:
A transactional group of single-record write operations.

All operations succeed or are rolled back together. Use as a context
manager or call :attr:`records` to add operations directly.
manager or call ``records`` to add operations directly.

Do not instantiate directly; use :meth:`BatchRequest.changeset`.

Expand Down Expand Up @@ -435,7 +435,7 @@ def list(

:param table: Table schema name (e.g. ``"account"``).
:type table: :class:`str`
:param filter: Optional OData ``$filter`` expression or :class:`FilterExpression`.
:param filter: Optional OData ``$filter`` expression or :class:`~PowerPlatform.Dataverse.models.filters.FilterExpression`.
:type filter: str or FilterExpression or None
:param select: Optional list of column logical names to include.
:type select: list[str] or None
Expand Down Expand Up @@ -597,7 +597,7 @@ def add_columns(self, table: str, columns: Dict[str, Any]) -> None:
Add column-create operations to the batch (one per column).

The table's ``MetadataId`` is resolved at execute time. Each column
produces one entry in :attr:`BatchResult.responses`.
produces one entry in :attr:`~PowerPlatform.Dataverse.models.batch.BatchResult.responses`.

:param table: Schema name of the target table.
:type table: :class:`str`
Expand All @@ -612,7 +612,7 @@ def remove_columns(self, table: str, columns: Union[str, List[str]]) -> None:

The table's ``MetadataId`` and each column's ``MetadataId`` are resolved
at execute time. Each column produces one entry in
:attr:`BatchResult.responses`.
:attr:`~PowerPlatform.Dataverse.models.batch.BatchResult.responses`.

:param table: Schema name of the target table.
:type table: :class:`str`
Expand Down Expand Up @@ -949,7 +949,7 @@ class BatchRequest:
Builder for constructing and executing a Dataverse OData ``$batch`` request.

Obtain via :meth:`BatchOperations.new` (``client.batch.new()``). Add operations
through :attr:`records`, :attr:`tables`, :attr:`query`, and :attr:`dataframe`,
through ``records``, ``tables``, ``query``, and ``dataframe``,
optionally group writes
into a :meth:`changeset`, then call :meth:`execute`.

Expand Down
Loading
Loading