Skip to content

Commit 8a2d8a3

Browse files
authored
Merge pull request #55 from microsoft/feature/readme-updates
Prepare for PyPI release: Update changelog, support, and README files + Cleanup inits for re-exports
2 parents 33bcca0 + 6b82982 commit 8a2d8a3

13 files changed

Lines changed: 96 additions & 142 deletions

File tree

.github/workflows/python-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
run: |
2929
python -m pip install --upgrade pip
3030
python -m pip install flake8 black build
31-
if [ -f dev_dependencies.txt ]; then pip install -r dev_dependencies.txt; fi
31+
python -m pip install -e .[dev]
3232
3333
- name: Check format with black
3434
continue-on-error: true # TODO: fix detected formatting errors and remove this line.

CHANGELOG.md

Lines changed: 34 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,42 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7-
8-
## [Unreleased]
5+
## [0.1.0b1] - 2025-11-14
96

107
### Added
11-
- Initial SDK implementation with CRUD operations
12-
- Service principal authentication support
13-
- Interactive browser authentication support
14-
- SQL query execution via `query_sql()`
15-
- File upload capabilities
16-
- Pandas integration for query results
17-
- Structured error handling with specific exception types
18-
- GitHub Actions CI pipeline for automated testing
8+
**Initial beta release** of Microsoft Dataverse SDK for Python
9+
10+
**Core Client & Authentication:**
11+
- Core `DataverseClient` with Azure Identity authentication support
12+
- Secure authentication using Azure Identity credentials (Service Principal, Managed Identity, Interactive Browser)
13+
- TLS 1.2+ encryption for all API communications
14+
- Proper credential handling without exposing secrets in logs
15+
16+
**Data Operations:**
17+
- Complete CRUD operations (create, read, update, delete) for Dataverse records
18+
- Advanced OData query support with filtering, sorting, and expansion
19+
- SQL query execution via `query_sql()` method with result pagination
20+
- Support for batch operations and transaction handling
21+
- File upload capabilities for file and image columns
22+
23+
**Table Management:**
24+
- Table metadata operations (create, inspect, delete custom tables)
25+
26+
**Integration & Analysis:**
27+
- Pandas DataFrame integration for seamless data analysis workflows
28+
29+
**Reliability & Error Handling:**
30+
- Comprehensive error handling with specific exception types (`DataverseError`, `AuthenticationError`, etc.)
31+
- HTTP retry logic with exponential backoff for resilient operations
32+
33+
**Developer Experience:**
34+
- Example scripts demonstrating common integration patterns
35+
- Complete documentation with quickstart guides and API reference
36+
- Modern Python packaging using `pyproject.toml` configuration
37+
38+
**Quality Assurance:**
39+
- Comprehensive test suite with unit and integration tests
40+
- GitHub Actions CI/CD pipeline for automated testing and validation
1941
- Azure DevOps PR validation pipeline
2042

2143
### Changed
@@ -32,42 +54,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3254

3355
### Security
3456
- N/A
35-
36-
## [0.1.0] - TBD
37-
38-
### Added
39-
- First alpha release
40-
- Core Dataverse client with authentication
41-
- Basic CRUD operations (create, get, update, delete)
42-
- OData query support
43-
- SQL query support
44-
- Error handling framework
45-
- Example scripts for common scenarios
46-
47-
---
48-
49-
## Release Notes Template
50-
51-
When creating a new release, copy this template:
52-
53-
```markdown
54-
## [X.Y.Z] - YYYY-MM-DD
55-
56-
### Added
57-
- New features
58-
59-
### Changed
60-
- Changes in existing functionality
61-
62-
### Deprecated
63-
- Soon-to-be removed features
64-
65-
### Removed
66-
- Removed features
67-
68-
### Fixed
69-
- Bug fixes
70-
71-
### Security
72-
- Security improvements or fixes
73-
```

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
A Python client library for Microsoft Dataverse that provides a unified interface for CRUD operations, SQL queries, table metadata management, and file uploads through the Dataverse Web API.
88

