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.
- notionary/database/notion_database.py +19 -19
- notionary/database/notion_database_provider.py +6 -6
- notionary/page/notion_page.py +1 -1
- notionary-0.2.15.dist-info/METADATA +223 -0
- {notionary-0.2.14.dist-info → notionary-0.2.15.dist-info}/RECORD +17 -21
- {notionary-0.2.14.dist-info → notionary-0.2.15.dist-info}/WHEEL +1 -2
- notionary/cli/main.py +0 -376
- notionary/cli/onboarding.py +0 -117
- notionary-0.2.14.dist-info/METADATA +0 -276
- notionary-0.2.14.dist-info/entry_points.txt +0 -2
- notionary-0.2.14.dist-info/top_level.txt +0 -1
- {notionary-0.2.14.dist-info/licenses → notionary-0.2.15.dist-info}/LICENSE +0 -0
@@ -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
|
-
|
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.
|
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,
|
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(
|
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
|
71
|
+
def id(self) -> str:
|
72
72
|
"""Get the database ID (readonly)."""
|
73
|
-
return self.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
202
|
+
id=db_response.id,
|
203
203
|
title=title,
|
204
204
|
url=db_response.url,
|
205
205
|
emoji_icon=emoji_icon,
|
notionary/page/notion_page.py
CHANGED
@@ -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
|
+
[](https://www.python.org/downloads/)
|
31
|
+
[](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
|
+

|
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
|
68
|
-
notionary-0.2.
|
69
|
-
notionary-0.2.
|
70
|
-
notionary-0.2.
|
71
|
-
notionary-0.2.
|
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,,
|
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()
|
notionary/cli/onboarding.py
DELETED
@@ -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
|
-
[](https://www.python.org/downloads/)
|
35
|
-
[](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
|
-

|
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 +0,0 @@
|
|
1
|
-
notionary
|
File without changes
|