notionary 0.2.13__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 (82) hide show
  1. notionary/__init__.py +3 -16
  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 -3
  5. notionary/{elements → blocks}/bookmark_element.py +3 -5
  6. notionary/{elements → blocks}/bulleted_list_element.py +5 -6
  7. notionary/{elements → blocks}/callout_element.py +4 -6
  8. notionary/{elements → blocks}/code_block_element.py +4 -5
  9. notionary/{elements → blocks}/column_element.py +3 -5
  10. notionary/{elements → blocks}/divider_element.py +3 -5
  11. notionary/{elements → blocks}/embed_element.py +4 -5
  12. notionary/{elements → blocks}/heading_element.py +4 -7
  13. notionary/{elements → blocks}/image_element.py +4 -5
  14. notionary/{elements → blocks}/mention_element.py +3 -6
  15. notionary/blocks/notion_block_client.py +26 -0
  16. notionary/{elements → blocks}/notion_block_element.py +2 -3
  17. notionary/{elements → blocks}/numbered_list_element.py +4 -6
  18. notionary/{elements → blocks}/paragraph_element.py +4 -6
  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 -5
  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 +5 -6
  25. notionary/{elements → blocks}/text_inline_formatter.py +1 -4
  26. notionary/{elements → blocks}/todo_element.py +5 -6
  27. notionary/{elements → blocks}/toggle_element.py +3 -5
  28. notionary/{elements → blocks}/toggleable_heading_element.py +4 -6
  29. notionary/{elements → blocks}/video_element.py +4 -5
  30. notionary/cli/main.py +157 -128
  31. notionary/cli/onboarding.py +10 -9
  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 -126
  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.13.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 -190
  65. notionary/exceptions/database_exceptions.py +0 -76
  66. notionary/exceptions/page_creation_exception.py +0 -9
  67. notionary/page/metadata/metadata_editor.py +0 -150
  68. notionary/page/metadata/notion_icon_manager.py +0 -77
  69. notionary/page/metadata/notion_page_cover_manager.py +0 -56
  70. notionary/page/notion_page_factory.py +0 -328
  71. notionary/page/properites/database_property_service.py +0 -302
  72. notionary/page/properites/page_property_manager.py +0 -152
  73. notionary/page/relations/notion_page_relation_manager.py +0 -350
  74. notionary/page/relations/notion_page_title_resolver.py +0 -104
  75. notionary/page/relations/page_database_relation.py +0 -68
  76. notionary/util/warn_direct_constructor_usage.py +0 -54
  77. notionary-0.2.13.dist-info/RECORD +0 -67
  78. /notionary/util/{singleton.py → singleton_decorator.py} +0 -0
  79. {notionary-0.2.13.dist-info → notionary-0.2.14.dist-info}/WHEEL +0 -0
  80. {notionary-0.2.13.dist-info → notionary-0.2.14.dist-info}/entry_points.txt +0 -0
  81. {notionary-0.2.13.dist-info → notionary-0.2.14.dist-info}/licenses/LICENSE +0 -0
  82. {notionary-0.2.13.dist-info → notionary-0.2.14.dist-info}/top_level.txt +0 -0
notionary/cli/main.py CHANGED
@@ -16,32 +16,35 @@ from rich.prompt import Prompt, Confirm
16
16
  from rich.table import Table
17
17
  from rich import box
18
18
  from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
19
- from notionary.notion_client import NotionClient
20
- from notionary.database.database_discovery import DatabaseDiscovery
19
+ from notionary.workspace import NotionWorkspace
20
+
21
21
 
22
22
  # Disable logging for CLI usage
23
23
  def disable_notionary_logging():
24
24
  """Disable logging for notionary modules when used in CLI"""
25
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)
26
+ logging.getLogger("notionary").setLevel(logging.WARNING)
27
+ logging.getLogger("DatabaseDiscovery").setLevel(logging.WARNING)
28
+ logging.getLogger("NotionClient").setLevel(logging.WARNING)
29
+
29
30
 
30
31
  def enable_verbose_logging():
31
32
  """Enable verbose logging for debugging (use with --verbose flag)"""
