chub-dev 0.1.0 → 0.1.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/bin/chub-mcp +2 -0
- package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
- package/dist/airtable/docs/database/python/DOC.md +1735 -0
- package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
- package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
- package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
- package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
- package/dist/asana/docs/tasks/DOC.md +1396 -0
- package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
- package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
- package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
- package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
- package/dist/auth0/docs/identity/python/DOC.md +1199 -0
- package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
- package/dist/aws/docs/s3/python/DOC.md +1807 -0
- package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
- package/dist/binance/docs/trading/python/DOC.md +1454 -0
- package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
- package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
- package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
- package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
- package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
- package/dist/clerk/docs/auth/python/DOC.md +274 -0
- package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
- package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
- package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
- package/dist/cohere/docs/llm/DOC.md +1335 -0
- package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
- package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
- package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
- package/dist/deepgram/docs/speech/python/DOC.md +685 -0
- package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
- package/dist/deepl/docs/translation/python/DOC.md +944 -0
- package/dist/deepseek/docs/llm/DOC.md +1220 -0
- package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
- package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
- package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
- package/dist/discord/docs/bot/python/DOC.md +1130 -0
- package/dist/elasticsearch/docs/search/DOC.md +1634 -0
- package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
- package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
- package/dist/firebase/docs/auth/DOC.md +1015 -0
- package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
- package/dist/gemini/docs/genai/python/DOC.md +555 -0
- package/dist/github/docs/octokit/DOC.md +1560 -0
- package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
- package/dist/google/docs/bigquery/python/DOC.md +1503 -0
- package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
- package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
- package/dist/huggingface/docs/transformers/DOC.md +948 -0
- package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
- package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
- package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
- package/dist/jira/docs/issues/python/DOC.md +1492 -0
- package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
- package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
- package/dist/landingai-ade/docs/api/DOC.md +620 -0
- package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
- package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
- package/dist/landingai-ade/skills/SKILL.md +489 -0
- package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
- package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
- package/dist/linear/docs/tracker/DOC.md +1554 -0
- package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
- package/dist/livekit/docs/realtime/python/DOC.md +163 -0
- package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
- package/dist/meilisearch/docs/search/DOC.md +1241 -0
- package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
- package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
- package/dist/mongodb/docs/atlas/DOC.md +2041 -0
- package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
- package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
- package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
- package/dist/okta/docs/identity/python/DOC.md +1401 -0
- package/dist/openai/docs/chat/javascript/DOC.md +407 -0
- package/dist/openai/docs/chat/python/DOC.md +568 -0
- package/dist/paypal/docs/checkout/DOC.md +278 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
- package/dist/plaid/docs/banking/python/DOC.md +1203 -0
- package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
- package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
- package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
- package/dist/prisma/docs/orm/python/DOC.md +1317 -0
- package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
- package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
- package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
- package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
- package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
- package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
- package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
- package/dist/redis/docs/key-value/python/DOC.md +2054 -0
- package/dist/registry.json +2817 -0
- package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
- package/dist/resend/docs/email/DOC.md +1271 -0
- package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
- package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
- package/dist/search-index.json +1 -0
- package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
- package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
- package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
- package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
- package/dist/shopify/docs/storefront/DOC.md +457 -0
- package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
- package/dist/slack/docs/workspace/python/DOC.md +271 -0
- package/dist/square/docs/payments/javascript/DOC.md +1855 -0
- package/dist/square/docs/payments/python/DOC.md +1728 -0
- package/dist/stripe/docs/api/DOC.md +1727 -0
- package/dist/stripe/docs/payments/DOC.md +1726 -0
- package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
- package/dist/stytch/docs/auth/python/DOC.md +1962 -0
- package/dist/supabase/docs/client/DOC.md +1606 -0
- package/dist/twilio/docs/messaging/python/DOC.md +469 -0
- package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
- package/dist/vercel/docs/platform/DOC.md +1940 -0
- package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
- package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
- package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
- package/dist/zendesk/docs/support/python/DOC.md +2297 -0
- package/package.json +22 -6
- package/skills/get-api-docs/SKILL.md +84 -0
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +12 -1
- package/src/commands/feedback.js +150 -0
- package/src/commands/get.js +83 -42
- package/src/commands/search.js +7 -0
- package/src/index.js +43 -17
- package/src/lib/analytics.js +90 -0
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +69 -6
- package/src/lib/config.js +8 -3
- package/src/lib/identity.js +99 -0
- package/src/lib/registry.js +103 -20
- package/src/lib/telemetry.js +86 -0
- package/src/mcp/server.js +177 -0
- package/src/mcp/tools.js +251 -0
|
@@ -0,0 +1,1549 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: onedrive
|
|
3
|
+
description: "Microsoft OneDrive API coding guidelines for Python using the official Microsoft Graph SDK"
|
|
4
|
+
metadata:
|
|
5
|
+
languages: "python"
|
|
6
|
+
versions: "1.48.0"
|
|
7
|
+
updated-on: "2026-03-02"
|
|
8
|
+
source: maintainer
|
|
9
|
+
tags: "microsoft,onedrive,storage,graph-api,files"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Microsoft OneDrive API Coding Guidelines (Python)
|
|
13
|
+
|
|
14
|
+
You are a **Microsoft OneDrive API coding expert**. Help me write correct, idiomatic Python code that accesses OneDrive files and folders using the official Microsoft Graph SDK.
|
|
15
|
+
|
|
16
|
+
Use **only official Microsoft sources** for behavior, fields, and constraints. This guide summarizes key patterns for Python applications.
|
|
17
|
+
|
|
18
|
+
> Ground truth: Microsoft Graph OneDrive API documentation on learn.microsoft.com.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Golden Rule: Use the Official Microsoft Graph SDK
|
|
22
|
+
|
|
23
|
+
**ALWAYS use `msgraph-sdk` version 1.48.0 or later** for OneDrive operations. This is the official Microsoft Graph Python SDK that provides access to OneDrive, SharePoint, and all other Microsoft Graph APIs.
|
|
24
|
+
|
|
25
|
+
**DO NOT use**:
|
|
26
|
+
- Deprecated `onedrivesdk` package (obsolete)
|
|
27
|
+
- Direct REST calls without the SDK (unless absolutely necessary)
|
|
28
|
+
- Unofficial third-party OneDrive libraries
|
|
29
|
+
|
|
30
|
+
**Install (Python):**
|
|
31
|
+
```bash
|
|
32
|
+
pip install msgraph-sdk
|
|
33
|
+
pip install azure-identity
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
### Complete Setup for Python Applications
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Core Microsoft Graph SDK
|
|
43
|
+
pip install msgraph-sdk
|
|
44
|
+
|
|
45
|
+
# Azure authentication library
|
|
46
|
+
pip install azure-identity
|
|
47
|
+
|
|
48
|
+
# For async support (recommended)
|
|
49
|
+
pip install aiohttp
|
|
50
|
+
|
|
51
|
+
# Environment variable management
|
|
52
|
+
pip install python-dotenv
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Using requirements.txt
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
msgraph-sdk>=1.48.0
|
|
59
|
+
azure-identity>=1.19.0
|
|
60
|
+
python-dotenv>=1.0.0
|
|
61
|
+
aiohttp>=3.9.0
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Using pyproject.toml (Poetry/UV)
|
|
65
|
+
|
|
66
|
+
```toml
|
|
67
|
+
[project]
|
|
68
|
+
dependencies = [
|
|
69
|
+
"msgraph-sdk>=1.48.0",
|
|
70
|
+
"azure-identity>=1.19.0",
|
|
71
|
+
"python-dotenv>=1.0.0",
|
|
72
|
+
"aiohttp>=3.9.0"
|
|
73
|
+
]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
## Authentication
|
|
78
|
+
|
|
79
|
+
OneDrive access through Microsoft Graph requires OAuth 2.0 authentication. You need to register an application in Azure Active Directory (Azure AD) to obtain credentials.
|
|
80
|
+
|
|
81
|
+
### Azure AD App Registration
|
|
82
|
+
|
|
83
|
+
1. Go to [Azure Portal](https://portal.azure.com) → Azure Active Directory → App registrations
|
|
84
|
+
2. Create a new registration
|
|
85
|
+
3. Note your **Application (client) ID** and **Directory (tenant) ID**
|
|
86
|
+
4. Create a client secret under "Certificates & secrets"
|
|
87
|
+
5. Add API permissions: Microsoft Graph → **Files.Read**, **Files.ReadWrite**, **Files.Read.All**, **Files.ReadWrite.All**
|
|
88
|
+
6. Grant admin consent for the permissions
|
|
89
|
+
|
|
90
|
+
### Required Scopes
|
|
91
|
+
|
|
92
|
+
Common OneDrive permission scopes:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
# Read-only access to user's files
|
|
96
|
+
SCOPES_READONLY = ['https://graph.microsoft.com/Files.Read']
|
|
97
|
+
|
|
98
|
+
# Read/write access to user's files
|
|
99
|
+
SCOPES_READWRITE = ['https://graph.microsoft.com/Files.ReadWrite']
|
|
100
|
+
|
|
101
|
+
# Read all files user can access (including shared)
|
|
102
|
+
SCOPES_READ_ALL = ['https://graph.microsoft.com/Files.Read.All']
|
|
103
|
+
|
|
104
|
+
# Full access to all files user can access
|
|
105
|
+
SCOPES_READWRITE_ALL = ['https://graph.microsoft.com/Files.ReadWrite.All']
|
|
106
|
+
|
|
107
|
+
# Application permissions (no user context, requires admin consent)
|
|
108
|
+
SCOPES_APP = ['https://graph.microsoft.com/.default']
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## Initialization
|
|
113
|
+
|
|
114
|
+
### Async Client with Client Credentials (Service/Daemon Apps)
|
|
115
|
+
|
|
116
|
+
For server-side applications using application permissions:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
import asyncio
|
|
120
|
+
from azure.identity.aio import ClientSecretCredential
|
|
121
|
+
from msgraph import GraphServiceClient
|
|
122
|
+
from msgraph.generated.users.item.user_item_request_builder import UserItemRequestBuilder
|
|
123
|
+
import os
|
|
124
|
+
from dotenv import load_dotenv
|
|
125
|
+
|
|
126
|
+
load_dotenv()
|
|
127
|
+
|
|
128
|
+
# Environment variables
|
|
129
|
+
tenant_id = os.getenv('AZURE_TENANT_ID')
|
|
130
|
+
client_id = os.getenv('AZURE_CLIENT_ID')
|
|
131
|
+
client_secret = os.getenv('AZURE_CLIENT_SECRET')
|
|
132
|
+
|
|
133
|
+
# Create credential
|
|
134
|
+
credentials = ClientSecretCredential(
|
|
135
|
+
tenant_id=tenant_id,
|
|
136
|
+
client_id=client_id,
|
|
137
|
+
client_secret=client_secret
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Scopes for application permissions
|
|
141
|
+
scopes = ['https://graph.microsoft.com/.default']
|
|
142
|
+
|
|
143
|
+
# Initialize Microsoft Graph client
|
|
144
|
+
client = GraphServiceClient(credentials=credentials, scopes=scopes)
|
|
145
|
+
|
|
146
|
+
# Example: List files in user's OneDrive root
|
|
147
|
+
async def list_files(user_id: str):
|
|
148
|
+
result = await client.users.by_user_id(user_id).drive.root.children.get()
|
|
149
|
+
return result.value
|
|
150
|
+
|
|
151
|
+
async def main():
|
|
152
|
+
user_id = os.getenv('USER_ID')
|
|
153
|
+
files = await list_files(user_id)
|
|
154
|
+
|
|
155
|
+
for file in files:
|
|
156
|
+
print(f"{file.name} - {file.size} bytes")
|
|
157
|
+
|
|
158
|
+
if __name__ == '__main__':
|
|
159
|
+
asyncio.run(main())
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Sync Client with Client Credentials
|
|
163
|
+
|
|
164
|
+
For synchronous applications:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from azure.identity import ClientSecretCredential
|
|
168
|
+
from msgraph import GraphServiceClient
|
|
169
|
+
import os
|
|
170
|
+
|
|
171
|
+
credentials = ClientSecretCredential(
|
|
172
|
+
tenant_id=os.getenv('AZURE_TENANT_ID'),
|
|
173
|
+
client_id=os.getenv('AZURE_CLIENT_ID'),
|
|
174
|
+
client_secret=os.getenv('AZURE_CLIENT_SECRET')
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
scopes = ['https://graph.microsoft.com/.default']
|
|
178
|
+
client = GraphServiceClient(credentials=credentials, scopes=scopes)
|
|
179
|
+
|
|
180
|
+
# Use sync methods
|
|
181
|
+
def list_files_sync(user_id: str):
|
|
182
|
+
# Note: The SDK is primarily async, synchronous usage requires running in event loop
|
|
183
|
+
import asyncio
|
|
184
|
+
loop = asyncio.get_event_loop()
|
|
185
|
+
result = loop.run_until_complete(
|
|
186
|
+
client.users.by_user_id(user_id).drive.root.children.get()
|
|
187
|
+
)
|
|
188
|
+
return result.value
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Async Client with Device Code Flow (Interactive)
|
|
192
|
+
|
|
193
|
+
For CLI applications that need user interaction:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from azure.identity.aio import DeviceCodeCredential
|
|
197
|
+
from msgraph import GraphServiceClient
|
|
198
|
+
import os
|
|
199
|
+
|
|
200
|
+
async def init_graph_client():
|
|
201
|
+
credentials = DeviceCodeCredential(
|
|
202
|
+
client_id=os.getenv('AZURE_CLIENT_ID'),
|
|
203
|
+
tenant_id=os.getenv('AZURE_TENANT_ID')
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
scopes = ['Files.ReadWrite', 'Files.Read.All']
|
|
207
|
+
client = GraphServiceClient(credentials=credentials, scopes=scopes)
|
|
208
|
+
|
|
209
|
+
return client
|
|
210
|
+
|
|
211
|
+
async def main():
|
|
212
|
+
client = await init_graph_client()
|
|
213
|
+
|
|
214
|
+
# List files in current user's drive
|
|
215
|
+
result = await client.me.drive.root.children.get()
|
|
216
|
+
|
|
217
|
+
for item in result.value:
|
|
218
|
+
print(f"{item.name}")
|
|
219
|
+
|
|
220
|
+
asyncio.run(main())
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Async Client with Interactive Browser
|
|
224
|
+
|
|
225
|
+
For desktop applications with interactive login:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
from azure.identity.aio import InteractiveBrowserCredential
|
|
229
|
+
from msgraph import GraphServiceClient
|
|
230
|
+
import os
|
|
231
|
+
|
|
232
|
+
async def init_graph_client():
|
|
233
|
+
credentials = InteractiveBrowserCredential(
|
|
234
|
+
client_id=os.getenv('AZURE_CLIENT_ID'),
|
|
235
|
+
tenant_id=os.getenv('AZURE_TENANT_ID')
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
scopes = ['Files.ReadWrite']
|
|
239
|
+
client = GraphServiceClient(credentials=credentials, scopes=scopes)
|
|
240
|
+
|
|
241
|
+
return client
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Environment Variables Setup
|
|
245
|
+
|
|
246
|
+
Create a `.env` file:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
AZURE_TENANT_ID=your-tenant-id
|
|
250
|
+
AZURE_CLIENT_ID=your-client-id
|
|
251
|
+
AZURE_CLIENT_SECRET=your-client-secret
|
|
252
|
+
USER_ID=user@domain.com
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Load environment variables:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from dotenv import load_dotenv
|
|
259
|
+
import os
|
|
260
|
+
|
|
261
|
+
load_dotenv()
|
|
262
|
+
|
|
263
|
+
tenant_id = os.getenv('AZURE_TENANT_ID')
|
|
264
|
+
client_id = os.getenv('AZURE_CLIENT_ID')
|
|
265
|
+
client_secret = os.getenv('AZURE_CLIENT_SECRET')
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
## Core API Surfaces
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
### 1. Listing Files and Folders
|
|
273
|
+
|
|
274
|
+
#### List Root Folder Contents
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
from msgraph import GraphServiceClient
|
|
278
|
+
|
|
279
|
+
async def list_root_files(client: GraphServiceClient):
|
|
280
|
+
# Current user's OneDrive root
|
|
281
|
+
result = await client.me.drive.root.children.get()
|
|
282
|
+
|
|
283
|
+
if result and result.value:
|
|
284
|
+
for item in result.value:
|
|
285
|
+
item_type = "Folder" if item.folder else "File"
|
|
286
|
+
print(f"{item_type}: {item.name} (ID: {item.id})")
|
|
287
|
+
|
|
288
|
+
return result.value
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### List Specific Folder Contents
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
# By item ID
|
|
295
|
+
async def list_folder_by_id(client: GraphServiceClient, item_id: str):
|
|
296
|
+
result = await client.me.drive.items.by_drive_item_id(item_id).children.get()
|
|
297
|
+
return result.value
|
|
298
|
+
|
|
299
|
+
# By path
|
|
300
|
+
async def list_folder_by_path(client: GraphServiceClient, folder_path: str):
|
|
301
|
+
# Example: /Documents/Projects
|
|
302
|
+
result = await client.me.drive.root.item_with_path(folder_path).children.get()
|
|
303
|
+
return result.value
|
|
304
|
+
|
|
305
|
+
# With pagination
|
|
306
|
+
async def list_all_items_paginated(client: GraphServiceClient):
|
|
307
|
+
items = []
|
|
308
|
+
result = await client.me.drive.root.children.get()
|
|
309
|
+
|
|
310
|
+
while result:
|
|
311
|
+
if result.value:
|
|
312
|
+
items.extend(result.value)
|
|
313
|
+
|
|
314
|
+
# Check for next page
|
|
315
|
+
if hasattr(result, 'odata_next_link') and result.odata_next_link:
|
|
316
|
+
# Fetch next page
|
|
317
|
+
result = await client.me.drive.root.children.get()
|
|
318
|
+
else:
|
|
319
|
+
break
|
|
320
|
+
|
|
321
|
+
return items
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
#### Advanced Listing with Query Parameters
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
from msgraph.generated.users.item.drive.root.children.children_request_builder import ChildrenRequestBuilder
|
|
328
|
+
|
|
329
|
+
async def list_with_filters(client: GraphServiceClient):
|
|
330
|
+
# Configure request
|
|
331
|
+
query_params = ChildrenRequestBuilder.ChildrenRequestBuilderGetQueryParameters(
|
|
332
|
+
select=['id', 'name', 'size', 'created_date_time', 'last_modified_date_time'],
|
|
333
|
+
filter='file ne null', # Only files
|
|
334
|
+
orderby=['name asc'],
|
|
335
|
+
top=10
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
request_config = ChildrenRequestBuilder.ChildrenRequestBuilderGetRequestConfiguration(
|
|
339
|
+
query_parameters=query_params
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
result = await client.me.drive.root.children.get(request_configuration=request_config)
|
|
343
|
+
return result.value
|
|
344
|
+
|
|
345
|
+
# Filter folders only
|
|
346
|
+
async def list_folders_only(client: GraphServiceClient):
|
|
347
|
+
query_params = ChildrenRequestBuilder.ChildrenRequestBuilderGetQueryParameters(
|
|
348
|
+
filter='folder ne null'
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
request_config = ChildrenRequestBuilder.ChildrenRequestBuilderGetRequestConfiguration(
|
|
352
|
+
query_parameters=query_params
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
result = await client.me.drive.root.children.get(request_configuration=request_config)
|
|
356
|
+
return result.value
|
|
357
|
+
|
|
358
|
+
# Order by last modified date
|
|
359
|
+
async def list_by_modified_date(client: GraphServiceClient):
|
|
360
|
+
query_params = ChildrenRequestBuilder.ChildrenRequestBuilderGetQueryParameters(
|
|
361
|
+
orderby=['lastModifiedDateTime desc'],
|
|
362
|
+
top=20
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
request_config = ChildrenRequestBuilder.ChildrenRequestBuilderGetRequestConfiguration(
|
|
366
|
+
query_parameters=query_params
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
result = await client.me.drive.root.children.get(request_configuration=request_config)
|
|
370
|
+
return result.value
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
#### List All Drives
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
# List all drives accessible to user
|
|
377
|
+
async def list_drives(client: GraphServiceClient):
|
|
378
|
+
drives = await client.me.drives.get()
|
|
379
|
+
|
|
380
|
+
if drives and drives.value:
|
|
381
|
+
for drive in drives.value:
|
|
382
|
+
print(f"Drive: {drive.name} (ID: {drive.id})")
|
|
383
|
+
print(f" Type: {drive.drive_type}")
|
|
384
|
+
print(f" Owner: {drive.owner.user.display_name if drive.owner else 'N/A'}")
|
|
385
|
+
|
|
386
|
+
return drives.value
|
|
387
|
+
|
|
388
|
+
# Get default drive
|
|
389
|
+
async def get_default_drive(client: GraphServiceClient):
|
|
390
|
+
drive = await client.me.drive.get()
|
|
391
|
+
return drive
|
|
392
|
+
|
|
393
|
+
# Get specific drive
|
|
394
|
+
async def get_drive_by_id(client: GraphServiceClient, drive_id: str):
|
|
395
|
+
drive = await client.drives.by_drive_id(drive_id).get()
|
|
396
|
+
return drive
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
### 2. Getting File/Folder Metadata
|
|
401
|
+
|
|
402
|
+
#### Get Item Metadata
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
# By item ID
|
|
406
|
+
async def get_item_metadata(client: GraphServiceClient, item_id: str):
|
|
407
|
+
item = await client.me.drive.items.by_drive_item_id(item_id).get()
|
|
408
|
+
|
|
409
|
+
print(f"ID: {item.id}")
|
|
410
|
+
print(f"Name: {item.name}")
|
|
411
|
+
print(f"Size: {item.size} bytes")
|
|
412
|
+
print(f"Created: {item.created_date_time}")
|
|
413
|
+
print(f"Modified: {item.last_modified_date_time}")
|
|
414
|
+
print(f"Web URL: {item.web_url}")
|
|
415
|
+
|
|
416
|
+
if item.file:
|
|
417
|
+
print(f"MIME Type: {item.file.mime_type}")
|
|
418
|
+
if item.file.hashes:
|
|
419
|
+
print(f"SHA1 Hash: {item.file.hashes.sha1_hash}")
|
|
420
|
+
|
|
421
|
+
return item
|
|
422
|
+
|
|
423
|
+
# By path
|
|
424
|
+
async def get_item_by_path(client: GraphServiceClient, file_path: str):
|
|
425
|
+
# Example: /Documents/report.pdf
|
|
426
|
+
item = await client.me.drive.root.item_with_path(file_path).get()
|
|
427
|
+
return item
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### Get File with Specific Properties
|
|
431
|
+
|
|
432
|
+
```python
|
|
433
|
+
from msgraph.generated.users.item.drive.items.item.drive_item_item_request_builder import DriveItemItemRequestBuilder
|
|
434
|
+
|
|
435
|
+
async def get_item_with_select(client: GraphServiceClient, item_id: str):
|
|
436
|
+
query_params = DriveItemItemRequestBuilder.DriveItemItemRequestBuilderGetQueryParameters(
|
|
437
|
+
select=['id', 'name', 'size', 'file', 'createdDateTime']
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
request_config = DriveItemItemRequestBuilder.DriveItemItemRequestBuilderGetRequestConfiguration(
|
|
441
|
+
query_parameters=query_params
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
item = await client.me.drive.items.by_drive_item_id(item_id).get(
|
|
445
|
+
request_configuration=request_config
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
return item
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
### 3. Downloading Files
|
|
453
|
+
|
|
454
|
+
#### Simple Download (All File Sizes)
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
import aiofiles
|
|
458
|
+
|
|
459
|
+
async def download_file(client: GraphServiceClient, item_id: str, save_path: str):
|
|
460
|
+
# Get file content as stream
|
|
461
|
+
stream = await client.me.drive.items.by_drive_item_id(item_id).content.get()
|
|
462
|
+
|
|
463
|
+
# Save to disk
|
|
464
|
+
async with aiofiles.open(save_path, 'wb') as f:
|
|
465
|
+
await f.write(stream)
|
|
466
|
+
|
|
467
|
+
print(f"Downloaded to {save_path}")
|
|
468
|
+
|
|
469
|
+
# Download by path
|
|
470
|
+
async def download_file_by_path(client: GraphServiceClient, file_path: str, save_path: str):
|
|
471
|
+
# Example: /Documents/report.pdf
|
|
472
|
+
stream = await client.me.drive.root.item_with_path(file_path).content.get()
|
|
473
|
+
|
|
474
|
+
async with aiofiles.open(save_path, 'wb') as f:
|
|
475
|
+
await f.write(stream)
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
#### Download Multiple Files
|
|
479
|
+
|
|
480
|
+
```python
|
|
481
|
+
import os
|
|
482
|
+
import asyncio
|
|
483
|
+
|
|
484
|
+
async def download_folder(client: GraphServiceClient, folder_id: str, local_path: str):
|
|
485
|
+
# Create local directory
|
|
486
|
+
os.makedirs(local_path, exist_ok=True)
|
|
487
|
+
|
|
488
|
+
# Get folder contents
|
|
489
|
+
items = await client.me.drive.items.by_drive_item_id(folder_id).children.get()
|
|
490
|
+
|
|
491
|
+
if not items or not items.value:
|
|
492
|
+
return
|
|
493
|
+
|
|
494
|
+
for item in items.value:
|
|
495
|
+
if item.folder:
|
|
496
|
+
# Recursively download subfolder
|
|
497
|
+
subfolder_path = os.path.join(local_path, item.name)
|
|
498
|
+
await download_folder(client, item.id, subfolder_path)
|
|
499
|
+
elif item.file:
|
|
500
|
+
# Download file
|
|
501
|
+
print(f"Downloading {item.name}...")
|
|
502
|
+
file_path = os.path.join(local_path, item.name)
|
|
503
|
+
|
|
504
|
+
stream = await client.me.drive.items.by_drive_item_id(item.id).content.get()
|
|
505
|
+
|
|
506
|
+
async with aiofiles.open(file_path, 'wb') as f:
|
|
507
|
+
await f.write(stream)
|
|
508
|
+
|
|
509
|
+
print(f"Downloaded {item.name}")
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
### 4. Uploading Files
|
|
514
|
+
|
|
515
|
+
#### Simple Upload (Files < 4MB)
|
|
516
|
+
|
|
517
|
+
```python
|
|
518
|
+
async def upload_small_file(client: GraphServiceClient, file_path: str, upload_path: str):
|
|
519
|
+
"""
|
|
520
|
+
Upload a file smaller than 4MB
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
file_path: Local file path
|
|
524
|
+
upload_path: OneDrive path (e.g., '/Documents/file.pdf')
|
|
525
|
+
"""
|
|
526
|
+
async with aiofiles.open(file_path, 'rb') as f:
|
|
527
|
+
content = await f.read()
|
|
528
|
+
|
|
529
|
+
# Upload to specific path
|
|
530
|
+
item = await client.me.drive.root.item_with_path(upload_path).content.put(content)
|
|
531
|
+
|
|
532
|
+
print(f"Uploaded: {item.name} (ID: {item.id})")
|
|
533
|
+
return item
|
|
534
|
+
|
|
535
|
+
# Upload to specific folder by ID
|
|
536
|
+
async def upload_to_folder(client: GraphServiceClient, file_path: str, folder_id: str, filename: str):
|
|
537
|
+
async with aiofiles.open(file_path, 'rb') as f:
|
|
538
|
+
content = await f.read()
|
|
539
|
+
|
|
540
|
+
item = await client.me.drive.items.by_drive_item_id(folder_id).item_with_path(filename).content.put(content)
|
|
541
|
+
|
|
542
|
+
return item
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
#### Large File Upload (Files > 4MB) - Resumable Upload Session
|
|
546
|
+
|
|
547
|
+
```python
|
|
548
|
+
import math
|
|
549
|
+
import aiohttp
|
|
550
|
+
|
|
551
|
+
async def upload_large_file(client: GraphServiceClient, file_path: str, upload_path: str):
|
|
552
|
+
"""
|
|
553
|
+
Upload large files using resumable upload session
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
file_path: Local file path
|
|
557
|
+
upload_path: OneDrive path (e.g., '/Documents/video.mp4')
|
|
558
|
+
"""
|
|
559
|
+
import os
|
|
560
|
+
from msgraph.generated.drives.item.items.item.create_upload_session.create_upload_session_post_request_body import CreateUploadSessionPostRequestBody
|
|
561
|
+
from msgraph.generated.models.drive_item_uploadable_properties import DriveItemUploadableProperties
|
|
562
|
+
|
|
563
|
+
file_size = os.path.getsize(file_path)
|
|
564
|
+
|
|
565
|
+
# Step 1: Create upload session
|
|
566
|
+
props = DriveItemUploadableProperties()
|
|
567
|
+
props.odata_type = "#microsoft.graph.driveItemUploadableProperties"
|
|
568
|
+
props.microsoft_graph_conflict_behavior = "rename" # or 'fail', 'replace'
|
|
569
|
+
props.name = os.path.basename(file_path)
|
|
570
|
+
|
|
571
|
+
request_body = CreateUploadSessionPostRequestBody()
|
|
572
|
+
request_body.item = props
|
|
573
|
+
|
|
574
|
+
upload_session = await client.me.drive.root.item_with_path(upload_path).create_upload_session.post(request_body)
|
|
575
|
+
|
|
576
|
+
# Step 2: Upload file in chunks
|
|
577
|
+
chunk_size = 320 * 1024 * 10 # 3.2 MB chunks (must be multiple of 320 KB)
|
|
578
|
+
|
|
579
|
+
async with aiofiles.open(file_path, 'rb') as f:
|
|
580
|
+
file_content = await f.read()
|
|
581
|
+
|
|
582
|
+
num_chunks = math.ceil(file_size / chunk_size)
|
|
583
|
+
|
|
584
|
+
for i in range(num_chunks):
|
|
585
|
+
start = i * chunk_size
|
|
586
|
+
end = min(start + chunk_size, file_size)
|
|
587
|
+
chunk = file_content[start:end]
|
|
588
|
+
|
|
589
|
+
content_range = f"bytes {start}-{end-1}/{file_size}"
|
|
590
|
+
|
|
591
|
+
headers = {
|
|
592
|
+
'Content-Length': str(len(chunk)),
|
|
593
|
+
'Content-Range': content_range
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
# Upload chunk
|
|
597
|
+
async with aiohttp.ClientSession() as session:
|
|
598
|
+
async with session.put(
|
|
599
|
+
upload_session.upload_url,
|
|
600
|
+
data=chunk,
|
|
601
|
+
headers=headers
|
|
602
|
+
) as response:
|
|
603
|
+
if response.status in [200, 201, 202]:
|
|
604
|
+
progress = (end / file_size) * 100
|
|
605
|
+
print(f"Upload progress: {progress:.2f}%")
|
|
606
|
+
else:
|
|
607
|
+
error_text = await response.text()
|
|
608
|
+
raise Exception(f"Upload failed: {error_text}")
|
|
609
|
+
|
|
610
|
+
print("Upload complete!")
|
|
611
|
+
return upload_session
|
|
612
|
+
|
|
613
|
+
# Alternative: Using helper function for large uploads
|
|
614
|
+
async def upload_large_file_simple(client: GraphServiceClient, file_path: str, folder_id: str):
|
|
615
|
+
"""
|
|
616
|
+
Simplified large file upload
|
|
617
|
+
"""
|
|
618
|
+
import os
|
|
619
|
+
from msgraph.generated.drives.item.items.item.create_upload_session.create_upload_session_post_request_body import CreateUploadSessionPostRequestBody
|
|
620
|
+
from msgraph.generated.models.drive_item_uploadable_properties import DriveItemUploadableProperties
|
|
621
|
+
|
|
622
|
+
filename = os.path.basename(file_path)
|
|
623
|
+
file_size = os.path.getsize(file_path)
|
|
624
|
+
|
|
625
|
+
# Create upload session
|
|
626
|
+
props = DriveItemUploadableProperties()
|
|
627
|
+
props.microsoft_graph_conflict_behavior = "replace"
|
|
628
|
+
props.name = filename
|
|
629
|
+
|
|
630
|
+
request_body = CreateUploadSessionPostRequestBody()
|
|
631
|
+
request_body.item = props
|
|
632
|
+
|
|
633
|
+
upload_session = await client.me.drive.items.by_drive_item_id(folder_id).item_with_path(filename).create_upload_session.post(request_body)
|
|
634
|
+
|
|
635
|
+
# Upload in chunks
|
|
636
|
+
chunk_size = 10 * 1024 * 1024 # 10 MB
|
|
637
|
+
|
|
638
|
+
async with aiofiles.open(file_path, 'rb') as f:
|
|
639
|
+
offset = 0
|
|
640
|
+
|
|
641
|
+
while offset < file_size:
|
|
642
|
+
chunk = await f.read(chunk_size)
|
|
643
|
+
chunk_len = len(chunk)
|
|
644
|
+
|
|
645
|
+
content_range = f"bytes {offset}-{offset + chunk_len - 1}/{file_size}"
|
|
646
|
+
|
|
647
|
+
headers = {
|
|
648
|
+
'Content-Length': str(chunk_len),
|
|
649
|
+
'Content-Range': content_range
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async with aiohttp.ClientSession() as session:
|
|
653
|
+
async with session.put(
|
|
654
|
+
upload_session.upload_url,
|
|
655
|
+
data=chunk,
|
|
656
|
+
headers=headers
|
|
657
|
+
) as response:
|
|
658
|
+
if response.status not in [200, 201, 202]:
|
|
659
|
+
raise Exception(f"Upload failed: {await response.text()}")
|
|
660
|
+
|
|
661
|
+
offset += chunk_len
|
|
662
|
+
progress = (offset / file_size) * 100
|
|
663
|
+
print(f"Uploaded: {progress:.1f}%")
|
|
664
|
+
|
|
665
|
+
print("Upload complete!")
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
### 5. Creating Folders
|
|
670
|
+
|
|
671
|
+
```python
|
|
672
|
+
from msgraph.generated.models.drive_item import DriveItem
|
|
673
|
+
from msgraph.generated.models.folder import Folder
|
|
674
|
+
|
|
675
|
+
async def create_folder(client: GraphServiceClient, folder_name: str, parent_id: str = None):
|
|
676
|
+
"""
|
|
677
|
+
Create a new folder
|
|
678
|
+
|
|
679
|
+
Args:
|
|
680
|
+
folder_name: Name of the folder to create
|
|
681
|
+
parent_id: Parent folder ID (None for root)
|
|
682
|
+
"""
|
|
683
|
+
drive_item = DriveItem()
|
|
684
|
+
drive_item.name = folder_name
|
|
685
|
+
drive_item.folder = Folder()
|
|
686
|
+
drive_item.microsoft_graph_conflict_behavior = "rename" # or 'fail', 'replace'
|
|
687
|
+
|
|
688
|
+
if parent_id:
|
|
689
|
+
folder = await client.me.drive.items.by_drive_item_id(parent_id).children.post(drive_item)
|
|
690
|
+
else:
|
|
691
|
+
folder = await client.me.drive.root.children.post(drive_item)
|
|
692
|
+
|
|
693
|
+
print(f"Created folder: {folder.name} (ID: {folder.id})")
|
|
694
|
+
return folder
|
|
695
|
+
|
|
696
|
+
# Create folder at specific path
|
|
697
|
+
async def create_folder_by_path(client: GraphServiceClient, folder_path: str):
|
|
698
|
+
"""
|
|
699
|
+
Create folder at path (e.g., '/Documents/Projects/New Folder')
|
|
700
|
+
"""
|
|
701
|
+
drive_item = DriveItem()
|
|
702
|
+
drive_item.name = os.path.basename(folder_path)
|
|
703
|
+
drive_item.folder = Folder()
|
|
704
|
+
drive_item.microsoft_graph_conflict_behavior = "fail"
|
|
705
|
+
|
|
706
|
+
parent_path = os.path.dirname(folder_path)
|
|
707
|
+
|
|
708
|
+
if parent_path and parent_path != '/':
|
|
709
|
+
folder = await client.me.drive.root.item_with_path(parent_path).children.post(drive_item)
|
|
710
|
+
else:
|
|
711
|
+
folder = await client.me.drive.root.children.post(drive_item)
|
|
712
|
+
|
|
713
|
+
return folder
|
|
714
|
+
|
|
715
|
+
# Create nested folder structure
|
|
716
|
+
async def create_nested_folders(client: GraphServiceClient, path_parts: list):
|
|
717
|
+
"""
|
|
718
|
+
Create nested folders
|
|
719
|
+
|
|
720
|
+
Args:
|
|
721
|
+
path_parts: List of folder names ['Parent', 'Child', 'Grandchild']
|
|
722
|
+
"""
|
|
723
|
+
parent_id = None
|
|
724
|
+
|
|
725
|
+
for folder_name in path_parts:
|
|
726
|
+
folder = await create_folder(client, folder_name, parent_id)
|
|
727
|
+
parent_id = folder.id
|
|
728
|
+
|
|
729
|
+
return parent_id
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
### 6. Searching Files
|
|
734
|
+
|
|
735
|
+
```python
|
|
736
|
+
from msgraph.generated.users.item.drive.root.search_with_q.search_with_q_request_builder import SearchWithQRequestBuilder
|
|
737
|
+
|
|
738
|
+
async def search_files(client: GraphServiceClient, query: str):
|
|
739
|
+
"""
|
|
740
|
+
Search for files and folders
|
|
741
|
+
|
|
742
|
+
Args:
|
|
743
|
+
query: Search query string
|
|
744
|
+
"""
|
|
745
|
+
result = await client.me.drive.root.search_with_q(query).get()
|
|
746
|
+
|
|
747
|
+
if result and result.value:
|
|
748
|
+
for item in result.value:
|
|
749
|
+
item_type = "Folder" if item.folder else "File"
|
|
750
|
+
print(f"{item_type}: {item.name} - {item.web_url}")
|
|
751
|
+
|
|
752
|
+
return result.value
|
|
753
|
+
|
|
754
|
+
# Search in specific folder
|
|
755
|
+
async def search_in_folder(client: GraphServiceClient, folder_id: str, query: str):
|
|
756
|
+
result = await client.me.drive.items.by_drive_item_id(folder_id).search_with_q(query).get()
|
|
757
|
+
return result.value
|
|
758
|
+
|
|
759
|
+
# Search with filters
|
|
760
|
+
async def search_pdfs(client: GraphServiceClient):
|
|
761
|
+
"""Search for PDF files only"""
|
|
762
|
+
result = await client.me.drive.root.search_with_q('.pdf').get()
|
|
763
|
+
|
|
764
|
+
# Additional filtering in code
|
|
765
|
+
pdf_files = [item for item in result.value if item.file and item.name.lower().endswith('.pdf')]
|
|
766
|
+
|
|
767
|
+
return pdf_files
|
|
768
|
+
|
|
769
|
+
# Advanced search with query parameters
|
|
770
|
+
async def advanced_search(client: GraphServiceClient, query: str):
|
|
771
|
+
query_params = SearchWithQRequestBuilder.SearchWithQRequestBuilderGetQueryParameters(
|
|
772
|
+
select=['id', 'name', 'size', 'webUrl'],
|
|
773
|
+
top=10
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
request_config = SearchWithQRequestBuilder.SearchWithQRequestBuilderGetRequestConfiguration(
|
|
777
|
+
query_parameters=query_params
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
result = await client.me.drive.root.search_with_q(query).get(request_configuration=request_config)
|
|
781
|
+
return result.value
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
### 7. Updating/Renaming Files and Folders
|
|
786
|
+
|
|
787
|
+
```python
|
|
788
|
+
from msgraph.generated.models.drive_item import DriveItem
|
|
789
|
+
from msgraph.generated.models.item_reference import ItemReference
|
|
790
|
+
|
|
791
|
+
async def rename_item(client: GraphServiceClient, item_id: str, new_name: str):
|
|
792
|
+
"""Rename a file or folder"""
|
|
793
|
+
drive_item = DriveItem()
|
|
794
|
+
drive_item.name = new_name
|
|
795
|
+
|
|
796
|
+
updated = await client.me.drive.items.by_drive_item_id(item_id).patch(drive_item)
|
|
797
|
+
|
|
798
|
+
print(f"Renamed to: {updated.name}")
|
|
799
|
+
return updated
|
|
800
|
+
|
|
801
|
+
# Update file metadata
|
|
802
|
+
async def update_metadata(client: GraphServiceClient, item_id: str, description: str):
|
|
803
|
+
drive_item = DriveItem()
|
|
804
|
+
drive_item.description = description
|
|
805
|
+
|
|
806
|
+
updated = await client.me.drive.items.by_drive_item_id(item_id).patch(drive_item)
|
|
807
|
+
return updated
|
|
808
|
+
|
|
809
|
+
# Move file to different folder
|
|
810
|
+
async def move_item(client: GraphServiceClient, item_id: str, new_parent_id: str):
|
|
811
|
+
"""Move file or folder to new location"""
|
|
812
|
+
drive_item = DriveItem()
|
|
813
|
+
|
|
814
|
+
parent_ref = ItemReference()
|
|
815
|
+
parent_ref.id = new_parent_id
|
|
816
|
+
drive_item.parent_reference = parent_ref
|
|
817
|
+
|
|
818
|
+
moved = await client.me.drive.items.by_drive_item_id(item_id).patch(drive_item)
|
|
819
|
+
|
|
820
|
+
print(f"Moved {moved.name} to new location")
|
|
821
|
+
return moved
|
|
822
|
+
|
|
823
|
+
# Move and rename simultaneously
|
|
824
|
+
async def move_and_rename(client: GraphServiceClient, item_id: str, new_parent_id: str, new_name: str):
|
|
825
|
+
drive_item = DriveItem()
|
|
826
|
+
drive_item.name = new_name
|
|
827
|
+
|
|
828
|
+
parent_ref = ItemReference()
|
|
829
|
+
parent_ref.id = new_parent_id
|
|
830
|
+
drive_item.parent_reference = parent_ref
|
|
831
|
+
|
|
832
|
+
updated = await client.me.drive.items.by_drive_item_id(item_id).patch(drive_item)
|
|
833
|
+
return updated
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
### 8. Copying Files
|
|
838
|
+
|
|
839
|
+
```python
|
|
840
|
+
import asyncio
|
|
841
|
+
from msgraph.generated.drives.item.items.item.copy.copy_post_request_body import CopyPostRequestBody
|
|
842
|
+
from msgraph.generated.models.item_reference import ItemReference
|
|
843
|
+
|
|
844
|
+
async def copy_file(client: GraphServiceClient, item_id: str, destination_folder_id: str, new_name: str = None):
|
|
845
|
+
"""
|
|
846
|
+
Copy file to another location
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
item_id: Source file ID
|
|
850
|
+
destination_folder_id: Destination folder ID
|
|
851
|
+
new_name: Optional new name for copied file
|
|
852
|
+
"""
|
|
853
|
+
request_body = CopyPostRequestBody()
|
|
854
|
+
|
|
855
|
+
parent_ref = ItemReference()
|
|
856
|
+
parent_ref.id = destination_folder_id
|
|
857
|
+
request_body.parent_reference = parent_ref
|
|
858
|
+
|
|
859
|
+
if new_name:
|
|
860
|
+
request_body.name = new_name
|
|
861
|
+
|
|
862
|
+
# Initiate copy operation
|
|
863
|
+
await client.me.drive.items.by_drive_item_id(item_id).copy.post(request_body)
|
|
864
|
+
|
|
865
|
+
# Copy is async operation; monitor using returned location header if needed
|
|
866
|
+
print(f"Copy operation initiated")
|
|
867
|
+
|
|
868
|
+
# Wait a bit for copy to complete
|
|
869
|
+
await asyncio.sleep(2)
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
### 9. Deleting Files and Folders
|
|
874
|
+
|
|
875
|
+
```python
|
|
876
|
+
async def delete_item(client: GraphServiceClient, item_id: str):
|
|
877
|
+
"""Delete a file or folder"""
|
|
878
|
+
await client.me.drive.items.by_drive_item_id(item_id).delete()
|
|
879
|
+
print(f"Deleted item: {item_id}")
|
|
880
|
+
|
|
881
|
+
# Delete by path
|
|
882
|
+
async def delete_by_path(client: GraphServiceClient, item_path: str):
|
|
883
|
+
"""
|
|
884
|
+
Delete file or folder by path
|
|
885
|
+
|
|
886
|
+
Args:
|
|
887
|
+
item_path: Path like '/Documents/old-file.pdf'
|
|
888
|
+
"""
|
|
889
|
+
await client.me.drive.root.item_with_path(item_path).delete()
|
|
890
|
+
print(f"Deleted: {item_path}")
|
|
891
|
+
|
|
892
|
+
# Safe delete with confirmation
|
|
893
|
+
async def safe_delete(client: GraphServiceClient, item_id: str):
|
|
894
|
+
"""Delete with metadata check first"""
|
|
895
|
+
# Get item info first
|
|
896
|
+
item = await client.me.drive.items.by_drive_item_id(item_id).get()
|
|
897
|
+
|
|
898
|
+
print(f"About to delete: {item.name}")
|
|
899
|
+
print(f"Size: {item.size} bytes")
|
|
900
|
+
print(f"Modified: {item.last_modified_date_time}")
|
|
901
|
+
|
|
902
|
+
# Confirm and delete
|
|
903
|
+
await client.me.drive.items.by_drive_item_id(item_id).delete()
|
|
904
|
+
print("Deleted successfully")
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
### 10. Sharing and Permissions
|
|
909
|
+
|
|
910
|
+
#### Create Sharing Link
|
|
911
|
+
|
|
912
|
+
```python
|
|
913
|
+
from msgraph.generated.drives.item.items.item.create_link.create_link_post_request_body import CreateLinkPostRequestBody
|
|
914
|
+
|
|
915
|
+
async def create_share_link(client: GraphServiceClient, item_id: str, link_type: str = "view", scope: str = "anonymous"):
|
|
916
|
+
"""
|
|
917
|
+
Create a sharing link
|
|
918
|
+
|
|
919
|
+
Args:
|
|
920
|
+
item_id: File or folder ID
|
|
921
|
+
link_type: 'view', 'edit', or 'embed'
|
|
922
|
+
scope: 'anonymous' or 'organization'
|
|
923
|
+
"""
|
|
924
|
+
request_body = CreateLinkPostRequestBody()
|
|
925
|
+
request_body.type = link_type
|
|
926
|
+
request_body.scope = scope
|
|
927
|
+
|
|
928
|
+
permission = await client.me.drive.items.by_drive_item_id(item_id).create_link.post(request_body)
|
|
929
|
+
|
|
930
|
+
print(f"Share link: {permission.link.web_url}")
|
|
931
|
+
return permission.link.web_url
|
|
932
|
+
|
|
933
|
+
# Create link with expiration
|
|
934
|
+
async def create_expiring_link(client: GraphServiceClient, item_id: str, expiration_date: str):
|
|
935
|
+
"""
|
|
936
|
+
Create link with expiration
|
|
937
|
+
|
|
938
|
+
Args:
|
|
939
|
+
expiration_date: ISO 8601 format like '2025-12-31T23:59:59Z'
|
|
940
|
+
"""
|
|
941
|
+
request_body = CreateLinkPostRequestBody()
|
|
942
|
+
request_body.type = "view"
|
|
943
|
+
request_body.scope = "anonymous"
|
|
944
|
+
request_body.expiration_date_time = expiration_date
|
|
945
|
+
|
|
946
|
+
permission = await client.me.drive.items.by_drive_item_id(item_id).create_link.post(request_body)
|
|
947
|
+
return permission.link.web_url
|
|
948
|
+
|
|
949
|
+
# Create password-protected link
|
|
950
|
+
async def create_protected_link(client: GraphServiceClient, item_id: str, password: str):
|
|
951
|
+
request_body = CreateLinkPostRequestBody()
|
|
952
|
+
request_body.type = "view"
|
|
953
|
+
request_body.scope = "anonymous"
|
|
954
|
+
request_body.password = password
|
|
955
|
+
|
|
956
|
+
permission = await client.me.drive.items.by_drive_item_id(item_id).create_link.post(request_body)
|
|
957
|
+
return permission.link.web_url
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
#### Grant Permissions to Specific Users
|
|
961
|
+
|
|
962
|
+
```python
|
|
963
|
+
from msgraph.generated.drives.item.items.item.invite.invite_post_request_body import InvitePostRequestBody
|
|
964
|
+
from msgraph.generated.models.drive_recipient import DriveRecipient
|
|
965
|
+
|
|
966
|
+
async def invite_users(client: GraphServiceClient, item_id: str, email_addresses: list, role: str = "read"):
|
|
967
|
+
"""
|
|
968
|
+
Invite users to access a file
|
|
969
|
+
|
|
970
|
+
Args:
|
|
971
|
+
item_id: File or folder ID
|
|
972
|
+
email_addresses: List of email addresses
|
|
973
|
+
role: 'read' or 'write'
|
|
974
|
+
"""
|
|
975
|
+
request_body = InvitePostRequestBody()
|
|
976
|
+
request_body.require_sign_in = True
|
|
977
|
+
request_body.send_invitation = True
|
|
978
|
+
request_body.roles = [role]
|
|
979
|
+
request_body.message = "I've shared a file with you"
|
|
980
|
+
|
|
981
|
+
recipients = []
|
|
982
|
+
for email in email_addresses:
|
|
983
|
+
recipient = DriveRecipient()
|
|
984
|
+
recipient.email = email
|
|
985
|
+
recipients.append(recipient)
|
|
986
|
+
|
|
987
|
+
request_body.recipients = recipients
|
|
988
|
+
|
|
989
|
+
permissions = await client.me.drive.items.by_drive_item_id(item_id).invite.post(request_body)
|
|
990
|
+
|
|
991
|
+
print(f"Invited {len(email_addresses)} users")
|
|
992
|
+
return permissions
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
#### List Permissions
|
|
996
|
+
|
|
997
|
+
```python
|
|
998
|
+
async def list_permissions(client: GraphServiceClient, item_id: str):
|
|
999
|
+
"""Get all permissions for an item"""
|
|
1000
|
+
permissions = await client.me.drive.items.by_drive_item_id(item_id).permissions.get()
|
|
1001
|
+
|
|
1002
|
+
if permissions and permissions.value:
|
|
1003
|
+
for perm in permissions.value:
|
|
1004
|
+
print(f"Permission ID: {perm.id}")
|
|
1005
|
+
print(f"Roles: {perm.roles}")
|
|
1006
|
+
|
|
1007
|
+
if perm.granted_to_v2:
|
|
1008
|
+
print(f"Granted to: {perm.granted_to_v2.user.display_name}")
|
|
1009
|
+
|
|
1010
|
+
if perm.link:
|
|
1011
|
+
print(f"Link: {perm.link.web_url}")
|
|
1012
|
+
|
|
1013
|
+
print("---")
|
|
1014
|
+
|
|
1015
|
+
return permissions.value
|
|
1016
|
+
|
|
1017
|
+
# Remove permission
|
|
1018
|
+
async def remove_permission(client: GraphServiceClient, item_id: str, permission_id: str):
|
|
1019
|
+
"""Delete a specific permission"""
|
|
1020
|
+
await client.me.drive.items.by_drive_item_id(item_id).permissions.by_permission_id(permission_id).delete()
|
|
1021
|
+
print(f"Removed permission: {permission_id}")
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
#### List Files Shared With Me
|
|
1025
|
+
|
|
1026
|
+
```python
|
|
1027
|
+
async def list_shared_with_me(client: GraphServiceClient):
|
|
1028
|
+
"""Get files shared with the current user"""
|
|
1029
|
+
shared_items = await client.me.drive.shared_with_me.get()
|
|
1030
|
+
|
|
1031
|
+
if shared_items and shared_items.value:
|
|
1032
|
+
for item in shared_items.value:
|
|
1033
|
+
print(f"Shared file: {item.name}")
|
|
1034
|
+
if item.remote_item:
|
|
1035
|
+
print(f" Size: {item.remote_item.size}")
|
|
1036
|
+
if item.remote_item.created_by:
|
|
1037
|
+
print(f" Owner: {item.remote_item.created_by.user.display_name}")
|
|
1038
|
+
|
|
1039
|
+
return shared_items.value
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
### 11. Thumbnails
|
|
1044
|
+
|
|
1045
|
+
```python
|
|
1046
|
+
async def get_thumbnails(client: GraphServiceClient, item_id: str):
|
|
1047
|
+
"""Get thumbnails for an item"""
|
|
1048
|
+
thumbnails = await client.me.drive.items.by_drive_item_id(item_id).thumbnails.get()
|
|
1049
|
+
|
|
1050
|
+
if thumbnails and thumbnails.value:
|
|
1051
|
+
thumb_set = thumbnails.value[0]
|
|
1052
|
+
|
|
1053
|
+
if thumb_set.small:
|
|
1054
|
+
print(f"Small: {thumb_set.small.url}")
|
|
1055
|
+
if thumb_set.medium:
|
|
1056
|
+
print(f"Medium: {thumb_set.medium.url}")
|
|
1057
|
+
if thumb_set.large:
|
|
1058
|
+
print(f"Large: {thumb_set.large.url}")
|
|
1059
|
+
|
|
1060
|
+
return thumbnails.value
|
|
1061
|
+
|
|
1062
|
+
# Get specific thumbnail size
|
|
1063
|
+
async def get_medium_thumbnail(client: GraphServiceClient, item_id: str):
|
|
1064
|
+
thumbnail = await client.me.drive.items.by_drive_item_id(item_id).thumbnails.by_thumbnail_set_id("0").medium.get()
|
|
1065
|
+
|
|
1066
|
+
if thumbnail:
|
|
1067
|
+
print(f"Thumbnail URL: {thumbnail.url}")
|
|
1068
|
+
print(f"Size: {thumbnail.width}x{thumbnail.height}")
|
|
1069
|
+
|
|
1070
|
+
return thumbnail
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
### 12. Delta (Change Tracking)
|
|
1075
|
+
|
|
1076
|
+
```python
|
|
1077
|
+
async def get_initial_delta(client: GraphServiceClient):
|
|
1078
|
+
"""Get initial delta token and all items"""
|
|
1079
|
+
all_items = []
|
|
1080
|
+
delta_link = None
|
|
1081
|
+
|
|
1082
|
+
result = await client.me.drive.root.delta.get()
|
|
1083
|
+
|
|
1084
|
+
while result:
|
|
1085
|
+
if result.value:
|
|
1086
|
+
all_items.extend(result.value)
|
|
1087
|
+
|
|
1088
|
+
# Check for next page or delta link
|
|
1089
|
+
if hasattr(result, 'odata_next_link') and result.odata_next_link:
|
|
1090
|
+
# More pages to fetch
|
|
1091
|
+
result = await client.me.drive.root.delta.get()
|
|
1092
|
+
elif hasattr(result, 'odata_delta_link') and result.odata_delta_link:
|
|
1093
|
+
# Save delta link for future syncs
|
|
1094
|
+
delta_link = result.odata_delta_link
|
|
1095
|
+
break
|
|
1096
|
+
else:
|
|
1097
|
+
break
|
|
1098
|
+
|
|
1099
|
+
print(f"Initial sync: {len(all_items)} items")
|
|
1100
|
+
print(f"Delta link: {delta_link}")
|
|
1101
|
+
|
|
1102
|
+
return all_items, delta_link
|
|
1103
|
+
|
|
1104
|
+
# Get changes since last sync
|
|
1105
|
+
async def get_delta_changes(client: GraphServiceClient, delta_token: str):
|
|
1106
|
+
"""
|
|
1107
|
+
Get changes since last delta sync
|
|
1108
|
+
|
|
1109
|
+
Args:
|
|
1110
|
+
delta_token: Token from previous delta sync
|
|
1111
|
+
"""
|
|
1112
|
+
# Note: Use the full delta link URL saved from previous call
|
|
1113
|
+
# This is a simplified example
|
|
1114
|
+
result = await client.me.drive.root.delta.get()
|
|
1115
|
+
|
|
1116
|
+
changes = []
|
|
1117
|
+
|
|
1118
|
+
if result and result.value:
|
|
1119
|
+
for item in result.value:
|
|
1120
|
+
if hasattr(item, 'deleted') and item.deleted:
|
|
1121
|
+
print(f"Deleted: {item.id}")
|
|
1122
|
+
changes.append(('deleted', item))
|
|
1123
|
+
else:
|
|
1124
|
+
print(f"Added/Modified: {item.name}")
|
|
1125
|
+
changes.append(('modified', item))
|
|
1126
|
+
|
|
1127
|
+
return changes
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
### 13. Special Folders
|
|
1132
|
+
|
|
1133
|
+
```python
|
|
1134
|
+
async def get_special_folders(client: GraphServiceClient):
|
|
1135
|
+
"""Access special OneDrive folders"""
|
|
1136
|
+
|
|
1137
|
+
# Documents folder
|
|
1138
|
+
documents = await client.me.drive.special.by_drive_item_id("documents").get()
|
|
1139
|
+
print(f"Documents: {documents.name}")
|
|
1140
|
+
|
|
1141
|
+
# Photos folder
|
|
1142
|
+
photos = await client.me.drive.special.by_drive_item_id("photos").get()
|
|
1143
|
+
print(f"Photos: {photos.name}")
|
|
1144
|
+
|
|
1145
|
+
# Camera roll
|
|
1146
|
+
cameraroll = await client.me.drive.special.by_drive_item_id("cameraroll").get()
|
|
1147
|
+
print(f"Camera Roll: {cameraroll.name}")
|
|
1148
|
+
|
|
1149
|
+
# App root folder
|
|
1150
|
+
approot = await client.me.drive.special.by_drive_item_id("approot").get()
|
|
1151
|
+
print(f"App Root: {approot.name}")
|
|
1152
|
+
|
|
1153
|
+
# List children of special folder
|
|
1154
|
+
async def list_documents_folder(client: GraphServiceClient):
|
|
1155
|
+
files = await client.me.drive.special.by_drive_item_id("documents").children.get()
|
|
1156
|
+
|
|
1157
|
+
if files and files.value:
|
|
1158
|
+
for file in files.value:
|
|
1159
|
+
print(f"{file.name}")
|
|
1160
|
+
|
|
1161
|
+
return files.value
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
### 14. Working with SharePoint Document Libraries
|
|
1166
|
+
|
|
1167
|
+
```python
|
|
1168
|
+
async def get_sharepoint_drive(client: GraphServiceClient, site_id: str):
|
|
1169
|
+
"""Access SharePoint site drive"""
|
|
1170
|
+
drive = await client.sites.by_site_id(site_id).drive.get()
|
|
1171
|
+
|
|
1172
|
+
print(f"Drive: {drive.name}")
|
|
1173
|
+
print(f"Type: {drive.drive_type}")
|
|
1174
|
+
|
|
1175
|
+
return drive
|
|
1176
|
+
|
|
1177
|
+
# List SharePoint document library contents
|
|
1178
|
+
async def list_sharepoint_files(client: GraphServiceClient, site_id: str):
|
|
1179
|
+
items = await client.sites.by_site_id(site_id).drive.root.children.get()
|
|
1180
|
+
|
|
1181
|
+
if items and items.value:
|
|
1182
|
+
for item in items.value:
|
|
1183
|
+
print(f"{item.name}")
|
|
1184
|
+
|
|
1185
|
+
return items.value
|
|
1186
|
+
|
|
1187
|
+
# Upload to SharePoint
|
|
1188
|
+
async def upload_to_sharepoint(client: GraphServiceClient, site_id: str, file_path: str, upload_path: str):
|
|
1189
|
+
async with aiofiles.open(file_path, 'rb') as f:
|
|
1190
|
+
content = await f.read()
|
|
1191
|
+
|
|
1192
|
+
item = await client.sites.by_site_id(site_id).drive.root.item_with_path(upload_path).content.put(content)
|
|
1193
|
+
|
|
1194
|
+
return item
|
|
1195
|
+
|
|
1196
|
+
# Get site by URL
|
|
1197
|
+
async def get_site_by_path(client: GraphServiceClient, hostname: str, server_relative_path: str):
|
|
1198
|
+
"""
|
|
1199
|
+
Get SharePoint site by path
|
|
1200
|
+
|
|
1201
|
+
Example:
|
|
1202
|
+
hostname: 'contoso.sharepoint.com'
|
|
1203
|
+
server_relative_path: '/sites/marketing'
|
|
1204
|
+
"""
|
|
1205
|
+
site = await client.sites.by_site_id(f"{hostname}:{server_relative_path}").get()
|
|
1206
|
+
return site
|
|
1207
|
+
```
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
### 15. Batch Requests
|
|
1211
|
+
|
|
1212
|
+
```python
|
|
1213
|
+
from msgraph.generated.models.batch_request_content import BatchRequestContent
|
|
1214
|
+
from msgraph.generated.models.batch_response_content import BatchResponseContent
|
|
1215
|
+
|
|
1216
|
+
async def batch_requests(client: GraphServiceClient):
|
|
1217
|
+
"""Execute multiple requests in a single batch"""
|
|
1218
|
+
|
|
1219
|
+
# Create batch request
|
|
1220
|
+
batch_request_content = BatchRequestContent()
|
|
1221
|
+
|
|
1222
|
+
# Add requests to batch
|
|
1223
|
+
request1_id = batch_request_content.add_batch_request_step(
|
|
1224
|
+
{
|
|
1225
|
+
"id": "1",
|
|
1226
|
+
"method": "GET",
|
|
1227
|
+
"url": "/me/drive/root/children"
|
|
1228
|
+
}
|
|
1229
|
+
)
|
|
1230
|
+
|
|
1231
|
+
request2_id = batch_request_content.add_batch_request_step(
|
|
1232
|
+
{
|
|
1233
|
+
"id": "2",
|
|
1234
|
+
"method": "GET",
|
|
1235
|
+
"url": "/me/drive/special/documents"
|
|
1236
|
+
}
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
# Execute batch
|
|
1240
|
+
batch_response = await client.batch.post(batch_request_content)
|
|
1241
|
+
|
|
1242
|
+
# Process responses
|
|
1243
|
+
for response_id, response in batch_response.get_responses().items():
|
|
1244
|
+
print(f"Request {response_id}: Status {response.status}")
|
|
1245
|
+
print(f"Body: {response.body}")
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
## Error Handling
|
|
1250
|
+
|
|
1251
|
+
```python
|
|
1252
|
+
from msgraph.generated.models.o_data_errors.o_data_error import ODataError
|
|
1253
|
+
from azure.core.exceptions import HttpResponseError
|
|
1254
|
+
|
|
1255
|
+
async def safe_get_item(client: GraphServiceClient, item_id: str):
|
|
1256
|
+
"""Example of comprehensive error handling"""
|
|
1257
|
+
try:
|
|
1258
|
+
item = await client.me.drive.items.by_drive_item_id(item_id).get()
|
|
1259
|
+
return item
|
|
1260
|
+
|
|
1261
|
+
except ODataError as e:
|
|
1262
|
+
print(f"OData Error: {e.error.code if e.error else 'Unknown'}")
|
|
1263
|
+
print(f"Message: {e.error.message if e.error else 'Unknown'}")
|
|
1264
|
+
|
|
1265
|
+
if e.response_status_code == 404:
|
|
1266
|
+
print("Item not found")
|
|
1267
|
+
elif e.response_status_code == 401:
|
|
1268
|
+
print("Unauthorized - check authentication")
|
|
1269
|
+
elif e.response_status_code == 403:
|
|
1270
|
+
print("Forbidden - check permissions")
|
|
1271
|
+
elif e.response_status_code == 429:
|
|
1272
|
+
print("Too many requests - rate limited")
|
|
1273
|
+
|
|
1274
|
+
return None
|
|
1275
|
+
|
|
1276
|
+
except HttpResponseError as e:
|
|
1277
|
+
print(f"HTTP Error: {e.status_code}")
|
|
1278
|
+
print(f"Message: {e.message}")
|
|
1279
|
+
return None
|
|
1280
|
+
|
|
1281
|
+
except Exception as e:
|
|
1282
|
+
print(f"Unexpected error: {type(e).__name__}")
|
|
1283
|
+
print(f"Details: {str(e)}")
|
|
1284
|
+
return None
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
|
|
1288
|
+
## Complete Working Examples
|
|
1289
|
+
|
|
1290
|
+
|
|
1291
|
+
### Example 1: File Backup Script
|
|
1292
|
+
|
|
1293
|
+
```python
|
|
1294
|
+
import asyncio
|
|
1295
|
+
import os
|
|
1296
|
+
from azure.identity.aio import ClientSecretCredential
|
|
1297
|
+
from msgraph import GraphServiceClient
|
|
1298
|
+
from dotenv import load_dotenv
|
|
1299
|
+
import aiofiles
|
|
1300
|
+
|
|
1301
|
+
load_dotenv()
|
|
1302
|
+
|
|
1303
|
+
async def backup_onedrive_folder(folder_id: str, local_backup_path: str):
|
|
1304
|
+
"""Backup an entire OneDrive folder to local disk"""
|
|
1305
|
+
|
|
1306
|
+
# Initialize client
|
|
1307
|
+
credentials = ClientSecretCredential(
|
|
1308
|
+
tenant_id=os.getenv('AZURE_TENANT_ID'),
|
|
1309
|
+
client_id=os.getenv('AZURE_CLIENT_ID'),
|
|
1310
|
+
client_secret=os.getenv('AZURE_CLIENT_SECRET')
|
|
1311
|
+
)
|
|
1312
|
+
|
|
1313
|
+
client = GraphServiceClient(
|
|
1314
|
+
credentials=credentials,
|
|
1315
|
+
scopes=['https://graph.microsoft.com/.default']
|
|
1316
|
+
)
|
|
1317
|
+
|
|
1318
|
+
user_id = os.getenv('USER_ID')
|
|
1319
|
+
|
|
1320
|
+
async def download_folder_recursive(folder_id: str, local_path: str):
|
|
1321
|
+
os.makedirs(local_path, exist_ok=True)
|
|
1322
|
+
|
|
1323
|
+
items = await client.users.by_user_id(user_id).drive.items.by_drive_item_id(folder_id).children.get()
|
|
1324
|
+
|
|
1325
|
+
if not items or not items.value:
|
|
1326
|
+
return
|
|
1327
|
+
|
|
1328
|
+
for item in items.value:
|
|
1329
|
+
if item.folder:
|
|
1330
|
+
subfolder_path = os.path.join(local_path, item.name)
|
|
1331
|
+
print(f"Backing up folder: {item.name}")
|
|
1332
|
+
await download_folder_recursive(item.id, subfolder_path)
|
|
1333
|
+
|
|
1334
|
+
elif item.file:
|
|
1335
|
+
file_path = os.path.join(local_path, item.name)
|
|
1336
|
+
print(f"Backing up file: {item.name} ({item.size} bytes)")
|
|
1337
|
+
|
|
1338
|
+
stream = await client.users.by_user_id(user_id).drive.items.by_drive_item_id(item.id).content.get()
|
|
1339
|
+
|
|
1340
|
+
async with aiofiles.open(file_path, 'wb') as f:
|
|
1341
|
+
await f.write(stream)
|
|
1342
|
+
|
|
1343
|
+
await download_folder_recursive(folder_id, local_backup_path)
|
|
1344
|
+
print("Backup complete!")
|
|
1345
|
+
|
|
1346
|
+
if __name__ == '__main__':
|
|
1347
|
+
asyncio.run(backup_onedrive_folder('FOLDER_ID_HERE', './backup'))
|
|
1348
|
+
```
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
### Example 2: Sync Local Directory to OneDrive
|
|
1352
|
+
|
|
1353
|
+
```python
|
|
1354
|
+
import asyncio
|
|
1355
|
+
import os
|
|
1356
|
+
from pathlib import Path
|
|
1357
|
+
from azure.identity.aio import ClientSecretCredential
|
|
1358
|
+
from msgraph import GraphServiceClient
|
|
1359
|
+
from msgraph.generated.models.drive_item import DriveItem
|
|
1360
|
+
from msgraph.generated.models.folder import Folder
|
|
1361
|
+
import aiofiles
|
|
1362
|
+
|
|
1363
|
+
async def sync_to_onedrive(local_path: str, onedrive_folder_id: str):
|
|
1364
|
+
"""Sync local directory to OneDrive"""
|
|
1365
|
+
|
|
1366
|
+
credentials = ClientSecretCredential(
|
|
1367
|
+
tenant_id=os.getenv('AZURE_TENANT_ID'),
|
|
1368
|
+
client_id=os.getenv('AZURE_CLIENT_ID'),
|
|
1369
|
+
client_secret=os.getenv('AZURE_CLIENT_SECRET')
|
|
1370
|
+
)
|
|
1371
|
+
|
|
1372
|
+
client = GraphServiceClient(
|
|
1373
|
+
credentials=credentials,
|
|
1374
|
+
scopes=['https://graph.microsoft.com/.default']
|
|
1375
|
+
)
|
|
1376
|
+
|
|
1377
|
+
user_id = os.getenv('USER_ID')
|
|
1378
|
+
|
|
1379
|
+
async def sync_folder(local_dir: str, parent_id: str):
|
|
1380
|
+
for entry in os.listdir(local_dir):
|
|
1381
|
+
entry_path = os.path.join(local_dir, entry)
|
|
1382
|
+
|
|
1383
|
+
if os.path.isdir(entry_path):
|
|
1384
|
+
# Create folder in OneDrive
|
|
1385
|
+
drive_item = DriveItem()
|
|
1386
|
+
drive_item.name = entry
|
|
1387
|
+
drive_item.folder = Folder()
|
|
1388
|
+
drive_item.microsoft_graph_conflict_behavior = "replace"
|
|
1389
|
+
|
|
1390
|
+
folder = await client.users.by_user_id(user_id).drive.items.by_drive_item_id(parent_id).children.post(drive_item)
|
|
1391
|
+
print(f"Created folder: {entry}")
|
|
1392
|
+
|
|
1393
|
+
# Recursively sync subfolder
|
|
1394
|
+
await sync_folder(entry_path, folder.id)
|
|
1395
|
+
|
|
1396
|
+
else:
|
|
1397
|
+
# Upload file
|
|
1398
|
+
file_size = os.path.getsize(entry_path)
|
|
1399
|
+
print(f"Uploading {entry} ({file_size} bytes)...")
|
|
1400
|
+
|
|
1401
|
+
if file_size < 4 * 1024 * 1024:
|
|
1402
|
+
# Small file
|
|
1403
|
+
async with aiofiles.open(entry_path, 'rb') as f:
|
|
1404
|
+
content = await f.read()
|
|
1405
|
+
|
|
1406
|
+
await client.users.by_user_id(user_id).drive.items.by_drive_item_id(parent_id).item_with_path(entry).content.put(content)
|
|
1407
|
+
else:
|
|
1408
|
+
# Large file - would need upload session (simplified here)
|
|
1409
|
+
print(f"Skipping large file: {entry}")
|
|
1410
|
+
|
|
1411
|
+
print(f"Uploaded: {entry}")
|
|
1412
|
+
|
|
1413
|
+
await sync_folder(local_path, onedrive_folder_id)
|
|
1414
|
+
print("Sync complete!")
|
|
1415
|
+
|
|
1416
|
+
if __name__ == '__main__':
|
|
1417
|
+
asyncio.run(sync_to_onedrive('./local-folder', 'ONEDRIVE_FOLDER_ID'))
|
|
1418
|
+
```
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
### Example 3: Share Files with Team
|
|
1422
|
+
|
|
1423
|
+
```python
|
|
1424
|
+
import asyncio
|
|
1425
|
+
from azure.identity.aio import ClientSecretCredential
|
|
1426
|
+
from msgraph import GraphServiceClient
|
|
1427
|
+
from msgraph.generated.drives.item.items.item.invite.invite_post_request_body import InvitePostRequestBody
|
|
1428
|
+
from msgraph.generated.models.drive_recipient import DriveRecipient
|
|
1429
|
+
|
|
1430
|
+
async def share_folder_with_team(folder_id: str, team_emails: list):
|
|
1431
|
+
"""Share a folder with team members"""
|
|
1432
|
+
|
|
1433
|
+
credentials = ClientSecretCredential(
|
|
1434
|
+
tenant_id=os.getenv('AZURE_TENANT_ID'),
|
|
1435
|
+
client_id=os.getenv('AZURE_CLIENT_ID'),
|
|
1436
|
+
client_secret=os.getenv('AZURE_CLIENT_SECRET')
|
|
1437
|
+
)
|
|
1438
|
+
|
|
1439
|
+
client = GraphServiceClient(
|
|
1440
|
+
credentials=credentials,
|
|
1441
|
+
scopes=['https://graph.microsoft.com/.default']
|
|
1442
|
+
)
|
|
1443
|
+
|
|
1444
|
+
user_id = os.getenv('USER_ID')
|
|
1445
|
+
|
|
1446
|
+
# Get folder info
|
|
1447
|
+
folder = await client.users.by_user_id(user_id).drive.items.by_drive_item_id(folder_id).get()
|
|
1448
|
+
print(f"Sharing folder: {folder.name}")
|
|
1449
|
+
|
|
1450
|
+
# Invite users
|
|
1451
|
+
request_body = InvitePostRequestBody()
|
|
1452
|
+
request_body.require_sign_in = True
|
|
1453
|
+
request_body.send_invitation = True
|
|
1454
|
+
request_body.roles = ["write"]
|
|
1455
|
+
request_body.message = f"You've been invited to collaborate on {folder.name}"
|
|
1456
|
+
|
|
1457
|
+
recipients = []
|
|
1458
|
+
for email in team_emails:
|
|
1459
|
+
recipient = DriveRecipient()
|
|
1460
|
+
recipient.email = email
|
|
1461
|
+
recipients.append(recipient)
|
|
1462
|
+
|
|
1463
|
+
request_body.recipients = recipients
|
|
1464
|
+
|
|
1465
|
+
permissions = await client.users.by_user_id(user_id).drive.items.by_drive_item_id(folder_id).invite.post(request_body)
|
|
1466
|
+
|
|
1467
|
+
print(f"Successfully shared with {len(team_emails)} team members")
|
|
1468
|
+
|
|
1469
|
+
return permissions
|
|
1470
|
+
|
|
1471
|
+
if __name__ == '__main__':
|
|
1472
|
+
team = ['alice@example.com', 'bob@example.com', 'charlie@example.com']
|
|
1473
|
+
asyncio.run(share_folder_with_team('FOLDER_ID', team))
|
|
1474
|
+
```
|
|
1475
|
+
|
|
1476
|
+
|
|
1477
|
+
## Rate Limiting and Throttling
|
|
1478
|
+
|
|
1479
|
+
```python
|
|
1480
|
+
import asyncio
|
|
1481
|
+
from msgraph.generated.models.o_data_errors.o_data_error import ODataError
|
|
1482
|
+
|
|
1483
|
+
async def request_with_retry(client, request_func, max_retries=3):
|
|
1484
|
+
"""Make request with automatic retry on rate limit"""
|
|
1485
|
+
retries = 0
|
|
1486
|
+
|
|
1487
|
+
while retries < max_retries:
|
|
1488
|
+
try:
|
|
1489
|
+
return await request_func()
|
|
1490
|
+
|
|
1491
|
+
except ODataError as e:
|
|
1492
|
+
if e.response_status_code == 429:
|
|
1493
|
+
# Rate limited
|
|
1494
|
+
retry_after = 5 # Default wait time
|
|
1495
|
+
|
|
1496
|
+
if e.response_headers and 'Retry-After' in e.response_headers:
|
|
1497
|
+
retry_after = int(e.response_headers['Retry-After'])
|
|
1498
|
+
|
|
1499
|
+
print(f"Rate limited. Waiting {retry_after} seconds...")
|
|
1500
|
+
await asyncio.sleep(retry_after)
|
|
1501
|
+
retries += 1
|
|
1502
|
+
else:
|
|
1503
|
+
raise
|
|
1504
|
+
|
|
1505
|
+
raise Exception("Max retries exceeded")
|
|
1506
|
+
|
|
1507
|
+
# Usage
|
|
1508
|
+
async def example_with_retry(client: GraphServiceClient):
|
|
1509
|
+
result = await request_with_retry(
|
|
1510
|
+
client,
|
|
1511
|
+
lambda: client.me.drive.root.children.get()
|
|
1512
|
+
)
|
|
1513
|
+
return result
|
|
1514
|
+
```
|
|
1515
|
+
|
|
1516
|
+
|
|
1517
|
+
## Testing Helper Functions
|
|
1518
|
+
|
|
1519
|
+
```python
|
|
1520
|
+
async def test_connection(client: GraphServiceClient):
|
|
1521
|
+
"""Test if client is properly configured"""
|
|
1522
|
+
try:
|
|
1523
|
+
me = await client.me.get()
|
|
1524
|
+
print(f"Connected as: {me.display_name}")
|
|
1525
|
+
print(f"Email: {me.user_principal_name}")
|
|
1526
|
+
return True
|
|
1527
|
+
except Exception as e:
|
|
1528
|
+
print(f"Connection test failed: {e}")
|
|
1529
|
+
return False
|
|
1530
|
+
|
|
1531
|
+
async def get_drive_info(client: GraphServiceClient):
|
|
1532
|
+
"""Get OneDrive quota and usage information"""
|
|
1533
|
+
drive = await client.me.drive.get()
|
|
1534
|
+
|
|
1535
|
+
quota = drive.quota
|
|
1536
|
+
|
|
1537
|
+
if quota:
|
|
1538
|
+
total = quota.total / (1024**3) # Convert to GB
|
|
1539
|
+
used = quota.used / (1024**3)
|
|
1540
|
+
remaining = quota.remaining / (1024**3)
|
|
1541
|
+
|
|
1542
|
+
print(f"OneDrive Storage:")
|
|
1543
|
+
print(f" Total: {total:.2f} GB")
|
|
1544
|
+
print(f" Used: {used:.2f} GB")
|
|
1545
|
+
print(f" Remaining: {remaining:.2f} GB")
|
|
1546
|
+
print(f" State: {quota.state}")
|
|
1547
|
+
|
|
1548
|
+
return quota
|
|
1549
|
+
```
|