notionhelper 0.3.0__tar.gz → 0.3.1__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.1/GETTING_STARTED.md +912 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/PKG-INFO +2 -2
- {notionhelper-0.3.0 → notionhelper-0.3.1}/README.md +1 -1
- notionhelper-0.3.1/images/notionh3.png +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/pyproject.toml +1 -1
- {notionhelper-0.3.0 → notionhelper-0.3.1}/src/notionhelper/helper.py +49 -15
- notionhelper-0.3.0/notion_api_examples.md +0 -234
- {notionhelper-0.3.0 → notionhelper-0.3.1}/.coverage +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/.github/workflows/claude-code-review.yml +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/.github/workflows/claude.yml +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/.gitignore +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/images/helper_logo.png +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/images/json_builder.png.png +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/images/logo.png +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/images/pillio.png +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/images/pillio2.png +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/notionapi_md_info.md +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/pytest.ini +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/src/notionhelper/__init__.py +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/tests/README.md +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/tests/__init__.py +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/tests/conftest.py +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/tests/test_helper.py +0 -0
- {notionhelper-0.3.0 → notionhelper-0.3.1}/uv.lock +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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: notionhelper
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: NotionHelper is a Python library that simplifies interactions with the Notion API, enabling easy management of databases, pages, and files within Notion workspaces.
|
|
5
5
|
Author-email: Jan du Plessis <drjanduplessis@icloud.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -27,7 +27,7 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|
|
|
28
28
|
# NotionHelper
|
|
29
29
|
|
|
30
|
-

