Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5cb9a7a
Add module-level exports via __all__ in package __init__.py files
Apr 22, 2026
8b4f454
Fix RelationshipInfo subscript access in relationships example
Apr 22, 2026
36da289
Fix remaining RelationshipInfo attribute access in relationships example
Apr 22, 2026
01562a2
Add unit tests for operations package-level exports
Copilot Apr 22, 2026
49a27a3
Add export tests for package-level __all__ symbols
Apr 22, 2026
691a4e0
Consolidate export tests into single file
Apr 22, 2026
5b37c2f
Improve docstrings in test_package_exports.py
Apr 22, 2026
8ad2282
Merge remote-tracking branch 'origin/main' into users/abelmilash/modu…
May 18, 2026
e1e5f78
Drop group-label comments from __all__ lists
May 18, 2026
9cdb668
Add setup.py shim for tools that require it
May 18, 2026
85742b2
Complete __all__ coverage in core/config and export tests
May 18, 2026
23cf7ae
Empty package __all__ lists to prevent doc-tool duplicate pages
May 21, 2026
753e363
Fix Sphinx cross-references in docstrings
May 21, 2026
3ef2746
Merge remote-tracking branch 'origin/main' into users/abelmilash/modu…
May 21, 2026
f6f41db
Remove setup.py and spec-module-level-exports.md from PR
May 21, 2026
034828f
Apply black formatting
May 21, 2026
20311f6
Fix remaining docfx warnings in query_builder docstrings
May 21, 2026
0a8c76c
Revert example, README, and skill import changes to reduce PR diff
May 22, 2026
0146b6b
Update remaining deep imports to module-level imports in docstrings a…
May 22, 2026
cc8f4e4
Remove accidentally committed chunking_verification.py
May 22, 2026
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
2 changes: 1 addition & 1 deletion examples/advanced/dataframe_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from azure.identity import InteractiveBrowserCredential

from PowerPlatform.Dataverse.client import DataverseClient
from PowerPlatform.Dataverse.models.filters import col, raw
from PowerPlatform.Dataverse.models import col, raw


def main():
Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/datascience_risk_assessment.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
from azure.identity import InteractiveBrowserCredential

from PowerPlatform.Dataverse.client import DataverseClient
from PowerPlatform.Dataverse.models.filters import col, raw
from PowerPlatform.Dataverse.models import col, raw

# -- Optional imports (graceful degradation if not installed) ------

Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/fetchxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

from azure.identity import InteractiveBrowserCredential
from PowerPlatform.Dataverse.client import DataverseClient
from PowerPlatform.Dataverse.core.errors import MetadataError
from PowerPlatform.Dataverse.core import MetadataError
import requests

# ---------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/prodev_quick_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
from azure.identity import InteractiveBrowserCredential

from PowerPlatform.Dataverse.client import DataverseClient
from PowerPlatform.Dataverse.models.filters import col
from PowerPlatform.Dataverse.models import col

# -- Table schema names --
# Uses the standard 'new_' publisher prefix (default Dataverse publisher).
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
4 changes: 2 additions & 2 deletions examples/advanced/sql_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
import pandas as pd
from azure.identity import InteractiveBrowserCredential
from PowerPlatform.Dataverse.client import DataverseClient
from PowerPlatform.Dataverse.core.errors import MetadataError
from PowerPlatform.Dataverse.core import MetadataError
import requests

# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -322,7 +322,7 @@ def _run_examples(client):
"infrastructure. Specify columns explicitly instead.\n"
"Use client.query.sql_columns('account') to discover column names."
)
from PowerPlatform.Dataverse.core.errors import ValidationError as _VE
from PowerPlatform.Dataverse.core import ValidationError as _VE

try:
client.query.sql(f"SELECT * FROM {parent_table}")
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
6 changes: 3 additions & 3 deletions src/PowerPlatform/Dataverse/models/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

Example::

from PowerPlatform.Dataverse.models.filters import col, raw
from PowerPlatform.Dataverse.models import col, raw

# Preferred GA idiom — col() proxy
expr = col("statecode") == 0
Expand Down Expand Up @@ -373,7 +373,7 @@ class ColumnProxy:

Example::

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

expr = col("statecode") == 0 # equality
expr = col("revenue") > 1_000_000 # comparison
Expand Down Expand Up @@ -512,7 +512,7 @@ def col(name: str) -> ColumnProxy:

This is the preferred GA idiom for constructing filter expressions::

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

expr = col("statecode") == 0
expr = col("revenue") > 1_000_000
Expand Down
Loading
Loading