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.
- notionary/__init__.py +3 -16
- notionary/{notion_client.py → base_notion_client.py} +92 -98
- notionary/blocks/__init__.py +61 -0
- notionary/{elements → blocks}/audio_element.py +6 -3
- notionary/{elements → blocks}/bookmark_element.py +3 -5
- notionary/{elements → blocks}/bulleted_list_element.py +5 -6
- notionary/{elements → blocks}/callout_element.py +4 -6
- notionary/{elements → blocks}/code_block_element.py +4 -5
- notionary/{elements → blocks}/column_element.py +3 -5
- notionary/{elements → blocks}/divider_element.py +3 -5
- notionary/{elements → blocks}/embed_element.py +4 -5
- notionary/{elements → blocks}/heading_element.py +4 -7
- notionary/{elements → blocks}/image_element.py +4 -5
- notionary/{elements → blocks}/mention_element.py +3 -6
- notionary/blocks/notion_block_client.py +26 -0
- notionary/{elements → blocks}/notion_block_element.py +2 -3
- notionary/{elements → blocks}/numbered_list_element.py +4 -6
- notionary/{elements → blocks}/paragraph_element.py +4 -6
- 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 -5
- 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 +5 -6
- notionary/{elements → blocks}/text_inline_formatter.py +1 -4
- notionary/{elements → blocks}/todo_element.py +5 -6
- notionary/{elements → blocks}/toggle_element.py +3 -5
- notionary/{elements → blocks}/toggleable_heading_element.py +4 -6
- notionary/{elements → blocks}/video_element.py +4 -5
- notionary/cli/main.py +157 -128
- notionary/cli/onboarding.py +10 -9
- 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 -126
- 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.13.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 -190
- 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 -328
- 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/util/warn_direct_constructor_usage.py +0 -54
- notionary-0.2.13.dist-info/RECORD +0 -67
- /notionary/util/{singleton.py → singleton_decorator.py} +0 -0
- {notionary-0.2.13.dist-info → notionary-0.2.14.dist-info}/WHEEL +0 -0
- {notionary-0.2.13.dist-info → notionary-0.2.14.dist-info}/entry_points.txt +0 -0
- {notionary-0.2.13.dist-info → notionary-0.2.14.dist-info}/licenses/LICENSE +0 -0
- {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.
|
20
|
-
|
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(
|
27
|
-
logging.getLogger(
|
28
|
-
logging.getLogger(
|
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(
|
33
|
-
logging.getLogger(
|
34
|
-
logging.getLogger(
|
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
|
-
|
77
|
-
|
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
|
93
|
-
|
96
|
+
databases = await workspace.list_all_databases(limit=50)
|
97
|
+
|
94
98
|
# Update progress to show completion
|
95
|
-
progress.update(
|
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(
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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(
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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(
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
-
|
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(
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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(
|
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(
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
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(
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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(
|
244
|
-
|
245
|
-
|
246
|
-
|
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
|
-
|
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(
|
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(
|
285
|
-
console.print(
|
286
|
-
|
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,
|
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(
|
319
|
-
updated_lines.append(f
|
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
|
327
|
-
|
352
|
+
updated_lines.append(f"NOTION_SECRET={integration_key}")
|
353
|
+
|
328
354
|
# .env Datei schreiben
|
329
|
-
with open(env_file,
|
330
|
-
f.write(
|
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(
|
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
|
-
|
347
|
-
|
374
|
+
|
375
|
+
if __name__ == "__main__":
|
376
|
+
main()
|
notionary/cli/onboarding.py
CHANGED
@@ -2,21 +2,23 @@ import asyncio
|
|
2
2
|
from dataclasses import dataclass
|
3
3
|
from notionary import NotionDatabase
|
4
4
|
|
5
|
-
|
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)
|