notionary 0.2.23__py3-none-any.whl → 0.2.25__py3-none-any.whl
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/__init__.py +1 -1
- notionary/blocks/__init__.py +3 -1
- notionary/blocks/audio/__init__.py +0 -2
- notionary/blocks/audio/audio_element.py +92 -49
- notionary/blocks/audio/audio_markdown_node.py +4 -17
- notionary/blocks/bookmark/__init__.py +0 -2
- notionary/blocks/bookmark/bookmark_markdown_node.py +5 -21
- notionary/blocks/breadcrumbs/__init__.py +0 -2
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +2 -21
- notionary/blocks/bulleted_list/__init__.py +0 -2
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +3 -17
- notionary/blocks/bulleted_list/bulleted_list_models.py +0 -1
- notionary/blocks/callout/__init__.py +0 -2
- notionary/blocks/callout/callout_markdown_node.py +4 -18
- notionary/blocks/callout/callout_models.py +3 -4
- notionary/blocks/code/code_markdown_node.py +5 -19
- notionary/blocks/column/__init__.py +0 -4
- notionary/blocks/column/column_list_markdown_node.py +3 -19
- notionary/blocks/column/column_markdown_node.py +4 -21
- notionary/blocks/divider/__init__.py +0 -2
- notionary/blocks/divider/divider_markdown_node.py +2 -16
- notionary/blocks/embed/__init__.py +0 -2
- notionary/blocks/embed/embed_markdown_node.py +4 -17
- notionary/blocks/equation/__init__.py +0 -1
- notionary/blocks/equation/equation_element_markdown_node.py +3 -15
- notionary/blocks/file/__init__.py +0 -2
- notionary/blocks/file/file_element.py +67 -46
- notionary/blocks/file/file_element_markdown_node.py +4 -17
- notionary/blocks/heading/__init__.py +0 -2
- notionary/blocks/heading/heading_markdown_node.py +5 -19
- notionary/blocks/heading/heading_models.py +3 -3
- notionary/blocks/image_block/__init__.py +0 -2
- notionary/blocks/image_block/image_element.py +66 -25
- notionary/blocks/image_block/image_markdown_node.py +5 -20
- notionary/{markdown → blocks/markdown}/markdown_builder.py +29 -233
- notionary/blocks/markdown/markdown_node.py +25 -0
- notionary/blocks/mixins/file_upload/__init__.py +3 -0
- notionary/blocks/mixins/file_upload/file_upload_mixin.py +320 -0
- notionary/blocks/numbered_list/__init__.py +0 -1
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -17
- notionary/blocks/numbered_list/numbered_list_models.py +3 -3
- notionary/blocks/paragraph/__init__.py +0 -2
- notionary/blocks/paragraph/paragraph_markdown_node.py +3 -13
- notionary/blocks/pdf/__init__.py +0 -2
- notionary/blocks/pdf/pdf_element.py +81 -32
- notionary/blocks/pdf/pdf_markdown_node.py +5 -18
- notionary/blocks/quote/__init__.py +0 -2
- notionary/blocks/quote/quote_markdown_node.py +3 -13
- notionary/blocks/registry/__init__.py +1 -2
- notionary/blocks/registry/block_registry.py +116 -61
- notionary/blocks/table/__init__.py +0 -2
- notionary/blocks/table/table_markdown_node.py +17 -16
- notionary/blocks/table_of_contents/__init__.py +0 -2
- notionary/blocks/table_of_contents/table_of_contents_element.py +27 -15
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +3 -17
- notionary/blocks/table_of_contents/table_of_contents_models.py +2 -2
- notionary/blocks/todo/__init__.py +0 -2
- notionary/blocks/todo/todo_markdown_node.py +9 -20
- notionary/blocks/todo/todo_models.py +2 -3
- notionary/blocks/toggle/__init__.py +0 -2
- notionary/blocks/toggle/toggle_markdown_node.py +5 -19
- notionary/blocks/toggleable_heading/__init__.py +0 -2
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +6 -23
- notionary/blocks/video/__init__.py +0 -2
- notionary/blocks/video/video_element.py +110 -34
- notionary/blocks/video/video_markdown_node.py +4 -15
- notionary/comments/client.py +1 -1
- notionary/file_upload/client.py +3 -2
- notionary/file_upload/models.py +10 -1
- notionary/file_upload/notion_file_upload.py +5 -5
- notionary/page/markdown_whitespace_processor.py +129 -0
- notionary/page/notion_page.py +35 -40
- notionary/page/page_content_deleting_service.py +1 -1
- notionary/page/page_content_writer.py +32 -129
- notionary/page/page_context.py +0 -5
- notionary/page/reader/handler/column_list_renderer.py +2 -2
- notionary/page/reader/handler/column_renderer.py +2 -2
- notionary/page/reader/handler/line_renderer.py +2 -2
- notionary/page/reader/handler/toggle_renderer.py +2 -2
- notionary/page/reader/handler/toggleable_heading_renderer.py +2 -2
- notionary/page/writer/handler/equation_handler.py +1 -1
- notionary/page/writer/handler/toggle_handler.py +8 -4
- notionary/page/writer/handler/toggleable_heading_handler.py +3 -2
- notionary/page/writer/markdown_to_notion_converter.py +74 -30
- notionary/schemas/__init__.py +3 -0
- notionary/schemas/base.py +73 -0
- notionary/shared/__init__.py +1 -3
- notionary-0.2.25.dist-info/METADATA +270 -0
- {notionary-0.2.23.dist-info → notionary-0.2.25.dist-info}/RECORD +92 -94
- notionary/blocks/guards.py +0 -22
- notionary/blocks/registry/block_registry_builder.py +0 -264
- notionary/markdown/makdown_document_model.py +0 -0
- notionary/markdown/markdown_document_model.py +0 -228
- notionary/markdown/markdown_node.py +0 -30
- notionary/models/notion_database_response.py +0 -0
- notionary/page/writer/markdown_to_notion_formatting_post_processor.py +0 -73
- notionary/page/writer/markdown_to_notion_post_processor.py +0 -0
- notionary-0.2.23.dist-info/METADATA +0 -235
- /notionary/{markdown/___init__.py → blocks/markdown/markdown_document_model.py} +0 -0
- {notionary-0.2.23.dist-info → notionary-0.2.25.dist-info}/LICENSE +0 -0
- {notionary-0.2.23.dist-info → notionary-0.2.25.dist-info}/WHEEL +0 -0
|
@@ -19,8 +19,9 @@ class ToggleableHeadingHandler(LineHandler):
|
|
|
19
19
|
|
|
20
20
|
def __init__(self):
|
|
21
21
|
super().__init__()
|
|
22
|
+
# Updated: Support both "+++# title" and "+++#title"
|
|
22
23
|
self._start_pattern = re.compile(
|
|
23
|
-
r"^[+]{3}(?P<level>#{1,3})\s
|
|
24
|
+
r"^[+]{3}\s*(?P<level>#{1,3})\s*(.+)$", re.IGNORECASE
|
|
24
25
|
)
|
|
25
26
|
# +++
|
|
26
27
|
self._end_pattern = re.compile(r"^[+]{3}\s*$")
|
|
@@ -49,7 +50,7 @@ class ToggleableHeadingHandler(LineHandler):
|
|
|
49
50
|
return await _handle(self._add_toggleable_heading_content)
|
|
50
51
|
|
|
51
52
|
def _is_toggleable_heading_start(self, context: LineProcessingContext) -> bool:
|
|
52
|
-
"""Check if line starts a toggleable heading (+++# "Title")."""
|
|
53
|
+
"""Check if line starts a toggleable heading (+++# "Title" or +++#"Title")."""
|
|
53
54
|
return self._start_pattern.match(context.line.strip()) is not None
|
|
54
55
|
|
|
55
56
|
def _is_toggleable_heading_end(self, context: LineProcessingContext) -> bool:
|
|
@@ -12,54 +12,32 @@ from notionary.page.writer.handler import (
|
|
|
12
12
|
ToggleableHeadingHandler,
|
|
13
13
|
ToggleHandler,
|
|
14
14
|
)
|
|
15
|
-
from notionary.page.writer.markdown_to_notion_formatting_post_processor import (
|
|
16
|
-
MarkdownToNotionFormattingPostProcessor,
|
|
17
|
-
)
|
|
18
15
|
from notionary.page.writer.notion_text_length_processor import (
|
|
19
16
|
NotionTextLengthProcessor,
|
|
20
17
|
)
|
|
18
|
+
from notionary.util.logging_mixin import LoggingMixin
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HandlerOrderValidationError(RuntimeError):
|
|
22
|
+
"""Raised when handler chain order is incorrect."""
|
|
21
23
|
|
|
24
|
+
pass
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
|
|
27
|
+
class MarkdownToNotionConverter(LoggingMixin):
|
|
24
28
|
"""Converts Markdown text to Notion API block format with unified stack-based processing."""
|
|
25
29
|
|
|
26
30
|
def __init__(self, block_registry: BlockRegistry) -> None:
|
|
27
31
|
self._block_registry = block_registry
|
|
28
|
-
self._formatting_post_processor = MarkdownToNotionFormattingPostProcessor()
|
|
29
32
|
self._text_length_post_processor = NotionTextLengthProcessor()
|
|
30
|
-
|
|
31
33
|
self._setup_handler_chain()
|
|
32
34
|
|
|
33
|
-
def _setup_handler_chain(self) -> None:
|
|
34
|
-
code_handler = CodeHandler()
|
|
35
|
-
equation_handler = EquationHandler()
|
|
36
|
-
table_handler = TableHandler()
|
|
37
|
-
column_list_handler = ColumnListHandler()
|
|
38
|
-
column_handler = ColumnHandler()
|
|
39
|
-
toggle_handler = ToggleHandler()
|
|
40
|
-
toggleable_heading_handler = ToggleableHeadingHandler()
|
|
41
|
-
regular_handler = RegularLineHandler()
|
|
42
|
-
|
|
43
|
-
# register more specific elements first
|
|
44
|
-
code_handler.set_next(equation_handler).set_next(table_handler).set_next(
|
|
45
|
-
column_list_handler
|
|
46
|
-
).set_next(column_handler).set_next(toggleable_heading_handler).set_next(
|
|
47
|
-
toggle_handler
|
|
48
|
-
).set_next(
|
|
49
|
-
regular_handler
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
self._handler_chain = code_handler
|
|
53
|
-
|
|
54
35
|
async def convert(self, markdown_text: str) -> list[BlockCreateRequest]:
|
|
55
36
|
if not markdown_text.strip():
|
|
56
37
|
return []
|
|
57
38
|
|
|
58
39
|
all_blocks = await self.process_lines(markdown_text)
|
|
59
40
|
|
|
60
|
-
# Apply formatting post-processing (empty paragraphs)
|
|
61
|
-
all_blocks = self._formatting_post_processor.process(all_blocks)
|
|
62
|
-
|
|
63
41
|
# Apply text length post-processing (truncation)
|
|
64
42
|
all_blocks = self._text_length_post_processor.process(all_blocks)
|
|
65
43
|
|
|
@@ -93,3 +71,69 @@ class MarkdownToNotionConverter:
|
|
|
93
71
|
continue
|
|
94
72
|
|
|
95
73
|
return result_blocks
|
|
74
|
+
|
|
75
|
+
def _setup_handler_chain(self) -> None:
|
|
76
|
+
code_handler = CodeHandler()
|
|
77
|
+
equation_handler = EquationHandler()
|
|
78
|
+
table_handler = TableHandler()
|
|
79
|
+
column_list_handler = ColumnListHandler()
|
|
80
|
+
column_handler = ColumnHandler()
|
|
81
|
+
toggle_handler = ToggleHandler()
|
|
82
|
+
toggleable_heading_handler = ToggleableHeadingHandler()
|
|
83
|
+
regular_handler = RegularLineHandler()
|
|
84
|
+
|
|
85
|
+
# Create handler chain
|
|
86
|
+
code_handler.set_next(equation_handler).set_next(table_handler).set_next(
|
|
87
|
+
column_handler
|
|
88
|
+
).set_next(column_list_handler).set_next(toggleable_heading_handler).set_next(
|
|
89
|
+
toggle_handler
|
|
90
|
+
).set_next(
|
|
91
|
+
regular_handler
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
self._handler_chain = code_handler
|
|
95
|
+
|
|
96
|
+
# Validate critical order - only log/error if something is wrong
|
|
97
|
+
self._validate_handler_order(
|
|
98
|
+
[
|
|
99
|
+
code_handler,
|
|
100
|
+
equation_handler,
|
|
101
|
+
table_handler,
|
|
102
|
+
column_handler,
|
|
103
|
+
column_list_handler,
|
|
104
|
+
toggleable_heading_handler,
|
|
105
|
+
toggle_handler,
|
|
106
|
+
regular_handler,
|
|
107
|
+
]
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def _validate_handler_order(self, handlers) -> None:
|
|
111
|
+
"""Validate critical handler positioning rules - only warns/errors when needed."""
|
|
112
|
+
handler_classes = [handler.__class__ for handler in handlers]
|
|
113
|
+
|
|
114
|
+
# Critical: ColumnHandler MUST come before ColumnListHandler
|
|
115
|
+
try:
|
|
116
|
+
column_handler_pos = handler_classes.index(ColumnHandler)
|
|
117
|
+
column_list_handler_pos = handler_classes.index(ColumnListHandler)
|
|
118
|
+
|
|
119
|
+
if column_handler_pos >= column_list_handler_pos:
|
|
120
|
+
error_msg = (
|
|
121
|
+
f"CRITICAL: ColumnHandler must come BEFORE ColumnListHandler. "
|
|
122
|
+
f"Current order: ColumnHandler at {column_handler_pos}, ColumnListHandler at {column_list_handler_pos}. "
|
|
123
|
+
f"Fix: Move ColumnHandler before ColumnListHandler in _setup_handler_chain()"
|
|
124
|
+
)
|
|
125
|
+
self.logger.error(error_msg)
|
|
126
|
+
raise HandlerOrderValidationError(error_msg)
|
|
127
|
+
|
|
128
|
+
except ValueError as e:
|
|
129
|
+
error_msg = f"Missing required handlers in chain: {e}"
|
|
130
|
+
self.logger.error(error_msg)
|
|
131
|
+
raise HandlerOrderValidationError(error_msg)
|
|
132
|
+
|
|
133
|
+
# Critical: RegularLineHandler should be last (fallback)
|
|
134
|
+
if handler_classes[-1] != RegularLineHandler:
|
|
135
|
+
error_msg = (
|
|
136
|
+
f"WARNING: RegularLineHandler should be last handler (fallback), "
|
|
137
|
+
f"but {handler_classes[-1].__name__} is at the end"
|
|
138
|
+
)
|
|
139
|
+
self.logger.warning(error_msg)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NotionContentSchema Base Class
|
|
3
|
+
=============================
|
|
4
|
+
|
|
5
|
+
Base class for all Notion structured output schemas with injected MarkdownBuilder
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from notionary.blocks.markdown.markdown_builder import MarkdownBuilder
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NotionContentSchema(BaseModel):
|
|
13
|
+
"""
|
|
14
|
+
Base class for all Notion content schemas.
|
|
15
|
+
|
|
16
|
+
Inherit from this and implement to_notion_content() to create
|
|
17
|
+
schemas that work with LLM structured output.
|
|
18
|
+
|
|
19
|
+
Example usage:
|
|
20
|
+
|
|
21
|
+
class BlogPost(NotionContentSchema):
|
|
22
|
+
title: str = Field(description="Catchy blog post title")
|
|
23
|
+
introduction: str = Field(description="Engaging opening paragraph")
|
|
24
|
+
main_points: List[str] = Field(description="3-5 key takeaways")
|
|
25
|
+
conclusion: str = Field(description="Summary and call-to-action")
|
|
26
|
+
|
|
27
|
+
def to_notion_content(self, builder: MarkdownBuilder) -> str:
|
|
28
|
+
return (builder
|
|
29
|
+
.h1(self.title)
|
|
30
|
+
.paragraph(self.introduction)
|
|
31
|
+
.h2("Key Points")
|
|
32
|
+
.bulleted_list(self.main_points)
|
|
33
|
+
.h2("Conclusion")
|
|
34
|
+
.paragraph(self.conclusion)
|
|
35
|
+
.build()
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Usage with LLM:
|
|
39
|
+
llm = ChatOpenAI(model="gpt-4o")
|
|
40
|
+
structured_llm = llm.with_structured_output(BlogPost)
|
|
41
|
+
blog = structured_llm.invoke("Write about Python async/await")
|
|
42
|
+
|
|
43
|
+
# Upload to Notion:
|
|
44
|
+
await blog.append_to_page("My Blog")
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def to_notion_content(self, builder: MarkdownBuilder) -> str:
|
|
48
|
+
"""
|
|
49
|
+
Build Notion content using the provided MarkdownBuilder.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
builder: Empty MarkdownBuilder instance to build content with
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
str: The final markdown string (user should call build() on the builder)
|
|
56
|
+
"""
|
|
57
|
+
raise NotImplementedError("Subclasses must implement to_notion_content()")
|
|
58
|
+
|
|
59
|
+
async def append_to_page(self, page_name: str):
|
|
60
|
+
"""
|
|
61
|
+
Upload this content directly to a Notion page.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
page_name: Name of the target Notion page
|
|
65
|
+
"""
|
|
66
|
+
from notionary import NotionPage
|
|
67
|
+
|
|
68
|
+
# Create fresh builder and let subclass build content
|
|
69
|
+
builder = MarkdownBuilder()
|
|
70
|
+
markdown = self.to_notion_content(builder)
|
|
71
|
+
|
|
72
|
+
page = await NotionPage.from_page_name(page_name)
|
|
73
|
+
await page.append_markdown(markdown)
|
notionary/shared/__init__.py
CHANGED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: notionary
|
|
3
|
+
Version: 0.2.25
|
|
4
|
+
Summary: Python library for programmatic Notion workspace management - databases, pages, and content with advanced Markdown support
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Mathis Arends
|
|
7
|
+
Author-email: mathisarends27@gmail.com
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
|
|
17
|
+
Requires-Dist: httpx (>=0.28.0)
|
|
18
|
+
Requires-Dist: posthog (>=6.3.1,<7.0.0)
|
|
19
|
+
Requires-Dist: pydantic (>=2.11.4)
|
|
20
|
+
Requires-Dist: python-dotenv (>=1.1.0)
|
|
21
|
+
Project-URL: Homepage, https://github.com/mathisarends/notionary
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
<picture>
|
|
25
|
+
<source media="(prefers-color-scheme: dark)" srcset="./static/notionary-dark.png">
|
|
26
|
+
<source media="(prefers-color-scheme: light)" srcset="./static/notionary-light.png">
|
|
27
|
+
<img alt="Notionary logo: dark mode shows a white logo, light mode shows a black logo." src="./static/browser-use.png" width="full">
|
|
28
|
+
</picture>
|
|
29
|
+
|
|
30
|
+
<h1 align="center">The Modern Notion API for Python & AI Agents</h1>
|
|
31
|
+
|
|
32
|
+
<div align="center">
|
|
33
|
+
|
|
34
|
+
[](https://www.python.org/downloads/)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
[](https://mathisarends.github.io/notionary/)
|
|
37
|
+
|
|
38
|
+
**Transform complex Notion API interactions into simple, Pythonic code.**
|
|
39
|
+
Perfect for developers building AI agents, automation workflows, and dynamic content systems.
|
|
40
|
+
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Why Notionary?
|
|
46
|
+
|
|
47
|
+
- **AI-Native Design** - Built specifically for AI agents with schema-driven markdown syntax
|
|
48
|
+
- **Smart Discovery** - Find pages and databases by name with fuzzy matching—no more hunting for IDs
|
|
49
|
+
- **Extended Markdown** - Rich syntax for toggles, columns, callouts, and media uploads
|
|
50
|
+
- **Async-First** - Modern Python with full async/await support and high performance
|
|
51
|
+
- **Round-Trip** - Read existing content, modify it, and write it back while preserving formatting
|
|
52
|
+
- **Complete Coverage** - Every Notion block type supported with type safety
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install notionary
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Set up your [Notion integration](https://www.notion.so/profile/integrations) and configure your token:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
export NOTION_SECRET=your_integration_key
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## See It in Action
|
|
71
|
+
|
|
72
|
+
### Creating Rich Database Entries
|
|
73
|
+
|
|
74
|
+
https://github.com/user-attachments/assets/da8b4691-bee4-4b0f-801e-dccacb630398
|
|
75
|
+
|
|
76
|
+
*Create styled project pages with properties, content, and rich formatting*
|
|
77
|
+
|
|
78
|
+
### Local File Uploads (Videos & Images)
|
|
79
|
+
|
|
80
|
+
https://github.com/user-attachments/assets/a079ec01-bb56-4c65-8260-7b1fca42ac68
|
|
81
|
+
|
|
82
|
+
*Upload videos and images using simple markdown syntax - files are automatically uploaded to Notion*
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Quick Start
|
|
87
|
+
|
|
88
|
+
### Find → Create → Update Flow
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import asyncio
|
|
92
|
+
from notionary import NotionPage, NotionDatabase
|
|
93
|
+
|
|
94
|
+
async def main():
|
|
95
|
+
# Find pages by name - fuzzy matching included!
|
|
96
|
+
page = await NotionPage.from_page_name("Meeting Notes")
|
|
97
|
+
|
|
98
|
+
# Option 1: Direct Extended Markdown
|
|
99
|
+
await page.append_markdown("""
|
|
100
|
+
## Action Items
|
|
101
|
+
- [x] Review project proposal
|
|
102
|
+
- [ ] Schedule team meeting
|
|
103
|
+
- [ ] Update documentation
|
|
104
|
+
|
|
105
|
+
[callout](Meeting decisions require follow-up "💡")
|
|
106
|
+
|
|
107
|
+
+++ Details
|
|
108
|
+
Additional context and next steps...
|
|
109
|
+
+++
|
|
110
|
+
""")
|
|
111
|
+
|
|
112
|
+
# Option 2: Type-Safe Builder (maps to same markdown internally)
|
|
113
|
+
await page.append_markdown(lambda builder: (
|
|
114
|
+
builder
|
|
115
|
+
.h2("Project Status")
|
|
116
|
+
.callout("Milestone reached!", "🎉")
|
|
117
|
+
.columns(
|
|
118
|
+
lambda col: col.h3("Completed").bulleted_list([
|
|
119
|
+
"API design", "Database setup", "Authentication"
|
|
120
|
+
]),
|
|
121
|
+
lambda col: col.h3("In Progress").bulleted_list([
|
|
122
|
+
"Frontend UI", "Testing", "Documentation"
|
|
123
|
+
]),
|
|
124
|
+
width_ratios=[0.6, 0.4]
|
|
125
|
+
)
|
|
126
|
+
.toggle("Budget Details", lambda t: t
|
|
127
|
+
.table(["Item", "Cost", "Status"], [
|
|
128
|
+
["Development", "$15,000", "Paid"],
|
|
129
|
+
["Design", "$8,000", "Pending"]
|
|
130
|
+
])
|
|
131
|
+
)
|
|
132
|
+
))
|
|
133
|
+
|
|
134
|
+
asyncio.run(main())
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Complete Block Support
|
|
138
|
+
|
|
139
|
+
Every Notion block type with extended syntax:
|
|
140
|
+
|
|
141
|
+
| Block Type | Markdown Syntax | Use Case |
|
|
142
|
+
|------------|-----------------|----------|
|
|
143
|
+
| **Callouts** | `[callout](Text "🔥")` | Highlighting key information |
|
|
144
|
+
| **Toggles** | `+++ Title\nContent\n+++` | Collapsible sections |
|
|
145
|
+
| **Columns** | `::: columns\n::: column\nContent\n:::\n:::` | Side-by-side layouts |
|
|
146
|
+
| **Tables** | Standard markdown tables | Structured data |
|
|
147
|
+
| **Media** | `[video](./file.mp4)(caption:Description)` | Auto-uploading files |
|
|
148
|
+
| **Code** | Standard code fences with captions | Code snippets |
|
|
149
|
+
| **Equations** | `$LaTeX$` | Mathematical expressions |
|
|
150
|
+
| **TOC** | `[toc](blue_background)` | Auto-generated navigation |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## What You Can Build 💡
|
|
155
|
+
|
|
156
|
+
### **AI Content Systems**
|
|
157
|
+
- **Report Generation**: AI agents that create structured reports, documentation, and analysis
|
|
158
|
+
- **Content Pipelines**: Automated workflows that process data and generate Notion pages
|
|
159
|
+
- **Knowledge Management**: AI-powered documentation systems with smart categorization
|
|
160
|
+
|
|
161
|
+
### **Workflow Automation**
|
|
162
|
+
- **Project Management**: Sync project status, update timelines, generate progress reports
|
|
163
|
+
- **Data Integration**: Connect external APIs and databases to Notion workspaces
|
|
164
|
+
- **Template Systems**: Dynamic page generation from templates and data sources
|
|
165
|
+
|
|
166
|
+
### **Content Management**
|
|
167
|
+
- **Bulk Operations**: Mass page updates, content migration, and database management
|
|
168
|
+
- **Media Handling**: Automated image/video uploads with proper organization
|
|
169
|
+
- **Cross-Platform**: Sync content between Notion and other platforms
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Key Features
|
|
174
|
+
|
|
175
|
+
<table>
|
|
176
|
+
<tr>
|
|
177
|
+
<td width="50%">
|
|
178
|
+
|
|
179
|
+
### Smart Discovery
|
|
180
|
+
- Find pages/databases by name
|
|
181
|
+
- Fuzzy matching for approximate searches
|
|
182
|
+
- No more hunting for IDs or URLs
|
|
183
|
+
|
|
184
|
+
### Extended Markdown
|
|
185
|
+
- Rich syntax beyond standard markdown
|
|
186
|
+
- Callouts, toggles, columns, media uploads
|
|
187
|
+
- Schema provided for AI agent integration
|
|
188
|
+
|
|
189
|
+
### Modern Python
|
|
190
|
+
- Full async/await support
|
|
191
|
+
- Type hints throughout
|
|
192
|
+
- High-performance batch operations
|
|
193
|
+
|
|
194
|
+
</td>
|
|
195
|
+
<td width="50%">
|
|
196
|
+
|
|
197
|
+
### Round-Trip Editing
|
|
198
|
+
- Read existing content as markdown
|
|
199
|
+
- Edit and modify preserving formatting
|
|
200
|
+
- Write back to Notion seamlessly
|
|
201
|
+
|
|
202
|
+
### AI-Ready Architecture
|
|
203
|
+
- Schema-driven syntax for LLM prompts
|
|
204
|
+
- Perfect for AI content generation
|
|
205
|
+
- Handles complex nested structures
|
|
206
|
+
|
|
207
|
+
### Complete Coverage
|
|
208
|
+
- Every Notion block type supported
|
|
209
|
+
- File uploads with automatic handling
|
|
210
|
+
- Database operations and properties
|
|
211
|
+
|
|
212
|
+
</td>
|
|
213
|
+
</tr>
|
|
214
|
+
</table>
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Examples & Documentation
|
|
219
|
+
|
|
220
|
+
### Full Documentation
|
|
221
|
+
[**mathisarends.github.io/notionary**](https://mathisarends.github.io/notionary/) - Complete API reference, guides, and tutorials
|
|
222
|
+
|
|
223
|
+
### Quick Links
|
|
224
|
+
- [**Getting Started**](https://mathisarends.github.io/notionary/get-started/) - Setup and first steps
|
|
225
|
+
- [**Page Management**](https://mathisarends.github.io/notionary/page/) - Content and properties
|
|
226
|
+
- [**Database Operations**](https://mathisarends.github.io/notionary/database/) - Queries and management
|
|
227
|
+
- [**Block Types Reference**](https://mathisarends.github.io/notionary/blocks/) - Complete syntax guide
|
|
228
|
+
|
|
229
|
+
### Hands-On Examples
|
|
230
|
+
|
|
231
|
+
**Core Functionality:**
|
|
232
|
+
- [Page Management](examples/page_example.py) - Create, update, and manage pages
|
|
233
|
+
- [Database Operations](examples/database.py) - Connect and query databases
|
|
234
|
+
- [Workspace Discovery](examples/workspace_discovery.py) - Explore your workspace
|
|
235
|
+
|
|
236
|
+
**Extended Markdown:**
|
|
237
|
+
- [Basic Formatting](examples/markdown/basic.py) - Text, lists, and links
|
|
238
|
+
- [Callouts & Highlights](examples/markdown/callout.py) - Information boxes
|
|
239
|
+
- [Toggle Sections](examples/markdown/toggle.py) - Collapsible content
|
|
240
|
+
- [Multi-Column Layouts](examples/markdown/columns.py) - Side-by-side design
|
|
241
|
+
- [Tables & Data](examples/markdown/table.py) - Structured presentations
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Contributing
|
|
246
|
+
|
|
247
|
+
We welcome contributions from the community! Whether you're:
|
|
248
|
+
- **Fixing bugs** - Help improve stability and reliability
|
|
249
|
+
- **Adding features** - Extend functionality for new use cases
|
|
250
|
+
- **Improving docs** - Make the library more accessible
|
|
251
|
+
- **Sharing examples** - Show creative applications and patterns
|
|
252
|
+
|
|
253
|
+
Check our [**Contributing Guide**](https://mathisarends.github.io/notionary/contributing/) to get started.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
<div align="center">
|
|
258
|
+
|
|
259
|
+
**Ready to revolutionize your Notion workflows?**
|
|
260
|
+
|
|
261
|
+
[📖 **Read the Docs**](https://mathisarends.github.io/notionary/) • [🚀 **Getting Started**](https://mathisarends.github.io/notionary/get-started/) • [💻 **Browse Examples**](examples/)
|
|
262
|
+
|
|
263
|
+
*Built with ❤️ for Python developers and AI agents*
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
**Transform complex Notion API interactions into simple, powerful code.**
|
|
268
|
+
|
|
269
|
+
</div>
|
|
270
|
+
|