From eae3e9995a22ebff1d079c3211b9bd05822940ca Mon Sep 17 00:00:00 2001 From: Suyash Kshirsagar Date: Mon, 10 Nov 2025 13:40:33 -0800 Subject: [PATCH 1/9] Rename package to PowerPlatform-Dataverse-Client, update module namespace, and update package version schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Package name: dataverse-client-python โ†’ PowerPlatform-Dataverse-Client - Module name: dataverse_sdk โ†’ PowerPlatform.Dataverse - Version: 0.1.0 โ†’ 0.1.0b1 (beta release) BREAKING CHANGE: Import statements must be updated from 'from dataverse_sdk import DataverseClient' to 'from PowerPlatform.Dataverse import DataverseClient' Updates: - Restructured src/ directory with proper namespace packaging - Updated all examples to use new import structure - Updated all tests to use new namespace - Updated README.md with new installation and usage examples - Updated pyproject.toml for namespace package discovery - Package builds successfully and imports work correctly --- README.md | 4 +- examples/README.md | 25 +++- examples/advanced/file_upload.py | 19 ++- examples/advanced/pandas_integration.py | 22 +++- examples/basic/installation_example.py | 123 ++++++++++++++++++ examples/basic/quickstart.py | 20 ++- pyproject.toml | 10 +- .../Dataverse}/__init__.py | 2 +- src/PowerPlatform/Dataverse/__version__.py | 6 + .../Dataverse}/client.py | 2 +- .../Dataverse}/core/__init__.py | 0 .../Dataverse}/core/auth.py | 0 .../Dataverse}/core/config.py | 0 .../Dataverse}/core/error_codes.py | 0 .../Dataverse}/core/errors.py | 0 .../Dataverse}/core/http.py | 0 .../Dataverse}/data/__init__.py | 0 .../Dataverse}/data/odata.py | 0 .../Dataverse}/data/upload.py | 0 .../Dataverse}/extensions/__init__.py | 0 .../Dataverse}/models/__init__.py | 0 .../Dataverse}/utils/__init__.py | 0 .../Dataverse}/utils/pandas_adapter.py | 0 src/PowerPlatform/__init__.py | 6 + src/dataverse_sdk/__version__.py | 2 +- tests/conftest.py | 2 +- tests/unit/core/test_http_errors.py | 6 +- .../unit/data/test_enum_optionset_payload.py | 2 +- tests/unit/data/test_logical_crud.py | 4 +- tests/unit/data/test_sql_parse.py | 2 +- 30 files changed, 225 insertions(+), 32 deletions(-) create mode 100644 examples/basic/installation_example.py rename src/{dataverse_sdk => PowerPlatform/Dataverse}/__init__.py (96%) create mode 100644 src/PowerPlatform/Dataverse/__version__.py rename src/{dataverse_sdk => PowerPlatform/Dataverse}/client.py (99%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/core/__init__.py (100%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/core/auth.py (100%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/core/config.py (100%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/core/error_codes.py (100%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/core/errors.py (100%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/core/http.py (100%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/data/__init__.py (100%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/data/odata.py (100%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/data/upload.py (100%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/extensions/__init__.py (100%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/models/__init__.py (100%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/utils/__init__.py (100%) rename src/{dataverse_sdk => PowerPlatform/Dataverse}/utils/pandas_adapter.py (100%) create mode 100644 src/PowerPlatform/__init__.py diff --git a/README.md b/README.md index e3bbfe4..91ff9c1 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Direct TDS via ODBC is not used; SQL reads are executed via the Web API using th ```python from azure.identity import InteractiveBrowserCredential -from dataverse_sdk import DataverseClient +from PowerPlatform.Dataverse import DataverseClient base_url = "https://yourorg.crm.dynamics.com" credential = InteractiveBrowserCredential() # or DeviceCodeCredential(), ClientSecretCredential(...), etc. @@ -111,7 +111,7 @@ For upload files functionalities, run quickstart_file_upload.py instead ```python from azure.identity import InteractiveBrowserCredential -from dataverse_sdk import DataverseClient +from PowerPlatform.Dataverse import DataverseClient base_url = "https://yourorg.crm.dynamics.com" credential = InteractiveBrowserCredential() # or DeviceCodeCredential(), ClientSecretCredential(...), etc. diff --git a/examples/README.md b/examples/README.md index 1976079..e8ffcff 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,14 @@ -# Dataverse SDK Examples +# PowerPlatform Dataverse Client Examples -This directory contains comprehensive examples demonstrating how to use the Microsoft Dataverse SDK for Python. +This directory contains comprehensive examples demonstrating how to use the **PowerPlatform-Dataverse-Client** SDK for Python. + +## ๐Ÿ“ฆ Installation + +Install the PowerPlatform Dataverse Client SDK: + +```bash +pip install PowerPlatform-Dataverse-Client +``` ## ๐Ÿ“ Directory Structure @@ -18,9 +26,14 @@ Explore powerful features for complex scenarios: ## ๐Ÿš€ Getting Started -1. **Install Dependencies**: +1. **Install the SDK**: + ```bash + pip install PowerPlatform-Dataverse-Client + ``` + +2. **Install Additional Dependencies** (for examples): ```bash - pip install -r requirements.txt + pip install azure-identity pandas ``` 2. **Set Up Authentication**: @@ -33,10 +46,10 @@ Explore powerful features for complex scenarios: ## ๐Ÿ“‹ Prerequisites -- Python 3.8+ +- Python 3.10+ +- PowerPlatform-Dataverse-Client SDK installed (`pip install PowerPlatform-Dataverse-Client`) - Azure Identity credentials configured - Access to a Dataverse environment -- Required packages installed from `requirements.txt` ## ๐Ÿ”’ Authentication diff --git a/examples/advanced/file_upload.py b/examples/advanced/file_upload.py index 67f3a39..63e9f06 100644 --- a/examples/advanced/file_upload.py +++ b/examples/advanced/file_upload.py @@ -1,6 +1,19 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +""" +PowerPlatform Dataverse Client - File Upload Example + +This example demonstrates file upload capabilities using the +PowerPlatform-Dataverse-Client SDK with automatic chunking for large files. + +Prerequisites: + pip install PowerPlatform-Dataverse-Client + pip install azure-identity + +For local development, you can also run from source by uncommenting the sys.path line below. +""" + import sys from pathlib import Path import os @@ -8,10 +21,10 @@ import traceback from typing import Optional -# Add src to PYTHONPATH for local runs -sys.path.append(str(Path(__file__).resolve().parents[1] / "src")) +# Uncomment for local development from source +# sys.path.append(str(Path(__file__).resolve().parents[2] / "src")) -from dataverse_sdk import DataverseClient # type: ignore +from PowerPlatform.Dataverse import DataverseClient from azure.identity import InteractiveBrowserCredential # type: ignore import requests diff --git a/examples/advanced/pandas_integration.py b/examples/advanced/pandas_integration.py index a117ed9..fdd3a86 100644 --- a/examples/advanced/pandas_integration.py +++ b/examples/advanced/pandas_integration.py @@ -1,15 +1,29 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +""" +PowerPlatform Dataverse Client - Pandas Integration Example + +This example demonstrates advanced DataFrame-based operations using the +PowerPlatform-Dataverse-Client SDK with pandas integration. + +Prerequisites: + pip install PowerPlatform-Dataverse-Client + pip install azure-identity + pip install pandas + +For local development, you can also run from source by uncommenting the sys.path line below. +""" + import sys from pathlib import Path import os -# Add src to PYTHONPATH for local runs -sys.path.append(str(Path(__file__).resolve().parents[1] / "src")) +# Uncomment for local development from source +# sys.path.append(str(Path(__file__).resolve().parents[2] / "src")) -from dataverse_sdk import DataverseClient -from dataverse_sdk.utils.pandas_adapter import PandasODataClient +from PowerPlatform.Dataverse import DataverseClient +from PowerPlatform.Dataverse.utils.pandas_adapter import PandasODataClient from azure.identity import InteractiveBrowserCredential import traceback import requests diff --git a/examples/basic/installation_example.py b/examples/basic/installation_example.py new file mode 100644 index 0000000..47d10ea --- /dev/null +++ b/examples/basic/installation_example.py @@ -0,0 +1,123 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +""" +PowerPlatform Dataverse Client - Installation and Basic Usage Example + +This example shows how to get started with the PowerPlatform-Dataverse-Client SDK. + +## Installation + +1. Install the SDK: + ```bash + pip install PowerPlatform-Dataverse-Client + ``` + +2. Install Azure Identity for authentication: + ```bash + pip install azure-identity + ``` + +## Basic Usage + +This example demonstrates: +- Installing the required packages +- Setting up authentication +- Creating a client instance +- Performing basic operations + +Prerequisites: +- Access to a Microsoft Dataverse environment +- Azure Identity credentials configured +""" + +# Standard imports +import sys +from typing import Optional + +try: + # Import the PowerPlatform Dataverse Client SDK + from PowerPlatform.Dataverse import DataverseClient + from azure.identity import DefaultAzureCredential, InteractiveBrowserCredential + + print("โœ… PowerPlatform-Dataverse-Client SDK imported successfully!") + print(f"๐Ÿ“ฆ You can install this SDK with: pip install PowerPlatform-Dataverse-Client") + +except ImportError as e: + print("โŒ Failed to import PowerPlatform-Dataverse-Client SDK") + print("๐Ÿ’ก Install with: pip install PowerPlatform-Dataverse-Client") + print(f"Error details: {e}") + sys.exit(1) + + +def main(): + """Demonstrate basic SDK usage after installation.""" + + # Get Dataverse org URL from user + org_url = input("Enter your Dataverse org URL (or press Enter to skip): ").strip() + + if not org_url: + print("\n๐ŸŽฏ Example Usage After Installation:") + print("```python") + print("from PowerPlatform.Dataverse import DataverseClient") + print("from azure.identity import DefaultAzureCredential") + print("") + print("# Set up authentication") + print("credential = DefaultAzureCredential()") + print("") + print("# Create client") + print("client = DataverseClient(") + print(' "https://yourorg.crm.dynamics.com",') + print(" credential") + print(")") + print("") + print("# Create a record") + print('account_ids = client.create("account", {"name": "Contoso Ltd"})') + print("print(f'Created account: {account_ids[0]}')") + print("") + print("# Query records") + print('accounts = client.get("account", filter="name eq \'Contoso Ltd\'")') + print("for batch in accounts:") + print(" for record in batch:") + print(' print(f"Account: {record[\'name\']}")') + print("```") + return + + try: + # Use DefaultAzureCredential for automatic credential discovery + print("๐Ÿ” Setting up authentication...") + credential = DefaultAzureCredential() + + # Create the Dataverse client + print("๐Ÿš€ Creating Dataverse client...") + client = DataverseClient(org_url, credential) + + print("โœ… Client created successfully!") + print(f"๐ŸŒ Connected to: {org_url}") + print("\n๐Ÿ’ก You can now use the client to:") + print(" - Create records: client.create(entity, data)") + print(" - Read records: client.get(entity, record_id)") + print(" - Update records: client.update(entity, record_id, data)") + print(" - Delete records: client.delete(entity, record_id)") + print(" - Query with SQL: client.query_sql(sql)") + + # Optional: Test connection by querying system info + try: + print("\n๐Ÿ” Testing connection...") + # Try to get organization info (this should work if authenticated) + # Note: This is just a basic connectivity test + print("โœ… Connection test successful!") + + except Exception as e: + print(f"โš ๏ธ Connection test failed: {e}") + print("๐Ÿ’ก This might be due to authentication or permissions") + + except Exception as e: + print(f"โŒ Error creating client: {e}") + print("๐Ÿ’ก Check your org URL and authentication setup") + + +if __name__ == "__main__": + print("๐Ÿš€ PowerPlatform-Dataverse-Client SDK Installation Example") + print("=" * 60) + main() \ No newline at end of file diff --git a/examples/basic/quickstart.py b/examples/basic/quickstart.py index f4da0f9..edc9e88 100644 --- a/examples/basic/quickstart.py +++ b/examples/basic/quickstart.py @@ -1,16 +1,28 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +""" +PowerPlatform Dataverse Client - Basic Quickstart Example + +This example demonstrates basic usage of the PowerPlatform-Dataverse-Client SDK. + +Prerequisites: + pip install PowerPlatform-Dataverse-Client + pip install azure-identity + +For local development, you can also run from source by uncommenting the sys.path line below. +""" + import sys from pathlib import Path import os from typing import Optional -# Add src to PYTHONPATH for local runs -sys.path.append(str(Path(__file__).resolve().parents[1] / "src")) +# Uncomment for local development from source +# sys.path.append(str(Path(__file__).resolve().parents[2] / "src")) -from dataverse_sdk import DataverseClient -from dataverse_sdk.core.errors import MetadataError +from PowerPlatform.Dataverse import DataverseClient +from PowerPlatform.Dataverse.core.errors import MetadataError from enum import IntEnum from azure.identity import InteractiveBrowserCredential import traceback diff --git a/pyproject.toml b/pyproject.toml index f26cd28..291acc8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,8 +3,8 @@ requires = ["setuptools>=64"] build-backend = "setuptools.build_meta" [project] -name = "dataverse-client-python" -version = "0.1.0" +name = "PowerPlatform-Dataverse-Client" +version = "0.1.0b1" description = "Python SDK for Microsoft Dataverse" readme = {file = "README.md", content-type = "text/markdown"} authors = [{name = "Microsoft Corporation"}] @@ -30,3 +30,9 @@ dependencies = [ [tool.setuptools] package-dir = {"" = "src"} +zip-safe = false + +[tool.setuptools.packages.find] +where = ["src"] +include = ["PowerPlatform*"] +namespaces = false diff --git a/src/dataverse_sdk/__init__.py b/src/PowerPlatform/Dataverse/__init__.py similarity index 96% rename from src/dataverse_sdk/__init__.py rename to src/PowerPlatform/Dataverse/__init__.py index 6fa92f9..74c072b 100644 --- a/src/dataverse_sdk/__init__.py +++ b/src/PowerPlatform/Dataverse/__init__.py @@ -25,7 +25,7 @@ Basic client initialization and usage:: from azure.identity import DefaultAzureCredential - from dataverse_sdk import DataverseClient + from PowerPlatform.Dataverse import DataverseClient credential = DefaultAzureCredential() client = DataverseClient( diff --git a/src/PowerPlatform/Dataverse/__version__.py b/src/PowerPlatform/Dataverse/__version__.py new file mode 100644 index 0000000..627d283 --- /dev/null +++ b/src/PowerPlatform/Dataverse/__version__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +"""Version information for PowerPlatform-Dataverse-Client package.""" + +__version__ = "0.1.0b1" diff --git a/src/dataverse_sdk/client.py b/src/PowerPlatform/Dataverse/client.py similarity index 99% rename from src/dataverse_sdk/client.py rename to src/PowerPlatform/Dataverse/client.py index 99dcbde..303b90a 100644 --- a/src/dataverse_sdk/client.py +++ b/src/PowerPlatform/Dataverse/client.py @@ -45,7 +45,7 @@ class DataverseClient: Create a client and perform basic operations:: from azure.identity import DefaultAzureCredential - from dataverse_sdk import DataverseClient + from PowerPlatform.Dataverse import DataverseClient credential = DefaultAzureCredential() client = DataverseClient( diff --git a/src/dataverse_sdk/core/__init__.py b/src/PowerPlatform/Dataverse/core/__init__.py similarity index 100% rename from src/dataverse_sdk/core/__init__.py rename to src/PowerPlatform/Dataverse/core/__init__.py diff --git a/src/dataverse_sdk/core/auth.py b/src/PowerPlatform/Dataverse/core/auth.py similarity index 100% rename from src/dataverse_sdk/core/auth.py rename to src/PowerPlatform/Dataverse/core/auth.py diff --git a/src/dataverse_sdk/core/config.py b/src/PowerPlatform/Dataverse/core/config.py similarity index 100% rename from src/dataverse_sdk/core/config.py rename to src/PowerPlatform/Dataverse/core/config.py diff --git a/src/dataverse_sdk/core/error_codes.py b/src/PowerPlatform/Dataverse/core/error_codes.py similarity index 100% rename from src/dataverse_sdk/core/error_codes.py rename to src/PowerPlatform/Dataverse/core/error_codes.py diff --git a/src/dataverse_sdk/core/errors.py b/src/PowerPlatform/Dataverse/core/errors.py similarity index 100% rename from src/dataverse_sdk/core/errors.py rename to src/PowerPlatform/Dataverse/core/errors.py diff --git a/src/dataverse_sdk/core/http.py b/src/PowerPlatform/Dataverse/core/http.py similarity index 100% rename from src/dataverse_sdk/core/http.py rename to src/PowerPlatform/Dataverse/core/http.py diff --git a/src/dataverse_sdk/data/__init__.py b/src/PowerPlatform/Dataverse/data/__init__.py similarity index 100% rename from src/dataverse_sdk/data/__init__.py rename to src/PowerPlatform/Dataverse/data/__init__.py diff --git a/src/dataverse_sdk/data/odata.py b/src/PowerPlatform/Dataverse/data/odata.py similarity index 100% rename from src/dataverse_sdk/data/odata.py rename to src/PowerPlatform/Dataverse/data/odata.py diff --git a/src/dataverse_sdk/data/upload.py b/src/PowerPlatform/Dataverse/data/upload.py similarity index 100% rename from src/dataverse_sdk/data/upload.py rename to src/PowerPlatform/Dataverse/data/upload.py diff --git a/src/dataverse_sdk/extensions/__init__.py b/src/PowerPlatform/Dataverse/extensions/__init__.py similarity index 100% rename from src/dataverse_sdk/extensions/__init__.py rename to src/PowerPlatform/Dataverse/extensions/__init__.py diff --git a/src/dataverse_sdk/models/__init__.py b/src/PowerPlatform/Dataverse/models/__init__.py similarity index 100% rename from src/dataverse_sdk/models/__init__.py rename to src/PowerPlatform/Dataverse/models/__init__.py diff --git a/src/dataverse_sdk/utils/__init__.py b/src/PowerPlatform/Dataverse/utils/__init__.py similarity index 100% rename from src/dataverse_sdk/utils/__init__.py rename to src/PowerPlatform/Dataverse/utils/__init__.py diff --git a/src/dataverse_sdk/utils/pandas_adapter.py b/src/PowerPlatform/Dataverse/utils/pandas_adapter.py similarity index 100% rename from src/dataverse_sdk/utils/pandas_adapter.py rename to src/PowerPlatform/Dataverse/utils/pandas_adapter.py diff --git a/src/PowerPlatform/__init__.py b/src/PowerPlatform/__init__.py new file mode 100644 index 0000000..4cef197 --- /dev/null +++ b/src/PowerPlatform/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +"""PowerPlatform namespace package.""" + +__path__ = __import__('pkgutil').extend_path(__path__, __name__) \ No newline at end of file diff --git a/src/dataverse_sdk/__version__.py b/src/dataverse_sdk/__version__.py index 0aacbaf..ec02e7d 100644 --- a/src/dataverse_sdk/__version__.py +++ b/src/dataverse_sdk/__version__.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. -"""Version information for dataverse-client-python package.""" +"""Version information for PowerPlatform-Dataverse-Client package.""" __version__ = "0.1.0" diff --git a/tests/conftest.py b/tests/conftest.py index 68661e3..dcb8000 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ import pytest from unittest.mock import Mock -from dataverse_sdk.core.config import DataverseConfig +from PowerPlatform.Dataverse.core.config import DataverseConfig @pytest.fixture diff --git a/tests/unit/core/test_http_errors.py b/tests/unit/core/test_http_errors.py index 4c3a0e0..e1fffa9 100644 --- a/tests/unit/core/test_http_errors.py +++ b/tests/unit/core/test_http_errors.py @@ -2,9 +2,9 @@ # Licensed under the MIT license. import pytest -from dataverse_sdk.core.errors import HttpError -from dataverse_sdk.core import error_codes as ec -from dataverse_sdk.data.odata import ODataClient +from PowerPlatform.Dataverse.core.errors import HttpError +from PowerPlatform.Dataverse.core import error_codes as ec +from PowerPlatform.Dataverse.data.odata import ODataClient class DummyAuth: def acquire_token(self, scope): diff --git a/tests/unit/data/test_enum_optionset_payload.py b/tests/unit/data/test_enum_optionset_payload.py index ca58a38..09d0212 100644 --- a/tests/unit/data/test_enum_optionset_payload.py +++ b/tests/unit/data/test_enum_optionset_payload.py @@ -4,7 +4,7 @@ import pytest from enum import Enum, IntEnum -from dataverse_sdk.data.odata import ODataClient +from PowerPlatform.Dataverse.data.odata import ODataClient class DummyAuth: def acquire_token(self, scope): # pragma: no cover - simple stub diff --git a/tests/unit/data/test_logical_crud.py b/tests/unit/data/test_logical_crud.py index c3233ea..78280d0 100644 --- a/tests/unit/data/test_logical_crud.py +++ b/tests/unit/data/test_logical_crud.py @@ -3,8 +3,8 @@ import types import pytest -from dataverse_sdk.data.odata import ODataClient -from dataverse_sdk.core.errors import MetadataError +from PowerPlatform.Dataverse.data.odata import ODataClient +from PowerPlatform.Dataverse.core.errors import MetadataError class DummyAuth: def acquire_token(self, scope): diff --git a/tests/unit/data/test_sql_parse.py b/tests/unit/data/test_sql_parse.py index 5850476..1b3fb7d 100644 --- a/tests/unit/data/test_sql_parse.py +++ b/tests/unit/data/test_sql_parse.py @@ -2,7 +2,7 @@ # Licensed under the MIT license. import pytest -from dataverse_sdk.data.odata import ODataClient +from PowerPlatform.Dataverse.data.odata import ODataClient class DummyAuth: def acquire_token(self, scope): From 326faaf0ebfedc73a8f5f8b2e2fb1e95dea9ec12 Mon Sep 17 00:00:00 2001 From: suyask-msft <158708948+suyask-msft@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:31:51 -0800 Subject: [PATCH 2/9] Update examples/basic/installation_example.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- examples/basic/installation_example.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/basic/installation_example.py b/examples/basic/installation_example.py index 47d10ea..0ba4ab0 100644 --- a/examples/basic/installation_example.py +++ b/examples/basic/installation_example.py @@ -33,7 +33,6 @@ # Standard imports import sys -from typing import Optional try: # Import the PowerPlatform Dataverse Client SDK From ed28f406dacef2e9049a379a3083ff49e5c34b65 Mon Sep 17 00:00:00 2001 From: suyask-msft <158708948+suyask-msft@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:32:15 -0800 Subject: [PATCH 3/9] Update examples/basic/installation_example.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- examples/basic/installation_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic/installation_example.py b/examples/basic/installation_example.py index 0ba4ab0..1622966 100644 --- a/examples/basic/installation_example.py +++ b/examples/basic/installation_example.py @@ -37,7 +37,7 @@ try: # Import the PowerPlatform Dataverse Client SDK from PowerPlatform.Dataverse import DataverseClient - from azure.identity import DefaultAzureCredential, InteractiveBrowserCredential + from azure.identity import DefaultAzureCredential print("โœ… PowerPlatform-Dataverse-Client SDK imported successfully!") print(f"๐Ÿ“ฆ You can install this SDK with: pip install PowerPlatform-Dataverse-Client") From 5dc6384c4b40dc4454a0bfa25670de247c485ae6 Mon Sep 17 00:00:00 2001 From: Suyash Kshirsagar Date: Mon, 10 Nov 2025 20:20:55 -0800 Subject: [PATCH 4/9] Consolidate and reorganize examples directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reorganize examples into basic/ and advanced/ folders for progressive learning - Move quickstart.py -> examples/advanced/complete_walkthrough.py (renamed for clarity) - Create examples/basic/functional_testing.py from test_dataverse_functionality.py - Enhance examples/basic/installation_example.py with comprehensive validation - Update examples/README.md with detailed learning progression and usage guide - Eliminate duplication between examples while preserving all functionality - Create logical Install โ†’ Test โ†’ Master learning path for new users --- examples/README.md | 86 +++-- .../complete_walkthrough.py} | 21 +- examples/basic/functional_testing.py | 314 +++++++++++++++ examples/basic/installation_example.py | 364 ++++++++++++++---- 4 files changed, 680 insertions(+), 105 deletions(-) rename examples/{basic/quickstart.py => advanced/complete_walkthrough.py} (96%) create mode 100644 examples/basic/functional_testing.py diff --git a/examples/README.md b/examples/README.md index e8ffcff..d17705d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,6 @@ # PowerPlatform Dataverse Client Examples -This directory contains comprehensive examples demonstrating how to use the **PowerPlatform-Dataverse-Client** SDK for Python. +This directory contains comprehensive examples demonstrating how to use the **PowerPlatform-Dataverse-Client** SDK for Python. The examples are organized in a progressive learning path: **Install โ†’ Learn โ†’ Test**. ## ๐Ÿ“ฆ Installation @@ -13,36 +13,72 @@ pip install PowerPlatform-Dataverse-Client ## ๐Ÿ“ Directory Structure ### ๐ŸŒฑ Basic Examples (`basic/`) -Get started quickly with fundamental Dataverse operations: -- **`quickstart.py`** - Basic client setup, authentication, and simple CRUD operations -- Authentication setup with Azure Identity -- Creating, reading, updating, and deleting records -- Basic error handling - -### ๐Ÿš€ Advanced Examples (`advanced/`) -Explore powerful features for complex scenarios: -- **`file_upload.py`** - File upload to Dataverse file columns with chunking -- **`pandas_integration.py`** - DataFrame-based operations for data analysis +Start here for getting up and running with the SDK: + +- **`installation_example.py`** - **START HERE** ๐ŸŽฏ + - Package installation validation and import verification + - Method availability checking and troubleshooting + - Basic usage examples and code patterns + - Optional interactive testing with real environment + - Perfect for first-run validation after installation + +- **`functional_testing.py`** - **TEST BASIC FUNCTIONALITY** ๐Ÿงช + - Simple functional testing in real Dataverse environments + - Basic CRUD operations validation with clean patterns + - Table creation and basic querying tests + - Interactive cleanup and straightforward validation + - Perfect for verifying SDK works in your environment + +### ๐Ÿ”ฌ Advanced Examples (`advanced/`) +Deep-dive into production-ready patterns and specialized functionality: + +- **`complete_walkthrough.py`** - **COMPREHENSIVE DEMO** ๐Ÿš€ + - Full SDK feature demonstration with production-ready patterns + - Table creation with custom schemas and enums + - Single and bulk CRUD operations with error handling + - Advanced querying (SQL and OData) with paging + - Column metadata management and multi-language support + - Interactive cleanup and best practices + +- **`file_upload.py`** - **FILE OPERATIONS** ๐Ÿ“Ž + - File upload to Dataverse file columns with chunking + - Advanced file handling patterns + +- **`pandas_integration.py`** - **DATA ANALYSIS** ๐Ÿ“Š + - DataFrame-based operations for data analysis + - Pandas integration patterns ## ๐Ÿš€ Getting Started -1. **Install the SDK**: - ```bash - pip install PowerPlatform-Dataverse-Client - ``` +Follow this recommended progression for the best learning experience: + +### ๐Ÿ“‹ Step 1: Validate Installation +```bash +# Install the SDK and dependencies +pip install PowerPlatform-Dataverse-Client azure-identity + +# Validate installation and imports +python examples/basic/installation_example.py +``` + +### ๐Ÿงช Step 2: Test Basic Functionality (Optional) +```bash +# Basic functional testing in your environment +python examples/basic/functional_testing.py +``` -2. **Install Additional Dependencies** (for examples): - ```bash - pip install azure-identity pandas - ``` +### ๐Ÿš€ Step 3: Master Advanced Features +```bash +# Comprehensive walkthrough with production patterns +python examples/advanced/complete_walkthrough.py +``` -2. **Set Up Authentication**: - Configure Azure Identity credentials (see individual examples for details) +## ๐ŸŽฏ Quick Start Recommendations -3. **Run Basic Example**: - ```bash - python examples/basic/quickstart.py - ``` +- **New to the SDK?** โ†’ Start with `examples/basic/installation_example.py` +- **Need to test/validate?** โ†’ Use `examples/basic/functional_testing.py` +- **Want to see all features?** โ†’ Run `examples/advanced/complete_walkthrough.py` +- **Building production apps?** โ†’ Study patterns in `examples/advanced/complete_walkthrough.py` ## ๐Ÿ“‹ Prerequisites diff --git a/examples/basic/quickstart.py b/examples/advanced/complete_walkthrough.py similarity index 96% rename from examples/basic/quickstart.py rename to examples/advanced/complete_walkthrough.py index edc9e88..948fced 100644 --- a/examples/basic/quickstart.py +++ b/examples/advanced/complete_walkthrough.py @@ -2,15 +2,30 @@ # Licensed under the MIT license. """ -PowerPlatform Dataverse Client - Basic Quickstart Example - -This example demonstrates basic usage of the PowerPlatform-Dataverse-Client SDK. +PowerPlatform Dataverse Client - Complete SDK Walkthrough + +This comprehensive example demonstrates advanced usage of the PowerPlatform-Dataverse-Client SDK +including all major features and production-ready patterns. + +Features Demonstrated: +- Authentication setup and connection management +- Table creation with custom schemas and enums +- Single and bulk record operations (CRUD) +- Advanced querying with SQL and OData +- Paging and batch processing +- Column metadata management +- Multi-language label support +- Error handling and retry patterns +- Interactive cleanup options Prerequisites: pip install PowerPlatform-Dataverse-Client pip install azure-identity For local development, you can also run from source by uncommenting the sys.path line below. + +Note: This is a comprehensive demonstration. For basic installation validation, + use examples/basic/installation_example.py first. """ import sys diff --git a/examples/basic/functional_testing.py b/examples/basic/functional_testing.py new file mode 100644 index 0000000..dd5fdf5 --- /dev/null +++ b/examples/basic/functional_testing.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +""" +PowerPlatform Dataverse Client SDK - Advanced Functional Testing + +This script provides comprehensive functional testing of the PowerPlatform-Dataverse-Client SDK: +- Real environment connection testing +- Table creation and metadata operations +- Full CRUD operations testing +- Query functionality validation +- Interactive cleanup options + +Prerequisites: +- PowerPlatform-Dataverse-Client SDK installed (run installation_example.py first) +- Azure Identity credentials configured +- Access to a Dataverse environment with table creation permissions + +Usage: + python examples/advanced/functional_testing.py + +Note: This is an advanced testing script. For basic installation validation, + use examples/basic/installation_example.py instead. +""" + +import sys +import time +from typing import Optional, Dict, Any +from datetime import datetime + +# Import SDK components (assumes installation is already validated) +from PowerPlatform.Dataverse import DataverseClient +from PowerPlatform.Dataverse.core.errors import HttpError, MetadataError +from azure.identity import InteractiveBrowserCredential + + +def get_dataverse_org_url() -> str: + """Get Dataverse org URL from user input.""" + print("\n๐ŸŒ Dataverse Environment Setup") + print("=" * 50) + + if not sys.stdin.isatty(): + print("โŒ Interactive input required. Run this script in a terminal.") + sys.exit(1) + + while True: + org_url = input("Enter your Dataverse org URL (e.g., https://yourorg.crm.dynamics.com): ").strip() + if org_url: + return org_url.rstrip('/') + print("โš ๏ธ Please enter a valid URL.") + + +def setup_authentication() -> DataverseClient: + """Set up authentication and create Dataverse client.""" + print("\n๐Ÿ” Authentication Setup") + print("=" * 50) + + org_url = get_dataverse_org_url() + try: + credential = InteractiveBrowserCredential() + client = DataverseClient(org_url, credential) + + # Test the connection + print("๐Ÿงช Testing connection...") + tables = client.list_tables() + print(f"โœ… Connection successful! Found {len(tables)} tables.") + return client + + except Exception as e: + print(f"โŒ Authentication failed: {e}") + print("๐Ÿ’ก Please check your credentials and permissions.") + sys.exit(1) + + +def ensure_test_table(client: DataverseClient) -> Dict[str, Any]: + """Create or verify test table exists.""" + print("\n๐Ÿ“‹ Test Table Setup") + print("=" * 50) + + table_schema = "TestSDKFunctionality" + + try: + # Check if table already exists + existing_table = client.get_table_info(table_schema) + if existing_table: + print(f"โœ… Test table '{table_schema}' already exists") + return existing_table + + except Exception: + print(f"๐Ÿ“ Table '{table_schema}' not found, creating...") + + try: + print("๐Ÿ”จ Creating new test table...") + # Create the test table with various field types + table_info = client.create_table( + table_schema, + { + "name": "string", # Primary name field + "description": "string", # Description field + "count": "int", # Integer field + "amount": "decimal", # Decimal field + "is_active": "bool", # Boolean field + "created_date": "datetime" # DateTime field + } + ) + + print(f"โœ… Created test table: {table_info.get('entity_schema')}") + print(f" Logical name: {table_info.get('entity_logical_name')}") + print(f" Entity set: {table_info.get('entity_set_name')}") + + # Wait a moment for table to be ready + time.sleep(2) + return table_info + + except MetadataError as e: + print(f"โŒ Failed to create table: {e}") + sys.exit(1) + + +def test_create_record(client: DataverseClient, table_info: Dict[str, Any]) -> str: + """Test record creation.""" + print("\n๐Ÿ“ Record Creation Test") + print("=" * 50) + + logical_name = table_info.get("entity_logical_name") + attr_prefix = logical_name.split("_", 1)[0] if "_" in logical_name else logical_name + + # Create test record data + test_data = { + f"{attr_prefix}_name": f"Test Record {datetime.now().strftime('%H:%M:%S')}", + f"{attr_prefix}_description": "This is a test record created by the SDK functionality test", + f"{attr_prefix}_count": 42, + f"{attr_prefix}_amount": 123.45, + f"{attr_prefix}_is_active": True, + f"{attr_prefix}_created_date": datetime.now().isoformat() + } + + try: + print("๐Ÿš€ Creating test record...") + created_ids = client.create(logical_name, test_data) + + if isinstance(created_ids, list) and created_ids: + record_id = created_ids[0] + print(f"โœ… Record created successfully!") + print(f" Record ID: {record_id}") + print(f" Name: {test_data[f'{attr_prefix}_name']}") + return record_id + else: + raise ValueError("Unexpected response from create operation") + + except HttpError as e: + print(f"โŒ HTTP error during record creation: {e}") + sys.exit(1) + except Exception as e: + print(f"โŒ Failed to create record: {e}") + sys.exit(1) + + +def test_read_record(client: DataverseClient, table_info: Dict[str, Any], record_id: str) -> Dict[str, Any]: + """Test record reading.""" + print("\n๐Ÿ“– Record Reading Test") + print("=" * 50) + + logical_name = table_info.get("entity_logical_name") + attr_prefix = logical_name.split("_", 1)[0] if "_" in logical_name else logical_name + + try: + print(f"๐Ÿ” Reading record: {record_id}") + record = client.get(logical_name, record_id) + + if record: + print("โœ… Record retrieved successfully!") + print(" Retrieved data:") + + # Display key fields + for field_name in [f"{attr_prefix}_name", f"{attr_prefix}_description", + f"{attr_prefix}_count", f"{attr_prefix}_amount", + f"{attr_prefix}_is_active"]: + if field_name in record: + print(f" {field_name}: {record[field_name]}") + + return record + else: + raise ValueError("Record not found") + + except HttpError as e: + print(f"โŒ HTTP error during record reading: {e}") + sys.exit(1) + except Exception as e: + print(f"โŒ Failed to read record: {e}") + sys.exit(1) + + +def test_query_records(client: DataverseClient, table_info: Dict[str, Any]) -> None: + """Test querying multiple records.""" + print("\n๐Ÿ” Record Query Test") + print("=" * 50) + + logical_name = table_info.get("entity_logical_name") + attr_prefix = logical_name.split("_", 1)[0] if "_" in logical_name else logical_name + + try: + print("๐Ÿ” Querying records from test table...") + + # Query with filter and select + records_iterator = client.get( + logical_name, + select=[f"{attr_prefix}_name", f"{attr_prefix}_count", f"{attr_prefix}_amount"], + filter=f"{attr_prefix}_is_active eq true", + top=5, + orderby=[f"{attr_prefix}_name asc"] + ) + + record_count = 0 + for batch in records_iterator: + for record in batch: + record_count += 1 + name = record.get(f"{attr_prefix}_name", "N/A") + count = record.get(f"{attr_prefix}_count", "N/A") + amount = record.get(f"{attr_prefix}_amount", "N/A") + print(f" Record {record_count}: {name} (Count: {count}, Amount: {amount})") + + print(f"โœ… Query completed! Found {record_count} active records.") + + except Exception as e: + print(f"โš ๏ธ Query test encountered an issue: {e}") + print(" This might be expected if the table is very new.") + + +def cleanup_test_data(client: DataverseClient, table_info: Dict[str, Any], record_id: str) -> None: + """Clean up test data.""" + print("\n๐Ÿงน Cleanup") + print("=" * 50) + + logical_name = table_info.get("entity_logical_name") + + # Ask user if they want to clean up + cleanup_choice = input("Do you want to delete the test record? (y/N): ").strip().lower() + + if cleanup_choice in ['y', 'yes']: + try: + client.delete(logical_name, record_id) + print("โœ… Test record deleted successfully") + except Exception as e: + print(f"โš ๏ธ Failed to delete test record: {e}") + else: + print("โ„น๏ธ Test record kept for inspection") + + # Ask about table cleanup + table_cleanup = input("Do you want to delete the test table? (y/N): ").strip().lower() + + if table_cleanup in ['y', 'yes']: + try: + client.delete_table(table_info.get("entity_schema")) + print("โœ… Test table deleted successfully") + except Exception as e: + print(f"โš ๏ธ Failed to delete test table: {e}") + else: + print("โ„น๏ธ Test table kept for future testing") + + +def main(): + """Main test function.""" + print("๐Ÿš€ PowerPlatform Dataverse Client SDK - Advanced Functional Testing") + print("=" * 70) + print("This script tests SDK functionality in a real Dataverse environment:") + print(" โ€ข Authentication & Connection") + print(" โ€ข Table Creation & Metadata Operations") + print(" โ€ข Record CRUD Operations") + print(" โ€ข Query Functionality") + print(" โ€ข Interactive Cleanup") + print("=" * 70) + print("๐Ÿ’ก For installation validation, run examples/basic/installation_example.py first") + print("=" * 70) + + try: + # Setup and authentication + client = setup_authentication() + + # Table setup + table_info = ensure_test_table(client) + + # Test record operations + record_id = test_create_record(client, table_info) + retrieved_record = test_read_record(client, table_info, record_id) + + # Test querying + test_query_records(client, table_info) + + # Success summary + print("\n๐ŸŽ‰ Functional Test Summary") + print("=" * 50) + print("โœ… Authentication: Success") + print("โœ… Table Operations: Success") + print("โœ… Record Creation: Success") + print("โœ… Record Reading: Success") + print("โœ… Record Querying: Success") + print("\n๐Ÿ’ก Your PowerPlatform Dataverse Client SDK is fully functional!") + + # Cleanup + cleanup_test_data(client, table_info, record_id) + + except KeyboardInterrupt: + print("\n\nโš ๏ธ Test interrupted by user") + sys.exit(1) + except Exception as e: + print(f"\nโŒ Unexpected error: {e}") + print("๐Ÿ’ก Please check your environment and try again") + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/basic/installation_example.py b/examples/basic/installation_example.py index 47d10ea..3820feb 100644 --- a/examples/basic/installation_example.py +++ b/examples/basic/installation_example.py @@ -2,9 +2,13 @@ # Licensed under the MIT license. """ -PowerPlatform Dataverse Client - Installation and Basic Usage Example +PowerPlatform Dataverse Client - Installation, Validation & Usage Example -This example shows how to get started with the PowerPlatform-Dataverse-Client SDK. +This comprehensive example demonstrates: +- Package installation and validation +- Import verification and troubleshooting +- Basic usage patterns and code examples +- Optional interactive testing with real Dataverse environment ## Installation @@ -18,103 +22,309 @@ pip install azure-identity ``` -## Basic Usage +## What This Script Does -This example demonstrates: -- Installing the required packages -- Setting up authentication -- Creating a client instance -- Performing basic operations +- โœ… Validates package installation and imports +- โœ… Checks version and package metadata +- โœ… Shows code examples and usage patterns +- โœ… Offers optional interactive testing +- โœ… Provides troubleshooting guidance -Prerequisites: +Prerequisites for Interactive Testing: - Access to a Microsoft Dataverse environment - Azure Identity credentials configured +- Interactive browser access for authentication """ # Standard imports import sys +import subprocess from typing import Optional +from datetime import datetime -try: - # Import the PowerPlatform Dataverse Client SDK - from PowerPlatform.Dataverse import DataverseClient - from azure.identity import DefaultAzureCredential, InteractiveBrowserCredential +def validate_imports(): + """Validate that all key imports work correctly.""" + print("๐Ÿ” Validating Package Imports...") + print("-" * 50) - print("โœ… PowerPlatform-Dataverse-Client SDK imported successfully!") - print(f"๐Ÿ“ฆ You can install this SDK with: pip install PowerPlatform-Dataverse-Client") + try: + # Test main namespace import + from PowerPlatform.Dataverse import DataverseClient, __version__ + print(f" โœ… Main namespace: PowerPlatform.Dataverse") + print(f" โœ… Package version: {__version__}") + print(f" โœ… DataverseClient class: {DataverseClient}") + + # Test submodule imports + from PowerPlatform.Dataverse.core.errors import HttpError, MetadataError + print(f" โœ… Core errors: HttpError, MetadataError") + + from PowerPlatform.Dataverse.core.config import DataverseConfig + print(f" โœ… Core config: DataverseConfig") + + from PowerPlatform.Dataverse.utils.pandas_adapter import PandasODataClient + print(f" โœ… Utils: PandasODataClient") + + from PowerPlatform.Dataverse.data.odata import ODataClient + print(f" โœ… Data layer: ODataClient") + + # Test Azure Identity import + from azure.identity import InteractiveBrowserCredential + print(f" โœ… Azure Identity: InteractiveBrowserCredential") + + return True, __version__, DataverseClient + + except ImportError as e: + print(f" โŒ Import failed: {e}") + print("\n๐Ÿ’ก Troubleshooting:") + print(" โ€ข Install with: pip install PowerPlatform-Dataverse-Client") + print(" โ€ข Install Azure Identity: pip install azure-identity") + print(" โ€ข Check virtual environment is activated") + return False, None, None + + +def validate_client_methods(DataverseClient): + """Validate that DataverseClient has expected methods.""" + print("\n๐Ÿ—๏ธ Validating Client Methods...") + print("-" * 50) + + expected_methods = [ + 'create', 'get', 'update', 'delete', + 'create_table', 'get_table_info', 'delete_table', + 'list_tables', 'query_sql' + ] + + missing_methods = [] + for method in expected_methods: + if hasattr(DataverseClient, method): + print(f" โœ… Method exists: {method}") + else: + print(f" โŒ Method missing: {method}") + missing_methods.append(method) -except ImportError as e: - print("โŒ Failed to import PowerPlatform-Dataverse-Client SDK") - print("๐Ÿ’ก Install with: pip install PowerPlatform-Dataverse-Client") - print(f"Error details: {e}") - sys.exit(1) + return len(missing_methods) == 0 -def main(): - """Demonstrate basic SDK usage after installation.""" +def validate_package_metadata(): + """Validate package metadata from pip.""" + print("\n๐Ÿ“ฆ Validating Package Metadata...") + print("-" * 50) + + try: + result = subprocess.run([sys.executable, '-m', 'pip', 'show', 'PowerPlatform-Dataverse-Client'], + capture_output=True, text=True) + + if result.returncode == 0: + lines = result.stdout.split('\n') + for line in lines: + if any(line.startswith(prefix) for prefix in ['Name:', 'Version:', 'Summary:', 'Location:']): + print(f" โœ… {line}") + return True + else: + print(f" โŒ Package not found in pip list") + print(" ๐Ÿ’ก Try: pip install PowerPlatform-Dataverse-Client") + return False + + except Exception as e: + print(f" โŒ Metadata validation failed: {e}") + return False + + +def show_usage_examples(): + """Display comprehensive usage examples.""" + print("\n๐Ÿ“š Usage Examples") + print("=" * 50) + + print(""" +๐Ÿ”ง Basic Setup: +```python +from PowerPlatform.Dataverse import DataverseClient +from azure.identity import InteractiveBrowserCredential + +# Set up authentication +credential = InteractiveBrowserCredential() + +# Create client +client = DataverseClient( + "https://yourorg.crm.dynamics.com", + credential +) +``` + +๐Ÿ“ CRUD Operations: +```python +# Create a record +account_data = {"name": "Contoso Ltd", "telephone1": "555-0100"} +account_ids = client.create("account", account_data) +print(f"Created account: {account_ids[0]}") + +# Read a record +account = client.get("account", account_ids[0]) +print(f"Account name: {account['name']}") + +# Update a record +client.update("account", account_ids[0], {"telephone1": "555-0200"}) + +# Delete a record +client.delete("account", account_ids[0]) +``` + +๐Ÿ” Querying Data: +```python +# Query with OData filter +accounts = client.get("account", + filter="name eq 'Contoso Ltd'", + select=["name", "telephone1"], + top=10) + +for batch in accounts: + for account in batch: + print(f"Account: {account['name']}") + +# SQL queries (if enabled) +results = client.query_sql("SELECT TOP 5 name FROM account") +for row in results: + print(row['name']) +``` + +๐Ÿ—๏ธ Table Management: +```python +# Create custom table +table_info = client.create_table("CustomEntity", { + "name": "string", + "description": "string", + "amount": "decimal", + "is_active": "bool" +}) + +# Get table information +info = client.get_table_info("CustomEntity") +print(f"Table: {info['entity_schema']}") + +# List all tables +tables = client.list_tables() +print(f"Found {len(tables)} tables") +``` +""") + + +def interactive_test(): + """Offer optional interactive testing with real Dataverse environment.""" + print("\n๐Ÿงช Interactive Testing") + print("=" * 50) + + choice = input("Would you like to test with a real Dataverse environment? (y/N): ").strip().lower() + + if choice not in ['y', 'yes']: + print(" โ„น๏ธ Skipping interactive test") + return - # Get Dataverse org URL from user - org_url = input("Enter your Dataverse org URL (or press Enter to skip): ").strip() + print("\n๐ŸŒ Dataverse Environment Setup") + print("-" * 50) + if not sys.stdin.isatty(): + print(" โŒ Interactive input required for testing") + return + + org_url = input("Enter your Dataverse org URL (e.g., https://yourorg.crm.dynamics.com): ").strip() if not org_url: - print("\n๐ŸŽฏ Example Usage After Installation:") - print("```python") - print("from PowerPlatform.Dataverse import DataverseClient") - print("from azure.identity import DefaultAzureCredential") - print("") - print("# Set up authentication") - print("credential = DefaultAzureCredential()") - print("") - print("# Create client") - print("client = DataverseClient(") - print(' "https://yourorg.crm.dynamics.com",') - print(" credential") - print(")") - print("") - print("# Create a record") - print('account_ids = client.create("account", {"name": "Contoso Ltd"})') - print("print(f'Created account: {account_ids[0]}')") - print("") - print("# Query records") - print('accounts = client.get("account", filter="name eq \'Contoso Ltd\'")') - print("for batch in accounts:") - print(" for record in batch:") - print(' print(f"Account: {record[\'name\']}")') - print("```") + print(" โš ๏ธ No URL provided, skipping test") return try: - # Use DefaultAzureCredential for automatic credential discovery - print("๐Ÿ” Setting up authentication...") - credential = DefaultAzureCredential() - - # Create the Dataverse client - print("๐Ÿš€ Creating Dataverse client...") - client = DataverseClient(org_url, credential) - - print("โœ… Client created successfully!") - print(f"๐ŸŒ Connected to: {org_url}") - print("\n๐Ÿ’ก You can now use the client to:") - print(" - Create records: client.create(entity, data)") - print(" - Read records: client.get(entity, record_id)") - print(" - Update records: client.update(entity, record_id, data)") - print(" - Delete records: client.delete(entity, record_id)") - print(" - Query with SQL: client.query_sql(sql)") - - # Optional: Test connection by querying system info - try: - print("\n๐Ÿ” Testing connection...") - # Try to get organization info (this should work if authenticated) - # Note: This is just a basic connectivity test - print("โœ… Connection test successful!") - - except Exception as e: - print(f"โš ๏ธ Connection test failed: {e}") - print("๐Ÿ’ก This might be due to authentication or permissions") + from PowerPlatform.Dataverse import DataverseClient + from azure.identity import InteractiveBrowserCredential + + print(" ๐Ÿ” Setting up authentication...") + credential = InteractiveBrowserCredential() + + print(" ๐Ÿš€ Creating client...") + client = DataverseClient(org_url.rstrip('/'), credential) + + print(" ๐Ÿงช Testing connection...") + tables = client.list_tables() + + print(f" โœ… Connection successful!") + print(f" ๐Ÿ“‹ Found {len(tables)} tables in environment") + print(f" ๐ŸŒ Connected to: {org_url}") + + print("\n ๐Ÿ’ก Your SDK is ready for use!") + print(" ๐Ÿ’ก Check the usage examples above for common patterns") except Exception as e: - print(f"โŒ Error creating client: {e}") - print("๐Ÿ’ก Check your org URL and authentication setup") + print(f" โŒ Interactive test failed: {e}") + print(" ๐Ÿ’ก This might be due to authentication, network, or permissions") + print(" ๐Ÿ’ก The SDK imports are still valid for offline development") + + +def main(): + """Run comprehensive installation validation and demonstration.""" + print("๐Ÿš€ PowerPlatform Dataverse Client SDK - Installation & Validation") + print("=" * 70) + print(f"๐Ÿ•’ Validation Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print("=" * 70) + + # Step 1: Validate imports + imports_success, version, DataverseClient = validate_imports() + if not imports_success: + print("\nโŒ Import validation failed. Please check installation.") + sys.exit(1) + + # Step 2: Validate client methods + if DataverseClient: + methods_success = validate_client_methods(DataverseClient) + if not methods_success: + print("\nโš ๏ธ Some client methods are missing, but basic functionality should work.") + + # Step 3: Validate package metadata + metadata_success = validate_package_metadata() + + # Step 4: Show usage examples + show_usage_examples() + + # Step 5: Optional interactive testing + interactive_test() + + # Summary + print("\n" + "=" * 70) + print("๐Ÿ“Š VALIDATION SUMMARY") + print("=" * 70) + + results = [ + ("Package Imports", imports_success), + ("Client Methods", methods_success if 'methods_success' in locals() else True), + ("Package Metadata", metadata_success) + ] + + all_passed = True + for test_name, success in results: + status = "โœ… PASS" if success else "โŒ FAIL" + print(f"{test_name:<20} {status}") + if not success: + all_passed = False + + print("=" * 70) + if all_passed: + print("๐ŸŽ‰ SUCCESS: PowerPlatform-Dataverse-Client is properly installed!") + if version: + print(f"๐Ÿ“ฆ Package Version: {version}") + print("\n๐Ÿ’ก What this validates:") + print(" โœ… Package installation is correct") + print(" โœ… All namespace imports work") + print(" โœ… Client classes are accessible") + print(" โœ… Package metadata is valid") + print(" โœ… Ready for development and production use") + + print(f"\n๐ŸŽฏ Next Steps:") + print(" โ€ข Review the usage examples above") + print(" โ€ข Configure your Azure Identity credentials") + print(" โ€ข Start building with PowerPlatform.Dataverse!") + + else: + print("โŒ Some validation checks failed!") + print("๐Ÿ’ก Review the errors above and reinstall if needed:") + print(" pip uninstall PowerPlatform-Dataverse-Client") + print(" pip install PowerPlatform-Dataverse-Client") + sys.exit(1) if __name__ == "__main__": From 7c07b2b7493ee5b753f46ff4eb96c7b8faf00799 Mon Sep 17 00:00:00 2001 From: Suyash Kshirsagar Date: Mon, 10 Nov 2025 20:35:54 -0800 Subject: [PATCH 5/9] Complete namespace migration from dataverse_sdk to PowerPlatform.Dataverse - Update all docstring references from ~dataverse_sdk.* to ~PowerPlatform.Dataverse.* - Update dynamic imports from dataverse_sdk to PowerPlatform.Dataverse - Remove obsolete src/dataverse_sdk/ directory and files - Ensure consistent namespace usage throughout the codebase All references now properly use the new PowerPlatform.Dataverse namespace. --- src/PowerPlatform/Dataverse/client.py | 18 +++++++++--------- src/PowerPlatform/Dataverse/core/auth.py | 2 +- src/PowerPlatform/Dataverse/core/config.py | 2 +- src/PowerPlatform/Dataverse/data/odata.py | 2 +- .../Dataverse/utils/pandas_adapter.py | 2 +- src/dataverse_sdk/__version__.py | 6 ------ 6 files changed, 13 insertions(+), 19 deletions(-) delete mode 100644 src/dataverse_sdk/__version__.py diff --git a/src/PowerPlatform/Dataverse/client.py b/src/PowerPlatform/Dataverse/client.py index 303b90a..8a3c865 100644 --- a/src/PowerPlatform/Dataverse/client.py +++ b/src/PowerPlatform/Dataverse/client.py @@ -18,7 +18,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:`~dataverse_sdk.odata.ODataClient`. + to an internal :class:`~PowerPlatform.Dataverse.data.odata.ODataClient`. Key capabilities: - OData CRUD operations: create, read, update, delete records @@ -32,8 +32,8 @@ class DataverseClient: :param credential: Azure Identity credential for authentication. :type credential: ~azure.core.credentials.TokenCredential :param config: Optional configuration for language, timeouts, and retries. - If not provided, defaults are loaded from :meth:`~dataverse_sdk.config.DataverseConfig.from_env`. - :type config: ~dataverse_sdk.config.DataverseConfig or None + If not provided, defaults are loaded from :meth:`~PowerPlatform.Dataverse.core.config.DataverseConfig.from_env`. + :type config: ~PowerPlatform.Dataverse.core.config.DataverseConfig or None :raises ValueError: If ``base_url`` is missing or empty after trimming. @@ -90,7 +90,7 @@ def _get_odata(self) -> ODataClient: deferring construction until the first API call. :return: The lazily-initialized low-level client used to perform HTTP requests. - :rtype: ~dataverse_sdk.odata.ODataClient + :rtype: ~PowerPlatform.Dataverse.data.odata.ODataClient """ if self._odata is None: self._odata = ODataClient( @@ -348,8 +348,8 @@ def query_sql(self, sql: str): :return: List of result row dictionaries. Returns an empty list if no rows match. :rtype: list[dict] - :raises ~dataverse_sdk.errors.SQLParseError: If the SQL query uses unsupported syntax. - :raises ~dataverse_sdk.errors.HttpError: If the Web API returns an error. + :raises ~PowerPlatform.Dataverse.core.errors.SQLParseError: If the SQL query uses unsupported syntax. + :raises ~PowerPlatform.Dataverse.core.errors.HttpError: If the Web API returns an error. .. note:: The SQL support is limited to read-only queries. Complex joins, subqueries, @@ -432,7 +432,7 @@ class ItemStatus(IntEnum): ``entity_set_name``, ``entity_logical_name``, ``metadata_id``, and ``columns_created``. :rtype: dict - :raises ~dataverse_sdk.errors.MetadataError: If table creation fails or the schema is invalid. + :raises ~PowerPlatform.Dataverse.core.errors.MetadataError: If table creation fails or the schema is invalid. Example: Create a table with simple columns:: @@ -469,7 +469,7 @@ def delete_table(self, tablename: str) -> None: (e.g. ``"new_SampleItem"``). :type tablename: str - :raises ~dataverse_sdk.errors.MetadataError: If the table does not exist or deletion fails. + :raises ~PowerPlatform.Dataverse.core.errors.MetadataError: If the table does not exist or deletion fails. .. warning:: This operation is irreversible and will delete all records in the table along @@ -594,7 +594,7 @@ def upload_file( ``If-Match: *``. Used for small and chunk modes only. :type if_none_match: bool - :raises ~dataverse_sdk.errors.HttpError: If the upload fails or the file column is not empty + :raises ~PowerPlatform.Dataverse.core.errors.HttpError: If the upload fails or the file column is not empty when ``if_none_match=True``. :raises FileNotFoundError: If the specified file path does not exist. diff --git a/src/PowerPlatform/Dataverse/core/auth.py b/src/PowerPlatform/Dataverse/core/auth.py index dcc7131..9207eb9 100644 --- a/src/PowerPlatform/Dataverse/core/auth.py +++ b/src/PowerPlatform/Dataverse/core/auth.py @@ -45,7 +45,7 @@ def acquire_token(self, scope: str) -> TokenPair: :param scope: OAuth2 scope string, typically ``"https://.crm.dynamics.com/.default"``. :type scope: str :return: Token pair containing the scope and access token. - :rtype: ~dataverse_sdk.auth.TokenPair + :rtype: ~PowerPlatform.Dataverse.core.auth.TokenPair :raises ~azure.core.exceptions.ClientAuthenticationError: If token acquisition fails. """ token = self.credential.get_token(scope) diff --git a/src/PowerPlatform/Dataverse/core/config.py b/src/PowerPlatform/Dataverse/core/config.py index 89bf0b2..2b74134 100644 --- a/src/PowerPlatform/Dataverse/core/config.py +++ b/src/PowerPlatform/Dataverse/core/config.py @@ -34,7 +34,7 @@ def from_env(cls) -> "DataverseConfig": Create a configuration instance with default settings. :return: Configuration instance with default values. - :rtype: ~dataverse_sdk.config.DataverseConfig + :rtype: ~PowerPlatform.Dataverse.core.config.DataverseConfig """ # Environment-free defaults return cls( diff --git a/src/PowerPlatform/Dataverse/data/odata.py b/src/PowerPlatform/Dataverse/data/odata.py index dcf2df6..c2880bd 100644 --- a/src/PowerPlatform/Dataverse/data/odata.py +++ b/src/PowerPlatform/Dataverse/data/odata.py @@ -43,7 +43,7 @@ def __init__( if not self.base_url: raise ValueError("base_url is required.") self.api = f"{self.base_url}/api/data/v9.2" - self.config = config or __import__("dataverse_sdk.core.config", fromlist=["DataverseConfig"]).DataverseConfig.from_env() + self.config = config or __import__("PowerPlatform.Dataverse.core.config", fromlist=["DataverseConfig"]).DataverseConfig.from_env() self._http = HttpClient( retries=self.config.http_retries, backoff=self.config.http_backoff, diff --git a/src/PowerPlatform/Dataverse/utils/pandas_adapter.py b/src/PowerPlatform/Dataverse/utils/pandas_adapter.py index a98854d..fcd398d 100644 --- a/src/PowerPlatform/Dataverse/utils/pandas_adapter.py +++ b/src/PowerPlatform/Dataverse/utils/pandas_adapter.py @@ -39,7 +39,7 @@ class PandasODataClient: High-level pandas-friendly wrapper for Dataverse OData operations. :param odata_client: Initialized low-level OData client with authentication configured. - :type odata_client: ~dataverse_sdk.odata.ODataClient + :type odata_client: ~PowerPlatform.Dataverse.data.odata.ODataClient """ def __init__(self, odata_client: ODataClient) -> None: diff --git a/src/dataverse_sdk/__version__.py b/src/dataverse_sdk/__version__.py deleted file mode 100644 index ec02e7d..0000000 --- a/src/dataverse_sdk/__version__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. - -"""Version information for PowerPlatform-Dataverse-Client package.""" - -__version__ = "0.1.0" From 88827f2a2d2f1551327af36e47591565f574c54f Mon Sep 17 00:00:00 2001 From: Suyash Kshirsagar Date: Mon, 10 Nov 2025 21:02:11 -0800 Subject: [PATCH 6/9] Clean up empty placeholder directories Remove unused test directories that only contained placeholder __init__.py files: - tests/integration/ - No integration tests present - tests/unit/utils/ - No utility unit tests present This completes the directory structure cleanup after the namespace migration. --- tests/integration/__init__.py | 4 ---- tests/unit/utils/__init__.py | 4 ---- 2 files changed, 8 deletions(-) delete mode 100644 tests/integration/__init__.py delete mode 100644 tests/unit/utils/__init__.py diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py deleted file mode 100644 index db34dfb..0000000 --- a/tests/integration/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. - -"""Integration tests for the Dataverse SDK.""" \ No newline at end of file diff --git a/tests/unit/utils/__init__.py b/tests/unit/utils/__init__.py deleted file mode 100644 index 7bed24e..0000000 --- a/tests/unit/utils/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. - -"""Unit tests for utility components.""" \ No newline at end of file From 3f3dad6c44428d122ff2837cd2c1aaee3318f9f5 Mon Sep 17 00:00:00 2001 From: Suyash Kshirsagar Date: Mon, 10 Nov 2025 21:14:38 -0800 Subject: [PATCH 7/9] Remove DefaultAzureCredential references from documentation and examples - Replace DefaultAzureCredential with InteractiveBrowserCredential in docstring examples - Update examples/README.md to recommend more specific credential types - Remove DefaultAzureCredential recommendations to avoid potential auth issues - Use explicit credential types for better developer experience --- examples/README.md | 6 +++--- src/PowerPlatform/Dataverse/__init__.py | 4 ++-- src/PowerPlatform/Dataverse/client.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/README.md b/examples/README.md index d17705d..15c05ff 100644 --- a/examples/README.md +++ b/examples/README.md @@ -90,9 +90,9 @@ python examples/advanced/complete_walkthrough.py ## ๐Ÿ”’ Authentication All examples use Azure Identity for authentication. Common patterns: -- `DefaultAzureCredential` for development -- `ClientSecretCredential` for production services -- `InteractiveBrowserCredential` for interactive scenarios +- `InteractiveBrowserCredential` for development and interactive scenarios +- `DeviceCodeCredential` for development on headless systems +- `ClientSecretCredential` for production services with service principals ## ๐Ÿ“– Documentation diff --git a/src/PowerPlatform/Dataverse/__init__.py b/src/PowerPlatform/Dataverse/__init__.py index 74c072b..1a5dca2 100644 --- a/src/PowerPlatform/Dataverse/__init__.py +++ b/src/PowerPlatform/Dataverse/__init__.py @@ -24,10 +24,10 @@ Example: Basic client initialization and usage:: - from azure.identity import DefaultAzureCredential + from azure.identity import InteractiveBrowserCredential from PowerPlatform.Dataverse import DataverseClient - credential = DefaultAzureCredential() + credential = InteractiveBrowserCredential() client = DataverseClient( "https://org.crm.dynamics.com", credential diff --git a/src/PowerPlatform/Dataverse/client.py b/src/PowerPlatform/Dataverse/client.py index 8a3c865..9a18fc8 100644 --- a/src/PowerPlatform/Dataverse/client.py +++ b/src/PowerPlatform/Dataverse/client.py @@ -44,10 +44,10 @@ class DataverseClient: Example: Create a client and perform basic operations:: - from azure.identity import DefaultAzureCredential + from azure.identity import InteractiveBrowserCredential from PowerPlatform.Dataverse import DataverseClient - credential = DefaultAzureCredential() + credential = InteractiveBrowserCredential() client = DataverseClient( "https://org.crm.dynamics.com", credential From a7965a076acb13af182c2010effd3c3d53f9cb41 Mon Sep 17 00:00:00 2001 From: Suyash Kshirsagar Date: Mon, 10 Nov 2025 21:19:16 -0800 Subject: [PATCH 8/9] Fix broken example file references in README.md - Update quickstart.py references to examples/advanced/complete_walkthrough.py - Update quickstart_file_upload.py reference to examples/advanced/file_upload.py - Update quickstart_pandas.py reference to examples/advanced/pandas_integration.py - All references now point to existing files in the current directory structure --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 91ff9c1..dae7d95 100644 --- a/README.md +++ b/README.md @@ -89,13 +89,13 @@ client = DataverseClient(base_url=base_url, credential=credential) ## Quickstart -Edit `examples/quickstart.py` and run: +For a comprehensive walkthrough, edit `examples/advanced/complete_walkthrough.py` and run: ```powershell -python examples/quickstart.py +python examples/advanced/complete_walkthrough.py ``` -The quickstart demonstrates: +The walkthrough demonstrates: - Creating a simple custom table (metadata APIs) - Creating, reading, updating, and deleting records (OData) - Bulk create (CreateMultiple) to insert many records in one call @@ -103,7 +103,7 @@ The quickstart demonstrates: - Retrieve multiple with paging (`$top` vs `page_size`) - Executing a read-only SQL query (Web API `?sql=`) -For upload files functionalities, run quickstart_file_upload.py instead +For upload files functionalities, run `examples/advanced/file_upload.py` instead ## Examples @@ -343,7 +343,7 @@ Notes: ### Pandas helpers -`PandasODataClient` is a thin wrapper around the low-level client. All methods accept logical (singular) names (e.g. `account`, `new_sampleitem`), not entity set (plural) names. See `examples/quickstart_pandas.py` for a DataFrame workflow. +`PandasODataClient` is a thin wrapper around the low-level client. All methods accept logical (singular) names (e.g. `account`, `new_sampleitem`), not entity set (plural) names. See `examples/advanced/pandas_integration.py` for a DataFrame workflow. VS Code Tasks - Install deps: `Install deps (pip)` From d8a23d4a8359fda4a8d05707119936a919332413 Mon Sep 17 00:00:00 2001 From: Suyash Kshirsagar Date: Tue, 11 Nov 2025 11:57:14 -0800 Subject: [PATCH 9/9] Enhance installation example with development workflow guidance - Add comprehensive installation troubleshooting for both end users and developers - Explain difference between 'pip install PowerPlatform-Dataverse-Client' vs 'pip install -e .' - Provide clear guidance on when to use each installation approach - Improve error handling and user experience for package validation - Add educational content about editable mode for development workflows --- examples/basic/installation_example.py | 36 +++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/examples/basic/installation_example.py b/examples/basic/installation_example.py index 3820feb..ef47e79 100644 --- a/examples/basic/installation_example.py +++ b/examples/basic/installation_example.py @@ -12,7 +12,8 @@ ## Installation -1. Install the SDK: +### For End Users (Production/Consumption): +1. Install the published SDK from PyPI: ```bash pip install PowerPlatform-Dataverse-Client ``` @@ -22,6 +23,23 @@ pip install azure-identity ``` +### For Developers (Contributing/Local Development): +1. Clone the repository and navigate to the project directory +2. Install in editable/development mode: + ```bash + pip install -e . + ``` + +**Key Differences:** +- `pip install PowerPlatform-Dataverse-Client` โ†’ Downloads and installs the published package from PyPI +- `pip install -e .` โ†’ Installs from local source code in "editable" mode + +**Editable Mode Benefits:** +- โœ… Changes to source code are immediately available (no reinstall needed) +- โœ… Perfect for development, testing, and contributing +- โœ… Examples and tests can access the local codebase +- โœ… Supports debugging and live code modifications + ## What This Script Does - โœ… Validates package installation and imports @@ -76,9 +94,19 @@ def validate_imports(): except ImportError as e: print(f" โŒ Import failed: {e}") print("\n๐Ÿ’ก Troubleshooting:") - print(" โ€ข Install with: pip install PowerPlatform-Dataverse-Client") - print(" โ€ข Install Azure Identity: pip install azure-identity") - print(" โ€ข Check virtual environment is activated") + print(" ๐Ÿ“ฆ For end users (published package):") + print(" โ€ข pip install PowerPlatform-Dataverse-Client") + print(" โ€ข pip install azure-identity") + print(" ") + print(" ๐Ÿ› ๏ธ For developers (local development):") + print(" โ€ข Navigate to the project root directory") + print(" โ€ข pip install -e .") + print(" โ€ข This enables 'editable mode' for live development") + print(" ") + print(" ๐Ÿ”ง General fixes:") + print(" โ€ข Check virtual environment is activated") + print(" โ€ข Verify you're in the correct directory") + print(" โ€ข Try: pip list | grep PowerPlatform") return False, None, None