better-notion 1.4.0__py3-none-any.whl → 1.5.1__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.
@@ -5,7 +5,6 @@ This module provides commands for managing Notion pages.
5
5
  """
6
6
  from __future__ import annotations
7
7
 
8
- import asyncio
9
8
  import json
10
9
  from typing import Any
11
10
 
@@ -28,40 +27,39 @@ def get_client() -> NotionClient:
28
27
 
29
28
 
30
29
  @app.command()
31
- def get(page_id: str) -> None:
30
+ async def get(page_id: str) -> None:
32
31
  """
33
32
  Get a page by ID.
34
33
 
35
34
  Retrieves detailed information about a specific Notion page.
36
35
  """
37
- async def _get() -> str:
38
- try:
39
- client = get_client()
40
- page = await client.pages.get(page_id)
36
+ try:
37
+ client = get_client()
38
+ page = await client.pages.get(page_id)
41
39
 
42
- # Get parent (async method - note the parentheses)
43
- parent_obj = await page.parent()
40
+ # Get parent (async method - note the parentheses)
41
+ parent_obj = await page.parent()
44
42
 
45
- return format_success({
46
- "id": page.id,
47
- "title": page.title,
48
- "url": page.url,
49
- "parent_id": parent_obj.id if parent_obj else None,
50
- "parent_type": parent_obj.object if parent_obj else None,
51
- "created_time": page._data.get("created_time"),
52
- "last_edited_time": page._data.get("last_edited_time"),
53
- "archived": page.archived,
54
- "properties": {name: str(value) for name, value in page._data.get("properties", {}).items()},
55
- })
56
- except Exception as e:
57
- return format_error("UNKNOWN_ERROR", str(e), retry=False)
58
-
59
- result = asyncio.run(_get())
60
- typer.echo(result)
43
+ result = format_success({
44
+ "id": page.id,
45
+ "title": page.title,
46
+ "url": page.url,
47
+ "parent_id": parent_obj.id if parent_obj else None,
48
+ "parent_type": parent_obj.object if parent_obj else None,
49
+ "created_time": page._data.get("created_time"),
50
+ "last_edited_time": page._data.get("last_edited_time"),
51
+ "archived": page.archived,
52
+ "properties": {name: str(value) for name, value in page._data.get("properties", {}).items()},
53
+ })
54
+ typer.echo(result)
55
+ except Exception as e:
56
+ result = format_error("UNKNOWN_ERROR", str(e), retry=False)
57
+ typer.echo(result)
58
+ raise typer.Exit(code=1)
61
59
 
62
60
 
63
61
  @app.command()
64
- def create(
62
+ async def create(
65
63
  root: bool = typer.Option(False, "--root", "-r", help="Create page at workspace root"),
66
64
  parent: str = typer.Option(None, "--parent", "-p", help="Parent database or page ID"),
67
65
  title: str = typer.Option(..., "--title", "-t", help="Page title"),
@@ -74,65 +72,66 @@ def create(
74
72
 
75
73
  Note: Workspace parent (--root) may require specific integration permissions.
76
74
  """
77
- async def _create() -> str:
78
- client = get_client()
79
- props = json.loads(properties) if properties else {}
80
-
81
- # Validate mutual exclusivity
82
- if root and parent:
83
- return format_error(
84
- "INVALID_ARGUMENT",
85
- "Cannot specify both --root and --parent",
86
- retry=False
87
- )
88
-
89
- # Handle workspace parent
90
- if root:
91
- from better_notion._sdk.parents import WorkspaceParent
92
- parent_obj = WorkspaceParent()
93
- else:
94
- # Existing parent resolution logic
95
- # Try database first
75
+ client = get_client()
76
+ props = json.loads(properties) if properties else {}
77
+
78
+ # Validate mutual exclusivity
79
+ if root and parent:
80
+ result = format_error(
81
+ "INVALID_ARGUMENT",
82
+ "Cannot specify both --root and --parent",
83
+ retry=False
84
+ )
85
+ typer.echo(result)
86
+ raise typer.Exit(code=1)
87
+
88
+ # Handle workspace parent
89
+ if root:
90
+ from better_notion._sdk.parents import WorkspaceParent
91
+ parent_obj = WorkspaceParent()
92
+ else:
93
+ # Existing parent resolution logic
94
+ # Try database first
95
+ try:
96
+ parent_obj = await client.databases.get(parent)
97
+ except Exception as db_err:
98
+ # If database fails, try as page
96
99
  try:
