Skip to content

Commit eb178f1

Browse files
authored
feat: add single_level query parameter support for folders API (#425)
1 parent 9bf6bd0 commit eb178f1

File tree

5 files changed

+303
-20
lines changed

5 files changed

+303
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ nylas-python Changelog
33

44
Unreleased
55
----------------
6+
* Added support for `single_level` query parameter in `ListFolderQueryParams` for Microsoft accounts to control folder hierarchy traversal
67
* Added support for `earliest_message_date` query parameter for threads
78
* Fixed `earliest_message_date` not being an optional response field
89
* Added support for new message fields query parameter values: `include_tracking_options` and `raw_mime`
@@ -97,7 +98,7 @@ v6.0.0
9798
* **BREAKING CHANGE**: Models no longer inherit from `dict` but instead either are a `dataclass` or inherit from `TypedDict`
9899
* **BREAKING CHANGE**: Renamed the SDK entrypoint from `APIClient` to `Client`
99100
* **REMOVED**: Local Webhook development support is removed due to incompatibility
100-
* Rewrote the majority of SDK to be more intuitive, explicit, and efficient
101+
* Rewritten the majority of SDK to be more intuitive, explicit, and efficient
101102
* Created models for all API resources and endpoints, for all HTTP methods to reduce confusion on which fields are available for each endpoint
102103
* Created error classes for the different API errors as well as SDK-specific errors
103104

examples/folders_demo/README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Folders Single Level Parameter Example
2+
3+
This example demonstrates how to use the `single_level` query parameter when listing folders to control folder hierarchy traversal for Microsoft accounts.
4+
5+
## Overview
6+
7+
The `single_level` parameter is a Microsoft-only feature that allows you to control whether the folders API returns:
8+
- **`single_level=true`**: Only top-level folders (single-level hierarchy)
9+
- **`single_level=false`**: All folders including nested ones (multi-level hierarchy, default)
10+
11+
This parameter is useful for:
12+
- **Performance optimization**: Reducing response size when you only need top-level folders
13+
- **UI simplification**: Building folder trees incrementally
14+
- **Microsoft-specific behavior**: Taking advantage of Microsoft's folder hierarchy structure
15+
16+
## Prerequisites
17+
18+
- Nylas API key
19+
- Nylas grant ID for a Microsoft account (this parameter only works with Microsoft accounts)
20+
21+
## Setup
22+
23+
1. Install the SDK in development mode:
24+
```bash
25+
cd /path/to/nylas-python
26+
pip install -e .
27+
```
28+
29+
2. Set your environment variables:
30+
```bash
31+
export NYLAS_API_KEY="your_api_key"
32+
export NYLAS_GRANT_ID="your_microsoft_grant_id"
33+
```
34+
35+
## Running the Example
36+
37+
```bash
38+
python examples/folders_demo/folders_single_level_example.py
39+
```
40+
41+
## What the Example Demonstrates
42+
43+
1. **Multi-level folder hierarchy** (default behavior)
44+
2. **Single-level folder hierarchy** using `single_level=true`
45+
3. **Combined parameters** showing how to use `single_level` with other query parameters
46+
4. **Hierarchy comparison** showing the difference in folder counts
47+
48+
## Expected Output
49+
50+
The example will show:
51+
- Folders returned with multi-level hierarchy
52+
- Folders returned with single-level hierarchy only
53+
- Count comparison between the two approaches
54+
- How to combine the parameter with other options like `limit` and `select`
55+
56+
## Use Cases
57+
58+
- **Folder tree UI**: Load top-level folders first, then expand as needed
59+
- **Performance**: Reduce API response size for Microsoft accounts with deep folder structures
60+
- **Microsoft-specific integrations**: Take advantage of Microsoft's native folder organization
61+
62+
## Note
63+
64+
This parameter only works with Microsoft accounts. If you use it with other providers, it will be ignored.
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Nylas SDK Example: Using Single Level Parameter for Folders
4+
5+
This example demonstrates how to use the 'single_level' query parameter when listing folders
6+
to control the folder hierarchy traversal for Microsoft accounts.
7+
8+
Required Environment Variables:
9+
NYLAS_API_KEY: Your Nylas API key
10+
NYLAS_GRANT_ID: Your Nylas grant ID (must be a Microsoft account)
11+
12+
Usage:
13+
First, install the SDK in development mode:
14+
cd /path/to/nylas-python
15+
pip install -e .
16+
17+
Then set environment variables and run:
18+
export NYLAS_API_KEY="your_api_key"
19+
export NYLAS_GRANT_ID="your_microsoft_grant_id"
20+
python examples/folders_demo/folders_single_level_example.py
21+
"""
22+
23+
import os
24+
import sys
25+
import json
26+
from nylas import Client
27+
28+
29+
def get_env_or_exit(var_name: str) -> str:
30+
"""Get an environment variable or exit if not found."""
31+
value = os.getenv(var_name)
32+
if not value:
33+
print(f"Error: {var_name} environment variable is required")
34+
sys.exit(1)
35+
return value
36+
37+
38+
def print_folders(folders: list, title: str) -> None:
39+
"""Pretty print the folders with a title."""
40+
print(f"\n{title}:")
41+
if not folders:
42+
print(" No folders found.")
43+
return
44+
45+
for folder in folders:
46+
# Convert to dict and pretty print relevant fields
47+
folder_dict = folder.to_dict()
48+
print(
49+
f" - {folder_dict.get('name', 'Unknown')} (ID: {folder_dict.get('id', 'Unknown')})"
50+
)
51+
if folder_dict.get("parent_id"):
52+
print(f" Parent ID: {folder_dict['parent_id']}")
53+
if folder_dict.get("child_count") is not None:
54+
print(f" Child Count: {folder_dict['child_count']}")
55+
56+
57+
def demonstrate_multi_level_folders(client: Client, grant_id: str) -> None:
58+
"""Demonstrate multi-level folder hierarchy (default behavior)."""
59+
print("\n=== Multi-Level Folder Hierarchy (Default) ===")
60+
61+
# Default behavior - retrieves folders across multi-level hierarchy
62+
print("\nFetching folders with multi-level hierarchy (single_level=False):")
63+
folders = client.folders.list(
64+
identifier=grant_id, query_params={"single_level": False}
65+
)
66+
print_folders(folders.data, "Multi-level folder hierarchy")
67+
68+
# Also demonstrate without explicitly setting single_level (default behavior)
69+
print("\nFetching folders without single_level parameter (default behavior):")
70+
folders = client.folders.list(identifier=grant_id)
71+
print_folders(folders.data, "Default folder hierarchy (multi-level)")
72+
73+
74+
def demonstrate_single_level_folders(client: Client, grant_id: str) -> None:
75+
"""Demonstrate single-level folder hierarchy."""
76+
print("\n=== Single-Level Folder Hierarchy ===")
77+
78+
# Single-level hierarchy - only retrieves folders from the top level
79+
print("\nFetching folders with single-level hierarchy (single_level=True):")
80+
folders = client.folders.list(
81+
identifier=grant_id, query_params={"single_level": True}
82+
)
83+
print_folders(folders.data, "Single-level folder hierarchy")
84+
85+
86+
def demonstrate_combined_parameters(client: Client, grant_id: str) -> None:
87+
"""Demonstrate single_level combined with other parameters."""
88+
print("\n=== Combined Parameters ===")
89+
90+
# Combine single_level with other query parameters
91+
print("\nFetching limited single-level folders with select fields:")
92+
folders = client.folders.list(
93+
identifier=grant_id,
94+
query_params={
95+
"single_level": True,
96+
"limit": 5,
97+
"select": "id,name,parent_id,child_count",
98+
},
99+
)
100+
print_folders(folders.data, "Limited single-level folders with selected fields")
101+
102+
103+
def compare_hierarchies(client: Client, grant_id: str) -> None:
104+
"""Compare single-level vs multi-level folder counts."""
105+
print("\n=== Hierarchy Comparison ===")
106+
107+
# Get multi-level count
108+
multi_level_folders = client.folders.list(
109+
identifier=grant_id, query_params={"single_level": False}
110+
)
111+
multi_level_count = len(multi_level_folders.data)
112+
113+
# Get single-level count
114+
single_level_folders = client.folders.list(
115+
identifier=grant_id, query_params={"single_level": True}
116+
)
117+
single_level_count = len(single_level_folders.data)
118+
119+
print(f"\nFolder count comparison:")
120+
print(f" Multi-level hierarchy: {multi_level_count} folders")
121+
print(f" Single-level hierarchy: {single_level_count} folders")
122+
123+
if multi_level_count > single_level_count:
124+
print(
125+
f" Difference: {multi_level_count - single_level_count} folders in sub-hierarchies"
126+
)
127+
elif single_level_count == multi_level_count:
128+
print(" No nested folders detected in this account")
129+
130+
131+
def main():
132+
"""Main function demonstrating single_level parameter usage for folders."""
133+
# Get required environment variables
134+
api_key = get_env_or_exit("NYLAS_API_KEY")
135+
grant_id = get_env_or_exit("NYLAS_GRANT_ID")
136+
137+
# Initialize Nylas client
138+
client = Client(api_key=api_key)
139+
140+
print("\nDemonstrating Single Level Parameter for Folders")
141+
print("===============================================")
142+
print("This parameter is Microsoft-only and controls folder hierarchy traversal")
143+
print(f"Using Grant ID: {grant_id}")
144+
145+
try:
146+
# Demonstrate different folder hierarchy options
147+
demonstrate_multi_level_folders(client, grant_id)
148+
demonstrate_single_level_folders(client, grant_id)
149+
demonstrate_combined_parameters(client, grant_id)
150+
compare_hierarchies(client, grant_id)
151+
152+
print("\n=== Summary ===")
153+
print("• single_level=True: Returns only top-level folders (Microsoft only)")
154+
print("• single_level=False: Returns folders from all levels (default)")
155+
print("• This parameter helps optimize performance for Microsoft accounts")
156+
print("• Can be combined with other query parameters like limit and select")
157+
158+
except Exception as e:
159+
print(f"\nError: {e}")
160+
print("\nNote: This example requires a Microsoft grant ID.")
161+
print("The single_level parameter only works with Microsoft accounts.")
162+
163+
print("\nExample completed!")
164+
165+
166+
if __name__ == "__main__":
167+
main()

nylas/models/folders.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class ListFolderQueryParams(ListQueryParams):
8585
8686
Attributes:
8787
parent_id: (Microsoft and EWS only.) Use the ID of a folder to find all child folders it contains.
88+
single_level: (Microsoft only) If true, retrieves folders from a single-level hierarchy only.
89+
If false, retrieves folders across a multi-level hierarchy. Defaults to false.
8890
select (NotRequired[str]): Comma-separated list of fields to return in the response.
8991
This allows you to receive only the portion of object data that you're interested in.
9092
limit (NotRequired[int]): The maximum number of objects to return.
@@ -94,6 +96,7 @@ class ListFolderQueryParams(ListQueryParams):
9496
"""
9597

9698
parent_id: NotRequired[str]
99+
single_level: NotRequired[bool]
97100

98101

99102
class FindFolderQueryParams(TypedDict):

tests/resources/test_folders.py

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,26 +58,71 @@ def test_list_folders_with_query_params(self, http_client_list_response):
5858
overrides=None,
5959
)
6060

