notionary 0.2.21__py3-none-any.whl → 0.2.22__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.
Files changed (96) hide show
  1. notionary/blocks/_bootstrap.py +9 -1
  2. notionary/blocks/audio/audio_element.py +53 -28
  3. notionary/blocks/audio/audio_markdown_node.py +10 -4
  4. notionary/blocks/base_block_element.py +15 -3
  5. notionary/blocks/bookmark/bookmark_element.py +39 -36
  6. notionary/blocks/bookmark/bookmark_markdown_node.py +16 -17
  7. notionary/blocks/breadcrumbs/breadcrumb_element.py +2 -2
  8. notionary/blocks/bulleted_list/bulleted_list_element.py +21 -4
  9. notionary/blocks/callout/callout_element.py +20 -4
  10. notionary/blocks/child_database/__init__.py +11 -4
  11. notionary/blocks/child_database/child_database_element.py +61 -0
  12. notionary/blocks/child_database/child_database_models.py +7 -14
  13. notionary/blocks/child_page/child_page_element.py +94 -0
  14. notionary/blocks/client.py +0 -1
  15. notionary/blocks/code/code_element.py +51 -2
  16. notionary/blocks/code/code_markdown_node.py +52 -1
  17. notionary/blocks/column/column_element.py +9 -3
  18. notionary/blocks/column/column_list_element.py +18 -3
  19. notionary/blocks/divider/divider_element.py +3 -11
  20. notionary/blocks/embed/embed_element.py +27 -6
  21. notionary/blocks/equation/equation_element.py +94 -41
  22. notionary/blocks/equation/equation_element_markdown_node.py +8 -9
  23. notionary/blocks/file/file_element.py +56 -37
  24. notionary/blocks/file/file_element_markdown_node.py +9 -7
  25. notionary/blocks/guards.py +22 -0
  26. notionary/blocks/heading/heading_element.py +23 -4
  27. notionary/blocks/image_block/image_element.py +43 -38
  28. notionary/blocks/image_block/image_markdown_node.py +10 -5
  29. notionary/blocks/mixins/captions/__init__.py +4 -0
  30. notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +31 -0
  31. notionary/blocks/mixins/captions/caption_mixin.py +92 -0
  32. notionary/blocks/models.py +3 -1
  33. notionary/blocks/numbered_list/numbered_list_element.py +21 -4
  34. notionary/blocks/paragraph/paragraph_element.py +21 -5
  35. notionary/blocks/pdf/pdf_element.py +47 -41
  36. notionary/blocks/pdf/pdf_markdown_node.py +9 -7
  37. notionary/blocks/quote/quote_element.py +26 -9
  38. notionary/blocks/quote/quote_markdown_node.py +2 -2
  39. notionary/blocks/registry/block_registry.py +1 -46
  40. notionary/blocks/registry/block_registry_builder.py +8 -0
  41. notionary/blocks/rich_text/name_to_id_resolver.py +205 -0
  42. notionary/blocks/rich_text/rich_text_models.py +62 -29
  43. notionary/blocks/rich_text/text_inline_formatter.py +432 -101
  44. notionary/blocks/syntax_prompt_builder.py +137 -0
  45. notionary/blocks/table/table_element.py +110 -9
  46. notionary/blocks/table_of_contents/table_of_contents_element.py +19 -2
  47. notionary/blocks/todo/todo_element.py +21 -4
  48. notionary/blocks/toggle/toggle_element.py +19 -3
  49. notionary/blocks/toggle/toggle_markdown_node.py +1 -1
  50. notionary/blocks/toggleable_heading/toggleable_heading_element.py +19 -4
  51. notionary/blocks/types.py +69 -0
  52. notionary/blocks/video/video_element.py +44 -39
  53. notionary/blocks/video/video_markdown_node.py +10 -5
  54. notionary/database/client.py +23 -0
  55. notionary/file_upload/models.py +2 -2
  56. notionary/markdown/markdown_builder.py +34 -27
  57. notionary/page/client.py +26 -6
  58. notionary/page/notion_page.py +37 -6
  59. notionary/page/page_content_deleting_service.py +117 -0
  60. notionary/page/page_content_writer.py +89 -113
  61. notionary/page/page_context.py +65 -0
  62. notionary/page/reader/handler/__init__.py +2 -0
  63. notionary/page/reader/handler/base_block_renderer.py +4 -4
  64. notionary/page/reader/handler/block_rendering_context.py +5 -0
  65. notionary/page/reader/handler/line_renderer.py +16 -3
  66. notionary/page/reader/handler/numbered_list_renderer.py +85 -0
  67. notionary/page/reader/page_content_retriever.py +17 -5
  68. notionary/page/writer/handler/__init__.py +2 -0
  69. notionary/page/writer/handler/code_handler.py +12 -40
  70. notionary/page/writer/handler/column_handler.py +12 -12
  71. notionary/page/writer/handler/column_list_handler.py +13 -13
  72. notionary/page/writer/handler/equation_handler.py +74 -0
  73. notionary/page/writer/handler/line_handler.py +4 -4
  74. notionary/page/writer/handler/regular_line_handler.py +31 -37
  75. notionary/page/writer/handler/table_handler.py +8 -72
  76. notionary/page/writer/handler/toggle_handler.py +14 -12
  77. notionary/page/writer/handler/toggleable_heading_handler.py +22 -16
  78. notionary/page/writer/markdown_to_notion_converter.py +28 -9
  79. notionary/page/writer/markdown_to_notion_converter_context.py +30 -0
  80. notionary/page/writer/markdown_to_notion_formatting_post_processor.py +73 -0
  81. notionary/page/writer/markdown_to_notion_post_processor.py +0 -0
  82. notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
  83. notionary/page/writer/notion_text_length_processor.py +150 -0
  84. notionary/telemetry/service.py +0 -1
  85. notionary/user/notion_user_manager.py +22 -95
  86. notionary/util/concurrency_limiter.py +0 -0
  87. notionary/workspace.py +4 -4
  88. notionary-0.2.22.dist-info/METADATA +237 -0
  89. {notionary-0.2.21.dist-info → notionary-0.2.22.dist-info}/RECORD +92 -77
  90. notionary/page/markdown_whitespace_processor.py +0 -80
  91. notionary/page/notion_text_length_utils.py +0 -119
  92. notionary/user/notion_user_provider.py +0 -1
  93. notionary-0.2.21.dist-info/METADATA +0 -229
  94. /notionary/page/reader/handler/{context.py → equation_renderer.py} +0 -0
  95. {notionary-0.2.21.dist-info → notionary-0.2.22.dist-info}/LICENSE +0 -0
  96. {notionary-0.2.21.dist-info → notionary-0.2.22.dist-info}/WHEEL +0 -0