32
- logging.getLogger('notionary').setLevel(logging.DEBUG)
33
- logging.getLogger('DatabaseDiscovery').setLevel(logging.DEBUG)
34
- logging.getLogger('NotionClient').setLevel(logging.DEBUG)
33
+ logging.getLogger("notionary").setLevel(logging.DEBUG)
34
+ logging.getLogger("DatabaseDiscovery").setLevel(logging.DEBUG)
35
+ logging.getLogger("NotionClient").setLevel(logging.DEBUG)
36
+
35
37
 
36
38
  # Initialize logging configuration for CLI
37
39
  disable_notionary_logging()
38
40
 
39
41
  console = Console()
40
42
 
43
+
41
44
  def get_paste_tips():
42
45
  """Get platform-specific paste tips"""
43
46
  system = platform.system().lower()
44
-
47
+
45
48
  if system == "darwin": # macOS
46
49
  return [
47
50
  "• Terminal: [cyan]Cmd+V[/cyan]",
@@ -58,6 +61,7 @@ def get_paste_tips():
58
61
  "• Some terminals: [cyan]Shift+Insert[/cyan]",
59
62
  ]
60
63
 
64
+
61
65
  def show_paste_tips():
62
66
  """Show platform-specific paste tips"""
63
67
  console.print("\n[bold yellow]💡 Paste Tips:[/bold yellow]")
@@ -65,124 +69,132 @@ def show_paste_tips():
65
69
  console.print(tip)
66
70
  console.print()
67
71
 
72
+
68
73
  def get_notion_secret() -> str:
69
74
  """Get NOTION_SECRET using the same logic as NotionClient"""
70
75
  load_dotenv()
71
76
  return os.getenv("NOTION_SECRET", "")
72
77
 
78
+
73
79
  async def fetch_notion_databases_with_progress():
74
80
  """Fetch databases using DatabaseDiscovery with progress animation"""
75
81
  try:
76
- # Initialize NotionClient and DatabaseDiscovery
77
- client = NotionClient()
78
- discovery = DatabaseDiscovery(client)
79
-
82
+ workspace = NotionWorkspace()
83
+
80
84
  # Create progress display with custom spinner
81
85
  with Progress(
82
86
  SpinnerColumn(spinner_name="dots12", style="cyan"),
83
87
  TextColumn("[bold blue]Discovering databases..."),
84
88
  TimeElapsedColumn(),
85
89
  console=console,
86
- transient=True
90
+ transient=True,
87
91
  ) as progress:
88
92
  # Add progress task
89
93
  task = progress.add_task("Fetching...", total=None)
90
-
94
+
91
95
  # Fetch databases
92
- databases = await discovery._discover(page_size=50)
93
-
96
+ databases = await workspace.list_all_databases(limit=50)
97
+
94
98
  # Update progress to show completion
95
- progress.update(task, description=f"[bold green]Found {len(databases)} databases!")
96
-
99
+ progress.update(
100
+ task, description=f"[bold green]Found {len(databases)} databases!"
101
+ )
102
+
97
103
  # Brief pause to show completion
98
104
  await asyncio.sleep(0.5)
99
-
105
+
100
106
  return {"databases": databases, "success": True}
101
-
107
+
102
108
  except Exception as e:
103
109
  return {"error": str(e), "success": False}
104
110
 
111
+
105
112
  def show_databases_overview(api_key: str):
106
113
  """Show available databases with nice formatting"""
107
114
  console.print("\n[bold blue]🔍 Connecting to Notion...[/bold blue]")
108
-
115
+
109
116
  # Run async function in sync context
110
117
  try:
111
118
  result = asyncio.run(fetch_notion_databases_with_progress())
112
119
  except Exception as e:
113
- console.print(Panel.fit(
114
- f"[bold red]❌ Unexpected error[/bold red]\n\n"
115
- f"[red]{str(e)}[/red]\n\n"
116
- "[yellow]Please check:[/yellow]\n"
117
- " Your internet connection\n"
118
- "• Your integration key validity\n"
119
- "• Try running the command again",
120
- title="Connection Error"
121
- ))
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
+ )
122
131
  return
