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.
Files changed (86) hide show
  1. notionary/__init__.py +3 -20
  2. notionary/{notion_client.py → base_notion_client.py} +92 -98
  3. notionary/blocks/__init__.py +61 -0
  4. notionary/{elements → blocks}/audio_element.py +6 -4
  5. notionary/{elements → blocks}/bookmark_element.py +3 -6
  6. notionary/{elements → blocks}/bulleted_list_element.py +5 -7
  7. notionary/{elements → blocks}/callout_element.py +5 -8
  8. notionary/{elements → blocks}/code_block_element.py +4 -6
  9. notionary/{elements → blocks}/column_element.py +3 -6
  10. notionary/{elements → blocks}/divider_element.py +3 -6
  11. notionary/{elements → blocks}/embed_element.py +4 -6
  12. notionary/{elements → blocks}/heading_element.py +5 -9
  13. notionary/{elements → blocks}/image_element.py +4 -6
  14. notionary/{elements → blocks}/mention_element.py +3 -7
  15. notionary/blocks/notion_block_client.py +26 -0
  16. notionary/blocks/notion_block_element.py +34 -0
  17. notionary/{elements → blocks}/numbered_list_element.py +4 -7
  18. notionary/{elements → blocks}/paragraph_element.py +4 -7
  19. notionary/{prompting/element_prompt_content.py → blocks/prompts/element_prompt_builder.py} +1 -40
  20. notionary/blocks/prompts/element_prompt_content.py +41 -0
  21. notionary/{elements → blocks}/qoute_element.py +4 -6
  22. notionary/{elements → blocks}/registry/block_registry.py +4 -26
  23. notionary/{elements → blocks}/registry/block_registry_builder.py +26 -25
  24. notionary/{elements → blocks}/table_element.py +6 -8
  25. notionary/{elements → blocks}/text_inline_formatter.py +1 -4
  26. notionary/{elements → blocks}/todo_element.py +6 -8
  27. notionary/{elements → blocks}/toggle_element.py +3 -6
  28. notionary/{elements → blocks}/toggleable_heading_element.py +5 -8
  29. notionary/{elements → blocks}/video_element.py +4 -6
  30. notionary/cli/main.py +245 -53
  31. notionary/cli/onboarding.py +117 -0
  32. notionary/database/__init__.py +0 -0
  33. notionary/database/client.py +132 -0
  34. notionary/database/database_exceptions.py +13 -0
  35. notionary/database/factory.py +0 -0
  36. notionary/database/filter_builder.py +175 -0
  37. notionary/database/notion_database.py +339 -128
  38. notionary/database/notion_database_provider.py +230 -0
  39. notionary/elements/__init__.py +0 -0
  40. notionary/models/notion_database_response.py +294 -13
  41. notionary/models/notion_page_response.py +9 -31
  42. notionary/models/search_response.py +0 -0
  43. notionary/page/__init__.py +0 -0
  44. notionary/page/client.py +110 -0
  45. notionary/page/content/page_content_retriever.py +5 -20
  46. notionary/page/content/page_content_writer.py +3 -4
  47. notionary/page/formatting/markdown_to_notion_converter.py +1 -3
  48. notionary/{prompting → page}/markdown_syntax_prompt_generator.py +1 -2
  49. notionary/page/notion_page.py +354 -317
  50. notionary/page/notion_to_markdown_converter.py +1 -4
  51. notionary/page/properites/property_value_extractor.py +0 -64
  52. notionary/page/{properites/property_formatter.py → property_formatter.py} +7 -4
  53. notionary/page/search_filter_builder.py +131 -0
  54. notionary/page/utils.py +60 -0
  55. notionary/util/__init__.py +12 -3
  56. notionary/util/factory_decorator.py +33 -0
  57. notionary/util/fuzzy_matcher.py +82 -0
  58. notionary/util/page_id_utils.py +0 -21
  59. notionary/util/singleton_metaclass.py +22 -0
  60. notionary/workspace.py +69 -0
  61. {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/METADATA +4 -1
  62. notionary-0.2.14.dist-info/RECORD +72 -0
  63. notionary/database/database_discovery.py +0 -142
  64. notionary/database/notion_database_factory.py +0 -193
  65. notionary/elements/notion_block_element.py +0 -70
  66. notionary/exceptions/database_exceptions.py +0 -76
  67. notionary/exceptions/page_creation_exception.py +0 -9
  68. notionary/page/metadata/metadata_editor.py +0 -150
  69. notionary/page/metadata/notion_icon_manager.py +0 -77
  70. notionary/page/metadata/notion_page_cover_manager.py +0 -56
  71. notionary/page/notion_page_factory.py +0 -332
  72. notionary/page/properites/database_property_service.py +0 -302
  73. notionary/page/properites/page_property_manager.py +0 -152
  74. notionary/page/relations/notion_page_relation_manager.py +0 -350
  75. notionary/page/relations/notion_page_title_resolver.py +0 -104
  76. notionary/page/relations/page_database_relation.py +0 -68
  77. notionary/telemetry/__init__.py +0 -7
  78. notionary/telemetry/telemetry.py +0 -226
  79. notionary/telemetry/track_usage_decorator.py +0 -76
  80. notionary/util/warn_direct_constructor_usage.py +0 -54
  81. notionary-0.2.12.dist-info/RECORD +0 -70
  82. /notionary/util/{singleton.py → singleton_decorator.py} +0 -0
  83. {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/WHEEL +0 -0
  84. {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/entry_points.txt +0 -0
  85. {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/licenses/LICENSE +0 -0
  86. {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
- def main():
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(Panel.fit(
67
- "[bold green]✅ You're all set![/bold green]\n"
68
- f"Your Notion Integration Key is already configured.\n"
69
- f"Key: [dim]{existing_key[:8]}...[/dim]",
70
- title="Already Configured"
71
- ))
72
-
73
- # Option to reconfigure
74
- if Confirm.ask("\n[yellow]Would you like to update your key?[/yellow]"):
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]No changes made. Happy coding! 🚀[/blue]")
238
+ console.print("\n[blue]Happy coding! 🚀[/blue]")
78
239
  else:
79
240
  # No key found, start setup
80
- console.print(Panel.fit(
81
- "[bold green]🚀 Notionary Setup[/bold green]\n"
82
- "Enter your Notion Integration Key to get started...\n\n"
83
- "[bold blue]🔗 Create an Integration Key or get an existing one:[/bold blue]\n"
84
- "[cyan]https://www.notion.so/profile/integrations[/cyan]",
85
- title="Initialization"
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
- "[bold cyan]Notion Integration Key[/bold cyan]"
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(f"[dim]Received: {masked_key} ({len(integration_key)} characters)[/dim]")
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('ntn_') or len(integration_key) < 30:
122
- console.print("[bold yellow]⚠️ Warning: This doesn't look like a valid Notion Integration Key[/bold yellow]")
123
- console.print("[dim]Notion keys usually start with 'ntn_' and are about 50+ characters long[/dim]")
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
- return # Success!
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, 'r', encoding='utf-8') as f:
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('NOTION_SECRET='):
155
- updated_lines.append(f'NOTION_SECRET={integration_key}')
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'NOTION_SECRET={integration_key}')
163
-
352
+ updated_lines.append(f"NOTION_SECRET={integration_key}")
353
+
164
354
  # .env Datei schreiben
165
- with open(env_file, 'w', encoding='utf-8') as f:
166
- f.write('\n'.join(updated_lines) + '\n')
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("\n[bold green]✅ Integration Key saved and verified![/bold green]")
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
- if __name__ == '__main__':
184
- main()
374
+
375
+ if __name__ == "__main__":
376
+ main()
@@ -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