9-
**[Source code](https://github.com/microsoft/PowerPlatform-DataverseClient-Python)** | **[Package (PyPI)](https://pypi.org/project/PowerPlatform-Dataverse-Client/)** | **[API reference documentation](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/tree/main/examples)** | **[Product documentation](https://learn.microsoft.com/power-apps/developer/data-platform/)** | **[Samples](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/tree/main/examples)**
9+
**[Source code](https://github.com/microsoft/PowerPlatform-DataverseClient-Python)** | **[Package (PyPI)](https://pypi.org/project/PowerPlatform-Dataverse-Client/)** | **[API reference documentation](https://learn.microsoft.com/en-us/power-apps/developer/data-platform/sdk-python/)** | **[Product documentation](https://learn.microsoft.com/en-us/python/api/dataverse-sdk-docs-python/dataverse-overview?view=dataverse-sdk-python-latest/)** | **[Samples](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/tree/main/examples)**
1010

1111
> [!IMPORTANT]
1212
> This library is currently in **preview**. Preview versions are provided for early access to new features and may contain breaking changes.

SUPPORT.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
1-
# TODO: The maintainer of this repo has not yet edited this file
1+
# Support
22

3-
**REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project?
3+
## How to file issues and get help
44

5-
- **No CSS support:** Fill out this template with information about how to file issues and get help.
6-
- **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps.
7-
- **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide.
5+
This project uses GitHub Issues to track bugs and feature requests. Please search the existing
6+
issues before filing new issues to avoid duplicates. For new issues, file your bug or
7+
feature request as a new Issue.
88

9-
*Then remove this first heading from this SUPPORT.MD file before publishing your repo.*
9+
### Getting Help
1010

11-
# Support
11+
For help and questions about using the Microsoft Dataverse SDK for Python:
1212

13-
## How to file issues and get help
13+
- **Documentation**: Check the [README](README.md) for quickstart guides and examples
14+
- **GitHub Issues**: [File an issue](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/issues) for bugs or feature requests
1415

15-
This project uses GitHub Issues to track bugs and feature requests. Please search the existing
16-
issues before filing new issues to avoid duplicates. For new issues, file your bug or
17-
feature request as a new Issue.
16+
### Reporting Security Issues
17+
18+
Security issues should be reported privately via the [Microsoft Security Response Center (MSRC)](https://aka.ms/opensource/security/msrc) or by emailing [secure@microsoft.com](mailto:secure@microsoft.com). Please do not report security vulnerabilities through public GitHub issues.
19+
20+
## Microsoft Support Policy
1821

19-
For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE
20-
FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER
21-
CHANNEL. WHERE WILL YOU HELP PEOPLE?**.
22+
This is a community-supported project. Support for the Microsoft Dataverse SDK for Python is provided on a best-effort basis through:
2223

23-
## Microsoft Support Policy
24+
- Community contributions via GitHub Issues and Pull Requests
25+
- Documentation and examples in this repository
2426

25-
Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
27+
This project is not covered by Microsoft's standard product support services. For issues with Microsoft Dataverse itself (not this SDK), please use the official Microsoft support channels.

dev_dependencies.txt

Lines changed: 0 additions & 10 deletions
This file was deleted.

requirements.txt

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/PowerPlatform/Dataverse/core/__init__.py

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,4 @@
88
configuration, HTTP client, and error handling.
99
"""
1010

11-
from .auth import AuthManager, TokenPair
12-
from .config import DataverseConfig
13-
from .errors import (
14-
DataverseError,
15-
HttpError,
16-
ValidationError,
17-
MetadataError,
18-
SQLParseError,
19-
)
20-
from .http import HttpClient
21-
22-
__all__ = [
23-
"AuthManager",
24-
"TokenPair",
25-
"DataverseConfig",
26-
"DataverseError",
27-
"HttpError",
28-
"ValidationError",
29-
"MetadataError",
30-
"SQLParseError",
31-
"HttpClient",
32-
]
11+
__all__ = []

src/PowerPlatform/Dataverse/core/errors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def __init__(
5353
self.details = details or {}
5454
self.source = source or "client"
5555
self.is_transient = is_transient
56-
self.timestamp = _dt.datetime.utcnow().isoformat() + "Z"
56+
self.timestamp = _dt.datetime.now(_dt.timezone.utc).isoformat().replace('+00:00', 'Z')
5757

5858
def to_dict(self) -> Dict[str, Any]:
5959
"""

src/PowerPlatform/Dataverse/data/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,4 @@
88
SQL query functionality, and file upload capabilities.
99
"""
1010

11-
from .odata import ODataClient
12-
from .upload import ODataFileUpload
13-
14-
__all__ = ["ODataClient", "ODataFileUpload"]
11+
__all__ = []

src/PowerPlatform/Dataverse/data/odata.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,18 @@
1717
from ..core.http import HttpClient
1818
from .upload import ODataFileUpload
1919
from ..core.errors import *
20-
from ..core import error_codes as ec
20+
from ..core.error_codes import (
21+
http_subcode,
22+
is_transient_status,
23+
VALIDATION_SQL_NOT_STRING,
24+
VALIDATION_SQL_EMPTY,
25+
METADATA_ENTITYSET_NOT_FOUND,
26+
METADATA_ENTITYSET_NAME_MISSING,
27+
METADATA_TABLE_NOT_FOUND,
28+
METADATA_TABLE_ALREADY_EXISTS,
29+
METADATA_COLUMN_NOT_FOUND,
30+
VALIDATION_UNSUPPORTED_CACHE_KIND,
31+
)
2132

2233
from ..__version__ import __version__ as _SDK_VERSION
2334

@@ -121,7 +132,7 @@ def _request(self, method: str, url: str, *, expected: tuple[int, ...] = (200, 2
121132
except Exception:
122133
pass
123134
sc = r.status_code
124-
subcode = ec.http_subcode(sc)
135+
subcode = http_subcode(sc)
125136
correlation_id = headers.get("x-ms-correlation-request-id") or headers.get("x-ms-correlation-id")
126137
request_id = headers.get("x-ms-client-request-id") or headers.get("request-id") or headers.get("x-ms-request-id")
127138
traceparent = headers.get("traceparent")
@@ -132,7 +143,7 @@ def _request(self, method: str, url: str, *, expected: tuple[int, ...] = (200, 2
132143
retry_after = int(ra)
133144
except Exception:
134145
retry_after = None
135-
is_transient = ec.is_transient_status(sc)
146+
is_transient = is_transient_status(sc)
136147
raise HttpError(
137148
msg,
138149
status_code=sc,
@@ -558,9 +569,9 @@ def _query_sql(self, sql: str) -> list[dict[str, Any]]:
558569
Endpoint form: ``GET /{entity_set}?sql=<encoded select>``. The client extracts the logical table name, resolves the entity set (metadata cached), then issues the request. Only a constrained SELECT subset is supported by the platform.
559570
"""
560571
if not isinstance(sql, str):
561-
raise ValidationError("sql must be a string", subcode=ec.VALIDATION_SQL_NOT_STRING)
572+
raise ValidationError("sql must be a string", subcode=VALIDATION_SQL_NOT_STRING)
562573
if not sql.strip():
563-
raise ValidationError("sql must be a non-empty string", subcode=ec.VALIDATION_SQL_EMPTY)
574+
raise ValidationError("sql must be a non-empty string", subcode=VALIDATION_SQL_EMPTY)
564575
sql = sql.strip()
565576

566577
# Extract logical table name via helper (robust to identifiers ending with 'from')
@@ -631,14 +642,14 @@ def _entity_set_from_logical(self, logical: str) -> str:
631642
plural_hint = " (did you pass a plural entity set name instead of the singular logical name?)" if logical.endswith("s") and not logical.endswith("ss") else ""
632643
raise MetadataError(
633644
f"Unable to resolve entity set for logical name '{logical}'. Provide the singular logical name.{plural_hint}",
634-
subcode=ec.METADATA_ENTITYSET_NOT_FOUND,
645+
subcode=METADATA_ENTITYSET_NOT_FOUND,
635646
)
636647
md = items[0]
637648
es = md.get("EntitySetName")
638649
if not es:
639650
raise MetadataError(
640651
f"Metadata response missing EntitySetName for logical '{logical}'.",
641-
subcode=ec.METADATA_ENTITYSET_NAME_MISSING,
652+
subcode=METADATA_ENTITYSET_NAME_MISSING,
642653
)
643654
self._logical_to_entityset_cache[logical] = es
644655
primary_id_attr = md.get("PrimaryIdAttribute")
@@ -1150,7 +1161,7 @@ def _delete_table(self, tablename: str) -> None:
11501161
if not ent or not ent.get("MetadataId"):
11511162
raise MetadataError(
11521163
f"Table '{entity_schema}' not found.",
1153-
subcode=ec.METADATA_TABLE_NOT_FOUND,
1164+
subcode=METADATA_TABLE_NOT_FOUND,
11541165
)
11551166
metadata_id = ent["MetadataId"]
11561167
url = f"{self.api}/EntityDefinitions({metadata_id})"
@@ -1191,7 +1202,7 @@ def _create_table(
11911202
if ent:
11921203
raise MetadataError(
11931204
f"Table '{entity_schema}' already exists.",
1194-
subcode=ec.METADATA_TABLE_ALREADY_EXISTS,
1205+
subcode=METADATA_TABLE_ALREADY_EXISTS,
11951206
)
11961207

11971208
created_cols: List[str] = []
@@ -1254,7 +1265,7 @@ def _create_columns(
12541265
if not ent or not ent.get("MetadataId"):
12551266
raise MetadataError(
12561267
f"Table '{entity_schema}' not found.",
1257-
subcode=ec.METADATA_TABLE_NOT_FOUND,
1268+
subcode=METADATA_TABLE_NOT_FOUND,
12581269
)
12591270

12601271
metadata_id = ent.get("MetadataId")
@@ -1317,7 +1328,7 @@ def _delete_columns(
13171328
if not ent or not ent.get("MetadataId"):
13181329
raise MetadataError(
13191330
f"Table '{entity_schema}' not found.",
1320-
subcode=ec.METADATA_TABLE_NOT_FOUND,
1331+
subcode=METADATA_TABLE_NOT_FOUND,
13211332
)
13221333

13231334
metadata_id = ent.get("MetadataId")
@@ -1330,7 +1341,7 @@ def _delete_columns(
13301341
if not attr_meta:
13311342
raise MetadataError(
13321343
f"Column '{schema_name}' not found on table '{entity_schema}'.",
1333-
subcode=ec.METADATA_COLUMN_NOT_FOUND,
1344+
subcode=METADATA_COLUMN_NOT_FOUND,
13341345
)
13351346

13361347
attr_metadata_id = attr_meta.get("MetadataId")
@@ -1372,7 +1383,7 @@ def _flush_cache(
13721383
if k != "picklist":
13731384
raise ValidationError(
13741385
f"Unsupported cache kind '{kind}' (only 'picklist' is implemented)",
1375-
subcode=ec.VALIDATION_UNSUPPORTED_CACHE_KIND,
1386+
subcode=VALIDATION_UNSUPPORTED_CACHE_KIND,
13761387
)
13771388

13781389
removed = len(self._picklist_label_cache)

0 commit comments

Comments
 (0)