61+
def test_list_folders_with_single_level_param(self, http_client_list_response):
62+
folders = Folders(http_client_list_response)
63+
64+
folders.list(identifier="abc-123", query_params={"single_level": True})
65+
66+
http_client_list_response._execute.assert_called_once_with(
67+
"GET",
68+
"/v3/grants/abc-123/folders",
69+
None,
70+
{"single_level": True},
71+
None,
72+
overrides=None,
73+
)
74+
75+
def test_list_folders_with_single_level_false(self, http_client_list_response):
76+
folders = Folders(http_client_list_response)
77+
78+
folders.list(identifier="abc-123", query_params={"single_level": False})
79+
80+
http_client_list_response._execute.assert_called_once_with(
81+
"GET",
82+
"/v3/grants/abc-123/folders",
83+
None,
84+
{"single_level": False},
85+
None,
86+
overrides=None,
87+
)
88+
89+
def test_list_folders_with_combined_params(self, http_client_list_response):
90+
folders = Folders(http_client_list_response)
91+
92+
folders.list(
93+
identifier="abc-123",
94+
query_params={"single_level": True, "parent_id": "parent-123", "limit": 10},
95+
)
96+
97+
http_client_list_response._execute.assert_called_once_with(
98+
"GET",
99+
"/v3/grants/abc-123/folders",
100+
None,
101+
{"single_level": True, "parent_id": "parent-123", "limit": 10},
102+
None,
103+
overrides=None,
104+
)
105+
61106
def test_list_folders_with_select_param(self, http_client_list_response):
62107
folders = Folders(http_client_list_response)
63108