97
- parent_obj = await client.databases.get(parent)
98
- except Exception as db_err:
99
- # If database fails, try as page
100
- try:
101
- parent_obj = await client.pages.get(parent)
102
- except Exception as page_err:
103
- # Both failed - return detailed error
104
- return format_error(
105
- "PARENT_NOT_FOUND",
106
- f"Could not find parent '{parent}' as database or page. "
107
- f"Database error: {str(db_err)}. Page error: {str(page_err)}",
108
- retry=False
109
- )
110
-
111
- page = await client.pages.create(parent=parent_obj, title=title, **props)
112
-
113
- # Get parent info safely
114
- parent_id = None
115
- parent_type = None
116
- if root:
117
- parent_type = "workspace"
118
- elif page.parent:
119
- parent_id = getattr(page.parent, 'id', None)
120
- parent_type = getattr(page.parent, 'object', None)
121
-
122
- return format_success({
123
- "id": page.id,
124
- "title": page.title,
125
- "url": page.url,
126
- "parent_id": parent_id,
127
- "parent_type": parent_type,
128
- })
129
-
130
- result = asyncio.run(_create())
100
+ parent_obj = await client.pages.get(parent)
101
+ except Exception as page_err:
102
+ # Both failed - return detailed error
103
+ result = format_error(
104
+ "PARENT_NOT_FOUND",
105
+ f"Could not find parent '{parent}' as database or page. "
106
+ f"Database error: {str(db_err)}. Page error: {str(page_err)}",
107
+ retry=False
108
+ )
109
+ typer.echo(result)
110
+ raise typer.Exit(code=1)
111
+
112
+ page = await client.pages.create(parent=parent_obj, title=title, **props)
113
+
114
+ # Get parent info safely
115
+ parent_id = None
116
+ parent_type = None
117
+ if root:
118
+ parent_type = "workspace"
119
+ elif page.parent:
120
+ parent_id = getattr(page.parent, 'id', None)
121
+ parent_type = getattr(page.parent, 'object', None)
122
+
123
+ result = format_success({
124
+ "id": page.id,
125
+ "title": page.title,
126
+ "url": page.url,
127
+ "parent_id": parent_id,
128
+ "parent_type": parent_type,
129
+ })
131
130
  typer.echo(result)
132
131
 
133
132
 
134
133
  @app.command()