123
-
132
+
124
133
  if not result["success"]:
125
- console.print(Panel.fit(
126
- f"[bold red]❌ Could not fetch databases[/bold red]\n\n"
127
- f"[red]{result['error']}[/red]\n\n"
128
- "[yellow]Common issues:[/yellow]\n"
129
- " Check your integration key\n"
130
- "• Make sure your integration has access to databases\n"
131
- "• Visit your integration settings to grant access",
132
- title="Connection Error"
133
- ))
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
+ )
134
145
  return
135
-
146
+
136
147
  databases = result["databases"]
137
-
148
+
138
149
  if not databases:
139
- console.print(Panel.fit(
140
- "[bold yellow]⚠️ No databases found[/bold yellow]\n\n"
141
- "Your integration key is valid, but no databases are accessible.\n\n"
142
- "[bold blue]To grant access:[/bold blue]\n"
143
- "1. Go to any Notion database\n"
144
- "2. Click the '...' menu (top right)\n"
145
- "3. Go to 'Add connections'\n"
146
- "4. Find and select your integration\n\n"
147
- "[cyan]https://www.notion.so/help/add-and-manage-connections-with-the-api[/cyan]",
148
- title="No Databases Available"
149
- ))
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
+ )
150
163
  return
151
-
164
+
152
165
  # Create beautiful table
153
166
  table = Table(
154
167
  title=f"📊 Available Databases ({len(databases)} found)",
155
168
  box=box.ROUNDED,
156
169
  title_style="bold green",
157
- header_style="bold cyan"
170
+ header_style="bold cyan",
158
171
  )
159
-
172
+
160
173
  table.add_column("#", style="dim", justify="right", width=3)
161
174
  table.add_column("Database Name", style="bold white", min_width=25)
162
175
  table.add_column("ID", style="dim cyan", min_width=36)
163
-
176
+
164
177
  for i, (title, db_id) in enumerate(databases, 1):
165
- table.add_row(
166
- str(i),
167
- title or "Untitled Database",
168
- db_id
169
- )
170
-
178
+ table.add_row(str(i), title or "Untitled Database", db_id)
179
+
171
180
  console.print("\n")
172
181
  console.print(table)
173
-
182
+
174
183
  # Success message with next steps
175
- console.print(Panel.fit(
176
- "[bold green]🎉 Setup Complete![/bold green]\n\n"
177
- f"Found [bold cyan]{len(databases)}[/bold cyan] accessible database(s).\n"
178
- "You can now use notionary in your Python code!\n\n"
179
- "[bold yellow]💡 Tip:[/bold yellow] Run [cyan]notionary db[/cyan] anytime to see this overview again.",
180
- title="Ready to Go!"
181
- ))
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
+
182
194
 
183
195
  @click.group()
184
196
  @click.version_option() # Automatische Version aus setup.py
185
- @click.option('--verbose', '-v', is_flag=True, help='Enable verbose logging')
197
+ @click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging")
186
198
  def main(verbose):
187
199
  """
188
200
  Notionary CLI - Notion API Integration
@@ -192,6 +204,7 @@ def main(verbose):
192
204
  console.print("[dim]Verbose logging enabled[/dim]")
193
205
  pass
194
206
 
207
+
195
208
  @main.command()
196
209
  def init():
197
210
  """
@@ -199,22 +212,24 @@ def init():
199
212
  """
200
213
  # Check if key already exists
201
214
  existing_key = get_notion_secret()
202
-
215
+
203
216
  if existing_key:
204
- console.print(Panel.fit(
205
- "[bold green]✅ You're all set![/bold green]\n"
206
- f"Your Notion Integration Key is already configured.\n"
207
- f"Key: [dim]{existing_key[:8]}...[/dim]",
208
- title="Already Configured"
209
- ))
210
-
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
+
211
226
  # Option to reconfigure or show databases
212
227
  choice = Prompt.ask(
213
228
  "\n[yellow]What would you like to do?[/yellow]",
214
229
  choices=["show", "update", "exit"],
215
- default="show"
230
+ default="show",
216
231
  )
217
-
232
+
218
233
  if choice == "show":
219
234
  show_databases_overview(existing_key)
220
235
  elif choice == "update":
@@ -223,32 +238,38 @@ def init():
223
238
  console.print("\n[blue]Happy coding! 🚀[/blue]")
224
239
  else:
225
240
  # No key found, start setup
226
- console.print(Panel.fit(
227
- "[bold green]🚀 Notionary Setup[/bold green]\n"
228
- "Enter your Notion Integration Key to get started...\n\n"
229
- "[bold blue]🔗 Create an Integration Key or get an existing one:[/bold blue]\n"
230
- "[cyan]https://www.notion.so/profile/integrations[/cyan]",
231
- title="Initialization"
232
- ))
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
+ )
233
250
  setup_new_key()
234
251
 
252
+
235
253
  @main.command()
236
254
  def db() -> None:
237
255
  """
