notionary 0.2.14__py3-none-any.whl → 0.2.15__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.
@@ -26,7 +26,7 @@ class NotionDatabase(LoggingMixin):
26
26
  @factory_only("from_database_id", "from_database_name")
27
27
  def __init__(
28
28
  self,
29
- database_id: str,
29
+ id: str,
30
30
  title: str,
31
31
  url: str,
32
32
  emoji_icon: Optional[str] = None,
@@ -36,7 +36,7 @@ class NotionDatabase(LoggingMixin):
36
36
  """
37
37
  Initialize the minimal database manager.
38
38
  """
39
- self._database_id = database_id
39
+ self._id = id
40
40
  self._title = title
41
41
  self._url = url
42
42
  self._emoji_icon = emoji_icon
@@ -46,13 +46,13 @@ class NotionDatabase(LoggingMixin):
46
46
 
47
47
  @classmethod
48
48
  async def from_database_id(
49
- cls, database_id: str, token: Optional[str] = None
49
+ cls, id: str, token: Optional[str] = None
50
50
  ) -> NotionDatabase:
51
51
  """
52
52
  Create a NotionDatabase from a database ID using NotionDatabaseProvider.
53
53
  """
54
54
  provider = cls.get_database_provider()
55
- return await provider.get_database_by_id(database_id, token)
55
+ return await provider.get_database_by_id(id, token)
56
56
 
57
57
  @classmethod
58
58
  async def from_database_name(
@@ -68,9 +68,9 @@ class NotionDatabase(LoggingMixin):
68
68
  return await provider.get_database_by_name(database_name, token, min_similarity)
69
69
 
70
70
  @property
71
- def database_id(self) -> str:
71
+ def id(self) -> str:
72
72
  """Get the database ID (readonly)."""
73
- return self._database_id
73
+ return self._id
74
74
 
75
75
  @property
76
76
  def title(self) -> str:
@@ -109,7 +109,7 @@ class NotionDatabase(LoggingMixin):
109
109
  """
110
110
  try:
111
111
  create_page_response: NotionPageResponse = await self.client.create_page(
112
- parent_database_id=self.database_id
112
+ parent_database_id=self.id
113
113
  )
114
114
 
115
115
  return await NotionPage.from_page_id(page_id=create_page_response.id)
@@ -124,13 +124,13 @@ class NotionDatabase(LoggingMixin):
124
124
  """
125
125
  try:
126
126
  result = await self.client.update_database_title(
127
- database_id=self.database_id, title=new_title
127
+ database_id=self.id, title=new_title
128
128
  )
129
129
 
130
130
  self._title = result.title[0].plain_text
131
131
  self.logger.info(f"Successfully updated database title to: {new_title}")
132
132
  self.database_provider.invalidate_database_cache(
133
- database_id=self.database_id
133
+ database_id=self.id
134
134
  )
135
135
  return True
136
136
 
@@ -144,13 +144,13 @@ class NotionDatabase(LoggingMixin):
144
144
  """
145
145
  try:
146
146
  result = await self.client.update_database_emoji(
147
- database_id=self.database_id, emoji=new_emoji
147
+ database_id=self.id, emoji=new_emoji
148
148
  )
149
149
 
150
150
  self._emoji_icon = result.icon.emoji if result.icon else None
151
151
  self.logger.info(f"Successfully updated database emoji to: {new_emoji}")
152
152
  self.database_provider.invalidate_database_cache(
153
- database_id=self.database_id
153
+ database_id=self.id
154
154
  )
155
155
  return True
156
156
 
@@ -164,12 +164,12 @@ class NotionDatabase(LoggingMixin):
164
164
  """
165
165
  try:
166
166
  result = await self.client.update_database_cover_image(
167
- database_id=self.database_id, image_url=image_url
167
+ database_id=self.id, image_url=image_url
168
168
  )
169
169
 
170
170
  if result.cover and result.cover.external:
171
171
  self.database_provider.invalidate_database_cache(
172
- database_id=self.database_id
172
+ database_id=self.id
173
173
  )
174
174
  return result.cover.external.url
175
175
  return None
@@ -193,12 +193,12 @@ class NotionDatabase(LoggingMixin):
193
193
  """
194
194
  try:
195
195
  result = await self.client.update_database_external_icon(
196
- database_id=self.database_id, icon_url=external_icon_url
196
+ database_id=self.id, icon_url=external_icon_url
197
197
  )
198
198
 
199
199
  if result.icon and result.icon.external:
200
200
  self.database_provider.invalidate_database_cache(
201
- database_id=self.database_id
201
+ database_id=self.id
202
202
  )
203
203
  return result.icon.external.url
204
204
  return None
@@ -244,7 +244,7 @@ class NotionDatabase(LoggingMixin):
244
244
  """
245
245
  search_results: NotionQueryDatabaseResponse = (
246
246
  await self.client.query_database_by_title(
247
- database_id=self.database_id, page_title=page_title
247
+ database_id=self.id, page_title=page_title
248
248
  )
249
249
  )
250
250
 
@@ -296,7 +296,7 @@ class NotionDatabase(LoggingMixin):
296
296
  except Exception as e:
297
297
  self.logger.error(
298
298
  "Error fetching last_edited_time for database %s: %s",
299
- self.database_id,
299
+ self.id,
300
300
  str(e),
301
301
  )
302
302
  return None
@@ -345,7 +345,7 @@ class NotionDatabase(LoggingMixin):
345
345
  current_body["start_cursor"] = start_cursor
346
346
 
347
347
  result = await self.client.query_database(
348
- database_id=self.database_id, query_data=current_body
348
+ database_id=self.id, query_data=current_body
349
349
  )
350
350
 
351
351
  if not result or not result.results:
@@ -368,7 +368,7 @@ class NotionDatabase(LoggingMixin):
368
368
  emoji_icon = cls._extract_emoji_icon(db_response)
369
369
 
370
370
  instance = cls(
371
- database_id=db_response.id,
371
+ id=db_response.id,
372
372
  title=title,
373
373
  url=db_response.url,
374
374
  emoji_icon=emoji_icon,
@@ -55,10 +55,10 @@ class NotionDatabaseProvider(LoggingMixin, metaclass=SingletonMetaClass):
55
55
  database_name, token, min_similarity
56
56
  )
57
57
 
58
- id_cache_key = self._create_id_cache_key(database.database_id)
58
+ id_cache_key = self._create_id_cache_key(database.id)
59
59
  if not force_refresh and id_cache_key in self._database_cache:
60
60
  self.logger.debug(
61
- f"Found existing cached database by ID: {database.database_id}"
61
+ f"Found existing cached database by ID: {database.id}"
62
62
  )
63
63
  existing_database = self._database_cache[id_cache_key]
64
64
 
@@ -67,7 +67,7 @@ class NotionDatabaseProvider(LoggingMixin, metaclass=SingletonMetaClass):
67
67
 
68
68
  self._cache_database(database, token, database_name)
69
69
  self.logger.debug(
70
- f"Cached database: {database.title} (ID: {database.database_id})"
70
+ f"Cached database: {database.title} (ID: {database.id})"
71
71
  )
72
72
 
73
73
  return database
@@ -96,7 +96,7 @@ class NotionDatabaseProvider(LoggingMixin, metaclass=SingletonMetaClass):
96
96
  name_keys_to_remove = [
97
97
  cache_key
98
98
  for cache_key, cached_db in self._database_cache.items()
99
- if (cache_key.startswith("name:") and cached_db.database_id == database_id)
99
+ if (cache_key.startswith("name:") and cached_db.id == database_id)
100
100
  ]
101
101
 
102
102
  for name_key in name_keys_to_remove:
@@ -173,7 +173,7 @@ class NotionDatabaseProvider(LoggingMixin, metaclass=SingletonMetaClass):
173
173
  ) -> None:
174
174
  """Cache a database by both ID and name (if provided)."""
175
175
  # Always cache by ID
176
- id_cache_key = self._create_id_cache_key(database.database_id)
176
+ id_cache_key = self._create_id_cache_key(database.id)
177
177
  self._database_cache[id_cache_key] = database
178
178
 
179
179
  if original_name:
@@ -199,7 +199,7 @@ class NotionDatabaseProvider(LoggingMixin, metaclass=SingletonMetaClass):
199
199
  emoji_icon = self._extract_emoji_icon(db_response)
200
200
 
201
201
  instance = NotionDatabase(
202
- database_id=db_response.id,
202
+ id=db_response.id,
203
203
  title=title,
204
204
  url=db_response.url,
205
205
  emoji_icon=emoji_icon,
@@ -501,7 +501,7 @@ class NotionPage(LoggingMixin):
501
501
 
502
502
  parent_database = (
503
503
  await NotionDatabase.from_database_id(
504
- database_id=parent_database_id, token=token
504
+ id=parent_database_id, token=token
505
505
  )
506
506
  if parent_database_id
507
507
  else None
@@ -0,0 +1,223 @@
1
+ Metadata-Version: 2.3
2
+ Name: notionary
3
+ Version: 0.2.15
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: httpx (>=0.28.0)
17
+ Requires-Dist: pydantic (>=2.11.4)
18
+ Requires-Dist: python-dotenv (>=1.1.0)
19
+ Project-URL: Homepage, https://github.com/mathisarends/notionary
20
+ Description-Content-Type: text/markdown
21
+
22
+ <picture>
23
+ <source media="(prefers-color-scheme: dark)" srcset="./static/notionary-dark.png">
24
+ <source media="(prefers-color-scheme: light)" srcset="./static/notionary-light.png">
25
+ <img alt="Notionary logo: dark mode shows a white logo, light mode shows a black logo." src="./static/browser-use.png" width="full">
26
+ </picture>
27
+
28
+ <h1 align="center">Notion API simplified for Python developers 🐍</h1>
29
+
30
+ [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/downloads/)
31
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
32
+
33
+ - **Object-Oriented Design**: Clean, intuitive classes for Pages, Databases, and Workspaces with full CRUD operations
34
+ - **Rich Markdown to Notion**: Convert extended Markdown (callouts, toggles, columns) directly into beautiful Notion blocks
35
+ - **Smart Discovery**: Find pages and databases by name with fuzzy matching - no more hunting for URLs
36
+ - **Async-First Architecture**: Built for modern Python with full async/await support and high performance
37
+ - **AI-Ready Integration**: Generate LLM system prompts and let AI agents create Notion content seamlessly
38
+
39
+ ---
40
+
41
+ # Quick start
42
+ ```bash
43
+ pip install notionary
44
+ ```
45
+
46
+ - Set up your Notion integration (notion.so/profile/integrations)
47
+ - Add your integration key in your `.env` file.
48
+
49
+ ```bash
50
+ NOTION_SECRET=YOUR_INTEGRATION_KEY
51
+ ```
52
+
53
+ ### Creating and Managing Pages 🚀
54
+ ```python
55
+ from notionary import NotionPage
56
+
57
+ async def main():
58
+ # Simpy find an existing page by its title
59
+ page = await NotionPage.from_page_name("My Test Page")
60
+
61
+ # Add rich content with custom Markdown
62
+ content = """
63
+ # 🚀 Generated with Notionary
64
+
65
+ !> [💡] This page was created programmatically!
66
+
67
+ ## Features
68
+ - **Rich** Markdown support
69
+ - Database integration
70
+ - AI-ready content generation
71
+
72
+ +++ Click to see more details
73
+ | Notionary makes it easy to create beautiful Notion pages
74
+ | directly from Python code with intuitive Markdown syntax.
75
+ """
76
+
77
+ await page.replace_content(content)
78
+ print(f"✅ Page updated: {page.url}")
79
+
80
+ asyncio.run(main())
81
+ ```
82
+
83
+ ---
84
+
85
+ ### Working with Databases 🔥
86
+
87
+ ```python
88
+ import asyncio
89
+ from notionary import NotionDatabase
90
+
91
+ async def main():
92
+ # Connect to database by name (fuzzy matching)
93
+ db = await NotionDatabase.from_database_name("Projects")
94
+
95
+ # Create a new page with properties
96
+ page = await db.create_blank_page()
97
+ await page.set_title("🆕 New Project Entry")
98
+ await page.set_property_value_by_name("Status", "In Progress")
99
+ await page.set_property_value_by_name("Priority", "High")
100
+
101
+ # find pages created in the last 7 days
102
+ count = 0
103
+ async for page in db.iter_pages_with_filter(
104
+ db.create_filter().with_created_last_n_days(7)
105
+ ):
106
+ count += 1
107
+ print(f"{count:2d}. {page.emoji_icon or '📄'} {page.title}")
108
+
109
+ asyncio.run(main())
110
+ ```
111
+
112
+ ## Custom Markdown Syntax
113
+
114
+ Notionary extends standard Markdown with special syntax to support Notion-specific features:
115
+
116
+ ### Text Formatting
117
+
118
+ - Standard: `**bold**`, `*italic*`, `~~strikethrough~~`, `` `code` ``
119
+ - Links: `[text](url)`
120
+ - Quotes: `> This is a quote`
121
+ - Divider: `---`
122
+
123
+ ### Callouts
124
+
125
+ ```markdown
126
+ !> [💡] This is a default callout with the light bulb emoji
127
+ !> [🔔] This is a notification with a bell emoji
128
+ !> [⚠️] Warning: This is an important note
129
+ ```
130
+
131
+ ### Toggles
132
+
133
+ ```markdown
134
+ +++ How to use Notionary
135
+ | 1. Initialize with NotionPage
136
+ | 2. Update metadata with set_title(), set_emoji_icon(), etc.
137
+ | 3. Add content with replace_content() or append_markdown()
138
+ ```
139
+
140
+ ### Multi-Column Layout
141
+
142
+ ```markdown
143
+ ::: columns
144
+ ::: column
145
+
146
+ ## Left Column
147
+
148
+ - Item 1
149
+ - Item 2
150
+ - Item 3
151
+ :::
152
+ ::: column
153
+
154
+ ## Right Column
155
+
156
+ This text appears in the second column. Multi-column layouts are perfect for:
157
+
158
+ - Comparing features
159
+ - Creating side-by-side content
160
+ - Improving readability of wide content
161
+ :::
162
+ :::
163
+ ```
164
+
165
+ ### Code Blocks
166
+
167
+ ```python
168
+ def hello_world():
169
+ print("Hello from Notionary!")
170
+ ```
171
+
172
+ ### To-do Lists
173
+
174
+ ```markdown
175
+ - [ ] Define project scope
176
+ - [x] Create timeline
177
+ - [ ] Assign resources
178
+ ```
179
+
180
+ ### Tables
181
+
182
+ ```markdown
183
+ | Feature | Status | Priority |
184
+ | --------------- | ----------- | -------- |
185
+ | API Integration | Complete | High |
186
+ | Documentation | In Progress | Medium |
187
+ ```
188
+
189
+ ### More Elements
190
+
191
+ ```markdown
192
+ ![Caption](https://example.com/image.jpg)
193
+ @[Caption](https://youtube.com/watch?v=...)
194
+ [bookmark](https://example.com "Title" "Description")
195
+ ```
196
+
197
+ ## Examples
198
+
199
+ Explore the `examples/` directory for comprehensive guides:
200
+
201
+ ### 🚀 Core Examples
202
+ - [**Page Management**](examples/page_example.py) - Create, update, and manage Notion pages
203
+ - [**Page Operations**](examples/page.py) - Advanced page manipulation and content handling
204
+ - [**Database Operations**](examples/database.py) - Connect to and manage Notion databases
205
+ - [**Database Iteration**](examples/database_iteration.py) - Query and filter database entries
206
+ - [**Workspace Discovery**](examples/workspace_discovery.py) - Explore and discover your Notion workspace
207
+
208
+ ### 📝 Markdown Examples
209
+ - [**Basic Formatting**](examples/markdown/basic.py) - Text formatting, lists, and basic elements
210
+ - [**Callouts**](examples/markdown/callout.py) - Create beautiful callout blocks with icons
211
+ - [**Toggles**](examples/markdown/toggle.py) - Collapsible content sections
212
+ - [**Multi-Column Layouts**](examples/markdown/columns.py) - Side-by-side content arrangement
213
+ - [**Code Blocks**](examples/markdown/code.py) - Syntax-highlighted code examples
214
+ - [**Tables**](examples/markdown/table.py) - Structured data presentation
215
+ - [**Media Embeds**](examples/markdown/embed.py) - Images, videos, and rich media
216
+ - [**Audio Content**](examples/markdown/audio.py) - Audio file integration
217
+
218
+ Each example is self-contained and demonstrates specific features with practical use cases.
219
+
220
+ ## Contributing
221
+
222
+ Contributions welcome — feel free to submit a pull request!
223
+
@@ -1,6 +1,5 @@
1
1
  notionary/__init__.py,sha256=4eO6Jx57VRR_Ejo9w7IJeET8SZOvxFl_1lOB39o39No,250
2
2
  notionary/base_notion_client.py,sha256=bqQu9uEdDmZhMAGGv6e_B8mBLOAWLWjoP8s9L6UaQks,6714
3
- notionary/workspace.py,sha256=kW9fbVUSECivlvABBwnks2nALfk09V6g6Oc2Eq_pK5U,2511
4
3
  notionary/blocks/__init__.py,sha256=MFBxK3zZ28tV_u8XT20Q6HY39KENCfJDfDflLTYVt4E,2019
5
4
  notionary/blocks/audio_element.py,sha256=rQbWz8akbobci8CFvnFuuHoDNJCG7mcuSXdB8hHjqLU,5355
6
5
  notionary/blocks/bookmark_element.py,sha256=gW6uKCkuWFpHEzq-g1CbvKvma6hyTMUH2XMczI0U-5M,8080
@@ -17,27 +16,25 @@ notionary/blocks/notion_block_client.py,sha256=mLkJ9mbfTZB7oml2hjXxxmr9XUCfM3u_8
17
16
  notionary/blocks/notion_block_element.py,sha256=r27KYICQvdmOg3AyzHE6ouWjX8WuJmX1bERCgkBdaGE,1263
18
17
  notionary/blocks/numbered_list_element.py,sha256=BL_mui9vJ0usOFbRrNZRP_IY8QLG3vGFRYiPPsq_OJw,2596
19
18
  notionary/blocks/paragraph_element.py,sha256=-zCwJOanOVjv07DRArD13yRYaxfL8sCob6oN3PKvzNc,3188
19
+ notionary/blocks/prompts/element_prompt_builder.py,sha256=rYMKPmpEFyk26JFZlwcTzMHATpvHnn4Dn284vewFog0,2953
20
+ notionary/blocks/prompts/element_prompt_content.py,sha256=ItnhGwKsHGnnY9E_LGgZZeTCT9ZfnkJY8xad4wFViWk,1567
20
21
  notionary/blocks/qoute_element.py,sha256=pkeT6N7PZrepIod8WLrY1DMe2DW6fM98Y4zXiiACenw,6059
22
+ notionary/blocks/registry/block_registry.py,sha256=hEBa8PdFn1CeevFBqKbcFX7yuBjulwGASUMKoHRsm9s,4305
23
+ notionary/blocks/registry/block_registry_builder.py,sha256=FA_0WOajaeVaqdphNh8EyN0p_7ItzFqEufYa6YVBLeY,8731
21
24
  notionary/blocks/table_element.py,sha256=DzXbSVm3KwTfnLF2cp765gj-VC50zWvj_0RU_WcQDJw,11184
22
25
  notionary/blocks/text_inline_formatter.py,sha256=aKnaR1LvmbBkRdJVId8xtMkrbw1xaw6e4ZLUH97XLfU,8583
23
26
  notionary/blocks/todo_element.py,sha256=6ndhgGJNiy7eb-Ll--Va7zEqQySxFAFYpzY4PWJbGUQ,4059
24
27
  notionary/blocks/toggle_element.py,sha256=2gofKL4ndVkRxkuH-iYVx0YFUc649gpQQbZtwh2BpY8,11017
25
28
  notionary/blocks/toggleable_heading_element.py,sha256=fkXvKtgCg6PuHqrHq7LupmqzpasJ1IyVf2RBLYTiVIo,9893
26
29
  notionary/blocks/video_element.py,sha256=C19XxFRyAUEbhhC9xvhAAGN8YBYP6ON1vm_x7b_gUrY,5664
27
- notionary/blocks/prompts/element_prompt_builder.py,sha256=rYMKPmpEFyk26JFZlwcTzMHATpvHnn4Dn284vewFog0,2953
28
- notionary/blocks/prompts/element_prompt_content.py,sha256=ItnhGwKsHGnnY9E_LGgZZeTCT9ZfnkJY8xad4wFViWk,1567
29
- notionary/blocks/registry/block_registry.py,sha256=hEBa8PdFn1CeevFBqKbcFX7yuBjulwGASUMKoHRsm9s,4305
30
- notionary/blocks/registry/block_registry_builder.py,sha256=FA_0WOajaeVaqdphNh8EyN0p_7ItzFqEufYa6YVBLeY,8731
31
- notionary/cli/main.py,sha256=-rQoDGvDrFIOvoWzJIIrXQQz4H12D3TkwdNdEF9SEGQ,12883
32
- notionary/cli/onboarding.py,sha256=KQornxGBxsyXa0PfVqt4KPq-3B3Ry1sLd5DB3boAB04,3350
33
30
  notionary/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
31
  notionary/database/client.py,sha256=ZcfydeYlpgGJt6wV1ib33KeXUiL-cGNJ1qraQZ4RVRc,4775
35
32
  notionary/database/database_exceptions.py,sha256=jwFdxoIQHLO3mO3p5t890--1FjbTX60fNyqBAe-sszo,452
36
33
  notionary/database/factory.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
34
  notionary/database/filter_builder.py,sha256=4EJnWUF73l5oi-HnvMu-mI1OncLzEs2o2mr_xG75quk,6315
38
- notionary/database/notion_database.py,sha256=fM6lmL673bKQPfDDj6tyj8K7yO0gSi8veEiUIE5enF8,15497
39
- notionary/database/notion_database_provider.py,sha256=2zaRycrbnceV_EbZugdNM_YF9iCGBen-A6E4jvZe2mU,9119
40
35
  notionary/database/models/page_result.py,sha256=Vmm5_oYpYAkIIJVoTd1ZZGloeC3cmFLMYP255mAmtaw,233
36
+ notionary/database/notion_database.py,sha256=7_BdQUbXjdSHgK9heV3aIWLHdr5jPtofsY3_LHo2ZKs,15317
37
+ notionary/database/notion_database_provider.py,sha256=-cw94OdvjX3aFeWxIf1eD4dLcRaDC6ni6DBTqO0JPaY,9065
41
38
  notionary/elements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
39
  notionary/models/notion_block_response.py,sha256=gzL4C6K9QPcaMS6NbAZaRceSEnMbNwYBVVzxysza5VU,6002
43
40
  notionary/models/notion_database_response.py,sha256=3kvADIP1dSxgITSK4n8Ex3QpF8n_Lxnu_IXbPVGcq4o,7648
@@ -45,18 +42,18 @@ notionary/models/notion_page_response.py,sha256=7ZwDYhlyK-avix_joQpGuNQZopjlQFI8
45
42
  notionary/models/search_response.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
43
  notionary/page/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
44
  notionary/page/client.py,sha256=XQ72lOEwn-gO8fmhKSKHqSHs3hRmoKH0TkJ3TtblcAg,4030
48
- notionary/page/markdown_syntax_prompt_generator.py,sha256=uHCPNV9aQi3GzLVimyUKwza29hfxu6DTMVIa_QevJbk,4987
49
- notionary/page/notion_page.py,sha256=xxvXJz3wg1TCUyjN6-U6na9zps4fsLlwoVAj3ylBLLA,19151
50
- notionary/page/notion_to_markdown_converter.py,sha256=_MJWWwsBvgZ3a8tLZ23ZCIA_G9Qfvt2JG1FqVTlRxHs,6308
51
- notionary/page/property_formatter.py,sha256=_978ViH83gfcr-XtDscWTfyBI2srGW2hzC-gzgp5NR8,3788
52
- notionary/page/search_filter_builder.py,sha256=wZpW_KHmPXql3sNIyQd9EzZ2-ERy2i0vYNdoLkoBUfc,4597
53
- notionary/page/utils.py,sha256=2nfBrWeczBdPH13R3q8dKP4OY4MwEdfKbcs2UJ9kg1o,2041
54
45
  notionary/page/content/notion_page_content_chunker.py,sha256=kWJnV9GLU5YLgSVPKOjwMBbG_CMAmVRkuDtwJYb_UAA,3316
55
46
  notionary/page/content/page_content_retriever.py,sha256=iNazSf0uv_gi0J816-SZn4Lw4qbAxRHG90k9Jy_qw2Q,1587
56
47
  notionary/page/content/page_content_writer.py,sha256=VVvK-Z8NvyIhi7Crcm9mZQuuD_L72NsqSQg9gf33Zwk,7369
57
48
  notionary/page/formatting/markdown_to_notion_converter.py,sha256=9RyGON8VrJv6XifdQdOt5zKgKT3irc974zcbGDBhmLY,17328
58
49
  notionary/page/formatting/spacer_rules.py,sha256=j2RHvdXT3HxXPVBEuCtulyy9cPxsEcOmj71pJqV-D3M,15677
50
+ notionary/page/markdown_syntax_prompt_generator.py,sha256=uHCPNV9aQi3GzLVimyUKwza29hfxu6DTMVIa_QevJbk,4987
51
+ notionary/page/notion_page.py,sha256=G639FNg8eIvQ9t3-Aa3W0vtmxHOhwCnuFICLFKUA4KE,19142
52
+ notionary/page/notion_to_markdown_converter.py,sha256=_MJWWwsBvgZ3a8tLZ23ZCIA_G9Qfvt2JG1FqVTlRxHs,6308
59
53
  notionary/page/properites/property_value_extractor.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
+ notionary/page/property_formatter.py,sha256=_978ViH83gfcr-XtDscWTfyBI2srGW2hzC-gzgp5NR8,3788
55
+ notionary/page/search_filter_builder.py,sha256=wZpW_KHmPXql3sNIyQd9EzZ2-ERy2i0vYNdoLkoBUfc,4597
56
+ notionary/page/utils.py,sha256=2nfBrWeczBdPH13R3q8dKP4OY4MwEdfKbcs2UJ9kg1o,2041
60
57
  notionary/util/__init__.py,sha256=EzAmP2uEFfazWapb41BY9kAua1ZEiuLRPaBMtw_cOYg,358
61
58
  notionary/util/factory_decorator.py,sha256=3SD63EPxXMmKQ8iF7sF88xUFMG8dy14L2DJZ7XdcYm4,1110
62
59
  notionary/util/fuzzy_matcher.py,sha256=RYR86hMTp8lrWl3PeOa3RpDpzh04HJ30qrIlrq6_qDo,2442
@@ -64,9 +61,8 @@ notionary/util/logging_mixin.py,sha256=d5sRSmUtgQeuckdNBkO025IXPGe4oOb-7ueVAIP8a
64
61
  notionary/util/page_id_utils.py,sha256=AA00kRO-g3Cc50tf_XW_tb5RBuPKLuBxRa0D8LYhLXg,736
65
62
  notionary/util/singleton_decorator.py,sha256=CKAvykndwPRZsA3n3MAY_XdCR59MBjjKP0vtm2BcvF0,428
66
63
  notionary/util/singleton_metaclass.py,sha256=uNeHiqS6TwhljvG1RE4NflIp2HyMuMmrCg2xI-vxmHE,809
67
- notionary-0.2.14.dist-info/licenses/LICENSE,sha256=zOm3cRT1qD49eg7vgw95MI79rpUAZa1kRBFwL2FkAr8,1120
68
- notionary-0.2.14.dist-info/METADATA,sha256=R94Tb7hWlk_LhA7bKSbgyLe6K9GgcZstI0WAfaNw1qU,7678
69
- notionary-0.2.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
70
- notionary-0.2.14.dist-info/entry_points.txt,sha256=V7X21u3QNm7h7p6Cx0Sx2SO3mtmA7gVwXM8lNYnv9fk,54
71
- notionary-0.2.14.dist-info/top_level.txt,sha256=fhONa6BMHQXqthx5PanWGbPL0b8rdFqhrJKVLf_adSs,10
72
- notionary-0.2.14.dist-info/RECORD,,
64
+ notionary/workspace.py,sha256=kW9fbVUSECivlvABBwnks2nALfk09V6g6Oc2Eq_pK5U,2511
65
+ notionary-0.2.15.dist-info/LICENSE,sha256=zOm3cRT1qD49eg7vgw95MI79rpUAZa1kRBFwL2FkAr8,1120
66
+ notionary-0.2.15.dist-info/METADATA,sha256=rFzkz08VRrwANPLLkbDTrfG5vhR7opqj35ApvYQd0kA,6784
67
+ notionary-0.2.15.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
68
+ notionary-0.2.15.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
notionary/cli/main.py DELETED
@@ -1,376 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Notionary CLI - Integration Key Setup
4
- """
5
-
6
- import click
7
- import os
8
- import platform
9
- import asyncio
10
- import logging
11
- from pathlib import Path
12
- from dotenv import load_dotenv
13
- from rich.console import Console
14
- from rich.panel import Panel
15
- from rich.prompt import Prompt, Confirm
16
- from rich.table import Table
17
- from rich import box
18
- from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
19
- from notionary.workspace import NotionWorkspace
20
-
21
-
22
- # Disable logging for CLI usage
23
- def disable_notionary_logging():
24
- """Disable logging for notionary modules when used in CLI"""
25
- # Option 1: Set to WARNING level (recommended for CLI)
26
- logging.getLogger("notionary").setLevel(logging.WARNING)
27
- logging.getLogger("DatabaseDiscovery").setLevel(logging.WARNING)
28
- logging.getLogger("NotionClient").setLevel(logging.WARNING)
29
-
30
-
31
- def enable_verbose_logging():
32
- """Enable verbose logging for debugging (use with --verbose flag)"""
33
- logging.getLogger("notionary").setLevel(logging.DEBUG)
34
- logging.getLogger("DatabaseDiscovery").setLevel(logging.DEBUG)
35
- logging.getLogger("NotionClient").setLevel(logging.DEBUG)
36
-
37
-
38
- # Initialize logging configuration for CLI
39
- disable_notionary_logging()
40
-
41
- console = Console()
42
-
43
-
44
- def get_paste_tips():
45
- """Get platform-specific paste tips"""
46
- system = platform.system().lower()
47
-
48
- if system == "darwin": # macOS
49
- return [
50
- "• Terminal: [cyan]Cmd+V[/cyan]",
51
- "• iTerm2: [cyan]Cmd+V[/cyan]",
52
- ]
53
- elif system == "windows":
54
- return [
55
- "• PowerShell: [cyan]Right-click[/cyan] or [cyan]Shift+Insert[/cyan]",
56
- "• cmd: [cyan]Right-click[/cyan]",
57
- ]
58
- else: # Linux and others
59
- return [
60
- "• Terminal: [cyan]Ctrl+Shift+V[/cyan] or [cyan]Right-click[/cyan]",
61
- "• Some terminals: [cyan]Shift+Insert[/cyan]",
62
- ]
63
-
64
-
65
- def show_paste_tips():
66
- """Show platform-specific paste tips"""
67
- console.print("\n[bold yellow]💡 Paste Tips:[/bold yellow]")
68
- for tip in get_paste_tips():
69
- console.print(tip)
70
- console.print()
71
-
72
-
73
- def get_notion_secret() -> str:
74
- """Get NOTION_SECRET using the same logic as NotionClient"""
75
- load_dotenv()
76
- return os.getenv("NOTION_SECRET", "")
77
-
78
-
79
- async def fetch_notion_databases_with_progress():
80
- """Fetch databases using DatabaseDiscovery with progress animation"""
81
- try:
82
- workspace = NotionWorkspace()
83
-
84
- # Create progress display with custom spinner
85
- with Progress(
86
- SpinnerColumn(spinner_name="dots12", style="cyan"),
87
- TextColumn("[bold blue]Discovering databases..."),
88
- TimeElapsedColumn(),
89
- console=console,
90
- transient=True,
91
- ) as progress:
92
- # Add progress task
93
- task = progress.add_task("Fetching...", total=None)
94
-
95
- # Fetch databases
96
- databases = await workspace.list_all_databases(limit=50)
97
-
98
- # Update progress to show completion
99
- progress.update(
100
- task, description=f"[bold green]Found {len(databases)} databases!"
101
- )
102
-
103
- # Brief pause to show completion
104
- await asyncio.sleep(0.5)
105
-
106
- return {"databases": databases, "success": True}
107
-
108
- except Exception as e:
109
- return {"error": str(e), "success": False}
110
-
111
-
112
- def show_databases_overview(api_key: str):
113
- """Show available databases with nice formatting"""
114
- console.print("\n[bold blue]🔍 Connecting to Notion...[/bold blue]")
115
-
116
- # Run async function in sync context
117
- try:
118
- result = asyncio.run(fetch_notion_databases_with_progress())
119
- except Exception as e:
120
- console.print(
121
- Panel.fit(
122
- f"[bold red]❌ Unexpected error[/bold red]\n\n"
123
- f"[red]{str(e)}[/red]\n\n"
124
- "[yellow]Please check:[/yellow]\n"
125
- "• Your internet connection\n"
126
- "• Your integration key validity\n"
127
- "• Try running the command again",
128
- title="Connection Error",
129
- )
130
- )
131
- return
132
-
133
- if not result["success"]:
134
- console.print(
135
- Panel.fit(
136
- f"[bold red]❌ Could not fetch databases[/bold red]\n\n"
137
- f"[red]{result['error']}[/red]\n\n"
138
- "[yellow]Common issues:[/yellow]\n"
139
- "• Check your integration key\n"
140
- "• Make sure your integration has access to databases\n"
141
- "• Visit your integration settings to grant access",
142
- title="Connection Error",
143
- )
144
- )
145
- return
146
-
147
- databases = result["databases"]
148
-
149
- if not databases:
150
- console.print(
151
- Panel.fit(
152
- "[bold yellow]⚠️ No databases found[/bold yellow]\n\n"
153
- "Your integration key is valid, but no databases are accessible.\n\n"
154
- "[bold blue]To grant access:[/bold blue]\n"
155
- "1. Go to any Notion database\n"
156
- "2. Click the '...' menu (top right)\n"
157
- "3. Go to 'Add connections'\n"
158
- "4. Find and select your integration\n\n"
159
- "[cyan]https://www.notion.so/help/add-and-manage-connections-with-the-api[/cyan]",
160
- title="No Databases Available",
161
- )
162
- )
163
- return
164
-
165
- # Create beautiful table
166
- table = Table(
167
- title=f"📊 Available Databases ({len(databases)} found)",
168
- box=box.ROUNDED,
169
- title_style="bold green",
170
- header_style="bold cyan",
171
- )
172
-
173
- table.add_column("#", style="dim", justify="right", width=3)
174
- table.add_column("Database Name", style="bold white", min_width=25)
175
- table.add_column("ID", style="dim cyan", min_width=36)
176
-
177
- for i, (title, db_id) in enumerate(databases, 1):
178
- table.add_row(str(i), title or "Untitled Database", db_id)
179
-
180
- console.print("\n")
181
- console.print(table)
182
-
183
- # Success message with next steps
184
- console.print(
185
- Panel.fit(
186
- "[bold green]🎉 Setup Complete![/bold green]\n\n"
187
- f"Found [bold cyan]{len(databases)}[/bold cyan] accessible database(s).\n"
188
- "You can now use notionary in your Python code!\n\n"
189
- "[bold yellow]💡 Tip:[/bold yellow] Run [cyan]notionary db[/cyan] anytime to see this overview again.",
190
- title="Ready to Go!",
191
- )
192
- )
193
-
194
-
195
- @click.group()
196
- @click.version_option() # Automatische Version aus setup.py
197
- @click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging")
198
- def main(verbose):
199
- """
200
- Notionary CLI - Notion API Integration
201
- """
202
- if verbose:
203
- enable_verbose_logging()
204
- console.print("[dim]Verbose logging enabled[/dim]")
205
- pass
206
-
207
-
208
- @main.command()
209
- def init():
210
- """
211
- Setup your Notion Integration Key
212
- """
213
- # Check if key already exists
214
- existing_key = get_notion_secret()
215
-
216
- if existing_key:
217
- console.print(
218
- Panel.fit(
219
- "[bold green]✅ You're all set![/bold green]\n"
220
- f"Your Notion Integration Key is already configured.\n"
221
- f"Key: [dim]{existing_key[:8]}...[/dim]",
222
- title="Already Configured",
223
- )
224
- )
225
-
226
- # Option to reconfigure or show databases
227
- choice = Prompt.ask(
228
- "\n[yellow]What would you like to do?[/yellow]",
229
- choices=["show", "update", "exit"],
230
- default="show",
231
- )
232
-
233
- if choice == "show":
234
- show_databases_overview(existing_key)
235
- elif choice == "update":
236
- setup_new_key()
237
- else:
238
- console.print("\n[blue]Happy coding! 🚀[/blue]")
239
- else:
240
- # No key found, start setup
241
- console.print(
242
- Panel.fit(
243
- "[bold green]🚀 Notionary Setup[/bold green]\n"
244
- "Enter your Notion Integration Key to get started...\n\n"
245
- "[bold blue]🔗 Create an Integration Key or get an existing one:[/bold blue]\n"
246
- "[cyan]https://www.notion.so/profile/integrations[/cyan]",
247
- title="Initialization",
248
- )
249
- )
250
- setup_new_key()
251
-
252
-
253
- @main.command()
254
- def db() -> None:
255
- """
256
- Show available Notion databases
257
- """
258
- existing_key = get_notion_secret()
259
-
260
- if not existing_key:
261
- console.print(
262
- Panel.fit(
263
- "[bold red]❌ No Integration Key found![/bold red]\n\n"
264
- "Please run [cyan]notionary init[/cyan] first to set up your key.",
265
- title="Not Configured",
266
- )
267
- )
268
- return
269
-
270
- show_databases_overview(existing_key)
271
-
272
-
273
- def setup_new_key():
274
- """Handle the key setup process"""
275
- try:
276
- # Show Integration Key creation link
277
- console.print("\n[bold blue]🔗 Create an Integration Key:[/bold blue]")
278
- console.print("[cyan]https://www.notion.so/profile/integrations[/cyan]")
279
- console.print()
280
-
281
- # Get integration key
282
- integration_key = Prompt.ask("[bold cyan]Notion Integration Key[/bold cyan]")
283
-
284
- # Input validation
285
- if not integration_key or not integration_key.strip():
286
- console.print("[bold red]❌ Integration Key cannot be empty![/bold red]")
287
- return
288
-
289
- # Trim whitespace
290
- integration_key = integration_key.strip()
291
-
292
- # Check for common paste issues
293
- if integration_key in ["^V", "^v", "^C", "^c"]:
294
- console.print("[bold red]❌ Paste didn't work! Try:[/bold red]")
295
- show_paste_tips()
296
- return
297
-
298
- # Show masked feedback that paste worked
299
- masked_key = "•" * len(integration_key)
300
- console.print(
301
- f"[dim]Received: {masked_key} ({len(integration_key)} characters)[/dim]"
302
- )
303
-
304
- # Basic validation for Notion keys
305
- if not integration_key.startswith("ntn_") or len(integration_key) < 30:
306
- console.print(
307
- "[bold yellow]⚠️ Warning: This doesn't look like a valid Notion Integration Key[/bold yellow]"
308
- )
309
- console.print(
310
- "[dim]Notion keys usually start with 'ntn_' and are about 50+ characters long[/dim]"
311
- )
312
- if not Confirm.ask("Continue anyway?"):
313
- return
314
-
315
- # Save the key
316
- if save_integration_key(integration_key):
317
- # Show databases overview after successful setup
318
- show_databases_overview(integration_key)
319
-
320
- except KeyboardInterrupt:
321
- console.print("\n[yellow]Setup cancelled.[/yellow]")
322
- except Exception as e:
323
- console.print(f"\n[bold red]❌ Error during setup: {e}[/bold red]")
324
- raise click.Abort()
325
-
326
-
327
- def save_integration_key(integration_key: str) -> bool:
328
- """Save the integration key to .env file"""
329
- try:
330
- # .env Datei im aktuellen Verzeichnis erstellen/aktualisieren
331
- env_file = Path.cwd() / ".env"
332
-
333
- # Bestehende .env lesen falls vorhanden
334
- existing_lines = []
335
- if env_file.exists():
336
- with open(env_file, "r", encoding="utf-8") as f:
337
- existing_lines = [line.rstrip() for line in f.readlines()]
338
-
339
- # NOTION_SECRET Zeile hinzufügen/ersetzen
340
- updated_lines = []
341
- notion_secret_found = False
342
-
343
- for line in existing_lines:
344
- if line.startswith("NOTION_SECRET="):
345
- updated_lines.append(f"NOTION_SECRET={integration_key}")
346
- notion_secret_found = True
347
- else:
348
- updated_lines.append(line)
349
-
350
- # Falls NOTION_SECRET noch nicht existiert, hinzufügen
351
- if not notion_secret_found:
352
- updated_lines.append(f"NOTION_SECRET={integration_key}")
353
-
354
- # .env Datei schreiben
355
- with open(env_file, "w", encoding="utf-8") as f:
356
- f.write("\n".join(updated_lines) + "\n")
357
-
358
- # Verification
359
- written_key = get_notion_secret()
360
- if written_key == integration_key:
361
- console.print(
362
- "\n[bold green]✅ Integration Key saved and verified![/bold green]"
363
- )
364
- console.print(f"[dim]Configuration: {env_file}[/dim]")
365
- return True
366
- else:
367
- console.print("\n[bold red]❌ Error: Key verification failed![/bold red]")
368
- return False
369
-
370
- except Exception as e:
371
- console.print(f"\n[bold red]❌ Error saving key: {e}[/bold red]")
372
- return False
373
-
374
-
375
- if __name__ == "__main__":
376
- main()
@@ -1,117 +0,0 @@
1
- import asyncio
2
- from dataclasses import dataclass
3
- from notionary import NotionDatabase
4
-
5
-
6
- @dataclass
7
- class OnboardingPageResult:
8
- url: str
9
- tile: str
10
- emoji: str
11
-
12
-
13
- async def generate_doc_for_database(
14
- datbase_name: str,
15
- ) -> OnboardingPageResult:
16
- database = await NotionDatabase.from_database_name(datbase_name)
17
- page = await database.create_blank_page()
18
-
19
- page_title = "Welcome to Notionary!"
20
- page_icon = "📚"
21
-
22
- markdown_content = """!> [🚀] This page was created fully automatically and serves as a showcase of what is possible with Notionary.
23
-
24
- ---
25
-
26
- ## 🗃️ Working with Databases
27
-
28
- Discover and manage your Notion databases programmatically:
29
-
30
- ```python
31
- import asyncio
32
- from notionary import NotionDatabase, DatabaseDiscovery
33
-
34
- async def main():
35
- # Discover available databases
36
- discovery = DatabaseDiscovery()
37
- await discovery()
38
-
39
- # Connect to a database by name
40
- db = await NotionDatabase.from_database_name("Projects")
41
-
42
- # Create a new page in the database
43
- page = await db.create_blank_page()
44
-
45
- # Query pages from database
46
- async for page in db.iter_pages():
47
- title = await page.get_title()
48
- print(f"Page: {title}")
49
-
50
- if __name__ == "__main__":
51
- asyncio.run(main())
52
- ```
53
-
54
- ## 📄 Creating and Managing Pages
55
- Create and update Notion pages with rich content:
56
- ```python
57
- import asyncio
58
- from notionary import NotionPage
59
-
60
- async def main():
61
- # Create a page from URL
62
- page = NotionPage.from_url("https://www.notion.so/your-page-url")
63
-
64
- # Or find by name
65
- page = await NotionPage.from_page_name("My Project Page")
66
-
67
- # Update page metadata
68
- await page.set_title("Updated Title")
69
- await page.set_emoji_icon("🚀")
70
- await page.set_random_gradient_cover()
71
-
72
- # Add markdown content
73
- markdown = '''
74
- # Project Overview
75
-
76
- !> [💡] This page was created programmatically using Notionary.
77
-
78
- ## Features
79
- - **Rich** Markdown support
80
- - Async functionality
81
- - Custom syntax extensions
82
- '''
83
-
84
- await page.replace_content(markdown)
85
-
86
- if __name__ == "__main__":
87
- asyncio.run(main())
88
- ```
89
-
90
- ## 📊 Tables and Structured Data
91
- Create tables for organizing information:
92
- FeatureStatusPriorityAPI IntegrationCompleteHighDocumentationIn ProgressMediumDatabase QueriesCompleteHighFile UploadsCompleteMedium
93
-
94
- 🎥 Media Embedding
95
- Embed videos directly in your pages:
96
- @[Caption](https://www.youtube.com/watch?v=dQw4w9WgXcQ) - Never gonna give you up!
97
-
98
- Happy building with Notionary! 🎉"""
99
-
100
- await page.set_title(page_title)
101
- await page.set_emoji_icon(page_icon)
102
- await page.set_random_gradient_cover()
103
- await page.append_markdown(markdown_content)
104
-
105
- url = await page.get_url()
106
-
107
- return OnboardingPageResult(
108
- url=url,
109
- tile=page_title,
110
- emoji=page_icon,
111
- )
112
-
113
-
114
- if __name__ == "__main__":
115
- print("🚀 Starting Notionary onboarding page generation...")
116
- result = asyncio.run(generate_doc_for_database("Wissen & Notizen"))
117
- print(f"✅ Onboarding page created: {result.tile} {result.emoji} - {result.url}")
@@ -1,276 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: notionary
3
- Version: 0.2.14
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
- Requires-Dist: posthog>=3.0.0
17
- Requires-Dist: click>=8.0.0
18
- Requires-Dist: openai>=1.30.1
19
- Requires-Dist: langchain>=0.2.0
20
- Requires-Dist: tiktoken>=0.6.0
21
- Dynamic: author
22
- Dynamic: author-email
23
- Dynamic: classifier
24
- Dynamic: description
25
- Dynamic: description-content-type
26
- Dynamic: home-page
27
- Dynamic: license-file
28
- Dynamic: requires-dist
29
- Dynamic: requires-python
30
- Dynamic: summary
31
-
32
- # Notionary 📝
33
-
34
- [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/downloads/)
35
- [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
36
-
37
- **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.
38
-
39
- ---
40
-
41
- ## Features
42
-
43
- - **Rich Markdown Support**: Create Notion pages using intuitive Markdown syntax with custom extensions
44
- - **Dynamic Database Operations**: Create, update, and query database entries with schema auto-detection
45
- - **Extensible Block Registry**: Add, customize, or remove Notion block elements with a flexible registry pattern
46
- - **LLM-Ready Prompts**: Generate system prompts explaining Markdown syntax for LLMs to create Notion content
47
- - **Async-First Design**: Built for modern Python with full async/await support
48
- - **Schema-Based Validation**: Automatic property validation based on database schemas
49
- - **Intelligent Content Conversion**: Bidirectional conversion between Markdown and Notion blocks
50
-
51
- ---
52
-
53
- ## Installation
54
-
55
- ```bash
56
- pip install notionary
57
- ```
58
-
59
- ---
60
-
61
- ## Quick Start
62
-
63
- ### Creating and Managing Pages
64
-
65
- ```python
66
- import asyncio
67
- from notionary import NotionPage
68
-
69
- async def main():
70
- # Create a page from URL
71
- page = NotionPage.from_url("https://www.notion.so/your-page-url")
72
-
73
- # Or find by name
74
- page = await NotionPage.from_page_name("My Project Page")
75
-
76
- # Update page metadata
77
- await page.set_title("Updated Title")
78
- await page.set_emoji_icon("🚀")
79
- await page.set_random_gradient_cover()
80
-
81
- # Add markdown content
82
- markdown = """
83
- # Project Overview
84
-
85
- !> [💡] This page was created programmatically using Notionary.
86
-
87
- ## Features
88
- - **Rich** Markdown support
89
- - Async functionality
90
- - Custom syntax extensions
91
-
92
- +++ Implementation Details
93
- | Notionary uses a custom converter to transform Markdown into Notion blocks.
94
- | This makes it easy to create rich content programmatically.
95
- """
96
-
97
- await page.replace_content(markdown)
98
-
99
- if __name__ == "__main__":
100
- asyncio.run(main())
101
- ```
102
-
103
- ### Working with Databases
104
-
105
- ```python
106
- import asyncio
107
- from notionary import NotionDatabase, DatabaseDiscovery
108
-
109
- async def main():
110
- # Discover available databases
111
- discovery = DatabaseDiscovery()
112
- await discovery()
113
-
114
- # Connect to a database by name
115
- db = await NotionDatabase.from_database_name("Projects")
116
-
117
- # Create a new page in the database
118
- page = await db.create_blank_page()
119
-
120
- # Set properties
121
- await page.set_property_value_by_name("Status", "In Progress")
122
- await page.set_property_value_by_name("Priority", "High")
123
-
124
- # Query pages from database
125
- async for page in db.iter_pages():
126
- title = await page.get_title()
127
- print(f"Page: {title}")
128
-
129
- if __name__ == "__main__":
130
- asyncio.run(main())
131
- ```
132
-
133
- ## Custom Markdown Syntax
134
-
135
- Notionary extends standard Markdown with special syntax to support Notion-specific features:
136
-
137
- ### Text Formatting
138
-
139
- - Standard: `**bold**`, `*italic*`, `~~strikethrough~~`, `` `code` ``
140
- - Links: `[text](url)`
141
- - Quotes: `> This is a quote`
142
- - Divider: `---`
143
-
144
- ### Callouts
145
-
146
- ```markdown
147
- !> [💡] This is a default callout with the light bulb emoji
148
- !> [🔔] This is a notification with a bell emoji
149
- !> [⚠️] Warning: This is an important note
150
- ```
151
-
152
- ### Toggles
153
-
154
- ```markdown
155
- +++ How to use Notionary
156
- | 1. Initialize with NotionPage
157
- | 2. Update metadata with set_title(), set_emoji_icon(), etc.
158
- | 3. Add content with replace_content() or append_markdown()
159
- ```
160
-
161
- ### Multi-Column Layout
162
-
163
- ```markdown
164
- ::: columns
165
- ::: column
166
-
167
- ## Left Column
168
-
169
- - Item 1
170
- - Item 2
171
- - Item 3
172
- :::
173
- ::: column
174
-
175
- ## Right Column
176
-
177
- This text appears in the second column. Multi-column layouts are perfect for:
178
-
179
- - Comparing features
180
- - Creating side-by-side content
181
- - Improving readability of wide content
182
- :::
183
- :::
184
- ```
185
-
186
- ### Code Blocks
187
-
188
- ```python
189
- def hello_world():
190
- print("Hello from Notionary!")
191
- ```
192
-
193
- ### To-do Lists
194
-
195
- ```markdown
196
- - [ ] Define project scope
197
- - [x] Create timeline
198
- - [ ] Assign resources
199
- ```
200
-
201
- ### Tables
202
-
203
- ```markdown
204
- | Feature | Status | Priority |
205
- | --------------- | ----------- | -------- |
206
- | API Integration | Complete | High |
207
- | Documentation | In Progress | Medium |
208
- ```
209
-
210
- ### More Elements
211
-
212
- ```markdown
213
- ![Caption](https://example.com/image.jpg)
214
- @[Caption](https://youtube.com/watch?v=...)
215
- [bookmark](https://example.com "Title" "Description")
216
- ```
217
-
218
- ## Block Registry & Customization
219
-
220
- ```python
221
- from notionary import NotionPage, BlockRegistryBuilder
222
-
223
- # Create a custom registry with only the elements you need
224
- custom_registry = (
225
- BlockRegistryBuilder()
226
- .with_headings()
227
- .with_callouts()
228
- .with_toggles()
229
- .with_columns() # Include multi-column support
230
- .with_code()
231
- .with_todos()
232
- .with_paragraphs()
233
- .build()
234
- )
235
-
236
- # Apply this registry to a page
237
- page = NotionPage.from_url("https://www.notion.so/your-page-url")
238
- page.block_registry = custom_registry
239
-
240
- # Replace content using only supported elements
241
- await page.replace_content("# Custom heading with selected elements only")
242
- ```
243
-
244
- ## AI-Ready: Generate LLM Prompts
245
-
246
- ```python
247
- from notionary import BlockRegistryBuilder
248
-
249
- # Create a registry with all standard elements
250
- registry = BlockRegistryBuilder.create_full_registry()
251
-
252
- # Generate the LLM system prompt
253
- llm_system_prompt = registry.get_notion_markdown_syntax_prompt()
254
- print(llm_system_prompt)
255
- ```
256
-
257
- ## Examples
258
-
259
- See the `examples/` folder for:
260
-
261
- - [Database discovery and querying](examples/database_discovery_example.py)
262
- - [Rich page creation with Markdown](examples/page_example.py)
263
- - [Database management](examples/database_management_example.py)
264
- - [Iterating through database entries](examples/database_iteration_example.py)
265
- - [Temporary usage & debugging](examples/temp.py)
266
-
267
- ## Perfect for AI Agents and Automation
268
-
269
- - **LLM Integration**: Generate Notion-compatible content with any LLM using the system prompt generator
270
- - **Dynamic Content Generation**: AI agents can generate content in Markdown and render it directly as Notion pages
271
- - **Schema-Aware Operations**: Automatically validate and format properties based on database schemas
272
- - **Simplified API**: Clean, intuitive interface for both human developers and AI systems
273
-
274
- ## Contributing
275
-
276
- Contributions welcome — feel free to submit a pull request!
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- notionary = notionary.cli.main:main
@@ -1 +0,0 @@
1
- notionary