135
- def update(
134
+ async def update(
136
135
  page_id: str = typer.Argument(..., help="Page ID to update"),
137
136
  properties: str = typer.Option(..., "--properties", "-p", help="JSON string of properties to update"),
138
137
  ) -> None:
@@ -141,46 +140,40 @@ def update(
141
140
 
142
141
  Updates the specified properties of a page.
143
142
  """
144
- async def _update() -> str:
145
- client = get_client()
146
- page = await client.pages.get(page_id)
147
- props = json.loads(properties)
143
+ client = get_client()
144
+ page = await client.pages.get(page_id)
145
+ props = json.loads(properties)
148
146
 
149
- updated_page = await page.update(**props)
147
+ updated_page = await page.update(**props)
150
148
 
151
- return format_success({
152
- "id": updated_page.id,
153
- "title": updated_page.title,
154
- "last_edited_time": updated_page.last_edited_time,
155
- })
156
-
157
- result = asyncio.run(_update())
149
+ result = format_success({
150
+ "id": updated_page.id,
151
+ "title": updated_page.title,
152
+ "last_edited_time": updated_page.last_edited_time,
153
+ })
158
154
  typer.echo(result)
159
155
 
160
156
 
161
157
  @app.command()
162
- def delete(page_id: str) -> None:
158
+ async def delete(page_id: str) -> None:
163
159
  """
164
160
  Delete a page.
165
161
 
166
162
  Permanently deletes a page and all its children.
167
163
  """
168
- async def _delete() -> str:
169
- client = get_client()
170
- page = await client.pages.get(page_id)
171
- await page.delete()
172
-
173
- return format_success({
174
- "id": page_id,
175
- "status": "deleted",
176
- })
177
-
178
- result = asyncio.run(_delete())
164
+ client = get_client()
165
+ page = await client.pages.get(page_id)
166
+ await page.delete()
167
+
168
+ result = format_success({
169
+ "id": page_id,
170
+ "status": "deleted",
171
+ })
179
172
  typer.echo(result)
180
173
 
181
174
 
182
175
  @app.command()
183
- def list(
176
+ async def list(
184
177
  database: str = typer.Option(..., "--database", "-d", help="Database ID to list pages from"),
185
178
  filter: str = typer.Option(None, "--filter", "-f", help="JSON filter for query"),
186
179
  ) -> None:
@@ -189,32 +182,29 @@ def list(
189
182
 
190
183
  Lists all pages in a database, optionally filtered.
191
184
  """
192
- async def _list() -> str:
193
- client = get_client()
194
- db = await client.databases.get(database)
195
-
196
- filters = json.loads(filter) if filter else {}
197
- pages = await db.query(client=client, **filters)
198
-
199
- return format_success({
200
- "database_id": database,
201
- "count": len(pages),
202
- "pages": [
203
- {
204
- "id": page.id,
205
- "title": page.title,
206
- "url": page.url,
207
- }
208
- for page in pages
209
- ],
210
- })
185
+ client = get_client()
186
+ db = await client.databases.get(database)
211
187
 
212
- result = asyncio.run(_list())
188
+ filters = json.loads(filter) if filter else {}
189
+ pages = await db.query(client=client, **filters)
190
+
191
+ result = format_success({
192
+ "database_id": database,
193
+ "count": len(pages),
194
+ "pages": [
195
+ {
196
+ "id": page.id,
197
+ "title": page.title,
198
+ "url": page.url,
199
+ }
200
+ for page in pages
201
+ ],
202
+ })
213
203
  typer.echo(result)
214
204
 
215
205
 
216
206
  @app.command()
217
- def search(
207
+ async def search(
218
208
  query: str = typer.Argument(..., help="Search query"),
219
209
  filter: str = typer.Option(None, "--filter", "-f", help="JSON filter for object type"),
220
210
  ) -> None:
@@ -223,60 +213,54 @@ def search(
223
213
 
224
214
  Searches for pages matching the query.
225
215
  """
226
- async def _search() -> str:
227
- client = get_client()
228
- filters = json.loads(filter) if filter else {}
229
-
230
- results = await client.search.search(query=query, filter=filters)
231
-
232
- pages = [r for r in results if hasattr(r, 'title')]
233
- return format_success({
234
- "query": query,
235
- "count": len(pages),
236
- "pages": [
237
- {
238
- "id": page.id,
239
- "title": page.title,
240
- "url": page.url,
241
- }
242
- for page in pages
243
- ],
244
- })
216
+ client = get_client()
217
+ filters = json.loads(filter) if filter else {}
245
218
 
246
- result = asyncio.run(_search())
219
+ results = await client.search.search(query=query, filter=filters)
220
+
221
+ pages = [r for r in results if hasattr(r, 'title')]
222
+ result = format_success({
223
+ "query": query,
224
+ "count": len(pages),
225
+ "pages": [
226
+ {
227
+ "id": page.id,
228
+ "title": page.title,
229
+ "url": page.url,
230
+ }
231
+ for page in pages
232
+ ],
233
+ })
247
234
  typer.echo(result)
248
235
 
249
236
 
250
237
  @app.command()
251
- def blocks(page_id: str) -> None:
238
+ async def blocks(page_id: str) -> None:
252
239
  """
253
240
  Get blocks in a page.
254
241
 
255
242
  Retrieves all blocks contained in a page.
256
243
  """
257
- async def _blocks() -> str:
258
- client = get_client()
259
- page = await client.pages.get(page_id)
260
-
261
- block_list = []
262
- async for block in page.children():
263
- block_list.append({
264
- "id": block.id,
265
- "type": block.type,
266
- })
267
-
268
- return format_success({
269
- "page_id": page_id,
270
- "count": len(block_list),
271
- "blocks": block_list,
244
+ client = get_client()
245
+ page = await client.pages.get(page_id)
246
+
247
+ block_list = []
248
+ async for block in page.children():
249
+ block_list.append({
250
+ "id": block.id,
251
+ "type": block.type,
272
252
  })
273
253
 
274
- result = asyncio.run(_blocks())
254
+ result = format_success({
255
+ "page_id": page_id,
256
+ "count": len(block_list),
257
+ "blocks": block_list,
258
+ })
275
259
  typer.echo(result)
276
260
 
277
261
 
278
262
  @app.command()
279
- def copy(
263
+ async def copy(
280
264
  page_id: str = typer.Argument(..., help="Page ID to copy"),
281
265
  destination: str = typer.Option(..., "--dest", "-d", help="Destination parent ID"),
282
266
  ) -> None:
@@ -285,34 +269,31 @@ def copy(
285
269
 
286
270
  Creates a copy of a page under a new parent.
287
271
  """
288
- async def _copy() -> str:
289
- client = get_client()
290
- page = await client.pages.get(page_id)
291
-
292
- # Get destination parent
293
- try:
294
- dest_parent = await client.databases.get(destination)
295
- except Exception:
296
- dest_parent = await client.pages.get(destination)
297
-
298
- # Create new page with same title
299
- new_page = await client.pages.create(
300
- parent=dest_parent,
301
- title=page.title,
302
- )
303
-
304
- return format_success({
305
- "original_id": page_id,
306
- "new_id": new_page.id,
307
- "new_url": new_page.url,
308
- })
309
-
310
- result = asyncio.run(_copy())
272
+ client = get_client()
273
+ page = await client.pages.get(page_id)
274
+
275
+ # Get destination parent
276
+ try:
277
+ dest_parent = await client.databases.get(destination)
278
+ except Exception:
279
+ dest_parent = await client.pages.get(destination)
280
+
281
+ # Create new page with same title
282
+ new_page = await client.pages.create(
283
+ parent=dest_parent,
284
+ title=page.title,
285
+ )
286
+
287
+ result = format_success({
288
+ "original_id": page_id,
289
+ "new_id": new_page.id,
290
+ "new_url": new_page.url,
291
+ })
311
292
  typer.echo(result)
312
293
 
313
294
 
314
295
  @app.command()
315
- def move(
296
+ async def move(
316
297
  page_id: str = typer.Argument(..., help="Page ID to move"),
317
298
  destination: str = typer.Argument(..., help="Destination parent ID"),
318
299
  ) -> None:
@@ -321,74 +302,65 @@ def move(
321
302
 
322
303
  Moves a page to a new parent (database or page).
323
304
  """
324
- async def _move() -> str:
325
- client = get_client()
326
- page = await client.pages.get(page_id)
327
-
328
- # Get destination parent
329
- try:
330
- dest_parent = await client.databases.get(destination)
331
- except Exception:
332
- dest_parent = await client.pages.get(destination)
333
-
334
- # Update parent
335
- await page.update(parent=dest_parent._data)
336
-
337
- return format_success({
338
- "id": page_id,
339
- "new_parent_id": destination,
340
- })
341
-
342
- result = asyncio.run(_move())
305
+ client = get_client()
306
+ page = await client.pages.get(page_id)
307
+
308
+ # Get destination parent
309
+ try:
310
+ dest_parent = await client.databases.get(destination)
311
+ except Exception:
312
+ dest_parent = await client.pages.get(destination)
313
+
314
+ # Update parent
315
+ await page.update(parent=dest_parent._data)
316
+
317
+ result = format_success({
318
+ "id": page_id,
319
+ "new_parent_id": destination,
320
+ })
343
321
  typer.echo(result)
344
322
 
345
323
 
346
324
  @app.command()
347
- def archive(page_id: str) -> None:
325
+ async def archive(page_id: str) -> None:
348
326
  """
349
327
  Archive a page.
350
328
 
351
329
  Archives a page (moves to trash).
352
330
  """
353
- async def _archive() -> str:
354
- client = get_client()
355
- page = await client.pages.get(page_id)
356
-
357
- await page.update(archived=True)
331
+ client = get_client()
332
+ page = await client.pages.get(page_id)
358
333
 
359
- return format_success({
360
- "id": page_id,
361
- "status": "archived",
362
- })
334
+ await page.update(archived=True)
363
335
 
364
- result = asyncio.run(_archive())
336
+ result = format_success({
337
+ "id": page_id,
338
+ "status": "archived",
339
+ })
365
340
  typer.echo(result)
366
341
 
367
342
 
368
343
  @app.command()
369
- def restore(page_id: str) -> None:
344
+ async def restore(page_id: str) -> None:
370
345
  """
371
346
  Restore an archived page.
372
347
 
373
348
  Restores a page from the trash/archive.
374
349
  """
375
- async def _restore() -> str:
376
- client = get_client()
377
- page = await client.pages.get(page_id)
350
+ client = get_client()
351
+ page = await client.pages.get(page_id)
378
352
 
379
- await page.update(archived=False)
353
+ await page.update(archived=False)
380
354
 
381
- return format_success({
382
- "id": page_id,
383
- "status": "restored",
384
- })
385
-
386
- result = asyncio.run(_restore())
355
+ result = format_success({
356
+ "id": page_id,
357
+ "status": "restored",
358
+ })
387
359
  typer.echo(result)
388
360
 
389
361
 
390
362
  @app.command("create-from-md")
391
- def create_from_md(
363
+ async def create_from_md(
392
364
  file: str = typer.Option(..., "--file", "-f", help="Path to markdown file"),
393
365
  parent: str = typer.Option(..., "--parent", "-p", help="Parent database or page ID"),
394
366
  title: str = typer.Option(None, "--title", "-t", help="Custom page title (default: first H1 or filename)"),
@@ -399,74 +371,79 @@ def create_from_md(
399
371
 
400
372
  Parses the markdown file and creates a new Notion page with all blocks.
401
373
  """
402
- async def _create() -> str:
403
- try:
404
- # Parse markdown file
405
- md_title, blocks = parse_markdown_file(file)
406
-
407
- # Use custom title if provided
408
- page_title = title or md_title
409
-
410
- if dry_run:
411
- # Show what would be created
412
- return format_success({
413
- "dry_run": True,
414
- "file": file,
415
- "title": page_title,
416
- "parent": parent,
417
- "blocks_count": len(blocks),
418
- "blocks_preview": [
419
- {
420
- "type": block.get("type"),
421
- "preview": str(block.get(block.get("type", {}), {}))[:100]
422
- }
423
- for block in blocks[:5] # Show first 5 blocks
424
- ]
425
- })
426
-
427
- client = get_client()
428
-
429
- # Resolve parent
430
- try:
431
- parent_obj = await client.databases.get(parent)
432
- except Exception:
433
- parent_obj = await client.pages.get(parent)
374
+ try:
375
+ # Parse markdown file
376
+ md_title, blocks = parse_markdown_file(file)
434
377
 
435
- # Create page
436
- page = await client.pages.create(
437
- parent=parent_obj,
438
- title=page_title,
439
- )
378
+ # Use custom title if provided
379
+ page_title = title or md_title
440
380
 
441
- # Add blocks to page
442
- if blocks:
443
- # Use BlockCollection to append blocks
444
- from better_notion._api.collections import BlockCollection
381
+ if dry_run:
382
+ # Show what would be created
383
+ result = format_success({
384
+ "dry_run": True,
385
+ "file": file,
386
+ "title": page_title,
387
+ "parent": parent,
388
+ "blocks_count": len(blocks),
389
+ "blocks_preview": [
390
+ {
391
+ "type": block.get("type"),
392
+ "preview": str(block.get(block.get("type", {}), {}))[:100]
393
+ }
394
+ for block in blocks[:5] # Show first 5 blocks
395
+ ]
396
+ })
397
+ typer.echo(result)
398
+ return
445
399
 
446
- blocks_collection = BlockCollection(client.api, parent_id=page.id)
400
+ client = get_client()
447
401
 
448
- # Add blocks one by one (Notion API limitation)
449
- for block_data in blocks:
450
- try:
451
- await blocks_collection.append(block_data)
452
- except Exception as e:
453
- # Continue with other blocks even if one fails
454
- pass
402
+ # Resolve parent
403
+ try:
404
+ parent_obj = await client.databases.get(parent)
405
+ except Exception:
406
+ parent_obj = await client.pages.get(parent)
455
407
 
456
- return format_success({
457
- "id": page.id,
458
- "title": page_title,
459
- "url": page.url,
460
- "blocks_created": len(blocks),
461
- "file": file,
462
- })
408
+ # Create page
409
+ page = await client.pages.create(
410
+ parent=parent_obj,
411
+ title=page_title,
412
+ )
463
413
 
464
- except FileNotFoundError as e:
465
- return format_error("FILE_NOT_FOUND", str(e), retry=False)
466
- except ValueError as e:
467
- return format_error("INVALID_FILE", str(e), retry=False)
468
- except Exception as e:
469
- return format_error("UNKNOWN_ERROR", str(e), retry=False)
414
+ # Add blocks to page
415
+ if blocks:
416
+ # Use BlockCollection to append blocks
417
+ from better_notion._api.collections import BlockCollection
470
418
 
471
- result = asyncio.run(_create())
472
- typer.echo(result)
419
+ blocks_collection = BlockCollection(client.api, parent_id=page.id)
420
+
421
+ # Add blocks one by one (Notion API limitation)
422
+ for block_data in blocks:
423
+ try:
424
+ await blocks_collection.append(block_data)
425
+ except Exception as e:
426
+ # Continue with other blocks even if one fails
427
+ pass
428
+
429
+ result = format_success({
430
+ "id": page.id,
431
+ "title": page_title,
432
+ "url": page.url,
433
+ "blocks_created": len(blocks),
434
+ "file": file,
435
+ })
436
+ typer.echo(result)
437
+
438
+ except FileNotFoundError as e:
439
+ result = format_error("FILE_NOT_FOUND", str(e), retry=False)
440
+ typer.echo(result)
441
+ raise typer.Exit(code=1)
442
+ except ValueError as e:
443
+ result = format_error("INVALID_FILE", str(e), retry=False)
444
+ typer.echo(result)
445
+ raise typer.Exit(code=1)
446
+ except Exception as e:
447
+ result = format_error("UNKNOWN_ERROR", str(e), retry=False)
448
+ typer.echo(result)
449
+ raise typer.Exit(code=1)