Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 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
4a9aacb
Use .errors submodule path for error class imports
May 22, 2026
a819aea
Remove accidentally committed example output CSVs + gitignore them
May 22, 2026
c1fb5e6
Revert .gitignore change
May 22, 2026
4623ee9
Restore package-level __all__ and add DataverseClient to top-level
May 22, 2026
caa24fe
Merge remote-tracking branch 'origin/main' into users/abelmilash/modu…
May 22, 2026
2a377b3
Drop unnecessary changes: revert example docstring imports, remove re…
May 22, 2026
1404b92
Revert example script import changes
May 22, 2026
3c880d0
Restore src/ changes accidentally reverted in 1404b92
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
17 changes: 9 additions & 8 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 @@ -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
17 changes: 14 additions & 3 deletions src/PowerPlatform/Dataverse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@

from importlib.metadata import version

# Set __version__ FIRST. Downstream modules (e.g. data/_odata_base.py) import
# this back from the top-level package, so it must be bound before any
# transitive import of those modules runs.
__version__ = version("PowerPlatform-Dataverse-Client")

from .client import DataverseClient
from .models.filters import col, raw
from .models.protocol import DataverseModel
from .models.record import QueryResult

__version__ = version("PowerPlatform-Dataverse-Client")

__all__ = ["__version__", "col", "raw", "DataverseModel", "QueryResult"]
__all__ = [
"DataverseClient",
"DataverseModel",
"QueryResult",
"__version__",
"col",
"raw",
]
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 @@ -535,9 +535,9 @@ The SDK provides structured exceptions with detailed error information:
from PowerPlatform.Dataverse.core.errors 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
15 changes: 14 additions & 1 deletion src/PowerPlatform/Dataverse/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,17 @@
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__ = [
"DataverseConfig",
"DataverseError",
"HttpError",
"LogConfig",
"MetadataError",
"OperationContext",
"SQLParseError",
"ValidationError",
]
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
57 changes: 47 additions & 10 deletions src/PowerPlatform/Dataverse/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,56 @@
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__ = [
"AlternateKeyInfo",
"BatchItemResponse",
"BatchResult",
"CascadeConfiguration",
"ColumnInfo",
"ColumnProxy",
"DataverseModel",
"ExpandOption",
"FetchXmlQuery",
"FilterExpression",
"Label",
"LocalizedLabel",
"LookupAttributeMetadata",
"ManyToManyRelationshipMetadata",
"OneToManyRelationshipMetadata",
"QueryBuilder",
"QueryParams",
"QueryResult",
"Record",
"RelationshipInfo",
"TableInfo",
"UpsertItem",
"col",
"raw",
]
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
22 changes: 11 additions & 11 deletions src/PowerPlatform/Dataverse/models/query_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
Example::

# Via client (recommended) -- flat iteration over records
from PowerPlatform.Dataverse.models.filters import col
from PowerPlatform.Dataverse.models import col

for record in (client.query.builder("account")
.select("name", "revenue")
Expand All @@ -22,7 +22,7 @@
print(record["name"])

# With composable expression tree
from PowerPlatform.Dataverse.models.filters import col, raw
from PowerPlatform.Dataverse.models import col, raw

for record in (client.query.builder("account")
.select("name", "revenue")
Expand Down Expand Up @@ -74,7 +74,7 @@


class QueryParams(TypedDict, total=False):
"""Typed dictionary returned by :meth:`QueryBuilder.build`.
"""Typed dictionary returned by ``QueryBuilder.build()``.

Provides IDE autocomplete when passing build results to
``client.records.list()`` manually.
Expand Down Expand Up @@ -187,7 +187,7 @@ class _QueryBuilderBase:
Holds all query state and chaining methods (``select``, ``where``,
``order_by``, ``top``, ``page_size``, ``count``, ``expand``,
``include_annotations``, ``include_formatted_values``) and
:meth:`build`.
``build()``.

Subclasses add execution: :class:`QueryBuilder` for sync clients,
:class:`~PowerPlatform.Dataverse.aio.models.async_query_builder.AsyncQueryBuilder`
Expand Down Expand Up @@ -245,7 +245,7 @@ def where(self, expression: filters.FilterExpression) -> Self:

Example::

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

query = (QueryBuilder("account")
.where((col("statecode") == 0) | (col("statecode") == 1))
Expand Down Expand Up @@ -451,7 +451,7 @@ class QueryBuilder(_QueryBuilderBase):
"""Fluent interface for building and executing OData queries against a sync client.

Provides method chaining for constructing complex queries with
composable filter expressions. Can be used standalone (via :meth:`build`)
composable filter expressions. Can be used standalone (via ``build()``)
or bound to a client (via :meth:`execute`).

:param table: Table schema name to query.
Expand All @@ -461,7 +461,7 @@ class QueryBuilder(_QueryBuilderBase):
Example:
Standalone query construction::

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

query = (QueryBuilder("account")
.select("name")
Expand All @@ -483,7 +483,7 @@ def execute(self, *, by_page=_BY_PAGE_UNSET) -> Union[QueryResult, Iterator[Quer

This method is only available when the QueryBuilder was created
via ``client.query.builder(table)``. Standalone ``QueryBuilder``
instances should use :meth:`build` to get parameters and pass them
instances should use ``build()`` to get parameters and pass them
to ``client.records.list()`` manually.

At least one of ``select()``, ``where()``, or ``top()`` must be
Expand All @@ -506,7 +506,7 @@ def execute(self, *, by_page=_BY_PAGE_UNSET) -> Union[QueryResult, Iterator[Quer

Example::

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

for record in (client.query.builder("account")
.select("name")
Expand Down Expand Up @@ -587,7 +587,7 @@ def execute_pages(self) -> Iterator[QueryResult]:

Example::

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

for page in (client.query.builder("account")
.select("name")
Expand Down Expand Up @@ -652,7 +652,7 @@ def to_dataframe(self) -> pd.DataFrame:

Example::

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

df = (client.query.builder("account")
.select("name", "telephone1")
Expand Down
Loading
Loading