notionhelper 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- notionhelper-0.3.2/GETTING_STARTED.md +912 -0
- notionhelper-0.3.2/ML_DEMO_README.md +315 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/PKG-INFO +2 -2
- {notionhelper-0.3.0 → notionhelper-0.3.2}/README.md +1 -1
- notionhelper-0.3.2/examples/ml_demo.py +391 -0
- notionhelper-0.3.2/images/notionh3.png +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/pyproject.toml +1 -1
- {notionhelper-0.3.0 → notionhelper-0.3.2}/src/notionhelper/helper.py +49 -15
- {notionhelper-0.3.0 → notionhelper-0.3.2}/uv.lock +1 -1
- notionhelper-0.3.0/notion_api_examples.md +0 -234
- {notionhelper-0.3.0 → notionhelper-0.3.2}/.coverage +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/.github/workflows/claude-code-review.yml +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/.github/workflows/claude.yml +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/.gitignore +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/images/helper_logo.png +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/images/json_builder.png.png +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/images/logo.png +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/images/pillio.png +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/images/pillio2.png +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/notionapi_md_info.md +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/pytest.ini +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/src/notionhelper/__init__.py +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/tests/README.md +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/tests/__init__.py +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/tests/conftest.py +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.2}/tests/test_helper.py +0 -0
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
# Getting Started with NotionHelper
|
|
2
|
+
|
|
3
|
+
A comprehensive guide to using NotionHelper for basic Notion API operations.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
1. [Installation & Setup](#installation--setup)
|
|
7
|
+
2. [Understanding Notion's Structure](#understanding-notions-structure)
|
|
8
|
+
3. [Basic Operations](#basic-operations)
|
|
9
|
+
4. [Working with Databases](#working-with-databases)
|
|
10
|
+
5. [Working with Data Sources](#working-with-data-sources)
|
|
11
|
+
6. [Working with Pages](#working-with-pages)
|
|
12
|
+
7. [File Operations](#file-operations)
|
|
13
|
+
8. [Data Retrieval & Analysis](#data-retrieval--analysis)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation & Setup
|
|
18
|
+
|
|
19
|
+
### 1. Install NotionHelper
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install notionhelper
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 2. Create a Notion Integration
|
|
26
|
+
|
|
27
|
+
1. Go to [https://www.notion.so/my-integrations](https://www.notion.so/my-integrations)
|
|
28
|
+
2. Click **"+ New integration"**
|
|
29
|
+
3. Give it a name (e.g., "My Python App")
|
|
30
|
+
4. Select the workspace where you want to use it
|
|
31
|
+
5. Copy the **"Internal Integration Secret"** (your API token)
|
|
32
|
+
|
|
33
|
+
### 3. Share Your Notion Pages/Databases
|
|
34
|
+
|
|
35
|
+
**IMPORTANT**: Your integration can only access pages and databases that have been explicitly shared with it.
|
|
36
|
+
|
|
37
|
+
To share a page or database:
|
|
38
|
+
1. Open the page/database in Notion
|
|
39
|
+
2. Click the **"Share"** button in the top-right
|
|
40
|
+
3. Click **"Invite"**
|
|
41
|
+
4. Search for your integration name
|
|
42
|
+
5. Click **"Invite"**
|
|
43
|
+
|
|
44
|
+
### 4. Store Your Token Securely
|
|
45
|
+
|
|
46
|
+
It's best practice to store your token as an environment variable:
|
|
47
|
+
|
|
48
|
+
**On macOS/Linux:**
|
|
49
|
+
```bash
|
|
50
|
+
export NOTION_TOKEN="secret_xxxxxxxxxxxxxxxxxxxx"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**On Windows (PowerShell):**
|
|
54
|
+
```powershell
|
|
55
|
+
$env:NOTION_TOKEN="secret_xxxxxxxxxxxxxxxxxxxx"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Or use a `.env` file:**
|
|
59
|
+
```
|
|
60
|
+
NOTION_TOKEN=secret_xxxxxxxxxxxxxxxxxxxx
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 5. Initialize NotionHelper
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import os
|
|
67
|
+
from notionhelper import NotionHelper
|
|
68
|
+
|
|
69
|
+
# Get token from environment variable
|
|
70
|
+
notion_token = os.getenv("NOTION_TOKEN")
|
|
71
|
+
|
|
72
|
+
# Initialize the helper
|
|
73
|
+
helper = NotionHelper(notion_token)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Understanding Notion's Structure
|
|
79
|
+
|
|
80
|
+
### API Version 2025-09-03 Changes
|
|
81
|
+
|
|
82
|
+
With the latest Notion API, the structure has changed:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Page (Parent)
|
|
86
|
+
└── Database (Container)
|
|
87
|
+
├── Data Source 1 (Table with schema/properties)
|
|
88
|
+
│ ├── Page 1
|
|
89
|
+
│ ├── Page 2
|
|
90
|
+
│ └── Page 3
|
|
91
|
+
└── Data Source 2 (Another table)
|
|
92
|
+
├── Page 1
|
|
93
|
+
└── Page 2
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Key Concepts:**
|
|
97
|
+
- **Database**: A container that holds one or more data sources
|
|
98
|
+
- **Data Source**: A table with a specific schema (columns/properties)
|
|
99
|
+
- **Page**: Individual rows in a data source, or standalone pages
|
|
100
|
+
|
|
101
|
+
**Important IDs:**
|
|
102
|
+
- `database_id`: Points to the container
|
|
103
|
+
- `data_source_id`: Points to a specific table (where you add pages)
|
|
104
|
+
- `page_id`: Points to individual pages
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Basic Operations
|
|
109
|
+
|
|
110
|
+
### Finding IDs in Notion
|
|
111
|
+
|
|
112
|
+
**To get a page, database, or data source ID:**
|
|
113
|
+
|
|
114
|
+
1. Open the item in Notion
|
|
115
|
+
2. Look at the URL in your browser:
|
|
116
|
+
```
|
|
117
|
+
https://www.notion.so/My-Page-1234567890abcdef1234567890abcdef
|
|
118
|
+
└── This is the ID ──────────┘
|
|
119
|
+
```
|
|
120
|
+
3. The ID is the 32-character string at the end (without hyphens)
|
|
121
|
+
4. Format with hyphens: `12345678-90ab-cdef-1234-567890abcdef`
|
|
122
|
+
|
|
123
|
+
### Searching for Pages and Databases
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
# Search for all pages
|
|
127
|
+
pages = helper.notion_search_db(query="", filter_object_type="page")
|
|
128
|
+
for page in pages:
|
|
129
|
+
print(f"Page: {page['id']}")
|
|
130
|
+
|
|
131
|
+
# Search for data sources
|
|
132
|
+
data_sources = helper.notion_search_db(query="", filter_object_type="data_source")
|
|
133
|
+
for ds in data_sources:
|
|
134
|
+
print(f"Data Source: {ds['id']}")
|
|
135
|
+
|
|
136
|
+
# Search with a specific query
|
|
137
|
+
results = helper.notion_search_db(query="Project Tasks", filter_object_type="page")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Working with Databases
|
|
143
|
+
|
|
144
|
+
### Retrieve a Database
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
# Get the database container
|
|
148
|
+
database_id = "12345678-90ab-cdef-1234-567890abcdef"
|
|
149
|
+
database = helper.get_database(database_id)
|
|
150
|
+
|
|
151
|
+
print(f"Database Title: {database['title'][0]['plain_text']}")
|
|
152
|
+
print(f"Number of Data Sources: {len(database['data_sources'])}")
|
|
153
|
+
|
|
154
|
+
# List all data sources in the database
|
|
155
|
+
for ds in database['data_sources']:
|
|
156
|
+
print(f"Data Source ID: {ds['id']}")
|
|
157
|
+
print(f"Data Source Type: {ds['type']}")
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Create a New Database
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# Define the parent page where the database will be created
|
|
164
|
+
parent_page_id = "your-parent-page-id"
|
|
165
|
+
|
|
166
|
+
# Define the database properties (schema)
|
|
167
|
+
properties = {
|
|
168
|
+
"Task Name": {"title": {}}, # This will be the title column
|
|
169
|
+
"Status": {
|
|
170
|
+
"select": {
|
|
171
|
+
"options": [
|
|
172
|
+
{"name": "Not Started", "color": "red"},
|
|
173
|
+
{"name": "In Progress", "color": "yellow"},
|
|
174
|
+
{"name": "Done", "color": "green"}
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
"Due Date": {"date": {}},
|
|
179
|
+
"Priority": {
|
|
180
|
+
"select": {
|
|
181
|
+
"options": [
|
|
182
|
+
{"name": "Low", "color": "gray"},
|
|
183
|
+
{"name": "Medium", "color": "blue"},
|
|
184
|
+
{"name": "High", "color": "red"}
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"Notes": {"rich_text": {}}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# Create the database
|
|
192
|
+
new_db = helper.create_database(
|
|
193
|
+
parent_page_id=parent_page_id,
|
|
194
|
+
database_title="My Task Database",
|
|
195
|
+
initial_data_source_properties=properties,
|
|
196
|
+
initial_data_source_title="Main Tasks"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
print(f"Created Database ID: {new_db['id']}")
|
|
200
|
+
print(f"Initial Data Source ID: {new_db['initial_data_source']['id']}")
|
|
201
|
+
|
|
202
|
+
# Save the data source ID for adding pages later
|
|
203
|
+
data_source_id = new_db['initial_data_source']['id']
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Working with Data Sources
|
|
209
|
+
|
|
210
|
+
### Retrieve a Data Source Schema
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
data_source_id = "your-data-source-id"
|
|
214
|
+
data_source = helper.get_data_source(data_source_id)
|
|
215
|
+
|
|
216
|
+
print(f"Data Source Title: {data_source['title'][0]['plain_text']}")
|
|
217
|
+
print("\nProperties (Columns):")
|
|
218
|
+
for prop_name, prop_data in data_source['properties'].items():
|
|
219
|
+
prop_type = prop_data['type']
|
|
220
|
+
print(f" - {prop_name}: {prop_type}")
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Update a Data Source
|
|
224
|
+
|
|
225
|
+
#### Rename a Column
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
data_source_id = "your-data-source-id"
|
|
229
|
+
|
|
230
|
+
updated = helper.update_data_source(
|
|
231
|
+
data_source_id,
|
|
232
|
+
properties={
|
|
233
|
+
"Old Column Name": {
|
|
234
|
+
"name": "New Column Name"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
print("Column renamed successfully!")
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### Add a New Column
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
updated = helper.update_data_source(
|
|
245
|
+
data_source_id,
|
|
246
|
+
properties={
|
|
247
|
+
"Email": {"email": {}}, # Add an email column
|
|
248
|
+
"Phone": {"phone_number": {}}, # Add a phone column
|
|
249
|
+
"Completed": {"checkbox": {}} # Add a checkbox column
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
print("New columns added!")
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Remove a Column
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
updated = helper.update_data_source(
|
|
259
|
+
data_source_id,
|
|
260
|
+
properties={
|
|
261
|
+
"Column To Remove": None # Set to None to remove
|
|
262
|
+
}
|
|
263
|
+
)
|
|
264
|
+
print("Column removed!")
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### Update Select Options
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
updated = helper.update_data_source(
|
|
271
|
+
data_source_id,
|
|
272
|
+
properties={
|
|
273
|
+
"Status": {
|
|
274
|
+
"select": {
|
|
275
|
+
"options": [
|
|
276
|
+
{"name": "Backlog", "color": "gray"},
|
|
277
|
+
{"name": "To Do", "color": "red"},
|
|
278
|
+
{"name": "In Progress", "color": "yellow"},
|
|
279
|
+
{"name": "Review", "color": "blue"},
|
|
280
|
+
{"name": "Done", "color": "green"}
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
print("Select options updated!")
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Working with Pages
|
|
292
|
+
|
|
293
|
+
### Create a New Page (Row)
|
|
294
|
+
|
|
295
|
+
**Basic Example:**
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
data_source_id = "your-data-source-id"
|
|
299
|
+
|
|
300
|
+
# Define the page properties
|
|
301
|
+
page_properties = {
|
|
302
|
+
"Task Name": { # Title property
|
|
303
|
+
"title": [
|
|
304
|
+
{
|
|
305
|
+
"text": {
|
|
306
|
+
"content": "Complete project documentation"
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
},
|
|
311
|
+
"Status": { # Select property
|
|
312
|
+
"select": {
|
|
313
|
+
"name": "In Progress"
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
"Due Date": { # Date property
|
|
317
|
+
"date": {
|
|
318
|
+
"start": "2025-12-31"
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
"Priority": { # Select property
|
|
322
|
+
"select": {
|
|
323
|
+
"name": "High"
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
"Notes": { # Rich text property
|
|
327
|
+
"rich_text": [
|
|
328
|
+
{
|
|
329
|
+
"text": {
|
|
330
|
+
"content": "Need to update README and add examples"
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
# Create the page
|
|
338
|
+
new_page = helper.new_page_to_data_source(data_source_id, page_properties)
|
|
339
|
+
page_id = new_page['id']
|
|
340
|
+
print(f"Created page with ID: {page_id}")
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Property Type Examples:**
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
# Number
|
|
347
|
+
"Age": {
|
|
348
|
+
"number": 25
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
# Checkbox
|
|
352
|
+
"Completed": {
|
|
353
|
+
"checkbox": True
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
# URL
|
|
357
|
+
"Website": {
|
|
358
|
+
"url": "https://example.com"
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# Email
|
|
362
|
+
"Contact": {
|
|
363
|
+
"email": "user@example.com"
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
# Phone
|
|
367
|
+
"Phone": {
|
|
368
|
+
"phone_number": "+1234567890"
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
# Multi-select
|
|
372
|
+
"Tags": {
|
|
373
|
+
"multi_select": [
|
|
374
|
+
{"name": "Urgent"},
|
|
375
|
+
{"name": "Bug"},
|
|
376
|
+
{"name": "Frontend"}
|
|
377
|
+
]
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
# Date with end date (range)
|
|
381
|
+
"Event Period": {
|
|
382
|
+
"date": {
|
|
383
|
+
"start": "2025-01-01",
|
|
384
|
+
"end": "2025-01-07"
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Retrieve a Page
|
|
390
|
+
|
|
391
|
+
```python
|
|
392
|
+
page_id = "your-page-id"
|
|
393
|
+
page_data = helper.get_page(page_id)
|
|
394
|
+
|
|
395
|
+
# Access properties
|
|
396
|
+
properties = page_data['properties']
|
|
397
|
+
print(f"Properties: {properties}")
|
|
398
|
+
|
|
399
|
+
# Access page content blocks
|
|
400
|
+
content = page_data['content']
|
|
401
|
+
print(f"Number of blocks: {len(content)}")
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Add Content to a Page
|
|
405
|
+
|
|
406
|
+
```python
|
|
407
|
+
page_id = "your-page-id"
|
|
408
|
+
|
|
409
|
+
# Define blocks to add
|
|
410
|
+
blocks = [
|
|
411
|
+
{
|
|
412
|
+
"object": "block",
|
|
413
|
+
"type": "heading_1",
|
|
414
|
+
"heading_1": {
|
|
415
|
+
"rich_text": [
|
|
416
|
+
{
|
|
417
|
+
"type": "text",
|
|
418
|
+
"text": {
|
|
419
|
+
"content": "Project Overview"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
]
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
"object": "block",
|
|
427
|
+
"type": "paragraph",
|
|
428
|
+
"paragraph": {
|
|
429
|
+
"rich_text": [
|
|
430
|
+
{
|
|
431
|
+
"type": "text",
|
|
432
|
+
"text": {
|
|
433
|
+
"content": "This project aims to improve user experience by implementing new features."
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
]
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
"object": "block",
|
|
441
|
+
"type": "heading_2",
|
|
442
|
+
"heading_2": {
|
|
443
|
+
"rich_text": [
|
|
444
|
+
{
|
|
445
|
+
"type": "text",
|
|
446
|
+
"text": {
|
|
447
|
+
"content": "Key Features"
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
"object": "block",
|
|
455
|
+
"type": "bulleted_list_item",
|
|
456
|
+
"bulleted_list_item": {
|
|
457
|
+
"rich_text": [
|
|
458
|
+
{
|
|
459
|
+
"type": "text",
|
|
460
|
+
"text": {
|
|
461
|
+
"content": "Enhanced search functionality"
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
]
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
"object": "block",
|
|
469
|
+
"type": "bulleted_list_item",
|
|
470
|
+
"bulleted_list_item": {
|
|
471
|
+
"rich_text": [
|
|
472
|
+
{
|
|
473
|
+
"type": "text",
|
|
474
|
+
"text": {
|
|
475
|
+
"content": "Improved mobile responsiveness"
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
]
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
"object": "block",
|
|
483
|
+
"type": "numbered_list_item",
|
|
484
|
+
"numbered_list_item": {
|
|
485
|
+
"rich_text": [
|
|
486
|
+
{
|
|
487
|
+
"type": "text",
|
|
488
|
+
"text": {
|
|
489
|
+
"content": "Step 1: Research user needs"
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
]
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
"object": "block",
|
|
497
|
+
"type": "numbered_list_item",
|
|
498
|
+
"numbered_list_item": {
|
|
499
|
+
"rich_text": [
|
|
500
|
+
{
|
|
501
|
+
"type": "text",
|
|
502
|
+
"text": {
|
|
503
|
+
"content": "Step 2: Design mockups"
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
]
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
"object": "block",
|
|
511
|
+
"type": "to_do",
|
|
512
|
+
"to_do": {
|
|
513
|
+
"rich_text": [
|
|
514
|
+
{
|
|
515
|
+
"type": "text",
|
|
516
|
+
"text": {
|
|
517
|
+
"content": "Complete initial testing"
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
],
|
|
521
|
+
"checked": False
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
]
|
|
525
|
+
|
|
526
|
+
# Append blocks to the page
|
|
527
|
+
helper.append_page_body(page_id, blocks)
|
|
528
|
+
print("Content added to page successfully!")
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## File Operations
|
|
534
|
+
|
|
535
|
+
### Upload and Attach a File
|
|
536
|
+
|
|
537
|
+
**Method 1: Two-step process**
|
|
538
|
+
|
|
539
|
+
```python
|
|
540
|
+
# Step 1: Upload the file
|
|
541
|
+
file_path = "/path/to/document.pdf"
|
|
542
|
+
upload_response = helper.upload_file(file_path)
|
|
543
|
+
file_upload_id = upload_response['id']
|
|
544
|
+
|
|
545
|
+
# Step 2: Attach to a page
|
|
546
|
+
page_id = "your-page-id"
|
|
547
|
+
attach_response = helper.attach_file_to_page(page_id, file_upload_id)
|
|
548
|
+
print("File attached successfully!")
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**Method 2: One-step process (recommended)**
|
|
552
|
+
|
|
553
|
+
```python
|
|
554
|
+
page_id = "your-page-id"
|
|
555
|
+
file_path = "/path/to/document.pdf"
|
|
556
|
+
|
|
557
|
+
response = helper.one_step_file_to_page(page_id, file_path)
|
|
558
|
+
print("File uploaded and attached in one step!")
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Upload and Embed an Image
|
|
562
|
+
|
|
563
|
+
```python
|
|
564
|
+
page_id = "your-page-id"
|
|
565
|
+
image_path = "/path/to/image.png"
|
|
566
|
+
|
|
567
|
+
response = helper.one_step_image_embed(page_id, image_path)
|
|
568
|
+
print("Image embedded in page!")
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Attach File to a Property
|
|
572
|
+
|
|
573
|
+
Some databases have a "Files & Media" property where you can attach files.
|
|
574
|
+
|
|
575
|
+
```python
|
|
576
|
+
page_id = "your-page-id"
|
|
577
|
+
property_name = "Attachments" # Name of your Files & Media property
|
|
578
|
+
file_path = "/path/to/document.pdf"
|
|
579
|
+
file_name = "Project Document.pdf" # Display name
|
|
580
|
+
|
|
581
|
+
response = helper.one_step_file_to_page_property(
|
|
582
|
+
page_id,
|
|
583
|
+
property_name,
|
|
584
|
+
file_path,
|
|
585
|
+
file_name
|
|
586
|
+
)
|
|
587
|
+
print("File attached to property!")
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Upload Multiple Files to a Property
|
|
591
|
+
|
|
592
|
+
```python
|
|
593
|
+
page_id = "your-page-id"
|
|
594
|
+
property_name = "Documents"
|
|
595
|
+
file_paths = [
|
|
596
|
+
"/path/to/document1.pdf",
|
|
597
|
+
"/path/to/document2.docx",
|
|
598
|
+
"/path/to/image.png"
|
|
599
|
+
]
|
|
600
|
+
|
|
601
|
+
response = helper.upload_multiple_files_to_property(page_id, property_name, file_paths)
|
|
602
|
+
print(f"Uploaded {len(file_paths)} files!")
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## Data Retrieval & Analysis
|
|
608
|
+
|
|
609
|
+
### Get All Page IDs from a Data Source
|
|
610
|
+
|
|
611
|
+
```python
|
|
612
|
+
data_source_id = "your-data-source-id"
|
|
613
|
+
page_ids = helper.get_data_source_page_ids(data_source_id)
|
|
614
|
+
|
|
615
|
+
print(f"Found {len(page_ids)} pages:")
|
|
616
|
+
for page_id in page_ids:
|
|
617
|
+
print(f" - {page_id}")
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Get All Pages as JSON
|
|
621
|
+
|
|
622
|
+
```python
|
|
623
|
+
data_source_id = "your-data-source-id"
|
|
624
|
+
|
|
625
|
+
# Get all pages
|
|
626
|
+
all_pages = helper.get_data_source_pages_as_json(data_source_id)
|
|
627
|
+
|
|
628
|
+
# Get only first 10 pages
|
|
629
|
+
limited_pages = helper.get_data_source_pages_as_json(data_source_id, limit=10)
|
|
630
|
+
|
|
631
|
+
print(f"Retrieved {len(all_pages)} pages")
|
|
632
|
+
print(f"First page properties: {all_pages[0]}")
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Get All Pages as Pandas DataFrame
|
|
636
|
+
|
|
637
|
+
**This is perfect for data analysis!**
|
|
638
|
+
|
|
639
|
+
```python
|
|
640
|
+
import pandas as pd
|
|
641
|
+
|
|
642
|
+
data_source_id = "your-data-source-id"
|
|
643
|
+
|
|
644
|
+
# Get all pages as a DataFrame
|
|
645
|
+
df = helper.get_data_source_pages_as_dataframe(data_source_id)
|
|
646
|
+
|
|
647
|
+
print(df.head())
|
|
648
|
+
print(f"\nDataFrame shape: {df.shape}")
|
|
649
|
+
print(f"Columns: {df.columns.tolist()}")
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
**With pagination:**
|
|
653
|
+
|
|
654
|
+
```python
|
|
655
|
+
# Get only first 50 pages
|
|
656
|
+
df = helper.get_data_source_pages_as_dataframe(data_source_id, limit=50)
|
|
657
|
+
|
|
658
|
+
# Get pages without IDs
|
|
659
|
+
df = helper.get_data_source_pages_as_dataframe(
|
|
660
|
+
data_source_id,
|
|
661
|
+
include_page_ids=False
|
|
662
|
+
)
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
**Data Analysis Example:**
|
|
666
|
+
|
|
667
|
+
```python
|
|
668
|
+
# Get the data
|
|
669
|
+
df = helper.get_data_source_pages_as_dataframe(data_source_id)
|
|
670
|
+
|
|
671
|
+
# Filter completed tasks
|
|
672
|
+
completed = df[df['Status'] == 'Done']
|
|
673
|
+
print(f"Completed tasks: {len(completed)}")
|
|
674
|
+
|
|
675
|
+
# Group by status
|
|
676
|
+
status_counts = df['Status'].value_counts()
|
|
677
|
+
print("\nTasks by status:")
|
|
678
|
+
print(status_counts)
|
|
679
|
+
|
|
680
|
+
# Find high priority tasks
|
|
681
|
+
high_priority = df[df['Priority'] == 'High']
|
|
682
|
+
print(f"\nHigh priority tasks: {len(high_priority)}")
|
|
683
|
+
|
|
684
|
+
# Export to CSV
|
|
685
|
+
df.to_csv('notion_tasks.csv', index=False)
|
|
686
|
+
print("Exported to CSV!")
|
|
687
|
+
|
|
688
|
+
# Calculate statistics
|
|
689
|
+
if 'Completion Rate' in df.columns:
|
|
690
|
+
avg_completion = df['Completion Rate'].mean()
|
|
691
|
+
print(f"Average completion rate: {avg_completion:.2f}%")
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
## Complete Example: Task Manager
|
|
697
|
+
|
|
698
|
+
Here's a complete example that creates a task management system:
|
|
699
|
+
|
|
700
|
+
```python
|
|
701
|
+
import os
|
|
702
|
+
from notionhelper import NotionHelper
|
|
703
|
+
from datetime import datetime, timedelta
|
|
704
|
+
|
|
705
|
+
# Initialize
|
|
706
|
+
notion_token = os.getenv("NOTION_TOKEN")
|
|
707
|
+
helper = NotionHelper(notion_token)
|
|
708
|
+
|
|
709
|
+
# 1. Create a task database
|
|
710
|
+
parent_page_id = "your-parent-page-id"
|
|
711
|
+
|
|
712
|
+
properties = {
|
|
713
|
+
"Task": {"title": {}},
|
|
714
|
+
"Status": {
|
|
715
|
+
"select": {
|
|
716
|
+
"options": [
|
|
717
|
+
{"name": "Backlog", "color": "gray"},
|
|
718
|
+
{"name": "To Do", "color": "red"},
|
|
719
|
+
{"name": "In Progress", "color": "yellow"},
|
|
720
|
+
{"name": "Done", "color": "green"}
|
|
721
|
+
]
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
"Priority": {
|
|
725
|
+
"select": {
|
|
726
|
+
"options": [
|
|
727
|
+
{"name": "Low", "color": "gray"},
|
|
728
|
+
{"name": "Medium", "color": "blue"},
|
|
729
|
+
{"name": "High", "color": "red"}
|
|
730
|
+
]
|
|
731
|
+
}
|
|
732
|
+
},
|
|
733
|
+
"Due Date": {"date": {}},
|
|
734
|
+
"Assignee": {"rich_text": {}},
|
|
735
|
+
"Completed": {"checkbox": {}}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
db = helper.create_database(
|
|
739
|
+
parent_page_id=parent_page_id,
|
|
740
|
+
database_title="Team Tasks",
|
|
741
|
+
initial_data_source_properties=properties
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
data_source_id = db['initial_data_source']['id']
|
|
745
|
+
print(f"Created database with data source: {data_source_id}")
|
|
746
|
+
|
|
747
|
+
# 2. Add some tasks
|
|
748
|
+
tasks = [
|
|
749
|
+
{
|
|
750
|
+
"Task": "Design homepage mockup",
|
|
751
|
+
"Status": "In Progress",
|
|
752
|
+
"Priority": "High",
|
|
753
|
+
"Due Date": (datetime.now() + timedelta(days=3)).strftime("%Y-%m-%d"),
|
|
754
|
+
"Assignee": "Alice",
|
|
755
|
+
"Completed": False
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
"Task": "Write API documentation",
|
|
759
|
+
"Status": "To Do",
|
|
760
|
+
"Priority": "Medium",
|
|
761
|
+
"Due Date": (datetime.now() + timedelta(days=7)).strftime("%Y-%m-%d"),
|
|
762
|
+
"Assignee": "Bob",
|
|
763
|
+
"Completed": False
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
"Task": "Set up CI/CD pipeline",
|
|
767
|
+
"Status": "Done",
|
|
768
|
+
"Priority": "High",
|
|
769
|
+
"Due Date": (datetime.now() - timedelta(days=2)).strftime("%Y-%m-%d"),
|
|
770
|
+
"Assignee": "Charlie",
|
|
771
|
+
"Completed": True
|
|
772
|
+
}
|
|
773
|
+
]
|
|
774
|
+
|
|
775
|
+
for task in tasks:
|
|
776
|
+
page_props = {
|
|
777
|
+
"Task": {
|
|
778
|
+
"title": [{"text": {"content": task["Task"]}}]
|
|
779
|
+
},
|
|
780
|
+
"Status": {
|
|
781
|
+
"select": {"name": task["Status"]}
|
|
782
|
+
},
|
|
783
|
+
"Priority": {
|
|
784
|
+
"select": {"name": task["Priority"]}
|
|
785
|
+
},
|
|
786
|
+
"Due Date": {
|
|
787
|
+
"date": {"start": task["Due Date"]}
|
|
788
|
+
},
|
|
789
|
+
"Assignee": {
|
|
790
|
+
"rich_text": [{"text": {"content": task["Assignee"]}}]
|
|
791
|
+
},
|
|
792
|
+
"Completed": {
|
|
793
|
+
"checkbox": task["Completed"]
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
new_page = helper.new_page_to_data_source(data_source_id, page_props)
|
|
798
|
+
print(f"Created task: {task['Task']}")
|
|
799
|
+
|
|
800
|
+
# 3. Retrieve and analyze
|
|
801
|
+
df = helper.get_data_source_pages_as_dataframe(data_source_id)
|
|
802
|
+
print("\n=== Task Summary ===")
|
|
803
|
+
print(f"Total tasks: {len(df)}")
|
|
804
|
+
print(f"\nBy Status:")
|
|
805
|
+
print(df['Status'].value_counts())
|
|
806
|
+
print(f"\nBy Priority:")
|
|
807
|
+
print(df['Priority'].value_counts())
|
|
808
|
+
print(f"\nCompleted: {df['Completed'].sum()}")
|
|
809
|
+
print(f"In Progress: {len(df[df['Status'] == 'In Progress'])}")
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
---
|
|
813
|
+
|
|
814
|
+
## Common Patterns
|
|
815
|
+
|
|
816
|
+
### Bulk Add Pages
|
|
817
|
+
|
|
818
|
+
```python
|
|
819
|
+
import pandas as pd
|
|
820
|
+
|
|
821
|
+
# Read from CSV
|
|
822
|
+
df = pd.read_csv('tasks.csv')
|
|
823
|
+
|
|
824
|
+
for _, row in df.iterrows():
|
|
825
|
+
page_props = {
|
|
826
|
+
"Task": {
|
|
827
|
+
"title": [{"text": {"content": str(row['task_name'])}}]
|
|
828
|
+
},
|
|
829
|
+
"Status": {
|
|
830
|
+
"select": {"name": str(row['status'])}
|
|
831
|
+
},
|
|
832
|
+
"Priority": {
|
|
833
|
+
"select": {"name": str(row['priority'])}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
helper.new_page_to_data_source(data_source_id, page_props)
|
|
838
|
+
print(f"Added: {row['task_name']}")
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
### Sync Data Between Notion and CSV
|
|
842
|
+
|
|
843
|
+
```python
|
|
844
|
+
# Export from Notion
|
|
845
|
+
df = helper.get_data_source_pages_as_dataframe(data_source_id)
|
|
846
|
+
df.to_csv('notion_export.csv', index=False)
|
|
847
|
+
print("Exported to CSV")
|
|
848
|
+
|
|
849
|
+
# Import to Notion
|
|
850
|
+
df = pd.read_csv('import_data.csv')
|
|
851
|
+
for _, row in df.iterrows():
|
|
852
|
+
# Create page from row data
|
|
853
|
+
pass
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
### Update Multiple Pages
|
|
857
|
+
|
|
858
|
+
```python
|
|
859
|
+
# Get all pages
|
|
860
|
+
page_ids = helper.get_data_source_page_ids(data_source_id)
|
|
861
|
+
|
|
862
|
+
# Note: There's no built-in batch update in the current version
|
|
863
|
+
# You would need to update pages individually using the Notion API
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
---
|
|
867
|
+
|
|
868
|
+
## Troubleshooting
|
|
869
|
+
|
|
870
|
+
### Common Errors
|
|
871
|
+
|
|
872
|
+
**1. "object not found"**
|
|
873
|
+
- Make sure you've shared the page/database with your integration
|
|
874
|
+
- Check that the ID is correct
|
|
875
|
+
|
|
876
|
+
**2. "body failed validation"**
|
|
877
|
+
- Check property names match exactly (case-sensitive)
|
|
878
|
+
- Verify property types match the schema
|
|
879
|
+
- Ensure select options exist in the database
|
|
880
|
+
|
|
881
|
+
**3. "Unauthorized"**
|
|
882
|
+
- Check your API token is correct
|
|
883
|
+
- Make sure the token is properly set in environment variables
|
|
884
|
+
|
|
885
|
+
**4. "Could not find database"**
|
|
886
|
+
- You might be using a `database_id` instead of a `data_source_id`
|
|
887
|
+
- Get the data source ID from the database object
|
|
888
|
+
|
|
889
|
+
### Best Practices
|
|
890
|
+
|
|
891
|
+
1. **Always use environment variables for tokens**
|
|
892
|
+
2. **Share resources with your integration before accessing**
|
|
893
|
+
3. **Use data_source_id when creating pages, not database_id**
|
|
894
|
+
4. **Handle errors with try-except blocks**
|
|
895
|
+
5. **Test with a single page before bulk operations**
|
|
896
|
+
6. **Use limit parameter when testing to avoid retrieving too much data**
|
|
897
|
+
|
|
898
|
+
---
|
|
899
|
+
|
|
900
|
+
## Next Steps
|
|
901
|
+
|
|
902
|
+
Now that you understand the basics:
|
|
903
|
+
|
|
904
|
+
1. Explore the [Complete ML Experiment Tracking Guide](README.md#machine-learning-experiment-tracking)
|
|
905
|
+
2. Check out advanced features in the [README](README.md)
|
|
906
|
+
3. Review the [Notion API Documentation](https://developers.notion.com)
|
|
907
|
+
|
|
908
|
+
---
|
|
909
|
+
|
|
910
|
+
**Need Help?**
|
|
911
|
+
- Check the [README.md](README.md) for complete function reference
|
|
912
|
+
- Visit [Notion API JSON Builder](https://notioinapiassistant.streamlit.app) for help with property JSON
|