@@ -1,119 +0,0 @@
1
- """
2
- Utility functions for handling Notion API text length limitations.
3
-
4
- This module provides functions to fix text content that exceeds Notion's
5
- rich_text character limit of 2000 characters per element.
6
-
7
- Resolves API errors like:
8
- "validation_error - body.children[79].toggle.children[2].paragraph.rich_text[0].text.content.length
9
- should be ≤ 2000, instead was 2162."
10
- """
11
-
12
- import logging
13
- import re
14
-
15
- from notionary.blocks.models import BlockCreateRequest
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- def fix_blocks_content_length(
21
- blocks: list[BlockCreateRequest], max_text_length: int = 1900
22
- ) -> list[BlockCreateRequest]:
23
- """Check each block and ensure text content doesn't exceed Notion's limit."""
24
- fixed_blocks: list[BlockCreateRequest] = []
25
-
26
- flattened_blocks = _flatten_blocks(blocks)
27
-
28
- for block in flattened_blocks:
29
- fixed_block = _fix_single_block_content(block, max_text_length)
30
- fixed_blocks.append(fixed_block)
31
- return fixed_blocks
32
-
33
-
34
- def _fix_single_block_content(
35
- block: BlockCreateRequest, max_text_length: int
36
- ) -> BlockCreateRequest:
37
- """Fix content length in a single block and its children recursively."""
38
- block_copy = block.model_copy(deep=True)
39
- _fix_block_rich_text_direct(block_copy, max_text_length)
40
-
41
- return block_copy
42
-
43
-
44
- def _fix_block_rich_text_direct(
45
- block: BlockCreateRequest, max_text_length: int
46
- ) -> None:
47
- """Fix rich text content directly on the Pydantic object."""
48
- block_content = _get_block_content(block)
49
- if not block_content:
50
- return
51
- if hasattr(block_content, "rich_text") and block_content.rich_text:
52
- _fix_rich_text_objects_direct(block_content.rich_text, max_text_length)
53
- if hasattr(block_content, "children") and block_content.children:
54
- for child in block_content.children:
55
- _fix_block_rich_text_direct(child, max_text_length)
56
-
57
-
58
- def _get_block_content(block: BlockCreateRequest):
59
- """Get the actual content object from a create block dynamically."""
60
- # Get all attributes that don't start with underscore and aren't methods
61
- for attr_name in dir(block):
62
- if attr_name.startswith("_") or attr_name in [
63
- "model_copy",
64
- "model_dump",
65
- "model_validate",
66
- ]:
67
- continue
68
-
69
- attr_value = getattr(block, attr_name, None)
70
-
71
- # Skip None values, strings (like 'type'), and callable methods
72
- if attr_value is None or isinstance(attr_value, str) or callable(attr_value):
73
- continue
74
-
75
- # If it's an object with rich_text or children, it's likely our content
76
- if hasattr(attr_value, "rich_text") or hasattr(attr_value, "children"):
77
- return attr_value
78
-
79
- return None
80
-
81
-
82
- def _fix_rich_text_objects_direct(rich_text_list: list, max_text_length: int) -> None:
83
- """Fix rich text objects directly without dict conversion."""
84
- if not rich_text_list:
85
- return
86
-
87
- for rich_text_item in rich_text_list:
88
- if not rich_text_item:
89
- continue
90
-
91
- # Check if this is a text type rich text object
92
- if (
93
- hasattr(rich_text_item, "text")
94
- and rich_text_item.text
95
- and hasattr(rich_text_item.text, "content")
96
- ):
97
-
98
- content = rich_text_item.text.content
99
- if content and len(content) > max_text_length:
100
- logger.warning(
101
- "Truncating text content from %d to %d chars",
102
- len(content),
103
- max_text_length,
104
- )
105
- # Direct assignment - no parsing needed!
106
- rich_text_item.text.content = content[:max_text_length]
107
-
108
-
109
- def _flatten_blocks(blocks: list) -> list[BlockCreateRequest]:
110
- """Flatten nested block lists."""
111
- flattened = []
112
- for item in blocks:
113
- if isinstance(item, list):
114
- # Rekursiv flatten für nested lists
115
- flattened.extend(_flatten_blocks(item))
116
- else:
117
- # Normal block
118
- flattened.append(item)
119
- return flattened
@@ -1 +0,0 @@
1
- # for caching shit
@@ -1,229 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: notionary
3
- Version: 0.2.21
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: isort (>=6.0.1,<7.0.0)
19
- Requires-Dist: posthog (>=6.3.1,<7.0.0)
20
- Requires-Dist: pydantic (>=2.11.4)
21
- Requires-Dist: python-dotenv (>=1.1.0)
22
- Project-URL: Homepage, https://github.com/mathisarends/notionary
23
- Description-Content-Type: text/markdown
24
-
25
- <picture>
26
- <source media="(prefers-color-scheme: dark)" srcset="./static/notionary-dark.png">
27
- <source media="(prefers-color-scheme: light)" srcset="./static/notionary-light.png">
28
- <img alt="Notionary logo: dark mode shows a white logo, light mode shows a black logo." src="./static/browser-use.png" width="full">
29
- </picture>
30
-
31
- <h1 align="center">Notion API simplified for Python developers 🐍</h1>
32
-
33
- [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/downloads/)
34
- [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
35
-
36
- - **Object-Oriented Design**: Clean, intuitive classes for Pages, Databases, and Workspaces with full CRUD operations
37
- - **Rich Markdown to Notion**: Convert extended Markdown (callouts, toggles, columns) directly into beautiful Notion blocks
38
- - **Smart Discovery**: Find pages and databases by name with fuzzy matching - no more hunting for URLs
39
- - **Async-First Architecture**: Built for modern Python with full async/await support and high performance
40
-
41
- ---
42
-
43
- # Quick start
44
-
45
- ```bash
46
- pip install notionary
47
- ```
48
-
49
- - Set up your Notion integration (notion.so/profile/integrations)
50
- - Add your integration key in your `.env` file.
51
-
52
- ```bash
53
- NOTION_SECRET=YOUR_INTEGRATION_KEY
54
- ```
55
-
56
- ### Creating and Managing Pages 🚀
57
-
58
- ```python
59
- from notionary import NotionPage
60
-
61
- async def main():
62
- # Simpy find an existing page by its title
63
- page = await NotionPage.from_page_name("My Test Page")
64
-
65
- # Add rich content with custom Markdown
66
- content = """
67
- # 🚀 Generated with Notionary
68
-
69
- !> [💡] This page was created programmatically!
70
-
71
- ## Features
72
- - **Rich** Markdown support
73
- - Database integration
74
- - AI-ready content generation
75
-
76
- +++ Click to see more details
77
- | Notionary makes it easy to create beautiful Notion pages
78
- | directly from Python code with intuitive Markdown syntax.
79
- """
80
-
81
- await page.replace_content(content)
82
- print(f"✅ Page updated: {page.url}")
83
-
84
- asyncio.run(main())
85
- ```
86
-
87
- ---
88
-
89
- ### Working with Databases 🔥
90
-
91
- ```python
92
- import asyncio
93
- from notionary import NotionDatabase
94
-
95
- async def main():
96
- # Connect to database by name (fuzzy matching)
97
- db = await NotionDatabase.from_database_name("Projects")
98
-
99
- # Create a new page with properties
100
- page = await db.create_blank_page()
101
- await page.set_title("🆕 New Project Entry")
102
- await page.set_property_value_by_name("Status", "In Progress")
103
- await page.set_property_value_by_name("Priority", "High")
104
-
105
- # find pages created in the last 7 days
106
- count = 0
107
- async for page in db.iter_pages_with_filter(
108
- db.create_filter().with_created_last_n_days(7)
109
- ):
110
- count += 1
111
- print(f"{count:2d}. {page.emoji_icon or '📄'} {page.title}")
112
-
113
- asyncio.run(main())
114
- ```
115
-
116
- ## Custom Markdown Syntax
117
-
118
- Notionary extends standard Markdown with special syntax to support Notion-specific features:
119
-
120
- ### Text Formatting
121
-
122
- - Standard: `**bold**`, `*italic*`, `~~strikethrough~~`, `` `code` ``
123
- - Links: `[text](url)`
124
- - Quotes: `> This is a quote`
125
- - Divider: `---`
126
-
127
- ### Callouts
128
-
129
- ```markdown
130
- !> [💡] This is a default callout with the light bulb emoji
131
- !> [🔔] This is a notification with a bell emoji
132
- !> [⚠️] Warning: This is an important note
133
- ```
134
-
135
- ### Toggles
136
-
137
- ```markdown
138
- +++ How to use Notionary
139
- | 1. Initialize with NotionPage
140
- | 2. Update metadata with set_title(), set_emoji_icon(), etc.
141
- | 3. Add content with replace_content() or append_markdown()
142
- ```
143
-
144
- ### Multi-Column Layout
145
-
146
- ```markdown
147
- ::: columns
148
- ::: column
149
-
150
- ## Left Column
151
-
152
- - Item 1
153
- - Item 2
154
- - Item 3
155
- :::
156
- ::: column
157
-
158
- ## Right Column
159
-
160
- This text appears in the second column. Multi-column layouts are perfect for:
161
-
162
- - Comparing features
163
- - Creating side-by-side content
164
- - Improving readability of wide content
165
- :::
166
- :::
167
- ```
168
-
169
- ### Code Blocks
170
-
171
- ```python
172
- def hello_world():
173
- print("Hello from Notionary!")
174
- ```
175
-
176
- ### To-do Lists
177
-
178
- ```markdown
179
- - [ ] Define project scope
180
- - [x] Create timeline
181
- - [ ] Assign resources
182
- ```
183
-
184
- ### Tables
185
-
186
- ```markdown
187
- | Feature | Status | Priority |
188
- | --------------- | ----------- | -------- |
189
- | API Integration | Complete | High |
190
- | Documentation | In Progress | Medium |
191
- ```
192
-
193
- ### More Elements
194
-
195
- ```markdown
196
- ![Caption](https://example.com/image.jpg)
197
- @[Caption](https://youtube.com/watch?v=...)
198
- [bookmark](https://example.com "Title" "Description")
199
- ```
200
-
201
- ## Examples
202
-
203
- Explore the `examples/` directory for comprehensive guides:
204
-
205
- ### 🚀 Core Examples
206
-
207
- - [**Page Management**](examples/page_example.py) - Create, update, and manage Notion pages
208
- - [**Page Operations**](examples/page.py) - Advanced page manipulation and content handling
209
- - [**Database Operations**](examples/database.py) - Connect to and manage Notion databases
210
- - [**Database Iteration**](examples/database_iteration.py) - Query and filter database entries
211
- - [**Workspace Discovery**](examples/workspace_discovery.py) - Explore and discover your Notion workspace
212
-
213
- ### 📝 Markdown Examples
214
-
215
- - [**Basic Formatting**](examples/markdown/basic.py) - Text formatting, lists, and basic elements
216
- - [**Callouts**](examples/markdown/callout.py) - Create beautiful callout blocks with icons
217
- - [**Toggles**](examples/markdown/toggle.py) - Collapsible content sections
218
- - [**Multi-Column Layouts**](examples/markdown/columns.py) - Side-by-side content arrangement
219
- - [**Code Blocks**](examples/markdown/code.py) - Syntax-highlighted code examples
220
- - [**Tables**](examples/markdown/table.py) - Structured data presentation
221
- - [**Media Embeds**](examples/markdown/embed.py) - Images, videos, and rich media
222
- - [**Audio Content**](examples/markdown/audio.py) - Audio file integration
223
-
224
- Each example is self-contained and demonstrates specific features with practical use cases.
225
-
226
- ## Contributing
227
-
228
- Contributions welcome — feel free to submit a pull request!
229
-