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.
Files changed (69) hide show
  1. notionary-0.2.7/PKG-INFO +245 -0
  2. notionary-0.2.7/README.md +219 -0
  3. {notionary-0.2.6 → notionary-0.2.7}/notionary/database/database_discovery.py +24 -24
  4. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/code_block_element.py +59 -18
  5. notionary-0.2.7/notionary/elements/column_element.py +204 -0
  6. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/divider_element.py +2 -2
  7. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/registry/block_registry.py +1 -1
  8. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/registry/block_registry_builder.py +7 -0
  9. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/content/page_content_writer.py +12 -2
  10. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/markdown_to_notion_converter.py +45 -14
  11. {notionary-0.2.6 → notionary-0.2.7}/notionary/prompting/markdown_syntax_prompt_generator.py +35 -29
  12. notionary-0.2.7/notionary.egg-info/PKG-INFO +245 -0
  13. {notionary-0.2.6 → notionary-0.2.7}/notionary.egg-info/SOURCES.txt +1 -0
  14. {notionary-0.2.6 → notionary-0.2.7}/setup.py +1 -1
  15. notionary-0.2.6/PKG-INFO +0 -256
  16. notionary-0.2.6/README.md +0 -230
  17. notionary-0.2.6/notionary.egg-info/PKG-INFO +0 -256
  18. {notionary-0.2.6 → notionary-0.2.7}/LICENSE +0 -0
  19. {notionary-0.2.6 → notionary-0.2.7}/notionary/__init__.py +0 -0
  20. {notionary-0.2.6 → notionary-0.2.7}/notionary/database/models/page_result.py +0 -0
  21. {notionary-0.2.6 → notionary-0.2.7}/notionary/database/notion_database.py +0 -0
  22. {notionary-0.2.6 → notionary-0.2.7}/notionary/database/notion_database_factory.py +0 -0
  23. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/audio_element.py +0 -0
  24. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/bookmark_element.py +0 -0
  25. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/bulleted_list_element.py +0 -0
  26. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/callout_element.py +0 -0
  27. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/embed_element.py +0 -0
  28. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/heading_element.py +0 -0
  29. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/image_element.py +0 -0
  30. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/mention_element.py +0 -0
  31. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/notion_block_element.py +0 -0
  32. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/numbered_list_element.py +0 -0
  33. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/paragraph_element.py +0 -0
  34. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/qoute_element.py +0 -0
  35. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/table_element.py +0 -0
  36. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/text_inline_formatter.py +0 -0
  37. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/todo_element.py +0 -0
  38. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/toggle_element.py +0 -0
  39. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/toggleable_heading_element.py +0 -0
  40. {notionary-0.2.6 → notionary-0.2.7}/notionary/elements/video_element.py +0 -0
  41. {notionary-0.2.6 → notionary-0.2.7}/notionary/exceptions/database_exceptions.py +0 -0
  42. {notionary-0.2.6 → notionary-0.2.7}/notionary/exceptions/page_creation_exception.py +0 -0
  43. {notionary-0.2.6 → notionary-0.2.7}/notionary/models/notion_block_response.py +0 -0
  44. {notionary-0.2.6 → notionary-0.2.7}/notionary/models/notion_database_response.py +0 -0
  45. {notionary-0.2.6 → notionary-0.2.7}/notionary/models/notion_page_response.py +0 -0
  46. {notionary-0.2.6 → notionary-0.2.7}/notionary/notion_client.py +0 -0
  47. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/content/notion_page_content_chunker.py +0 -0
  48. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/content/page_content_retriever.py +0 -0
  49. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/metadata/metadata_editor.py +0 -0
  50. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/metadata/notion_icon_manager.py +0 -0
  51. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/metadata/notion_page_cover_manager.py +0 -0
  52. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/notion_page.py +0 -0
  53. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/notion_page_factory.py +0 -0
  54. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/notion_to_markdown_converter.py +0 -0
  55. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/properites/database_property_service.py +0 -0
  56. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/properites/page_property_manager.py +0 -0
  57. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/properites/property_formatter.py +0 -0
  58. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/properites/property_value_extractor.py +0 -0
  59. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/relations/notion_page_relation_manager.py +0 -0
  60. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/relations/notion_page_title_resolver.py +0 -0
  61. {notionary-0.2.6 → notionary-0.2.7}/notionary/page/relations/page_database_relation.py +0 -0
  62. {notionary-0.2.6 → notionary-0.2.7}/notionary/prompting/element_prompt_content.py +0 -0
  63. {notionary-0.2.6 → notionary-0.2.7}/notionary/util/logging_mixin.py +0 -0
  64. {notionary-0.2.6 → notionary-0.2.7}/notionary/util/page_id_utils.py +0 -0
  65. {notionary-0.2.6 → notionary-0.2.7}/notionary/util/warn_direct_constructor_usage.py +0 -0
  66. {notionary-0.2.6 → notionary-0.2.7}/notionary.egg-info/dependency_links.txt +0 -0
  67. {notionary-0.2.6 → notionary-0.2.7}/notionary.egg-info/requires.txt +0 -0
  68. {notionary-0.2.6 → notionary-0.2.7}/notionary.egg-info/top_level.txt +0 -0
  69. {notionary-0.2.6 → notionary-0.2.7}/setup.cfg +0 -0
@@ -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
+ [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/downloads/)
30
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](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
+ ![Caption](https://example.com/image.jpg)
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
+ [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/downloads/)
4
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](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
+ ![Caption](https://example.com/image.jpg)
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 discover(self, page_size: int = 100) -> List[Tuple[str, str]]:
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.discover(page_size)
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(r"```(\w*)\n([\s\S]+?)```", re.MULTILINE)
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
- return {
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
- return f"```{language}\n{content}\n```"
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
  )