notionary 0.2.6__tar.gz → 0.2.7__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.
- notionary-0.2.7/PKG-INFO +245 -0
- notionary-0.2.7/README.md +219 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/database/database_discovery.py +24 -24
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/code_block_element.py +59 -18
- notionary-0.2.7/notionary/elements/column_element.py +204 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/divider_element.py +2 -2
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/registry/block_registry.py +1 -1
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/registry/block_registry_builder.py +7 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/content/page_content_writer.py +12 -2
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/markdown_to_notion_converter.py +45 -14
- {notionary-0.2.6 → notionary-0.2.7}/notionary/prompting/markdown_syntax_prompt_generator.py +35 -29
- notionary-0.2.7/notionary.egg-info/PKG-INFO +245 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary.egg-info/SOURCES.txt +1 -0
- {notionary-0.2.6 → notionary-0.2.7}/setup.py +1 -1
- notionary-0.2.6/PKG-INFO +0 -256
- notionary-0.2.6/README.md +0 -230
- notionary-0.2.6/notionary.egg-info/PKG-INFO +0 -256
- {notionary-0.2.6 → notionary-0.2.7}/LICENSE +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/__init__.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/database/models/page_result.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/database/notion_database.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/database/notion_database_factory.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/audio_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/bookmark_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/bulleted_list_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/callout_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/embed_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/heading_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/image_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/mention_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/notion_block_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/numbered_list_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/paragraph_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/qoute_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/table_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/text_inline_formatter.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/todo_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/toggle_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/toggleable_heading_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/video_element.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/exceptions/database_exceptions.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/exceptions/page_creation_exception.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/models/notion_block_response.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/models/notion_database_response.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/models/notion_page_response.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/notion_client.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/content/notion_page_content_chunker.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/content/page_content_retriever.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/metadata/metadata_editor.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/metadata/notion_icon_manager.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/metadata/notion_page_cover_manager.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/notion_page.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/notion_page_factory.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/notion_to_markdown_converter.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/properites/database_property_service.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/properites/page_property_manager.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/properites/property_formatter.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/properites/property_value_extractor.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/relations/notion_page_relation_manager.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/relations/notion_page_title_resolver.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/page/relations/page_database_relation.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/prompting/element_prompt_content.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/util/logging_mixin.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/util/page_id_utils.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary/util/warn_direct_constructor_usage.py +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary.egg-info/dependency_links.txt +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary.egg-info/requires.txt +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/notionary.egg-info/top_level.txt +0 -0
- {notionary-0.2.6 → notionary-0.2.7}/setup.cfg +0 -0
notionary-0.2.7/PKG-INFO
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: notionary
|
3
|
+
Version: 0.2.7
|
4
|
+
Summary: A toolkit to convert between Markdown and Notion blocks
|
5
|
+
Home-page: https://github.com/mathisarends/notionary
|
6
|
+
Author: Mathis Arends
|
7
|
+
Author-email: mathisarends27@gmail.com
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
10
|
+
Requires-Python: >=3.7
|
11
|
+
Description-Content-Type: text/markdown
|
12
|
+
License-File: LICENSE
|
13
|
+
Requires-Dist: httpx>=0.28.0
|
14
|
+
Requires-Dist: python-dotenv>=1.1.0
|
15
|
+
Requires-Dist: pydantic>=2.11.4
|
16
|
+
Dynamic: author
|
17
|
+
Dynamic: author-email
|
18
|
+
Dynamic: classifier
|
19
|
+
Dynamic: description
|
20
|
+
Dynamic: description-content-type
|
21
|
+
Dynamic: home-page
|
22
|
+
Dynamic: license-file
|
23
|
+
Dynamic: requires-dist
|
24
|
+
Dynamic: requires-python
|
25
|
+
Dynamic: summary
|
26
|
+
|
27
|
+
# Notionary 📝
|
28
|
+
|
29
|
+
[](https://www.python.org/downloads/)
|
30
|
+
[](LICENSE)
|
31
|
+
|
32
|
+
**Notionary** is a powerful Python library for interacting with the Notion API, making it easy to create, update, and manage Notion pages and databases programmatically with a clean, intuitive interface. It's specifically designed to be the foundation for AI-driven Notion content generation.
|
33
|
+
|
34
|
+
---
|
35
|
+
|
36
|
+
## Features
|
37
|
+
|
38
|
+
- **Rich Markdown Support**: Create Notion pages using intuitive Markdown syntax with custom extensions
|
39
|
+
- **Dynamic Database Operations**: Create, update, and query database entries with schema auto-detection
|
40
|
+
- **Extensible Block Registry**: Add, customize, or remove Notion block elements with a flexible registry pattern
|
41
|
+
- **LLM-Ready Prompts**: Generate system prompts explaining Markdown syntax for LLMs to create Notion content
|
42
|
+
- **Async-First Design**: Built for modern Python with full async/await support
|
43
|
+
- **Schema-Based Validation**: Automatic property validation based on database schemas
|
44
|
+
- **Intelligent Content Conversion**: Bidirectional conversion between Markdown and Notion blocks
|
45
|
+
|
46
|
+
---
|
47
|
+
|
48
|
+
## Installation
|
49
|
+
|
50
|
+
```bash
|
51
|
+
pip install notionary
|
52
|
+
```
|
53
|
+
|
54
|
+
---
|
55
|
+
|
56
|
+
## Quick Start
|
57
|
+
|
58
|
+
### Creating and Managing Pages
|
59
|
+
|
60
|
+
```python
|
61
|
+
import asyncio
|
62
|
+
from notionary import NotionPage
|
63
|
+
|
64
|
+
async def main():
|
65
|
+
# Create a page from URL
|
66
|
+
page = NotionPage.from_url("https://www.notion.so/your-page-url")
|
67
|
+
|
68
|
+
# Or find by name
|
69
|
+
page = await NotionPage.from_page_name("My Project Page")
|
70
|
+
|
71
|
+
# Update page metadata
|
72
|
+
await page.set_title("Updated Title")
|
73
|
+
await page.set_emoji_icon("🚀")
|
74
|
+
await page.set_random_gradient_cover()
|
75
|
+
|
76
|
+
# Add markdown content
|
77
|
+
markdown = """
|
78
|
+
# Project Overview
|
79
|
+
|
80
|
+
!> [💡] This page was created programmatically using Notionary.
|
81
|
+
|
82
|
+
## Features
|
83
|
+
- **Rich** Markdown support
|
84
|
+
- Async functionality
|
85
|
+
- Custom syntax extensions
|
86
|
+
|
87
|
+
+++ Implementation Details
|
88
|
+
| Notionary uses a custom converter to transform Markdown into Notion blocks.
|
89
|
+
| This makes it easy to create rich content programmatically.
|
90
|
+
"""
|
91
|
+
|
92
|
+
await page.replace_content(markdown)
|
93
|
+
|
94
|
+
if __name__ == "__main__":
|
95
|
+
asyncio.run(main())
|
96
|
+
```
|
97
|
+
|
98
|
+
### Working with Databases
|
99
|
+
|
100
|
+
```python
|
101
|
+
import asyncio
|
102
|
+
from notionary import NotionDatabase, DatabaseDiscovery
|
103
|
+
|
104
|
+
async def main():
|
105
|
+
# Discover available databases
|
106
|
+
discovery = DatabaseDiscovery()
|
107
|
+
await discovery()
|
108
|
+
|
109
|
+
# Connect to a database by name
|
110
|
+
db = await NotionDatabase.from_database_name("Projects")
|
111
|
+
|
112
|
+
# Create a new page in the database
|
113
|
+
page = await db.create_blank_page()
|
114
|
+
|
115
|
+
# Set properties
|
116
|
+
await page.set_property_value_by_name("Status", "In Progress")
|
117
|
+
await page.set_property_value_by_name("Priority", "High")
|
118
|
+
|
119
|
+
# Query pages from database
|
120
|
+
async for page in db.iter_pages():
|
121
|
+
title = await page.get_title()
|
122
|
+
print(f"Page: {title}")
|
123
|
+
|
124
|
+
if __name__ == "__main__":
|
125
|
+
asyncio.run(main())
|
126
|
+
```
|
127
|
+
|
128
|
+
## Custom Markdown Syntax
|
129
|
+
|
130
|
+
Notionary extends standard Markdown with special syntax to support Notion-specific features:
|
131
|
+
|
132
|
+
### Text Formatting
|
133
|
+
|
134
|
+
- Standard: `**bold**`, `*italic*`, `~~strikethrough~~`, `` `code` ``
|
135
|
+
- Links: `[text](url)`
|
136
|
+
- Quotes: `> This is a quote`
|
137
|
+
- Divider: `---`
|
138
|
+
|
139
|
+
### Callouts
|
140
|
+
|
141
|
+
```markdown
|
142
|
+
!> [💡] This is a default callout with the light bulb emoji
|
143
|
+
!> [🔔] This is a notification with a bell emoji
|
144
|
+
!> [⚠️] Warning: This is an important note
|
145
|
+
```
|
146
|
+
|
147
|
+
### Toggles
|
148
|
+
|
149
|
+
```markdown
|
150
|
+
+++ How to use Notionary
|
151
|
+
| 1. Initialize with NotionPage
|
152
|
+
| 2. Update metadata with set_title(), set_emoji_icon(), etc.
|
153
|
+
| 3. Add content with replace_content() or append_markdown()
|
154
|
+
```
|
155
|
+
|
156
|
+
### Code Blocks
|
157
|
+
|
158
|
+
```python
|
159
|
+
def hello_world():
|
160
|
+
print("Hello from Notionary!")
|
161
|
+
```
|
162
|
+
|
163
|
+
### To-do Lists
|
164
|
+
|
165
|
+
```markdown
|
166
|
+
- [ ] Define project scope
|
167
|
+
- [x] Create timeline
|
168
|
+
- [ ] Assign resources
|
169
|
+
```
|
170
|
+
|
171
|
+
### Tables
|
172
|
+
|
173
|
+
```markdown
|
174
|
+
| Feature | Status | Priority |
|
175
|
+
| --------------- | ----------- | -------- |
|
176
|
+
| API Integration | Complete | High |
|
177
|
+
| Documentation | In Progress | Medium |
|
178
|
+
```
|
179
|
+
|
180
|
+
### More Elements
|
181
|
+
|
182
|
+
```markdown
|
183
|
+

|
184
|
+
@[Caption](https://youtube.com/watch?v=...)
|
185
|
+
[bookmark](https://example.com "Title" "Description")
|
186
|
+
```
|
187
|
+
|
188
|
+
## Block Registry & Customization
|
189
|
+
|
190
|
+
```python
|
191
|
+
from notionary import NotionPage, BlockRegistryBuilder
|
192
|
+
|
193
|
+
# Create a custom registry with only the elements you need
|
194
|
+
custom_registry = (
|
195
|
+
BlockRegistryBuilder()
|
196
|
+
.with_headings()
|
197
|
+
.with_callouts()
|
198
|
+
.with_toggles()
|
199
|
+
.with_code()
|
200
|
+
.with_todos()
|
201
|
+
.with_paragraphs()
|
202
|
+
.build()
|
203
|
+
)
|
204
|
+
|
205
|
+
# Apply this registry to a page
|
206
|
+
page = NotionPage.from_url("https://www.notion.so/your-page-url")
|
207
|
+
page.block_registry = custom_registry
|
208
|
+
ark
|
209
|
+
# Replace content using only supported elements
|
210
|
+
await page.replace_content("# Custom heading with selected elements only")
|
211
|
+
```
|
212
|
+
|
213
|
+
## AI-Ready: Generate LLM Prompts
|
214
|
+
|
215
|
+
```python
|
216
|
+
from notionary import BlockRegistryBuilder
|
217
|
+
|
218
|
+
# Create a registry with all standard elements
|
219
|
+
registry = BlockRegistryBuilder.create_full_registry()
|
220
|
+
|
221
|
+
# Generate the LLM system prompt
|
222
|
+
llm_system_prompt = registry.get_notion_markdown_syntax_prompt()
|
223
|
+
print(llm_system_prompt)
|
224
|
+
```
|
225
|
+
|
226
|
+
## Examples
|
227
|
+
|
228
|
+
See the `examples/` folder for:
|
229
|
+
|
230
|
+
- [Database discovery and querying](examples/database_discovery_example.py)
|
231
|
+
- [Rich page creation with Markdown](examples/page_example.py)
|
232
|
+
- [Database management](examples/database_management_example.py)
|
233
|
+
- [Iterating through database entries](examples/database_iteration_example.py)
|
234
|
+
- [Temporary usage & debugging](examples/temp.py)
|
235
|
+
|
236
|
+
## Perfect for AI Agents and Automation
|
237
|
+
|
238
|
+
- **LLM Integration**: Generate Notion-compatible content with any LLM using the system prompt generator
|
239
|
+
- **Dynamic Content Generation**: AI agents can generate content in Markdown and render it directly as Notion pages
|
240
|
+
- **Schema-Aware Operations**: Automatically validate and format properties based on database schemas
|
241
|
+
- **Simplified API**: Clean, intuitive interface for both human developers and AI systems
|
242
|
+
|
243
|
+
## Contributing
|
244
|
+
|
245
|
+
Contributions welcome — feel free to submit a pull request!
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# Notionary 📝
|
2
|
+
|
3
|
+
[](https://www.python.org/downloads/)
|
4
|
+
[](LICENSE)
|
5
|
+
|
6
|
+
**Notionary** is a powerful Python library for interacting with the Notion API, making it easy to create, update, and manage Notion pages and databases programmatically with a clean, intuitive interface. It's specifically designed to be the foundation for AI-driven Notion content generation.
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
## Features
|
11
|
+
|
12
|
+
- **Rich Markdown Support**: Create Notion pages using intuitive Markdown syntax with custom extensions
|
13
|
+
- **Dynamic Database Operations**: Create, update, and query database entries with schema auto-detection
|
14
|
+
- **Extensible Block Registry**: Add, customize, or remove Notion block elements with a flexible registry pattern
|
15
|
+
- **LLM-Ready Prompts**: Generate system prompts explaining Markdown syntax for LLMs to create Notion content
|
16
|
+
- **Async-First Design**: Built for modern Python with full async/await support
|
17
|
+
- **Schema-Based Validation**: Automatic property validation based on database schemas
|
18
|
+
- **Intelligent Content Conversion**: Bidirectional conversion between Markdown and Notion blocks
|
19
|
+
|
20
|
+
---
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
```bash
|
25
|
+
pip install notionary
|
26
|
+
```
|
27
|
+
|
28
|
+
---
|
29
|
+
|
30
|
+
## Quick Start
|
31
|
+
|
32
|
+
### Creating and Managing Pages
|
33
|
+
|
34
|
+
```python
|
35
|
+
import asyncio
|
36
|
+
from notionary import NotionPage
|
37
|
+
|
38
|
+
async def main():
|
39
|
+
# Create a page from URL
|
40
|
+
page = NotionPage.from_url("https://www.notion.so/your-page-url")
|
41
|
+
|
42
|
+
# Or find by name
|
43
|
+
page = await NotionPage.from_page_name("My Project Page")
|
44
|
+
|
45
|
+
# Update page metadata
|
46
|
+
await page.set_title("Updated Title")
|
47
|
+
await page.set_emoji_icon("🚀")
|
48
|
+
await page.set_random_gradient_cover()
|
49
|
+
|
50
|
+
# Add markdown content
|
51
|
+
markdown = """
|
52
|
+
# Project Overview
|
53
|
+
|
54
|
+
!> [💡] This page was created programmatically using Notionary.
|
55
|
+
|
56
|
+
## Features
|
57
|
+
- **Rich** Markdown support
|
58
|
+
- Async functionality
|
59
|
+
- Custom syntax extensions
|
60
|
+
|
61
|
+
+++ Implementation Details
|
62
|
+
| Notionary uses a custom converter to transform Markdown into Notion blocks.
|
63
|
+
| This makes it easy to create rich content programmatically.
|
64
|
+
"""
|
65
|
+
|
66
|
+
await page.replace_content(markdown)
|
67
|
+
|
68
|
+
if __name__ == "__main__":
|
69
|
+
asyncio.run(main())
|
70
|
+
```
|
71
|
+
|
72
|
+
### Working with Databases
|
73
|
+
|
74
|
+
```python
|
75
|
+
import asyncio
|
76
|
+
from notionary import NotionDatabase, DatabaseDiscovery
|
77
|
+
|
78
|
+
async def main():
|
79
|
+
# Discover available databases
|
80
|
+
discovery = DatabaseDiscovery()
|
81
|
+
await discovery()
|
82
|
+
|
83
|
+
# Connect to a database by name
|
84
|
+
db = await NotionDatabase.from_database_name("Projects")
|
85
|
+
|
86
|
+
# Create a new page in the database
|
87
|
+
page = await db.create_blank_page()
|
88
|
+
|
89
|
+
# Set properties
|
90
|
+
await page.set_property_value_by_name("Status", "In Progress")
|
91
|
+
await page.set_property_value_by_name("Priority", "High")
|
92
|
+
|
93
|
+
# Query pages from database
|
94
|
+
async for page in db.iter_pages():
|
95
|
+
title = await page.get_title()
|
96
|
+
print(f"Page: {title}")
|
97
|
+
|
98
|
+
if __name__ == "__main__":
|
99
|
+
asyncio.run(main())
|
100
|
+
```
|
101
|
+
|
102
|
+
## Custom Markdown Syntax
|
103
|
+
|
104
|
+
Notionary extends standard Markdown with special syntax to support Notion-specific features:
|
105
|
+
|
106
|
+
### Text Formatting
|
107
|
+
|
108
|
+
- Standard: `**bold**`, `*italic*`, `~~strikethrough~~`, `` `code` ``
|
109
|
+
- Links: `[text](url)`
|
110
|
+
- Quotes: `> This is a quote`
|
111
|
+
- Divider: `---`
|
112
|
+
|
113
|
+
### Callouts
|
114
|
+
|
115
|
+
```markdown
|
116
|
+
!> [💡] This is a default callout with the light bulb emoji
|
117
|
+
!> [🔔] This is a notification with a bell emoji
|
118
|
+
!> [⚠️] Warning: This is an important note
|
119
|
+
```
|
120
|
+
|
121
|
+
### Toggles
|
122
|
+
|
123
|
+
```markdown
|
124
|
+
+++ How to use Notionary
|
125
|
+
| 1. Initialize with NotionPage
|
126
|
+
| 2. Update metadata with set_title(), set_emoji_icon(), etc.
|
127
|
+
| 3. Add content with replace_content() or append_markdown()
|
128
|
+
```
|
129
|
+
|
130
|
+
### Code Blocks
|
131
|
+
|
132
|
+
```python
|
133
|
+
def hello_world():
|
134
|
+
print("Hello from Notionary!")
|
135
|
+
```
|
136
|
+
|
137
|
+
### To-do Lists
|
138
|
+
|
139
|
+
```markdown
|
140
|
+
- [ ] Define project scope
|
141
|
+
- [x] Create timeline
|
142
|
+
- [ ] Assign resources
|
143
|
+
```
|
144
|
+
|
145
|
+
### Tables
|
146
|
+
|
147
|
+
```markdown
|
148
|
+
| Feature | Status | Priority |
|
149
|
+
| --------------- | ----------- | -------- |
|
150
|
+
| API Integration | Complete | High |
|
151
|
+
| Documentation | In Progress | Medium |
|
152
|
+
```
|
153
|
+
|
154
|
+
### More Elements
|
155
|
+
|
156
|
+
```markdown
|
157
|
+

|
158
|
+
@[Caption](https://youtube.com/watch?v=...)
|
159
|
+
[bookmark](https://example.com "Title" "Description")
|
160
|
+
```
|
161
|
+
|
162
|
+
## Block Registry & Customization
|
163
|
+
|
164
|
+
```python
|
165
|
+
from notionary import NotionPage, BlockRegistryBuilder
|
166
|
+
|
167
|
+
# Create a custom registry with only the elements you need
|
168
|
+
custom_registry = (
|
169
|
+
BlockRegistryBuilder()
|
170
|
+
.with_headings()
|
171
|
+
.with_callouts()
|
172
|
+
.with_toggles()
|
173
|
+
.with_code()
|
174
|
+
.with_todos()
|
175
|
+
.with_paragraphs()
|
176
|
+
.build()
|
177
|
+
)
|
178
|
+
|
179
|
+
# Apply this registry to a page
|
180
|
+
page = NotionPage.from_url("https://www.notion.so/your-page-url")
|
181
|
+
page.block_registry = custom_registry
|
182
|
+
ark
|
183
|
+
# Replace content using only supported elements
|
184
|
+
await page.replace_content("# Custom heading with selected elements only")
|
185
|
+
```
|
186
|
+
|
187
|
+
## AI-Ready: Generate LLM Prompts
|
188
|
+
|
189
|
+
```python
|
190
|
+
from notionary import BlockRegistryBuilder
|
191
|
+
|
192
|
+
# Create a registry with all standard elements
|
193
|
+
registry = BlockRegistryBuilder.create_full_registry()
|
194
|
+
|
195
|
+
# Generate the LLM system prompt
|
196
|
+
llm_system_prompt = registry.get_notion_markdown_syntax_prompt()
|
197
|
+
print(llm_system_prompt)
|
198
|
+
```
|
199
|
+
|
200
|
+
## Examples
|
201
|
+
|
202
|
+
See the `examples/` folder for:
|
203
|
+
|
204
|
+
- [Database discovery and querying](examples/database_discovery_example.py)
|
205
|
+
- [Rich page creation with Markdown](examples/page_example.py)
|
206
|
+
- [Database management](examples/database_management_example.py)
|
207
|
+
- [Iterating through database entries](examples/database_iteration_example.py)
|
208
|
+
- [Temporary usage & debugging](examples/temp.py)
|
209
|
+
|
210
|
+
## Perfect for AI Agents and Automation
|
211
|
+
|
212
|
+
- **LLM Integration**: Generate Notion-compatible content with any LLM using the system prompt generator
|
213
|
+
- **Dynamic Content Generation**: AI agents can generate content in Markdown and render it directly as Notion pages
|
214
|
+
- **Schema-Aware Operations**: Automatically validate and format properties based on database schemas
|
215
|
+
- **Simplified API**: Clean, intuitive interface for both human developers and AI systems
|
216
|
+
|
217
|
+
## Contributing
|
218
|
+
|
219
|
+
Contributions welcome — feel free to submit a pull request!
|
@@ -26,29 +26,7 @@ class DatabaseDiscovery(LoggingMixin):
|
|
26
26
|
self._client = client if client else NotionClient()
|
27
27
|
self.logger.info("DatabaseDiscovery initialized")
|
28
28
|
|
29
|
-
async def
|
30
|
-
"""
|
31
|
-
Discover all accessible databases and return their titles and IDs.
|
32
|
-
|
33
|
-
Args:
|
34
|
-
page_size: The number of databases to fetch per request
|
35
|
-
|
36
|
-
Returns:
|
37
|
-
List of tuples containing (database_title, database_id)
|
38
|
-
"""
|
39
|
-
databases = []
|
40
|
-
|
41
|
-
async for database in self._iter_databases(page_size):
|
42
|
-
db_id = database.get("id")
|
43
|
-
if not db_id:
|
44
|
-
continue
|
45
|
-
|
46
|
-
title = self._extract_database_title(database)
|
47
|
-
databases.append((title, db_id))
|
48
|
-
|
49
|
-
return databases
|
50
|
-
|
51
|
-
async def discover_and_print(self, page_size: int = 100) -> List[Tuple[str, str]]:
|
29
|
+
async def __call__(self, page_size: int = 100) -> List[Tuple[str, str]]:
|
52
30
|
"""
|
53
31
|
Discover databases and print the results in a nicely formatted way.
|
54
32
|
|
@@ -61,7 +39,7 @@ class DatabaseDiscovery(LoggingMixin):
|
|
61
39
|
Returns:
|
62
40
|
The same list of databases as discover() for further processing
|
63
41
|
"""
|
64
|
-
databases = await self.
|
42
|
+
databases = await self._discover(page_size)
|
65
43
|
|
66
44
|
if not databases:
|
67
45
|
print("\n⚠️ No databases found!")
|
@@ -78,6 +56,28 @@ class DatabaseDiscovery(LoggingMixin):
|
|
78
56
|
|
79
57
|
return databases
|
80
58
|
|
59
|
+
async def _discover(self, page_size: int = 100) -> List[Tuple[str, str]]:
|
60
|
+
"""
|
61
|
+
Discover all accessible databases and return their titles and IDs.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
page_size: The number of databases to fetch per request
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
List of tuples containing (database_title, database_id)
|
68
|
+
"""
|
69
|
+
databases = []
|
70
|
+
|
71
|
+
async for database in self._iter_databases(page_size):
|
72
|
+
db_id = database.get("id")
|
73
|
+
if not db_id:
|
74
|
+
continue
|
75
|
+
|
76
|
+
title = self._extract_database_title(database)
|
77
|
+
databases.append((title, db_id))
|
78
|
+
|
79
|
+
return databases
|
80
|
+
|
81
81
|
async def _iter_databases(
|
82
82
|
self, page_size: int = 100
|
83
83
|
) -> AsyncGenerator[Dict[str, Any], None]:
|
@@ -15,13 +15,17 @@ class CodeBlockElement(NotionBlockElement):
|
|
15
15
|
```language
|
16
16
|
code content
|
17
17
|
```
|
18
|
+
Caption: optional caption text
|
18
19
|
|
19
20
|
Where:
|
20
21
|
- language is optional and specifies the programming language
|
21
22
|
- code content is the code to be displayed
|
23
|
+
- Caption line is optional and must appear immediately after the closing ```
|
22
24
|
"""
|
23
25
|
|
24
|
-
PATTERN = re.compile(
|
26
|
+
PATTERN = re.compile(
|
27
|
+
r"```(\w*)\n([\s\S]+?)```(?:\n(?:Caption|caption):\s*(.+))?", re.MULTILINE
|
28
|
+
)
|
25
29
|
|
26
30
|
@classmethod
|
27
31
|
def match_markdown(cls, text: str) -> bool:
|
@@ -42,25 +46,18 @@ class CodeBlockElement(NotionBlockElement):
|
|
42
46
|
|
43
47
|
language = match.group(1) or "plain text"
|
44
48
|
content = match.group(2)
|
49
|
+
caption = match.group(3)
|
45
50
|
|
46
51
|
if content.endswith("\n"):
|
47
52
|
content = content[:-1]
|
48
53
|
|
49
|
-
|
54
|
+
block = {
|
50
55
|
"type": "code",
|
51
56
|
"code": {
|
52
57
|
"rich_text": [
|
53
58
|
{
|
54
59
|
"type": "text",
|
55
60
|
"text": {"content": content},
|
56
|
-
"annotations": {
|
57
|
-
"bold": False,
|
58
|
-
"italic": False,
|
59
|
-
"strikethrough": False,
|
60
|
-
"underline": False,
|
61
|
-
"code": False,
|
62
|
-
"color": "default",
|
63
|
-
},
|
64
61
|
"plain_text": content,
|
65
62
|
}
|
66
63
|
],
|
@@ -68,6 +65,18 @@ class CodeBlockElement(NotionBlockElement):
|
|
68
65
|
},
|
69
66
|
}
|
70
67
|
|
68
|
+
# Add caption if provided
|
69
|
+
if caption and caption.strip():
|
70
|
+
block["code"]["caption"] = [
|
71
|
+
{
|
72
|
+
"type": "text",
|
73
|
+
"text": {"content": caption.strip()},
|
74
|
+
"plain_text": caption.strip(),
|
75
|
+
}
|
76
|
+
]
|
77
|
+
|
78
|
+
return block
|
79
|
+
|
71
80
|
@classmethod
|
72
81
|
def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
|
73
82
|
"""Convert Notion code block to markdown code block."""
|
@@ -84,8 +93,20 @@ class CodeBlockElement(NotionBlockElement):
|
|
84
93
|
|
85
94
|
language = code_data.get("language", "")
|
86
95
|
|
96
|
+
# Extract caption if present
|
97
|
+
caption_text = ""
|
98
|
+
caption_data = code_data.get("caption", [])
|
99
|
+
for caption_block in caption_data:
|
100
|
+
caption_text += caption_block.get("plain_text", "")
|
101
|
+
|
87
102
|
# Format as a markdown code block
|
88
|
-
|
103
|
+
result = f"```{language}\n{content}\n```"
|
104
|
+
|
105
|
+
# Add caption if present
|
106
|
+
if caption_text.strip():
|
107
|
+
result += f"\nCaption: {caption_text}"
|
108
|
+
|
109
|
+
return result
|
89
110
|
|
90
111
|
@classmethod
|
91
112
|
def find_matches(cls, text: str) -> List[Tuple[int, int, Dict[str, Any]]]:
|
@@ -102,6 +123,7 @@ class CodeBlockElement(NotionBlockElement):
|
|
102
123
|
for match in CodeBlockElement.PATTERN.finditer(text):
|
103
124
|
language = match.group(1) or "plain text"
|
104
125
|
content = match.group(2)
|
126
|
+
caption = match.group(3)
|
105
127
|
|
106
128
|
# Remove trailing newline if present
|
107
129
|
if content.endswith("\n"):
|
@@ -121,6 +143,16 @@ class CodeBlockElement(NotionBlockElement):
|
|
121
143
|
},
|
122
144
|
}
|
123
145
|
|
146
|
+
# Add caption if provided
|
147
|
+
if caption and caption.strip():
|
148
|
+
block["code"]["caption"] = [
|
149
|
+
{
|
150
|
+
"type": "text",
|
151
|
+
"text": {"content": caption.strip()},
|
152
|
+
"plain_text": caption.strip(),
|
153
|
+
}
|
154
|
+
]
|
155
|
+
|
124
156
|
matches.append((match.start(), match.end(), block))
|
125
157
|
|
126
158
|
return matches
|
@@ -139,25 +171,34 @@ class CodeBlockElement(NotionBlockElement):
|
|
139
171
|
.with_description(
|
140
172
|
"Use fenced code blocks to format content as code. Supports language annotations like "
|
141
173
|
"'python', 'json', or 'mermaid'. Useful for displaying code, configurations, command-line "
|
142
|
-
"examples, or diagram syntax. Also suitable for explaining or visualizing systems with diagram languages."
|
174
|
+
"examples, or diagram syntax. Also suitable for explaining or visualizing systems with diagram languages. "
|
175
|
+
"Code blocks can include optional captions for better documentation."
|
143
176
|
)
|
144
177
|
.with_usage_guidelines(
|
145
178
|
"Use code blocks when you want to present technical content like code snippets, terminal commands, "
|
146
|
-
"JSON structures, or system diagrams. Especially helpful when structure and formatting are essential."
|
179
|
+
"JSON structures, or system diagrams. Especially helpful when structure and formatting are essential. "
|
180
|
+
"Add captions to provide context, explanations, or titles for your code blocks."
|
181
|
+
)
|
182
|
+
.with_syntax(
|
183
|
+
"```language\ncode content\n```\nCaption: optional caption text\n\n"
|
184
|
+
"OR\n\n"
|
185
|
+
"```language\ncode content\n```"
|
147
186
|
)
|
148
|
-
.with_syntax("```language\ncode content\n```")
|
149
187
|
.with_examples(
|
150
188
|
[
|
151
|
-
"```python\nprint('Hello, world!')\n
|
152
|
-
'```json\n{"name": "Alice", "age": 30}\n
|
153
|
-
"```mermaid\nflowchart TD\n A --> B\n
|
189
|
+
"```python\nprint('Hello, world!')\n```\nCaption: Basic Python greeting example",
|
190
|
+
'```json\n{"name": "Alice", "age": 30}\n```\nCaption: User data structure',
|
191
|
+
"```mermaid\nflowchart TD\n A --> B\n```\nCaption: Simple flow diagram",
|
192
|
+
'```bash\ngit commit -m "Initial commit"\n```', # Without caption
|
154
193
|
]
|
155
194
|
)
|
156
195
|
.with_avoidance_guidelines(
|
157
196
|
"NEVER EVER wrap markdown content with ```markdown. Markdown should be written directly without code block formatting. "
|
158
197
|
"NEVER use ```markdown under any circumstances. "
|
159
198
|
"For Mermaid diagrams, use ONLY the default styling without colors, backgrounds, or custom styling attributes. "
|
160
|
-
"Keep Mermaid diagrams simple and minimal without any styling or color modifications."
|
199
|
+
"Keep Mermaid diagrams simple and minimal without any styling or color modifications. "
|
200
|
+
"Captions must appear immediately after the closing ``` on a new line starting with 'Caption:' - "
|
201
|
+
"no empty lines between the code block and the caption."
|
161
202
|
)
|
162
203
|
.build()
|
163
204
|
)
|