|
|
31
31
|
|
|
32
32
|
`NotionHelper` is a Python library that provides a convenient interface for interacting with the Notion API, specifically designed to leverage the **Notion API Version 2025-09-03**. It simplifies common tasks such as managing databases, data sources, pages, and file uploads, allowing you to integrate Notion's powerful features into your applications with ease.
|
|
33
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# NotionHelper
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
`NotionHelper` is a Python library that provides a convenient interface for interacting with the Notion API, specifically designed to leverage the **Notion API Version 2025-09-03**. It simplifies common tasks such as managing databases, data sources, pages, and file uploads, allowing you to integrate Notion's powerful features into your applications with ease.
|
|
6
6
|
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "notionhelper"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.1"
|
|
4
4
|
description = "NotionHelper is a Python library that simplifies interactions with the Notion API, enabling easy management of databases, pages, and files within Notion workspaces."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -655,7 +655,51 @@ class NotionHelper:
|
|
|
655
655
|
response = requests.patch(update_url, headers=headers, json=data)
|
|
656
656
|
return response.json()
|
|
657
657
|
|
|
658
|
+
def dict_to_notion_schema(self, data: Dict[str, Any], title_key: str) -> Dict[str, Any]:
|
|
659
|
+
"""Converts a dictionary into a Notion property schema for database creation.
|
|
660
|
+
|
|
661
|
+
Parameters:
|
|
662
|
+
data (dict): Dictionary containing sample values to infer types from.
|
|
663
|
+
title_key (str): The key that should be used as the title property.
|
|
664
|
+
|
|
665
|
+
Returns:
|
|
666
|
+
dict: A dictionary defining the Notion property schema.
|
|
667
|
+
"""
|
|
668
|
+
properties = {}
|
|
669
|
+
|
|
670
|
+
for key, value in data.items():
|
|
671
|
+
# Handle NumPy types
|
|
672
|
+
if hasattr(value, "item"):
|
|
673
|
+
value = value.item()
|
|
674
|
+
|
|
675
|
+
# Debug output to help diagnose type issues
|
|
676
|
+
print(f"DEBUG: key='{key}', value={value}, type={type(value).__name__}, isinstance(bool)={isinstance(value, bool)}, isinstance(int)={isinstance(value, int)}")
|
|
677
|
+
|
|
678
|
+
if key == title_key:
|
|
679
|
+
properties[key] = {"title": {}}
|
|
680
|
+
# IMPORTANT: Check for bool BEFORE (int, float) because bool is a subclass of int in Python
|
|
681
|
+
elif isinstance(value, bool):
|
|
682
|
+
properties[key] = {"checkbox": {}}
|
|
683
|
+
print(f" → Assigned as CHECKBOX")
|
|
684
|
+
elif isinstance(value, (int, float)):
|
|
685
|
+
properties[key] = {"number": {"format": "number"}}
|
|
686
|
+
print(f" → Assigned as NUMBER")
|
|
687
|
+
else:
|
|
688
|
+
properties[key] = {"rich_text": {}}
|
|
689
|
+
print(f" → Assigned as RICH_TEXT")
|
|
690
|
+
|
|
691
|
+
return properties
|
|
692
|
+
|
|
658
693
|
def dict_to_notion_props(self, data: Dict[str, Any], title_key: str) -> Dict[str, Any]:
|
|
694
|
+
"""Converts a dictionary into Notion property values for page creation.
|
|
695
|
+
|
|
696
|
+
Parameters:
|
|
697
|
+
data (dict): Dictionary containing the values to convert.
|
|
698
|
+
title_key (str): The key that should be used as the title property.
|
|
699
|
+
|
|
700
|
+
Returns:
|
|
701
|
+
dict: A dictionary defining the Notion property values.
|
|
702
|
+
"""
|
|
659
703
|
notion_props = {}
|
|
660
704
|
for key, value in data.items():
|
|
661
705
|
# Handle NumPy types
|
|
@@ -759,29 +803,19 @@ class NotionHelper:
|
|
|
759
803
|
def create_ml_database(self, parent_page_id: str, db_title: str, config: Dict, metrics: Dict, file_property_name: str = "Output Files") -> str:
|
|
760
804
|
"""
|
|
761
805
|
Analyzes dicts to create a new Notion Database with the correct schema.
|
|
806
|
+
Uses dict_to_notion_schema() for universal type conversion.
|
|
762
807
|
"""
|
|
763
|
-
# --- ENSURE ALL LINES BELOW ARE INDENTED BY 8 SPACES (assuming 4 for class) ---
|
|
764
808
|
combined = {**config, **metrics}
|
|
765
809
|
title_key = list(config.keys())[0]
|
|
766
810
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
# 1. Map dictionary keys to Notion Property Types
|
|
770
|
-
for key, value in combined.items():
|
|
771
|
-
if key == title_key:
|
|
772
|
-
properties[key] = {"title": {}}
|
|
773
|
-
elif isinstance(value, (int, float)):
|
|
774
|
-
properties[key] = {"number": {"format": "number"}}
|
|
775
|
-
elif isinstance(value, bool):
|
|
776
|
-
properties[key] = {"checkbox": {}}
|
|
777
|
-
else:
|
|
778
|
-
properties[key] = {"rich_text": {}}
|
|
811
|
+
# Use the universal dict_to_notion_schema() method
|
|
812
|
+
properties = self.dict_to_notion_schema(combined, title_key)
|
|
779
813
|
|
|
780
|
-
#
|
|
814
|
+
# Add 'Run Status' if not already present
|
|
781
815
|
if "Run Status" not in properties:
|
|
782
816
|
properties["Run Status"] = {"rich_text": {}}
|
|
783
817
|
|
|
784
|
-
#
|
|
818
|
+
# Add the Multi-file property
|
|
785
819
|
properties[file_property_name] = {"files": {}}
|
|
786
820
|
|
|
787
821
|
print(f"Creating database '{db_title}' with {len(properties)} columns...")
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
# Notion API Examples (API Version 2025-09-03)
|
|
2
|
-
|
|
3
|
-
This document provides example JSON payloads for interacting with the Notion API, specifically focusing on database and data source management under API version `2025-09-03`.
|
|
4
|
-
|
|
5
|
-
## 1. Create a New Database with an Initial Data Source
|
|
6
|
-
|
|
7
|
-
This example demonstrates how to create a new Notion database, which acts as a container, along with its first data source (table). The `initial_data_source` object is crucial here for defining the schema of your first table.
|
|
8
|
-
|
|
9
|
-
**Endpoint:** `POST /v1/databases`
|
|
10
|
-
|
|
11
|
-
**Payload Example:**
|
|
12
|
-
|
|
13
|
-
```json
|
|
14
|
-
{
|
|
15
|
-
"parent": {
|
|
16
|
-
"type": "page_id",
|
|
17
|
-
"page_id": "YOUR_PARENT_PAGE_ID"
|
|
18
|
-
},
|
|
19
|
-
"title": [
|
|
20
|
-
{
|
|
21
|
-
"type": "text",
|
|
22
|
-
"text": {
|
|
23
|
-
"content": "My Project Tracker"
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
],
|
|
27
|
-
"icon": {
|
|
28
|
-
"type": "emoji",
|
|
29
|
-
"emoji": "📊"
|
|
30
|
-
},
|
|
31
|
-
"cover": {
|
|
32
|
-
"type": "external",
|
|
33
|
-
"external": {
|
|
34
|
-
"url": "https://www.notion.so/images/page-cover/gradients_1.jpg"
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
"initial_data_source": {
|
|
38
|
-
"title": [
|
|
39
|
-
{
|
|
40
|
-
"type": "text",
|
|
41
|
-
"text": {
|
|
42
|
-
"content": "Tasks"
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
],
|
|
46
|
-
"properties": {
|
|
47
|
-
"Task Name": {
|
|
48
|
-
"title": {}
|
|
49
|
-
},
|
|
50
|
-
"Status": {
|
|
51
|
-
"select": {
|
|
52
|
-
"options": [
|
|
53
|
-
{
|
|
54
|
-
"name": "Not Started",
|
|
55
|
-
"color": "red"
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"name": "In Progress",
|
|
59
|
-
"color": "blue"
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"name": "Completed",
|
|
63
|
-
"color": "green"
|
|
64
|
-
}
|
|
65
|
-
]
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
"Due Date": {
|
|
69
|
-
"date": {}
|
|
70
|
-
},
|
|
71
|
-
"Priority": {
|
|
72
|
-
"multi_select": {
|
|
73
|
-
"options": [
|
|
74
|
-
{
|
|
75
|
-
"name": "High",
|
|
76
|
-
"color": "orange"
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
"name": "Medium",
|
|
80
|
-
"color": "yellow"
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"name": "Low",
|
|
84
|
-
"color": "gray"
|
|
85
|
-
}
|
|
86
|
-
]
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
"Assigned To": {
|
|
90
|
-
"people": {}
|
|
91
|
-
},
|
|
92
|
-
"Notes": {
|
|
93
|
-
"rich_text": {}
|
|
94
|
-
},
|
|
95
|
-
"Estimated Hours": {
|
|
96
|
-
"number": {
|
|
97
|
-
"format": "number"
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
"URL": {
|
|
101
|
-
"url": {}
|
|
102
|
-
},
|
|
103
|
-
"Checkbox": {
|
|
104
|
-
"checkbox": {}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
**Key points:**
|
|
112
|
-
* Replace `"YOUR_PARENT_PAGE_ID"` with the actual ID of the Notion page where you want to create this database.
|
|
113
|
-
* `title` at the top level is for the database container.
|
|
114
|
-
* `initial_data_source.title` is for the first data source (table) within the database.
|
|
115
|
-
* `initial_data_source.properties` defines the columns (schema) of your first table. A `title` property (e.g., "Task Name") is mandatory for any data source.
|
|
116
|
-
* **Important Note on Property Definitions:** When defining properties in the `properties` object (for both database creation and data source updates), the value for each property type should be an empty object `{}`, not a rich text array or other content. For example:
|
|
117
|
-
* For a `title` property: `"My Title Column": {"title": {}}` (NOT `"My Title Column": {"title": [{"text": {"content": "Some text"}}]}`)
|
|
118
|
-
* For a `rich_text` property: `"My Text Column": {"rich_text": {}}` (NOT `"My Text Column": {"rich_text": [{"text": {"content": "Some text"}}]}`)
|
|
119
|
-
The rich text arrays are used when *creating or updating pages* (rows) within a data source, not when defining the data source's schema.
|
|
120
|
-
|
|
121
|
-
## 2. Add a New Data Source to an Existing Database
|
|
122
|
-
|
|
123
|
-
This example shows how to add an additional data source (another table) to a database that already exists. This is useful for creating multi-source databases.
|
|
124
|
-
|
|
125
|
-
**Endpoint:** `POST /v1/data_sources`
|
|
126
|
-
|
|
127
|
-
**Payload Example:**
|
|
128
|
-
|
|
129
|
-
```json
|
|
130
|
-
{
|
|
131
|
-
"parent": {
|
|
132
|
-
"type": "database_id",
|
|
133
|
-
"database_id": "YOUR_EXISTING_DATABASE_ID"
|
|
134
|
-
},
|
|
135
|
-
"title": [
|
|
136
|
-
{
|
|
137
|
-
"type": "text",
|
|
138
|
-
"text": {
|
|
139
|
-
"content": "Project Members"
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
],
|
|
143
|
-
"icon": {
|
|
144
|
-
"type": "emoji",
|
|
145
|
-
"emoji": "👥"
|
|
146
|
-
},
|
|
147
|
-
"properties": {
|
|
148
|
-
"Member Name": {
|
|
149
|
-
"title": {}
|
|
150
|
-
},
|
|
151
|
-
"Role": {
|
|
152
|
-
"select": {
|
|
153
|
-
"options": [
|
|
154
|
-
{
|
|
155
|
-
"name": "Developer",
|
|
156
|
-
"color": "blue"
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
"name": "Designer",
|
|
160
|
-
"color": "purple"
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
"name": "Manager",
|
|
164
|
-
"color": "green"
|
|
165
|
-
}
|
|
166
|
-
]
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
"Email": {
|
|
170
|
-
"email": {}
|
|
171
|
-
},
|
|
172
|
-
"Phone Number": {
|
|
173
|
-
"phone_number": {}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
**Key points:**
|
|
180
|
-
* Replace `"YOUR_EXISTING_DATABASE_ID"` with the ID of the database to which you want to add this new data source.
|
|
181
|
-
* The `parent.type` must be `"database_id"`.
|
|
182
|
-
* The `title` here is for the new data source itself.
|
|
183
|
-
* `properties` defines the schema for this new table.
|
|
184
|
-
|
|
185
|
-
## 3. Update a Data Source's Properties
|
|
186
|
-
|
|
187
|
-
This example demonstrates how to modify the schema (properties/columns) of an existing data source. You can rename properties, add new ones, or remove existing ones.
|
|
188
|
-
|
|
189
|
-
**Endpoint:** `PATCH /v1/data_sources/{data_source_id}`
|
|
190
|
-
|
|
191
|
-
**Payload Example (Renaming, Adding, and Removing Properties):**
|
|
192
|
-
|
|
193
|
-
```json
|
|
194
|
-
{
|
|
195
|
-
"properties": {
|
|
196
|
-
"Old Property Name": {
|
|
197
|
-
"name": "New Property Name"
|
|
198
|
-
},
|
|
199
|
-
"New Text Property": {
|
|
200
|
-
"rich_text": {}
|
|
201
|
-
},
|
|
202
|
-
"Status": {
|
|
203
|
-
"select": {
|
|
204
|
-
"options": [
|
|
205
|
-
{
|
|
206
|
-
"name": "To Do",
|
|
207
|
-
"color": "gray"
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
"name": "In Progress",
|
|
211
|
-
"color": "blue"
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
"name": "Done",
|
|
215
|
-
"color": "green"
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
"name": "Blocked",
|
|
219
|
-
"color": "red"
|
|
220
|
-
}
|
|
221
|
-
]
|
|
222
|
-
}
|
|
223
|
-
},
|
|
224
|
-
"Property To Remove": null
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
**Key points:**
|
|
230
|
-
* Replace `{data_source_id}` in the URL with the actual ID of the data source you want to update.
|
|
231
|
-
* To rename a property, provide its current name (or ID) as the key and an object with the new `name` as its value.
|
|
232
|
-
* To add a new property, provide its desired name as the key and its type definition (e.g., `"rich_text": {}`) as the value.
|
|
233
|
-
* To remove a property, provide its name (or ID) as the key and `null` as its value.
|
|
234
|
-
* For `select` and `multi_select` properties, you can update their options by providing the full `options` array. Existing options not included will be removed, and new ones will be added.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|