Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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

Expand Down
64 changes: 64 additions & 0 deletions examples/folders_demo/README.md
Original file line number Diff line number Diff line change
@@ -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.
167 changes: 167 additions & 0 deletions examples/folders_demo/folders_single_level_example.py
Original file line number Diff line number Diff line change
@@ -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()
3 changes: 3 additions & 0 deletions nylas/models/folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -94,6 +96,7 @@ class ListFolderQueryParams(ListQueryParams):
"""

parent_id: NotRequired[str]
single_level: NotRequired[bool]


class FindFolderQueryParams(TypedDict):
Expand Down
86 changes: 67 additions & 19 deletions tests/resources/test_folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down