64109
# Set up mock response data
65110
http_client_list_response._execute.return_value = {
66111
"request_id": "abc-123",
67-
"data": [{
68-
"id": "folder-123",
69-
"name": "Important",
70-
"total_count": 42,
71-
"unread_count": 5
72-
}]
112+
"data": [
113+
{
114+
"id": "folder-123",
115+
"name": "Important",
116+
"total_count": 42,
117+
"unread_count": 5,
118+
}
119+
],
73120
}
74121

75122
# Call the API method
76123
result = folders.list(
77124
identifier="abc-123",
78-
query_params={
79-
"select": "id,name,total_count,unread_count"
80-
}
125+
query_params={"select": "id,name,total_count,unread_count"},
81126
)
82127

83128
# Verify API call
@@ -111,21 +156,24 @@ def test_find_folder_with_select_param(self, http_client_response):
111156
folders = Folders(http_client_response)
112157

113158
# Set up mock response data
114-
http_client_response._execute.return_value = ({
115-
"request_id": "abc-123",
116-
"data": {
117-
"id": "folder-123",
118-
"name": "Important",
119-
"total_count": 42,
120-
"unread_count": 5
121-
}
122-
}, {"X-Test-Header": "test"})
159+
http_client_response._execute.return_value = (
160+
{
161+
"request_id": "abc-123",
162+
"data": {
163+
"id": "folder-123",
164+
"name": "Important",
165+
"total_count": 42,
166+
"unread_count": 5,
167+
},
168+
},
169+
{"X-Test-Header": "test"},
170+
)
123171

124172
# Call the API method
125173
result = folders.find(
126174
identifier="abc-123",
127175
folder_id="folder-123",
128-
query_params={"select": "id,name,total_count,unread_count"}
176+
query_params={"select": "id,name,total_count,unread_count"},
129177
)
130178

131179
# Verify API call

0 commit comments

Comments
 (0)