diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..1976079 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,59 @@ +# Dataverse SDK Examples + +This directory contains comprehensive examples demonstrating how to use the Microsoft Dataverse SDK for Python. + +## 📁 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 + +## 🚀 Getting Started + +1. **Install Dependencies**: + ```bash + pip install -r requirements.txt + ``` + +2. **Set Up Authentication**: + Configure Azure Identity credentials (see individual examples for details) + +3. **Run Basic Example**: + ```bash + python examples/basic/quickstart.py + ``` + +## 📋 Prerequisites + +- Python 3.8+ +- Azure Identity credentials configured +- Access to a Dataverse environment +- Required packages installed from `requirements.txt` + +## 🔒 Authentication + +All examples use Azure Identity for authentication. Common patterns: +- `DefaultAzureCredential` for development +- `ClientSecretCredential` for production services +- `InteractiveBrowserCredential` for interactive scenarios + +## 📖 Documentation + +For detailed API documentation, visit: [Dataverse SDK Documentation](link-to-docs) + +## 🤝 Contributing + +When adding new examples: +1. Follow the existing code style and structure +2. Include comprehensive comments and docstrings +3. Add error handling and validation +4. Update this README with your example +5. Test thoroughly before submitting \ No newline at end of file diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 0000000..87c2ba9 --- /dev/null +++ b/examples/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +"""Examples package for the Dataverse SDK.""" \ No newline at end of file diff --git a/examples/advanced/__init__.py b/examples/advanced/__init__.py new file mode 100644 index 0000000..fc6b584 --- /dev/null +++ b/examples/advanced/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +"""Advanced examples showcasing complex Dataverse SDK features.""" \ No newline at end of file diff --git a/examples/quickstart_file_upload.py b/examples/advanced/file_upload.py similarity index 100% rename from examples/quickstart_file_upload.py rename to examples/advanced/file_upload.py diff --git a/examples/quickstart_pandas.py b/examples/advanced/pandas_integration.py similarity index 99% rename from examples/quickstart_pandas.py rename to examples/advanced/pandas_integration.py index 5f8ac8a..a117ed9 100644 --- a/examples/quickstart_pandas.py +++ b/examples/advanced/pandas_integration.py @@ -9,7 +9,7 @@ sys.path.append(str(Path(__file__).resolve().parents[1] / "src")) from dataverse_sdk import DataverseClient -from dataverse_sdk.odata_pandas_wrappers import PandasODataClient +from dataverse_sdk.utils.pandas_adapter import PandasODataClient from azure.identity import InteractiveBrowserCredential import traceback import requests diff --git a/examples/basic/__init__.py b/examples/basic/__init__.py new file mode 100644 index 0000000..5eff67a --- /dev/null +++ b/examples/basic/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +"""Basic examples for getting started with the Dataverse SDK.""" \ No newline at end of file diff --git a/examples/quickstart.py b/examples/basic/quickstart.py similarity index 99% rename from examples/quickstart.py rename to examples/basic/quickstart.py index 7b6d713..f4da0f9 100644 --- a/examples/quickstart.py +++ b/examples/basic/quickstart.py @@ -10,7 +10,7 @@ sys.path.append(str(Path(__file__).resolve().parents[1] / "src")) from dataverse_sdk import DataverseClient -from dataverse_sdk.errors import MetadataError +from dataverse_sdk.core.errors import MetadataError from enum import IntEnum from azure.identity import InteractiveBrowserCredential import traceback diff --git a/src/dataverse_sdk/client.py b/src/dataverse_sdk/client.py index 055622e..99dcbde 100644 --- a/src/dataverse_sdk/client.py +++ b/src/dataverse_sdk/client.py @@ -7,9 +7,9 @@ from azure.core.credentials import TokenCredential -from .auth import AuthManager -from .config import DataverseConfig -from .odata import ODataClient +from .core.auth import AuthManager +from .core.config import DataverseConfig +from .data.odata import ODataClient class DataverseClient: diff --git a/src/dataverse_sdk/core/__init__.py b/src/dataverse_sdk/core/__init__.py new file mode 100644 index 0000000..ccf2a6e --- /dev/null +++ b/src/dataverse_sdk/core/__init__.py @@ -0,0 +1,32 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +""" +Core infrastructure components for the Dataverse SDK. + +This module contains the foundational components including authentication, +configuration, HTTP client, and error handling. +""" + +from .auth import AuthManager, TokenPair +from .config import DataverseConfig +from .errors import ( + DataverseError, + HttpError, + ValidationError, + MetadataError, + SQLParseError, +) +from .http import HttpClient + +__all__ = [ + "AuthManager", + "TokenPair", + "DataverseConfig", + "DataverseError", + "HttpError", + "ValidationError", + "MetadataError", + "SQLParseError", + "HttpClient", +] \ No newline at end of file diff --git a/src/dataverse_sdk/auth.py b/src/dataverse_sdk/core/auth.py similarity index 100% rename from src/dataverse_sdk/auth.py rename to src/dataverse_sdk/core/auth.py diff --git a/src/dataverse_sdk/config.py b/src/dataverse_sdk/core/config.py similarity index 100% rename from src/dataverse_sdk/config.py rename to src/dataverse_sdk/core/config.py diff --git a/src/dataverse_sdk/error_codes.py b/src/dataverse_sdk/core/error_codes.py similarity index 100% rename from src/dataverse_sdk/error_codes.py rename to src/dataverse_sdk/core/error_codes.py diff --git a/src/dataverse_sdk/errors.py b/src/dataverse_sdk/core/errors.py similarity index 100% rename from src/dataverse_sdk/errors.py rename to src/dataverse_sdk/core/errors.py diff --git a/src/dataverse_sdk/http.py b/src/dataverse_sdk/core/http.py similarity index 100% rename from src/dataverse_sdk/http.py rename to src/dataverse_sdk/core/http.py diff --git a/src/dataverse_sdk/data/__init__.py b/src/dataverse_sdk/data/__init__.py new file mode 100644 index 0000000..a5854b8 --- /dev/null +++ b/src/dataverse_sdk/data/__init__.py @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +""" +Data access layer for the Dataverse SDK. + +This module contains OData protocol handling, CRUD operations, metadata management, +SQL query functionality, and file upload capabilities. +""" + +from .odata import ODataClient +from .upload import ODataFileUpload + +__all__ = ["ODataClient", "ODataFileUpload"] \ No newline at end of file diff --git a/src/dataverse_sdk/odata.py b/src/dataverse_sdk/data/odata.py similarity index 99% rename from src/dataverse_sdk/odata.py rename to src/dataverse_sdk/data/odata.py index bf1755c..87551c3 100644 --- a/src/dataverse_sdk/odata.py +++ b/src/dataverse_sdk/data/odata.py @@ -12,10 +12,10 @@ from datetime import datetime, timezone import importlib.resources as ir -from .http import HttpClient -from .odata_upload_files import ODataFileUpload -from .errors import * -from . import error_codes as ec +from ..core.http import HttpClient +from .upload import ODataFileUpload +from ..core.errors import * +from ..core import error_codes as ec _GUID_RE = re.compile(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}") @@ -40,7 +40,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.config", fromlist=["DataverseConfig"]).DataverseConfig.from_env() + self.config = config or __import__("dataverse_sdk.core.config", fromlist=["DataverseConfig"]).DataverseConfig.from_env() self._http = HttpClient( retries=self.config.http_retries, backoff=self.config.http_backoff, diff --git a/src/dataverse_sdk/odata_upload_files.py b/src/dataverse_sdk/data/upload.py similarity index 100% rename from src/dataverse_sdk/odata_upload_files.py rename to src/dataverse_sdk/data/upload.py diff --git a/src/dataverse_sdk/extensions/__init__.py b/src/dataverse_sdk/extensions/__init__.py new file mode 100644 index 0000000..2a69d30 --- /dev/null +++ b/src/dataverse_sdk/extensions/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +""" +Optional extensions for the Dataverse SDK. + +This module contains optional features like CLI interfaces, async clients, +and other extended functionality. +""" + +# Will be populated with extensions as they are created +__all__ = [] \ No newline at end of file diff --git a/src/dataverse_sdk/models/__init__.py b/src/dataverse_sdk/models/__init__.py new file mode 100644 index 0000000..c6c679e --- /dev/null +++ b/src/dataverse_sdk/models/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +""" +Data models and type definitions for the Dataverse SDK. + +This module contains entity models, response models, enums, and other +type definitions used throughout the SDK. +""" + +# Will be populated with models as they are created +__all__ = [] \ No newline at end of file diff --git a/src/dataverse_sdk/utils/__init__.py b/src/dataverse_sdk/utils/__init__.py new file mode 100644 index 0000000..e524cc1 --- /dev/null +++ b/src/dataverse_sdk/utils/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +""" +Utilities and adapters for the Dataverse SDK. + +This module contains helper functions, adapters (like Pandas integration), +logging utilities, and validation helpers. +""" + +from .pandas_adapter import PandasODataClient + +__all__ = ["PandasODataClient"] \ No newline at end of file diff --git a/src/dataverse_sdk/odata_pandas_wrappers.py b/src/dataverse_sdk/utils/pandas_adapter.py similarity index 99% rename from src/dataverse_sdk/odata_pandas_wrappers.py rename to src/dataverse_sdk/utils/pandas_adapter.py index 185a9fc..a98854d 100644 --- a/src/dataverse_sdk/odata_pandas_wrappers.py +++ b/src/dataverse_sdk/utils/pandas_adapter.py @@ -17,7 +17,7 @@ import pandas as pd -from .odata import ODataClient +from ..data.odata import ODataClient @dataclass diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..cdfc326 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +"""Test package for the Dataverse SDK.""" \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..68661e3 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,65 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +""" +Shared pytest fixtures and configuration for Dataverse SDK tests. + +This module provides common test fixtures, mock objects, and configuration +that can be used across all test modules. +""" + +import pytest +from unittest.mock import Mock +from dataverse_sdk.core.config import DataverseConfig + + +@pytest.fixture +def dummy_auth(): + """Mock authentication object for testing.""" + class DummyAuth: + def acquire_token(self, scope): + class Token: + access_token = "test_token_12345" + return Token() + return DummyAuth() + + +@pytest.fixture +def test_config(): + """Test configuration with safe defaults.""" + return DataverseConfig( + language_code=1033, + http_retries=0, + http_backoff=0.1, + http_timeout=5 + ) + + +@pytest.fixture +def mock_http_client(): + """Mock HTTP client for unit tests.""" + mock = Mock() + mock.request.return_value = Mock() + return mock + + +@pytest.fixture +def sample_base_url(): + """Standard test base URL.""" + return "https://org.example.com" + + +@pytest.fixture +def sample_entity_data(): + """Sample entity data for testing.""" + return { + "name": "Test Account", + "telephone1": "555-0100", + "websiteurl": "https://example.com" + } + + +@pytest.fixture +def sample_guid(): + """Sample GUID for testing.""" + return "11111111-2222-3333-4444-555555555555" \ No newline at end of file diff --git a/tests/fixtures/test_data.py b/tests/fixtures/test_data.py new file mode 100644 index 0000000..3e2fc73 --- /dev/null +++ b/tests/fixtures/test_data.py @@ -0,0 +1,69 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +""" +Sample test data and fixtures for Dataverse SDK tests. + +This module contains reusable test data, mock responses, and fixtures +that can be used across different test modules. +""" + +# Sample entity metadata response +SAMPLE_ENTITY_METADATA = { + "value": [ + { + "LogicalName": "account", + "EntitySetName": "accounts", + "PrimaryIdAttribute": "accountid", + "DisplayName": {"UserLocalizedLabel": {"Label": "Account"}} + }, + { + "LogicalName": "contact", + "EntitySetName": "contacts", + "PrimaryIdAttribute": "contactid", + "DisplayName": {"UserLocalizedLabel": {"Label": "Contact"}} + } + ] +} + +# Sample OData response for accounts +SAMPLE_ACCOUNTS_RESPONSE = { + "value": [ + { + "accountid": "11111111-2222-3333-4444-555555555555", + "name": "Contoso Ltd", + "telephone1": "555-0100", + "websiteurl": "https://contoso.com" + }, + { + "accountid": "22222222-3333-4444-5555-666666666666", + "name": "Fabrikam Inc", + "telephone1": "555-0200", + "websiteurl": "https://fabrikam.com" + } + ] +} + +# Sample error responses +SAMPLE_ERROR_RESPONSES = { + "404": { + "error": { + "code": "0x80040217", + "message": "The requested resource was not found." + } + }, + "429": { + "error": { + "code": "0x80072321", + "message": "Too many requests. Please retry after some time." + } + } +} + +# Sample SQL query results +SAMPLE_SQL_RESPONSE = { + "value": [ + {"name": "Account 1", "revenue": 1000000}, + {"name": "Account 2", "revenue": 2000000} + ] +} \ No newline at end of file diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..db34dfb --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1,4 @@ +# 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/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..bce68aa --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +"""Unit tests for the Dataverse SDK.""" \ No newline at end of file diff --git a/tests/unit/core/__init__.py b/tests/unit/core/__init__.py new file mode 100644 index 0000000..b3b8cd6 --- /dev/null +++ b/tests/unit/core/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +"""Unit tests for core infrastructure components.""" \ No newline at end of file diff --git a/tests/test_http_errors.py b/tests/unit/core/test_http_errors.py similarity index 94% rename from tests/test_http_errors.py rename to tests/unit/core/test_http_errors.py index fd08d33..4c3a0e0 100644 --- a/tests/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.errors import HttpError -from dataverse_sdk import error_codes as ec -from dataverse_sdk.odata import ODataClient +from dataverse_sdk.core.errors import HttpError +from dataverse_sdk.core import error_codes as ec +from dataverse_sdk.data.odata import ODataClient class DummyAuth: def acquire_token(self, scope): diff --git a/tests/unit/data/__init__.py b/tests/unit/data/__init__.py new file mode 100644 index 0000000..c0c3a00 --- /dev/null +++ b/tests/unit/data/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +"""Unit tests for data access components.""" \ No newline at end of file diff --git a/tests/test_enum_optionset_payload.py b/tests/unit/data/test_enum_optionset_payload.py similarity index 99% rename from tests/test_enum_optionset_payload.py rename to tests/unit/data/test_enum_optionset_payload.py index 5d85ddb..ca58a38 100644 --- a/tests/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.odata import ODataClient +from dataverse_sdk.data.odata import ODataClient class DummyAuth: def acquire_token(self, scope): # pragma: no cover - simple stub diff --git a/tests/test_logical_crud.py b/tests/unit/data/test_logical_crud.py similarity index 97% rename from tests/test_logical_crud.py rename to tests/unit/data/test_logical_crud.py index 58ad3f6..c3233ea 100644 --- a/tests/test_logical_crud.py +++ b/tests/unit/data/test_logical_crud.py @@ -3,8 +3,8 @@ import types import pytest -from dataverse_sdk.odata import ODataClient -from dataverse_sdk.errors import MetadataError +from dataverse_sdk.data.odata import ODataClient +from dataverse_sdk.core.errors import MetadataError class DummyAuth: def acquire_token(self, scope): diff --git a/tests/test_sql_parse.py b/tests/unit/data/test_sql_parse.py similarity index 96% rename from tests/test_sql_parse.py rename to tests/unit/data/test_sql_parse.py index 68049f7..5850476 100644 --- a/tests/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.odata import ODataClient +from dataverse_sdk.data.odata import ODataClient class DummyAuth: def acquire_token(self, scope): diff --git a/tests/unit/utils/__init__.py b/tests/unit/utils/__init__.py new file mode 100644 index 0000000..7bed24e --- /dev/null +++ b/tests/unit/utils/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +"""Unit tests for utility components.""" \ No newline at end of file