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.
Files changed (24) hide show
  1. notionhelper-0.3.1/GETTING_STARTED.md +912 -0
  2. {notionhelper-0.3.0 → notionhelper-0.3.1}/PKG-INFO +2 -2
  3. {notionhelper-0.3.0 → notionhelper-0.3.1}/README.md +1 -1
  4. notionhelper-0.3.1/images/notionh3.png +0 -0
  5. {notionhelper-0.3.0 → notionhelper-0.3.1}/pyproject.toml +1 -1
  6. {notionhelper-0.3.0 → notionhelper-0.3.1}/src/notionhelper/helper.py +49 -15
  7. notionhelper-0.3.0/notion_api_examples.md +0 -234
  8. {notionhelper-0.3.0 → notionhelper-0.3.1}/.coverage +0 -0
  9. {notionhelper-0.3.0 → notionhelper-0.3.1}/.github/workflows/claude-code-review.yml +0 -0
  10. {notionhelper-0.3.0 → notionhelper-0.3.1}/.github/workflows/claude.yml +0 -0
  11. {notionhelper-0.3.0 → notionhelper-0.3.1}/.gitignore +0 -0
  12. {notionhelper-0.3.0 → notionhelper-0.3.1}/images/helper_logo.png +0 -0
  13. {notionhelper-0.3.0 → notionhelper-0.3.1}/images/json_builder.png.png +0 -0
  14. {notionhelper-0.3.0 → notionhelper-0.3.1}/images/logo.png +0 -0
  15. {notionhelper-0.3.0 → notionhelper-0.3.1}/images/pillio.png +0 -0
  16. {notionhelper-0.3.0 → notionhelper-0.3.1}/images/pillio2.png +0 -0
  17. {notionhelper-0.3.0 → notionhelper-0.3.1}/notionapi_md_info.md +0 -0
  18. {notionhelper-0.3.0 → notionhelper-0.3.1}/pytest.ini +0 -0
  19. {notionhelper-0.3.0 → notionhelper-0.3.1}/src/notionhelper/__init__.py +0 -0
  20. {notionhelper-0.3.0 → notionhelper-0.3.1}/tests/README.md +0 -0
  21. {notionhelper-0.3.0 → notionhelper-0.3.1}/tests/__init__.py +0 -0
  22. {notionhelper-0.3.0 → notionhelper-0.3.1}/tests/conftest.py +0 -0
  23. {notionhelper-0.3.0 → notionhelper-0.3.1}/tests/test_helper.py +0 -0
  24. {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.0
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
- ![NotionHelper](https://github.com/janduplessis883/notionhelper/blob/master/images/helper_logo.png?raw=true)
30
+ ![NotionHelper](https://github.com/janduplessis883/notionhelper/blob/master/images/notionh3.png?raw=true)
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
- ![NotionHelper](https://github.com/janduplessis883/notionhelper/blob/master/images/helper_logo.png?raw=true)
3
+ ![NotionHelper](https://github.com/janduplessis883/notionhelper/blob/master/images/notionh3.png?raw=true)
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.0"
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
- properties = {}
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
- # 2. Add 'Run Status'
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
- # 3. Add the Multi-file property
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