diff --git a/CHANGELOG.md b/CHANGELOG.md index 81f7ecc..66d519e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ nylas-python Changelog Unreleased ---------------- +* Added support for `single_level` query parameter in `ListFolderQueryParams` for Microsoft accounts to control folder hierarchy traversal * Added support for `earliest_message_date` query parameter for threads * Fixed `earliest_message_date` not being an optional response field * Added support for new message fields query parameter values: `include_tracking_options` and `raw_mime` @@ -97,7 +98,7 @@ v6.0.0 * **BREAKING CHANGE**: Models no longer inherit from `dict` but instead either are a `dataclass` or inherit from `TypedDict` * **BREAKING CHANGE**: Renamed the SDK entrypoint from `APIClient` to `Client` * **REMOVED**: Local Webhook development support is removed due to incompatibility -* Rewrote the majority of SDK to be more intuitive, explicit, and efficient +* Rewritten the majority of SDK to be more intuitive, explicit, and efficient * Created models for all API resources and endpoints, for all HTTP methods to reduce confusion on which fields are available for each endpoint * Created error classes for the different API errors as well as SDK-specific errors diff --git a/examples/folders_demo/README.md b/examples/folders_demo/README.md new file mode 100644 index 0000000..413d2a3 --- /dev/null +++ b/examples/folders_demo/README.md @@ -0,0 +1,64 @@ +# Folders Single Level Parameter Example + +This example demonstrates how to use the `single_level` query parameter when listing folders to control folder hierarchy traversal for Microsoft accounts. + +## Overview + +The `single_level` parameter is a Microsoft-only feature that allows you to control whether the folders API returns: +- **`single_level=true`**: Only top-level folders (single-level hierarchy) +- **`single_level=false`**: All folders including nested ones (multi-level hierarchy, default) + +This parameter is useful for: +- **Performance optimization**: Reducing response size when you only need top-level folders +- **UI simplification**: Building folder trees incrementally +- **Microsoft-specific behavior**: Taking advantage of Microsoft's folder hierarchy structure + +## Prerequisites + +- Nylas API key +- Nylas grant ID for a Microsoft account (this parameter only works with Microsoft accounts) + +## Setup + +1. Install the SDK in development mode: + ```bash + cd /path/to/nylas-python + pip install -e . + ``` + +2. Set your environment variables: + ```bash + export NYLAS_API_KEY="your_api_key" + export NYLAS_GRANT_ID="your_microsoft_grant_id" + ``` + +## Running the Example + +```bash +python examples/folders_demo/folders_single_level_example.py +``` + +## What the Example Demonstrates + +1. **Multi-level folder hierarchy** (default behavior) +2. **Single-level folder hierarchy** using `single_level=true` +3. **Combined parameters** showing how to use `single_level` with other query parameters +4. **Hierarchy comparison** showing the difference in folder counts + +## Expected Output + +The example will show: +- Folders returned with multi-level hierarchy +- Folders returned with single-level hierarchy only +- Count comparison between the two approaches +- How to combine the parameter with other options like `limit` and `select` + +## Use Cases + +- **Folder tree UI**: Load top-level folders first, then expand as needed +- **Performance**: Reduce API response size for Microsoft accounts with deep folder structures +- **Microsoft-specific integrations**: Take advantage of Microsoft's native folder organization + +## Note + +This parameter only works with Microsoft accounts. If you use it with other providers, it will be ignored. \ No newline at end of file diff --git a/examples/folders_demo/folders_single_level_example.py b/examples/folders_demo/folders_single_level_example.py new file mode 100644 index 0000000..335b464 --- /dev/null +++ b/examples/folders_demo/folders_single_level_example.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +""" +Nylas SDK Example: Using Single Level Parameter for Folders + +This example demonstrates how to use the 'single_level' query parameter when listing folders +to control the folder hierarchy traversal for Microsoft accounts. + +Required Environment Variables: + NYLAS_API_KEY: Your Nylas API key + NYLAS_GRANT_ID: Your Nylas grant ID (must be a Microsoft account) + +Usage: + First, install the SDK in development mode: + cd /path/to/nylas-python + pip install -e . + + Then set environment variables and run: + export NYLAS_API_KEY="your_api_key" + export NYLAS_GRANT_ID="your_microsoft_grant_id" + python examples/folders_demo/folders_single_level_example.py +""" + +import os +import sys +import json +from nylas import Client + + +def get_env_or_exit(var_name: str) -> str: + """Get an environment variable or exit if not found.""" + value = os.getenv(var_name) + if not value: + print(f"Error: {var_name} environment variable is required") + sys.exit(1) + return value + + +def print_folders(folders: list, title: str) -> None: + """Pretty print the folders with a title.""" + print(f"\n{title}:") + if not folders: + print(" No folders found.") + return + + for folder in folders: + # Convert to dict and pretty print relevant fields + folder_dict = folder.to_dict() + print( + f" - {folder_dict.get('name', 'Unknown')} (ID: {folder_dict.get('id', 'Unknown')})" + ) + if folder_dict.get("parent_id"): + print(f" Parent ID: {folder_dict['parent_id']}") + if folder_dict.get("child_count") is not None: + print(f" Child Count: {folder_dict['child_count']}") + + +def demonstrate_multi_level_folders(client: Client, grant_id: str) -> None: + """Demonstrate multi-level folder hierarchy (default behavior).""" + print("\n=== Multi-Level Folder Hierarchy (Default) ===") + + # Default behavior - retrieves folders across multi-level hierarchy + print("\nFetching folders with multi-level hierarchy (single_level=False):") + folders = client.folders.list( + identifier=grant_id, query_params={"single_level": False} + ) + print_folders(folders.data, "Multi-level folder hierarchy") + + # Also demonstrate without explicitly setting single_level (default behavior) + print("\nFetching folders without single_level parameter (default behavior):") + folders = client.folders.list(identifier=grant_id) + print_folders(folders.data, "Default folder hierarchy (multi-level)") + + +def demonstrate_single_level_folders(client: Client, grant_id: str) -> None: + """Demonstrate single-level folder hierarchy.""" + print("\n=== Single-Level Folder Hierarchy ===") + + # Single-level hierarchy - only retrieves folders from the top level + print("\nFetching folders with single-level hierarchy (single_level=True):") + folders = client.folders.list( + identifier=grant_id, query_params={"single_level": True} + ) + print_folders(folders.data, "Single-level folder hierarchy") + + +def demonstrate_combined_parameters(client: Client, grant_id: str) -> None: + """Demonstrate single_level combined with other parameters.""" + print("\n=== Combined Parameters ===") + + # Combine single_level with other query parameters + print("\nFetching limited single-level folders with select fields:") + folders = client.folders.list( + identifier=grant_id, + query_params={ + "single_level": True, + "limit": 5, + "select": "id,name,parent_id,child_count", + }, + ) + print_folders(folders.data, "Limited single-level folders with selected fields") + + +def compare_hierarchies(client: Client, grant_id: str) -> None: + """Compare single-level vs multi-level folder counts.""" + print("\n=== Hierarchy Comparison ===") + + # Get multi-level count + multi_level_folders = client.folders.list( + identifier=grant_id, query_params={"single_level": False} + ) + multi_level_count = len(multi_level_folders.data) + + # Get single-level count + single_level_folders = client.folders.list( + identifier=grant_id, query_params={"single_level": True} + ) + single_level_count = len(single_level_folders.data) + + print(f"\nFolder count comparison:") + print(f" Multi-level hierarchy: {multi_level_count} folders") + print(f" Single-level hierarchy: {single_level_count} folders") + + if multi_level_count > single_level_count: + print( + f" Difference: {multi_level_count - single_level_count} folders in sub-hierarchies" + ) + elif single_level_count == multi_level_count: + print(" No nested folders detected in this account") + + +def main(): + """Main function demonstrating single_level parameter usage for folders.""" + # Get required environment variables + api_key = get_env_or_exit("NYLAS_API_KEY") + grant_id = get_env_or_exit("NYLAS_GRANT_ID") + + # Initialize Nylas client + client = Client(api_key=api_key) + + print("\nDemonstrating Single Level Parameter for Folders") + print("===============================================") + print("This parameter is Microsoft-only and controls folder hierarchy traversal") + print(f"Using Grant ID: {grant_id}") + + try: + # Demonstrate different folder hierarchy options + demonstrate_multi_level_folders(client, grant_id) + demonstrate_single_level_folders(client, grant_id) + demonstrate_combined_parameters(client, grant_id) + compare_hierarchies(client, grant_id) + + print("\n=== Summary ===") + print("• single_level=True: Returns only top-level folders (Microsoft only)") + print("• single_level=False: Returns folders from all levels (default)") + print("• This parameter helps optimize performance for Microsoft accounts") + print("• Can be combined with other query parameters like limit and select") + + except Exception as e: + print(f"\nError: {e}") + print("\nNote: This example requires a Microsoft grant ID.") + print("The single_level parameter only works with Microsoft accounts.") + + print("\nExample completed!") + + +if __name__ == "__main__": + main() diff --git a/nylas/models/folders.py b/nylas/models/folders.py index 205502d..0d5e979 100644 --- a/nylas/models/folders.py +++ b/nylas/models/folders.py @@ -85,6 +85,8 @@ class ListFolderQueryParams(ListQueryParams): Attributes: parent_id: (Microsoft and EWS only.) Use the ID of a folder to find all child folders it contains. + single_level: (Microsoft only) If true, retrieves folders from a single-level hierarchy only. + If false, retrieves folders across a multi-level hierarchy. Defaults to false. select (NotRequired[str]): Comma-separated list of fields to return in the response. This allows you to receive only the portion of object data that you're interested in. limit (NotRequired[int]): The maximum number of objects to return. @@ -94,6 +96,7 @@ class ListFolderQueryParams(ListQueryParams): """ parent_id: NotRequired[str] + single_level: NotRequired[bool] class FindFolderQueryParams(TypedDict): diff --git a/tests/resources/test_folders.py b/tests/resources/test_folders.py index da53dec..021e7b4 100644 --- a/tests/resources/test_folders.py +++ b/tests/resources/test_folders.py @@ -58,26 +58,71 @@ def test_list_folders_with_query_params(self, http_client_list_response): overrides=None, ) + def test_list_folders_with_single_level_param(self, http_client_list_response): + folders = Folders(http_client_list_response) + + folders.list(identifier="abc-123", query_params={"single_level": True}) + + http_client_list_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/folders", + None, + {"single_level": True}, + None, + overrides=None, + ) + + def test_list_folders_with_single_level_false(self, http_client_list_response): + folders = Folders(http_client_list_response) + + folders.list(identifier="abc-123", query_params={"single_level": False}) + + http_client_list_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/folders", + None, + {"single_level": False}, + None, + overrides=None, + ) + + def test_list_folders_with_combined_params(self, http_client_list_response): + folders = Folders(http_client_list_response) + + folders.list( + identifier="abc-123", + query_params={"single_level": True, "parent_id": "parent-123", "limit": 10}, + ) + + http_client_list_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/folders", + None, + {"single_level": True, "parent_id": "parent-123", "limit": 10}, + None, + overrides=None, + ) + def test_list_folders_with_select_param(self, http_client_list_response): folders = Folders(http_client_list_response) # Set up mock response data http_client_list_response._execute.return_value = { "request_id": "abc-123", - "data": [{ - "id": "folder-123", - "name": "Important", - "total_count": 42, - "unread_count": 5 - }] + "data": [ + { + "id": "folder-123", + "name": "Important", + "total_count": 42, + "unread_count": 5, + } + ], } # Call the API method result = folders.list( identifier="abc-123", - query_params={ - "select": "id,name,total_count,unread_count" - } + query_params={"select": "id,name,total_count,unread_count"}, ) # Verify API call @@ -111,21 +156,24 @@ def test_find_folder_with_select_param(self, http_client_response): folders = Folders(http_client_response) # Set up mock response data - http_client_response._execute.return_value = ({ - "request_id": "abc-123", - "data": { - "id": "folder-123", - "name": "Important", - "total_count": 42, - "unread_count": 5 - } - }, {"X-Test-Header": "test"}) + http_client_response._execute.return_value = ( + { + "request_id": "abc-123", + "data": { + "id": "folder-123", + "name": "Important", + "total_count": 42, + "unread_count": 5, + }, + }, + {"X-Test-Header": "test"}, + ) # Call the API method result = folders.find( identifier="abc-123", folder_id="folder-123", - query_params={"select": "id,name,total_count,unread_count"} + query_params={"select": "id,name,total_count,unread_count"}, ) # Verify API call