notionary 0.2.12__py3-none-any.whl → 0.2.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notionary/__init__.py +3 -20
- notionary/{notion_client.py → base_notion_client.py} +92 -98
- notionary/blocks/__init__.py +61 -0
- notionary/{elements → blocks}/audio_element.py +6 -4
- notionary/{elements → blocks}/bookmark_element.py +3 -6
- notionary/{elements → blocks}/bulleted_list_element.py +5 -7
- notionary/{elements → blocks}/callout_element.py +5 -8
- notionary/{elements → blocks}/code_block_element.py +4 -6
- notionary/{elements → blocks}/column_element.py +3 -6
- notionary/{elements → blocks}/divider_element.py +3 -6
- notionary/{elements → blocks}/embed_element.py +4 -6
- notionary/{elements → blocks}/heading_element.py +5 -9
- notionary/{elements → blocks}/image_element.py +4 -6
- notionary/{elements → blocks}/mention_element.py +3 -7
- notionary/blocks/notion_block_client.py +26 -0
- notionary/blocks/notion_block_element.py +34 -0
- notionary/{elements → blocks}/numbered_list_element.py +4 -7
- notionary/{elements → blocks}/paragraph_element.py +4 -7
- notionary/{prompting/element_prompt_content.py → blocks/prompts/element_prompt_builder.py} +1 -40
- notionary/blocks/prompts/element_prompt_content.py +41 -0
- notionary/{elements → blocks}/qoute_element.py +4 -6
- notionary/{elements → blocks}/registry/block_registry.py +4 -26
- notionary/{elements → blocks}/registry/block_registry_builder.py +26 -25
- notionary/{elements → blocks}/table_element.py +6 -8
- notionary/{elements → blocks}/text_inline_formatter.py +1 -4
- notionary/{elements → blocks}/todo_element.py +6 -8
- notionary/{elements → blocks}/toggle_element.py +3 -6
- notionary/{elements → blocks}/toggleable_heading_element.py +5 -8
- notionary/{elements → blocks}/video_element.py +4 -6
- notionary/cli/main.py +245 -53
- notionary/cli/onboarding.py +117 -0
- notionary/database/__init__.py +0 -0
- notionary/database/client.py +132 -0
- notionary/database/database_exceptions.py +13 -0
- notionary/database/factory.py +0 -0
- notionary/database/filter_builder.py +175 -0
- notionary/database/notion_database.py +339 -128
- notionary/database/notion_database_provider.py +230 -0
- notionary/elements/__init__.py +0 -0
- notionary/models/notion_database_response.py +294 -13
- notionary/models/notion_page_response.py +9 -31
- notionary/models/search_response.py +0 -0
- notionary/page/__init__.py +0 -0
- notionary/page/client.py +110 -0
- notionary/page/content/page_content_retriever.py +5 -20
- notionary/page/content/page_content_writer.py +3 -4
- notionary/page/formatting/markdown_to_notion_converter.py +1 -3
- notionary/{prompting → page}/markdown_syntax_prompt_generator.py +1 -2
- notionary/page/notion_page.py +354 -317
- notionary/page/notion_to_markdown_converter.py +1 -4
- notionary/page/properites/property_value_extractor.py +0 -64
- notionary/page/{properites/property_formatter.py → property_formatter.py} +7 -4
- notionary/page/search_filter_builder.py +131 -0
- notionary/page/utils.py +60 -0
- notionary/util/__init__.py +12 -3
- notionary/util/factory_decorator.py +33 -0
- notionary/util/fuzzy_matcher.py +82 -0
- notionary/util/page_id_utils.py +0 -21
- notionary/util/singleton_metaclass.py +22 -0
- notionary/workspace.py +69 -0
- {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/METADATA +4 -1
- notionary-0.2.14.dist-info/RECORD +72 -0
- notionary/database/database_discovery.py +0 -142
- notionary/database/notion_database_factory.py +0 -193
- notionary/elements/notion_block_element.py +0 -70
- notionary/exceptions/database_exceptions.py +0 -76
- notionary/exceptions/page_creation_exception.py +0 -9
- notionary/page/metadata/metadata_editor.py +0 -150
- notionary/page/metadata/notion_icon_manager.py +0 -77
- notionary/page/metadata/notion_page_cover_manager.py +0 -56
- notionary/page/notion_page_factory.py +0 -332
- notionary/page/properites/database_property_service.py +0 -302
- notionary/page/properites/page_property_manager.py +0 -152
- notionary/page/relations/notion_page_relation_manager.py +0 -350
- notionary/page/relations/notion_page_title_resolver.py +0 -104
- notionary/page/relations/page_database_relation.py +0 -68
- notionary/telemetry/__init__.py +0 -7
- notionary/telemetry/telemetry.py +0 -226
- notionary/telemetry/track_usage_decorator.py +0 -76
- notionary/util/warn_direct_constructor_usage.py +0 -54
- notionary-0.2.12.dist-info/RECORD +0 -70
- /notionary/util/{singleton.py → singleton_decorator.py} +0 -0
- {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/WHEEL +0 -0
- {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/entry_points.txt +0 -0
- {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/licenses/LICENSE +0 -0
- {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/top_level.txt +0 -0
notionary/cli/main.py
CHANGED
@@ -6,18 +6,45 @@ Notionary CLI - Integration Key Setup
|
|
6
6
|
import click
|
7
7
|
import os
|
8
8
|
import platform
|
9
|
+
import asyncio
|
10
|
+
import logging
|
9
11
|
from pathlib import Path
|
10
12
|
from dotenv import load_dotenv
|
11
13
|
from rich.console import Console
|
12
14
|
from rich.panel import Panel
|
13
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()
|
14
40
|
|
15
41
|
console = Console()
|
16
42
|
|
43
|
+
|
17
44
|
def get_paste_tips():
|
18
45
|
"""Get platform-specific paste tips"""
|
19
46
|
system = platform.system().lower()
|
20
|
-
|
47
|
+
|
21
48
|
if system == "darwin": # macOS
|
22
49
|
return [
|
23
50
|
"• Terminal: [cyan]Cmd+V[/cyan]",
|
@@ -34,6 +61,7 @@ def get_paste_tips():
|
|
34
61
|
"• Some terminals: [cyan]Shift+Insert[/cyan]",
|
35
62
|
]
|
36
63
|
|
64
|
+
|
37
65
|
def show_paste_tips():
|
38
66
|
"""Show platform-specific paste tips"""
|
39
67
|
console.print("\n[bold yellow]💡 Paste Tips:[/bold yellow]")
|
@@ -41,19 +69,142 @@ def show_paste_tips():
|
|
41
69
|
console.print(tip)
|
42
70
|
console.print()
|
43
71
|
|
72
|
+
|
44
73
|
def get_notion_secret() -> str:
|
45
74
|
"""Get NOTION_SECRET using the same logic as NotionClient"""
|
46
75
|
load_dotenv()
|
47
76
|
return os.getenv("NOTION_SECRET", "")
|
48
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
|
+
|
49
195
|
@click.group()
|
50
196
|
@click.version_option() # Automatische Version aus setup.py
|
51
|
-
|
197
|
+
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging")
|
198
|
+
def main(verbose):
|
52
199
|
"""
|
53
200
|
Notionary CLI - Notion API Integration
|
54
201
|
"""
|
202
|
+
if verbose:
|
203
|
+
enable_verbose_logging()
|
204
|
+
console.print("[dim]Verbose logging enabled[/dim]")
|
55
205
|
pass
|
56
206
|
|
207
|
+
|
57
208
|
@main.command()
|
58
209
|
def init():
|
59
210
|
"""
|
@@ -61,31 +212,64 @@ def init():
|
|
61
212
|
"""
|
62
213
|
# Check if key already exists
|
63
214
|
existing_key = get_notion_secret()
|
64
|
-
|
215
|
+
|
65
216
|
if existing_key:
|
66
|
-
console.print(
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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":
|
75
236
|
setup_new_key()
|
76
237
|
else:
|
77
|
-
console.print("\n[blue]
|
238
|
+
console.print("\n[blue]Happy coding! 🚀[/blue]")
|
78
239
|
else:
|
79
240
|
# No key found, start setup
|
80
|
-
console.print(
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
+
)
|
87
250
|
setup_new_key()
|
88
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
|
+
|
89
273
|
def setup_new_key():
|
90
274
|
"""Handle the key setup process"""
|
91
275
|
try:
|
@@ -93,92 +277,100 @@ def setup_new_key():
|
|
93
277
|
console.print("\n[bold blue]🔗 Create an Integration Key:[/bold blue]")
|
94
278
|
console.print("[cyan]https://www.notion.so/profile/integrations[/cyan]")
|
95
279
|
console.print()
|
96
|
-
|
280
|
+
|
97
281
|
# Get integration key
|
98
|
-
integration_key = Prompt.ask(
|
99
|
-
|
100
|
-
)
|
101
|
-
|
282
|
+
integration_key = Prompt.ask("[bold cyan]Notion Integration Key[/bold cyan]")
|
283
|
+
|
102
284
|
# Input validation
|
103
285
|
if not integration_key or not integration_key.strip():
|
104
286
|
console.print("[bold red]❌ Integration Key cannot be empty![/bold red]")
|
105
287
|
return
|
106
|
-
|
288
|
+
|
107
289
|
# Trim whitespace
|
108
290
|
integration_key = integration_key.strip()
|
109
|
-
|
291
|
+
|
110
292
|
# Check for common paste issues
|
111
293
|
if integration_key in ["^V", "^v", "^C", "^c"]:
|
112
294
|
console.print("[bold red]❌ Paste didn't work! Try:[/bold red]")
|
113
295
|
show_paste_tips()
|
114
296
|
return
|
115
|
-
|
297
|
+
|
116
298
|
# Show masked feedback that paste worked
|
117
299
|
masked_key = "•" * len(integration_key)
|
118
|
-
console.print(
|
119
|
-
|
300
|
+
console.print(
|
301
|
+
f"[dim]Received: {masked_key} ({len(integration_key)} characters)[/dim]"
|
302
|
+
)
|
303
|
+
|
120
304
|
# Basic validation for Notion keys
|
121
|
-
if not integration_key.startswith(
|
122
|
-
console.print(
|
123
|
-
|
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
|
+
)
|
124
312
|
if not Confirm.ask("Continue anyway?"):
|
125
313
|
return
|
126
|
-
|
314
|
+
|
127
315
|
# Save the key
|
128
316
|
if save_integration_key(integration_key):
|
129
|
-
|
130
|
-
|
317
|
+
# Show databases overview after successful setup
|
318
|
+
show_databases_overview(integration_key)
|
319
|
+
|
131
320
|
except KeyboardInterrupt:
|
132
321
|
console.print("\n[yellow]Setup cancelled.[/yellow]")
|
133
322
|
except Exception as e:
|
134
323
|
console.print(f"\n[bold red]❌ Error during setup: {e}[/bold red]")
|
135
324
|
raise click.Abort()
|
136
325
|
|
326
|
+
|
137
327
|
def save_integration_key(integration_key: str) -> bool:
|
138
328
|
"""Save the integration key to .env file"""
|
139
329
|
try:
|
140
330
|
# .env Datei im aktuellen Verzeichnis erstellen/aktualisieren
|
141
331
|
env_file = Path.cwd() / ".env"
|
142
|
-
|
332
|
+
|
143
333
|
# Bestehende .env lesen falls vorhanden
|
144
334
|
existing_lines = []
|
145
335
|
if env_file.exists():
|
146
|
-
with open(env_file,
|
336
|
+
with open(env_file, "r", encoding="utf-8") as f:
|
147
337
|
existing_lines = [line.rstrip() for line in f.readlines()]
|
148
|
-
|
338
|
+
|
149
339
|
# NOTION_SECRET Zeile hinzufügen/ersetzen
|
150
340
|
updated_lines = []
|
151
341
|
notion_secret_found = False
|
152
|
-
|
342
|
+
|
153
343
|
for line in existing_lines:
|
154
|
-
if line.startswith(
|
155
|
-
updated_lines.append(f
|
344
|
+
if line.startswith("NOTION_SECRET="):
|
345
|
+
updated_lines.append(f"NOTION_SECRET={integration_key}")
|
156
346
|
notion_secret_found = True
|
157
347
|
else:
|
158
348
|
updated_lines.append(line)
|
159
|
-
|
349
|
+
|
160
350
|
# Falls NOTION_SECRET noch nicht existiert, hinzufügen
|
161
351
|
if not notion_secret_found:
|
162
|
-
updated_lines.append(f
|
163
|
-
|
352
|
+
updated_lines.append(f"NOTION_SECRET={integration_key}")
|
353
|
+
|
164
354
|
# .env Datei schreiben
|
165
|
-
with open(env_file,
|
166
|
-
f.write(
|
167
|
-
|
355
|
+
with open(env_file, "w", encoding="utf-8") as f:
|
356
|
+
f.write("\n".join(updated_lines) + "\n")
|
357
|
+
|
168
358
|
# Verification
|
169
359
|
written_key = get_notion_secret()
|
170
360
|
if written_key == integration_key:
|
171
|
-
console.print(
|
361
|
+
console.print(
|
362
|
+
"\n[bold green]✅ Integration Key saved and verified![/bold green]"
|
363
|
+
)
|
172
364
|
console.print(f"[dim]Configuration: {env_file}[/dim]")
|
173
|
-
console.print("\n[blue]Ready to use notionary in your Python code! 🚀[/blue]")
|
174
365
|
return True
|
175
366
|
else:
|
176
367
|
console.print("\n[bold red]❌ Error: Key verification failed![/bold red]")
|
177
368
|
return False
|
178
|
-
|
369
|
+
|
179
370
|
except Exception as e:
|
180
371
|
console.print(f"\n[bold red]❌ Error saving key: {e}[/bold red]")
|
181
372
|
return False
|
182
373
|
|
183
|
-
|
184
|
-
|
374
|
+
|
375
|
+
if __name__ == "__main__":
|
376
|
+
main()
|
notionary/cli/onboarding.py
CHANGED
@@ -0,0 +1,117 @@
|
|
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}")
|
File without changes
|
@@ -0,0 +1,132 @@
|
|
1
|
+
from typing import Dict, Any, Optional
|
2
|
+
from dotenv import load_dotenv
|
3
|
+
from notionary.base_notion_client import BaseNotionClient
|
4
|
+
|
5
|
+
from notionary.models.notion_database_response import (
|
6
|
+
NotionDatabaseResponse,
|
7
|
+
NotionDatabaseSearchResponse,
|
8
|
+
NotionPageResponse,
|
9
|
+
NotionQueryDatabaseResponse,
|
10
|
+
)
|
11
|
+
from notionary.util import singleton
|
12
|
+
|
13
|
+
load_dotenv()
|
14
|
+
|
15
|
+
|
16
|
+
@singleton
|
17
|
+
class NotionDatabaseClient(BaseNotionClient):
|
18
|
+
"""
|
19
|
+
Specialized Notion client for database operations.
|
20
|
+
Inherits connection management and HTTP methods from BaseNotionClient.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, token: Optional[str] = None, timeout: int = 30):
|
24
|
+
super().__init__(token, timeout)
|
25
|
+
|
26
|
+
async def get_database(self, database_id: str) -> NotionDatabaseResponse:
|
27
|
+
"""
|
28
|
+
Gets metadata for a Notion database by its ID.
|
29
|
+
"""
|
30
|
+
response = await self.get(f"databases/{database_id}")
|
31
|
+
return NotionDatabaseResponse.model_validate(response)
|
32
|
+
|
33
|
+
async def patch_database(
|
34
|
+
self, database_id: str, data: Dict[str, Any]
|
35
|
+
) -> NotionDatabaseResponse:
|
36
|
+
"""
|
37
|
+
Updates a Notion database with the provided data.
|
38
|
+
"""
|
39
|
+
response = await self.patch(f"databases/{database_id}", data=data)
|
40
|
+
return NotionDatabaseResponse.model_validate(response)
|
41
|
+
|
42
|
+
async def query_database(
|
43
|
+
self, database_id: str, query_data: Dict[str, Any] = None
|
44
|
+
) -> NotionQueryDatabaseResponse:
|
45
|
+
"""
|
46
|
+
Queries a Notion database with the provided filter and sorts.
|
47
|
+
"""
|
48
|
+
response = await self.post(f"databases/{database_id}/query", data=query_data)
|
49
|
+
return NotionQueryDatabaseResponse.model_validate(response)
|
50
|
+
|
51
|
+
async def query_database_by_title(
|
52
|
+
self, database_id: str, page_title: str
|
53
|
+
) -> NotionQueryDatabaseResponse:
|
54
|
+
"""
|
55
|
+
Queries a Notion database by title.
|
56
|
+
"""
|
57
|
+
query_data = {
|
58
|
+
"filter": {"property": "title", "title": {"contains": page_title}}
|
59
|
+
}
|
60
|
+
|
61
|
+
return await self.query_database(database_id=database_id, query_data=query_data)
|
62
|
+
|
63
|
+
async def search_databases(
|
64
|
+
self, query: str = "", sort_ascending: bool = True, limit: int = 100
|
65
|
+
) -> NotionDatabaseSearchResponse:
|
66
|
+
"""
|
67
|
+
Searches for databases in Notion using the search endpoint.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
query: Search query string
|
71
|
+
sort_ascending: Whether to sort in ascending order
|
72
|
+
limit: Maximum number of results to return
|
73
|
+
"""
|
74
|
+
search_data = {
|
75
|
+
"query": query,
|
76
|
+
"filter": {"value": "database", "property": "object"},
|
77
|
+
"sort": {
|
78
|
+
"direction": "ascending" if sort_ascending else "descending",
|
79
|
+
"timestamp": "last_edited_time",
|
80
|
+
},
|
81
|
+
"page_size": limit,
|
82
|
+
}
|
83
|
+
|
84
|
+
response = await self.post("search", search_data)
|
85
|
+
return NotionDatabaseSearchResponse.model_validate(response)
|
86
|
+
|
87
|
+
async def create_page(self, parent_database_id: str) -> NotionPageResponse:
|
88
|
+
"""
|
89
|
+
Creates a new blank page in the given database with minimal properties.
|
90
|
+
"""
|
91
|
+
page_data = {
|
92
|
+
"parent": {"database_id": parent_database_id},
|
93
|
+
"properties": {},
|
94
|
+
}
|
95
|
+
response = await self.post("pages", page_data)
|
96
|
+
return NotionPageResponse.model_validate(response)
|
97
|
+
|
98
|
+
async def update_database_title(
|
99
|
+
self, database_id: str, title: str
|
100
|
+
) -> NotionDatabaseResponse:
|
101
|
+
"""
|
102
|
+
Updates the title of a database.
|
103
|
+
"""
|
104
|
+
data = {"title": [{"text": {"content": title}}]}
|
105
|
+
return await self.patch_database(database_id, data)
|
106
|
+
|
107
|
+
async def update_database_emoji(
|
108
|
+
self, database_id: str, emoji: str
|
109
|
+
) -> NotionDatabaseResponse:
|
110
|
+
"""
|
111
|
+
Updates the emoji/icon of a database.
|
112
|
+
"""
|
113
|
+
data = {"icon": {"type": "emoji", "emoji": emoji}}
|
114
|
+
return await self.patch_database(database_id, data)
|
115
|
+
|
116
|
+
async def update_database_cover_image(
|
117
|
+
self, database_id: str, image_url: str
|
118
|
+
) -> NotionDatabaseResponse:
|
119
|
+
"""
|
120
|
+
Updates the cover image of a database.
|
121
|
+
"""
|
122
|
+
data = {"cover": {"type": "external", "external": {"url": image_url}}}
|
123
|
+
return await self.patch_database(database_id, data)
|
124
|
+
|
125
|
+
async def update_database_external_icon(
|
126
|
+
self, database_id: str, icon_url: str
|
127
|
+
) -> NotionDatabaseResponse:
|
128
|
+
"""
|
129
|
+
Updates the database icon with an external image URL.
|
130
|
+
"""
|
131
|
+
data = {"icon": {"type": "external", "external": {"url": icon_url}}}
|
132
|
+
return await self.patch_database(database_id, data)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class NotionDatabaseException(Exception):
|
2
|
+
"""Base exception for all Notion database operations."""
|
3
|
+
|
4
|
+
pass
|
5
|
+
|
6
|
+
|
7
|
+
class DatabaseNotFoundException(NotionDatabaseException):
|
8
|
+
"""Exception raised when a database is not found."""
|
9
|
+
|
10
|
+
def __init__(self, identifier: str, message: str = None):
|
11
|
+
self.identifier = identifier
|
12
|
+
self.message = message or f"Database not found: {identifier}"
|
13
|
+
super().__init__(self.message)
|
File without changes
|