238
256
  Show available Notion databases
239
257
  """
240
258
  existing_key = get_notion_secret()
241
-
259
+
242
260
  if not existing_key:
243
- console.print(Panel.fit(
244
- "[bold red]❌ No Integration Key found![/bold red]\n\n"
245
- "Please run [cyan]notionary init[/cyan] first to set up your key.",
246
- title="Not Configured"
247
- ))
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
+ )
248
268
  return
249
-
269
+
250
270
  show_databases_overview(existing_key)
251
271
 
272
+
252
273
  def setup_new_key():
253
274
  """Handle the key setup process"""
254
275
  try:
@@ -256,92 +277,100 @@ def setup_new_key():
256
277
  console.print("\n[bold blue]🔗 Create an Integration Key:[/bold blue]")
257
278
  console.print("[cyan]https://www.notion.so/profile/integrations[/cyan]")
258
279
  console.print()
259
-
280
+
260
281
  # Get integration key
261
- integration_key = Prompt.ask(
262
- "[bold cyan]Notion Integration Key[/bold cyan]"
263
- )
264
-
282
+ integration_key = Prompt.ask("[bold cyan]Notion Integration Key[/bold cyan]")
283
+
265
284
  # Input validation
266
285
  if not integration_key or not integration_key.strip():
267
286
  console.print("[bold red]❌ Integration Key cannot be empty![/bold red]")
268
287
  return
269
-
288
+
270
289
  # Trim whitespace
271
290
  integration_key = integration_key.strip()
272
-
291
+
273
292
  # Check for common paste issues
274
293
  if integration_key in ["^V", "^v", "^C", "^c"]:
275
294
  console.print("[bold red]❌ Paste didn't work! Try:[/bold red]")
276
295
  show_paste_tips()
277
296
  return
278
-
297
+
279
298
  # Show masked feedback that paste worked
280
299
  masked_key = "•" * len(integration_key)
281
- console.print(f"[dim]Received: {masked_key} ({len(integration_key)} characters)[/dim]")
282
-
300
+ console.print(
301
+ f"[dim]Received: {masked_key} ({len(integration_key)} characters)[/dim]"
302
+ )
303
+
283
304
  # Basic validation for Notion keys
284
- if not integration_key.startswith('ntn_') or len(integration_key) < 30:
285
- console.print("[bold yellow]⚠️ Warning: This doesn't look like a valid Notion Integration Key[/bold yellow]")
286
- 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
+ )
287
312
  if not Confirm.ask("Continue anyway?"):
288
313
  return
289
-
314
+
290
315
  # Save the key
291
316
  if save_integration_key(integration_key):
292
317
  # Show databases overview after successful setup
293
318
  show_databases_overview(integration_key)
294
-
319
+
295
320
  except KeyboardInterrupt:
296
321
  console.print("\n[yellow]Setup cancelled.[/yellow]")
297
322
  except Exception as e:
298
323
  console.print(f"\n[bold red]❌ Error during setup: {e}[/bold red]")
299
324
  raise click.Abort()
300
325
 
326
+
301
327
  def save_integration_key(integration_key: str) -> bool:
302
328
  """Save the integration key to .env file"""
303
329
  try:
304
330
  # .env Datei im aktuellen Verzeichnis erstellen/aktualisieren
305
331
  env_file = Path.cwd() / ".env"
306
-
332
+
307
333
  # Bestehende .env lesen falls vorhanden
308
334
  existing_lines = []
309
335
  if env_file.exists():
310
- with open(env_file, 'r', encoding='utf-8') as f:
336
+ with open(env_file, "r", encoding="utf-8") as f:
311
337
  existing_lines = [line.rstrip() for line in f.readlines()]
312
-
338
+
313
339
  # NOTION_SECRET Zeile hinzufügen/ersetzen
314
340
  updated_lines = []
315
341
  notion_secret_found = False
316
-
342
+
317
343
  for line in existing_lines:
318
- if line.startswith('NOTION_SECRET='):
319
- updated_lines.append(f'NOTION_SECRET={integration_key}')
344
+ if line.startswith("NOTION_SECRET="):
345
+ updated_lines.append(f"NOTION_SECRET={integration_key}")
320
346
  notion_secret_found = True
321
347
  else:
322
348
  updated_lines.append(line)
323
-
349
+
324
350
  # Falls NOTION_SECRET noch nicht existiert, hinzufügen
325
351
  if not notion_secret_found:
326
- updated_lines.append(f'NOTION_SECRET={integration_key}')
327
-
352
+ updated_lines.append(f"NOTION_SECRET={integration_key}")
353
+
328
354
  # .env Datei schreiben
329
- with open(env_file, 'w', encoding='utf-8') as f:
330
- f.write('\n'.join(updated_lines) + '\n')
331
-
355
+ with open(env_file, "w", encoding="utf-8") as f:
356
+ f.write("\n".join(updated_lines) + "\n")
357
+
332
358
  # Verification
333
359
  written_key = get_notion_secret()
334
360
  if written_key == integration_key:
335
- 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
+ )
336
364
  console.print(f"[dim]Configuration: {env_file}[/dim]")
337
365
  return True
338
366
  else:
339
367
  console.print("\n[bold red]❌ Error: Key verification failed![/bold red]")
340
368
  return False
341
-
369
+
342
370
  except Exception as e:
343
371
  console.print(f"\n[bold red]❌ Error saving key: {e}[/bold red]")
344
372
  return False
345
373
 
346
- if __name__ == '__main__':
347
- main()
374
+
375
+ if __name__ == "__main__":
376
+ main()
@@ -2,21 +2,23 @@ import asyncio
2
2
  from dataclasses import dataclass
3
3
  from notionary import NotionDatabase
4
4
 
5
- @dataclass
5
+
6
+ @dataclass
6
7
  class OnboardingPageResult:
7
8
  url: str
8
9
  tile: str
9
10
  emoji: str
10
11
 
12
+
11
13
  async def generate_doc_for_database(
12
14
  datbase_name: str,
13
15
  ) -> OnboardingPageResult:
14
16
  database = await NotionDatabase.from_database_name(datbase_name)
15
17
  page = await database.create_blank_page()
16
-
18
+
17
19
  page_title = "Welcome to Notionary!"
18
20
  page_icon = "📚"
19
-
21
+
20
22
  markdown_content = """!> [🚀] This page was created fully automatically and serves as a showcase of what is possible with Notionary.
21
23
 
22
24
  ---
@@ -95,22 +97,21 @@ async def generate_doc_for_database(
95
97
 
96
98
  Happy building with Notionary! 🎉"""
97
99
 
98
-
99
100
  await page.set_title(page_title)
100
101
  await page.set_emoji_icon(page_icon)
101
102
  await page.set_random_gradient_cover()
102
103
  await page.append_markdown(markdown_content)
103
-
104
+
104
105
  url = await page.get_url()
105
-
106
+
106
107
  return OnboardingPageResult(
107
108
  url=url,
108
109
  tile=page_title,
109
110
  emoji=page_icon,
110
111
  )
111
-
112
-
112
+
113
+
113
114
  if __name__ == "__main__":
114
115
  print("🚀 Starting Notionary onboarding page generation...")
115
116
  result = asyncio.run(generate_doc_for_database("Wissen & Notizen"))
116
- print(f"✅ Onboarding page created: {result.tile} {result.emoji} - {result.url}")
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)