better-notion 1.4.0__tar.gz → 1.5.1__tar.gz
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.
- {better_notion-1.4.0 → better_notion-1.5.1}/PKG-INFO +1 -1
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/collections/databases.py +32 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/commands/databases.py +147 -24
- better_notion-1.5.1/better_notion/_cli/commands/pages.py +449 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/database.py +12 -7
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/plugins/official/agents.py +22 -2
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/plugins/official/agents_cli.py +29 -4
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/plugins/official/agents_sdk/managers.py +29 -1
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/utils/agents/schemas.py +91 -68
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/utils/agents/workspace.py +43 -29
- better_notion-1.5.1/better_notion/utils/validators.py +132 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/pyproject.toml +1 -1
- better_notion-1.4.0/better_notion/_cli/commands/pages.py +0 -472
- {better_notion-1.4.0 → better_notion-1.5.1}/.gitignore +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/LICENSE +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/README.md +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/client.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/collections/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/collections/blocks.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/collections/comments.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/collections/pages.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/collections/users.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/entities/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/entities/block.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/entities/comment.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/entities/database.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/entities/page.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/entities/user.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/errors.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/oauth.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/properties/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/properties/base.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/properties/checkbox.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/properties/date.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/properties/email.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/properties/number.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/properties/phone.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/properties/rich_text.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/properties/select.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/properties/title.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/properties/url.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/utils/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_api/utils/pagination.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/async_typer.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/commands/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/commands/auth.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/commands/blocks.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/commands/comments.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/commands/config.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/commands/plugins.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/commands/search.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/commands/update.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/commands/users.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/commands/workspace.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/config.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/display.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/errors.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/main.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/markdown.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/response.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_cli/utils/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/base/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/base/entity.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/cache/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/cache/cache.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/client.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/managers/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/managers/block_manager.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/managers/comment_manager.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/managers/database_manager.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/managers/page_manager.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/managers/user_manager.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/block.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/audio.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/bookmark.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/breadcrumb.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/bullet.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/callout.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/code.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/column.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/column_list.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/divider.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/embed.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/equation.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/file.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/heading.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/image.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/numbered.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/paragraph.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/pdf.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/quote.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/synced_block.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/table.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/table_row.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/template.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/todo.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/toggle.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/blocks/video.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/comment.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/page.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/models/user.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/parents/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/plugins.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/properties/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/properties/formula.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/properties/parsers.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/properties/relation.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/query/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/query/database_query.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/_sdk/query/filter_translator.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/plugins/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/plugins/base.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/plugins/loader.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/plugins/official/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/plugins/official/agents_sdk/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/plugins/official/agents_sdk/models.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/plugins/official/agents_sdk/plugin.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/plugins/official/productivity.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/plugins/state.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/utils/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/utils/agents/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/utils/agents/auth.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/utils/agents/dependency_resolver.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/utils/agents/project_context.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/utils/agents/rbac.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/utils/agents/state_machine.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/utils/helpers.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/better_notion/utils/retry.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/base/test_entity.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/cache/test_cache.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/models/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/models/blocks/test_advanced_blocks.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/models/test_block.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/models/test_database.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/models/test_database_bug.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/models/test_page.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/models/test_user.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/properties/test_formula.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/properties/test_parsers.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/properties/test_relation.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/query/test_database_query.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/query/test_filter_translator.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/test_client.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/_sdk/test_comment.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/agents/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/agents/test_auth.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/agents/test_dependency_resolver.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/agents/test_plugin.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/agents/test_project_context.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/agents/test_rbac.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/agents/test_schemas.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/agents/test_state_machine.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/agents/test_workspace.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/cli/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/cli/test_async_typer.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/cli/test_config.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/cli/test_display.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/cli/test_errors.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/cli/test_main.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/cli/test_pages_commands.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/cli/test_response.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/cli/test_update.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/conftest.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/integration/conftest.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/integration/test_blocks.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/integration/test_databases.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/integration/test_pages.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/integration/test_search.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/integration/test_users.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/plugins/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/plugins/test_agents_cli.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/plugins/test_agents_sdk_integration.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/plugins/test_agents_sdk_managers.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/plugins/test_agents_sdk_models.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/plugins/test_base.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/plugins/test_loader.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/plugins/test_marketplace.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/plugins/test_plugin_commands_state.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/plugins/test_productivity_plugin.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/plugins/test_state.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/sdk/__init__.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/sdk/test_plugin_system.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/unit/test_client.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/unit/test_collections.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/unit/test_entities.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/unit/test_errors.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/unit/test_helpers.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/unit/test_properties.py +0 -0
- {better_notion-1.4.0 → better_notion-1.5.1}/tests/utils/test_retry.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: better-notion
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.1
|
|
4
4
|
Summary: A high-level Python SDK for the Notion API with developer experience in mind.
|
|
5
5
|
Project-URL: Homepage, https://github.com/nesalia-inc/better-notion
|
|
6
6
|
Project-URL: Documentation, https://github.com/nesalia-inc/better-notion#readme
|
|
@@ -73,3 +73,35 @@ class DatabaseCollection:
|
|
|
73
73
|
# Ensure parent is set to the database
|
|
74
74
|
page_data = {"parent": {"database_id": database_id}, **kwargs}
|
|
75
75
|
return await self._api._request("POST", "/pages", json=page_data)
|
|
76
|
+
|
|
77
|
+
async def create(
|
|
78
|
+
self,
|
|
79
|
+
parent: dict[str, Any],
|
|
80
|
+
title: str,
|
|
81
|
+
properties: dict[str, Any]
|
|
82
|
+
) -> dict[str, Any]:
|
|
83
|
+
"""Create a new database.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
parent: Parent object (e.g., {"type": "page_id", "page_id": "..."})
|
|
87
|
+
title: Database title
|
|
88
|
+
properties: Database schema/properties configuration
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Raw database data dict from Notion API.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
ValidationError: If the database configuration is invalid.
|
|
95
|
+
NotFoundError: If the parent page does not exist.
|
|
96
|
+
"""
|
|
97
|
+
# Build title array
|
|
98
|
+
title_array = [{"type": "text", "text": {"content": title}}]
|
|
99
|
+
|
|
100
|
+
# Create database request
|
|
101
|
+
database_data = {
|
|
102
|
+
"parent": parent,
|
|
103
|
+
"title": title_array,
|
|
104
|
+
"properties": properties
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return await self._api._request("POST", "/databases", json=database_data)
|
|
@@ -5,7 +5,6 @@ This module provides commands for managing Notion databases.
|
|
|
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
|
|
|
@@ -19,6 +18,49 @@ from better_notion._sdk.client import NotionClient
|
|
|
19
18
|
app = AsyncTyper(help="Databases commands")
|
|
20
19
|
|
|
21
20
|
|
|
21
|
+
# Schema templates for common use cases
|
|
22
|
+
SCHEMA_TEMPLATES = {
|
|
23
|
+
"minimal": {
|
|
24
|
+
"description": "Database with only a title property (minimal schema)",
|
|
25
|
+
"schema": {}
|
|
26
|
+
},
|
|
27
|
+
"simple": {
|
|
28
|
+
"description": "Simple database with title, text, and number properties",
|
|
29
|
+
"schema": {
|
|
30
|
+
"Notes": {"type": "rich_text"},
|
|
31
|
+
"Count": {"type": "number"}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"task": {
|
|
35
|
+
"description": "Task tracking database with status, priority, and due date",
|
|
36
|
+
"schema": {
|
|
37
|
+
"Status": {
|
|
38
|
+
"type": "select",
|
|
39
|
+
"select": {
|
|
40
|
+
"options": [
|
|
41
|
+
{"name": "Not Started", "color": "gray"},
|
|
42
|
+
{"name": "In Progress", "color": "blue"},
|
|
43
|
+
{"name": "Completed", "color": "green"}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"Priority": {
|
|
48
|
+
"type": "select",
|
|
49
|
+
"select": {
|
|
50
|
+
"options": [
|
|
51
|
+
{"name": "Low", "color": "gray"},
|
|
52
|
+
{"name": "Medium", "color": "yellow"},
|
|
53
|
+
{"name": "High", "color": "orange"},
|
|
54
|
+
{"name": "Critical", "color": "red"}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"Due Date": {"type": "date"}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
22
64
|
def get_client() -> NotionClient:
|
|
23
65
|
"""Get authenticated Notion client."""
|
|
24
66
|
config = Config.load()
|
|
@@ -48,34 +90,115 @@ def get(database_id: str) -> None:
|
|
|
48
90
|
|
|
49
91
|
|
|
50
92
|
@app.command()
|
|
51
|
-
def create(
|
|
93
|
+
async def create(
|
|
52
94
|
parent: str = typer.Option(..., "--parent", "-p", help="Parent page ID"),
|
|
53
95
|
title: str = typer.Option(..., "--title", "-t", help="Database title"),
|
|
54
96
|
schema: str = typer.Option("{}", "--schema", "-s", help="JSON schema for properties"),
|
|
97
|
+
template: str = typer.Option(None, "--template", help="Use a predefined schema template (minimal, simple, task)"),
|
|
55
98
|
) -> None:
|
|
56
|
-
"""Create a new database.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
99
|
+
"""Create a new database.
|
|
100
|
+
|
|
101
|
+
Schema Format:
|
|
102
|
+
The schema should be a JSON object mapping property names to their types.
|
|
103
|
+
Each property should have a "type" field and type-specific configuration.
|
|
104
|
+
|
|
105
|
+
Common property types:
|
|
106
|
+
- title: {"type": "title"} (Note: Every database must have exactly one title property)
|
|
107
|
+
- rich_text: {"type": "rich_text"}
|
|
108
|
+
- number: {"type": "number", "number": {"format": "number"}}
|
|
109
|
+
- select: {"type": "select", "select": {"options": [{"name": "Option1", "color": "gray"}]}}
|
|
110
|
+
- date: {"type": "date"}
|
|
111
|
+
- checkbox: {"type": "checkbox"}
|
|
112
|
+
- email: {"type": "email"}
|
|
113
|
+
- url: {"type": "url"}
|
|
114
|
+
- phone: {"type": "phone_number"}
|
|
115
|
+
|
|
116
|
+
Examples:
|
|
117
|
+
# Minimal database (title only)
|
|
118
|
+
notion databases create --parent page123 --title "My Database" --template minimal
|
|
119
|
+
|
|
120
|
+
# Simple custom schema
|
|
121
|
+
notion databases create --parent page123 --title "Tasks" \\
|
|
122
|
+
--schema '{"Status": {"type": "select", "select": {"options": [{"name": "Todo", "color": "gray"}]}}}'
|
|
123
|
+
|
|
124
|
+
# Use a predefined template
|
|
125
|
+
notion databases create --parent page123 --title "Task Tracker" --template task
|
|
126
|
+
|
|
127
|
+
# Show available templates
|
|
128
|
+
notion database-templates
|
|
129
|
+
"""
|
|
130
|
+
try:
|
|
131
|
+
client = get_client()
|
|
132
|
+
parent_page = await client.pages.get(parent)
|
|
133
|
+
|
|
134
|
+
# Use template if specified
|
|
135
|
+
if template:
|
|
136
|
+
if template not in SCHEMA_TEMPLATES:
|
|
137
|
+
result = format_error(
|
|
138
|
+
"INVALID_TEMPLATE",
|
|
139
|
+
f"Template '{template}' not found. Available templates: {', '.join(SCHEMA_TEMPLATES.keys())}. Use 'notion database-templates' to see all templates.",
|
|
140
|
+
retry=False
|
|
141
|
+
)
|
|
142
|
+
typer.echo(result)
|
|
143
|
+
raise typer.Exit(code=1)
|
|
144
|
+
schema_dict = SCHEMA_TEMPLATES[template]["schema"]
|
|
145
|
+
else:
|
|
146
|
+
# Parse custom schema
|
|
147
|
+
try:
|
|
148
|
+
schema_dict = json.loads(schema)
|
|
149
|
+
except json.JSONDecodeError as e:
|
|
150
|
+
result = format_error(
|
|
151
|
+
"INVALID_SCHEMA",
|
|
152
|
+
f"Invalid JSON schema: {str(e)}",
|
|
153
|
+
retry=False
|
|
154
|
+
)
|
|
155
|
+
typer.echo(result)
|
|
156
|
+
raise typer.Exit(code=1)
|
|
157
|
+
|
|
158
|
+
# Validate schema structure
|
|
159
|
+
if not isinstance(schema_dict, dict):
|
|
160
|
+
result = format_error(
|
|
161
|
+
"INVALID_SCHEMA",
|
|
162
|
+
"Schema must be a JSON object (dictionary)",
|
|
163
|
+
retry=False
|
|
67
164
|
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
165
|
+
typer.echo(result)
|
|
166
|
+
raise typer.Exit(code=1)
|
|
167
|
+
|
|
168
|
+
db = await client.databases.create(
|
|
169
|
+
parent=parent_page,
|
|
170
|
+
title=title,
|
|
171
|
+
schema=schema_dict,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
result = format_success({
|
|
175
|
+
"id": db.id,
|
|
176
|
+
"title": db.title,
|
|
177
|
+
"url": db.url,
|
|
178
|
+
"properties_count": len(db.schema) if db.schema else 0,
|
|
179
|
+
})
|
|
180
|
+
typer.echo(result)
|
|
181
|
+
|
|
182
|
+
except typer.Exit:
|
|
183
|
+
raise
|
|
184
|
+
except Exception as e:
|
|
185
|
+
error_msg = str(e)
|
|
186
|
+
# Add helpful context for common errors
|
|
187
|
+
if "schema" in error_msg.lower():
|
|
188
|
+
error_msg += "\n\nHint: Use 'notion database-templates' to see schema examples or --template minimal for a simple database"
|
|
189
|
+
result = format_error("CREATE_ERROR", error_msg, retry=False)
|
|
190
|
+
typer.echo(result)
|
|
191
|
+
raise typer.Exit(code=1)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@app.command("database-templates")
|
|
195
|
+
def templates_cmd() -> None:
|
|
196
|
+
"""Show available database schema templates.
|
|
197
|
+
|
|
198
|
+
Examples:
|
|
199
|
+
notion database-templates
|
|
200
|
+
"""
|
|
201
|
+
typer.echo(json.dumps(SCHEMA_TEMPLATES, indent=2))
|
|
79
202
|
|
|
80
203
|
|
|
81
204
|
@app.command()
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pages commands for Better Notion CLI.
|
|
3
|
+
|
|
4
|
+
This module provides commands for managing Notion pages.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from typer.testing import CliRunner
|
|
13
|
+
|
|
14
|
+
from better_notion._cli.async_typer import AsyncTyper
|
|
15
|
+
from better_notion._cli.config import Config
|
|
16
|
+
from better_notion._cli.response import format_error, format_success
|
|
17
|
+
from better_notion._cli.markdown import parse_markdown_file
|
|
18
|
+
from better_notion._sdk.client import NotionClient
|
|
19
|
+
|
|
20
|
+
app = AsyncTyper(help="Pages commands")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_client() -> NotionClient:
|
|
24
|
+
"""Get authenticated Notion client."""
|
|
25
|
+
config = Config.load()
|
|
26
|
+
return NotionClient(auth=config.token, timeout=config.timeout)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command()
|
|
30
|
+
async def get(page_id: str) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Get a page by ID.
|
|
33
|
+
|
|
34
|
+
Retrieves detailed information about a specific Notion page.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
client = get_client()
|
|
38
|
+
page = await client.pages.get(page_id)
|
|
39
|
+
|
|
40
|
+
# Get parent (async method - note the parentheses)
|
|
41
|
+
parent_obj = await page.parent()
|
|
42
|
+
|
|
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)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.command()
|
|
62
|
+
async def create(
|
|
63
|
+
root: bool = typer.Option(False, "--root", "-r", help="Create page at workspace root"),
|
|
64
|
+
parent: str = typer.Option(None, "--parent", "-p", help="Parent database or page ID"),
|
|
65
|
+
title: str = typer.Option(..., "--title", "-t", help="Page title"),
|
|
66
|
+
properties: str = typer.Option(None, "--properties", help="JSON string of additional properties"),
|
|
67
|
+
) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Create a new page.
|
|
70
|
+
|
|
71
|
+
Creates a new page under a parent database/page or at workspace root.
|
|
72
|
+
|
|
73
|
+
Note: Workspace parent (--root) may require specific integration permissions.
|
|
74
|
+
"""
|
|
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
|
|
99
|
+
try:
|
|
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
|
+
})
|
|
130
|
+
typer.echo(result)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@app.command()
|
|
134
|
+
async def update(
|
|
135
|
+
page_id: str = typer.Argument(..., help="Page ID to update"),
|
|
136
|
+
properties: str = typer.Option(..., "--properties", "-p", help="JSON string of properties to update"),
|
|
137
|
+
) -> None:
|
|
138
|
+
"""
|
|
139
|
+
Update a page.
|
|
140
|
+
|
|
141
|
+
Updates the specified properties of a page.
|
|
142
|
+
"""
|
|
143
|
+
client = get_client()
|
|
144
|
+
page = await client.pages.get(page_id)
|
|
145
|
+
props = json.loads(properties)
|
|
146
|
+
|
|
147
|
+
updated_page = await page.update(**props)
|
|
148
|
+
|
|
149
|
+
result = format_success({
|
|
150
|
+
"id": updated_page.id,
|
|
151
|
+
"title": updated_page.title,
|
|
152
|
+
"last_edited_time": updated_page.last_edited_time,
|
|
153
|
+
})
|
|
154
|
+
typer.echo(result)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@app.command()
|
|
158
|
+
async def delete(page_id: str) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Delete a page.
|
|
161
|
+
|
|
162
|
+
Permanently deletes a page and all its children.
|
|
163
|
+
"""
|
|
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
|
+
})
|
|
172
|
+
typer.echo(result)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@app.command()
|
|
176
|
+
async def list(
|
|
177
|
+
database: str = typer.Option(..., "--database", "-d", help="Database ID to list pages from"),
|
|
178
|
+
filter: str = typer.Option(None, "--filter", "-f", help="JSON filter for query"),
|
|
179
|
+
) -> None:
|
|
180
|
+
"""
|
|
181
|
+
List pages in a database.
|
|
182
|
+
|
|
183
|
+
Lists all pages in a database, optionally filtered.
|
|
184
|
+
"""
|
|
185
|
+
client = get_client()
|
|
186
|
+
db = await client.databases.get(database)
|
|
187
|
+
|
|
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
|
+
})
|
|
203
|
+
typer.echo(result)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@app.command()
|
|
207
|
+
async def search(
|
|
208
|
+
query: str = typer.Argument(..., help="Search query"),
|
|
209
|
+
filter: str = typer.Option(None, "--filter", "-f", help="JSON filter for object type"),
|
|
210
|
+
) -> None:
|
|
211
|
+
"""
|
|
212
|
+
Search for pages.
|
|
213
|
+
|
|
214
|
+
Searches for pages matching the query.
|
|
215
|
+
"""
|
|
216
|
+
client = get_client()
|
|
217
|
+
filters = json.loads(filter) if filter else {}
|
|
218
|
+
|
|
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
|
+
})
|
|
234
|
+
typer.echo(result)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@app.command()
|
|
238
|
+
async def blocks(page_id: str) -> None:
|
|
239
|
+
"""
|
|
240
|
+
Get blocks in a page.
|
|
241
|
+
|
|
242
|
+
Retrieves all blocks contained in a page.
|
|
243
|
+
"""
|
|
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,
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
result = format_success({
|
|
255
|
+
"page_id": page_id,
|
|
256
|
+
"count": len(block_list),
|
|
257
|
+
"blocks": block_list,
|
|
258
|
+
})
|
|
259
|
+
typer.echo(result)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@app.command()
|
|
263
|
+
async def copy(
|
|
264
|
+
page_id: str = typer.Argument(..., help="Page ID to copy"),
|
|
265
|
+
destination: str = typer.Option(..., "--dest", "-d", help="Destination parent ID"),
|
|
266
|
+
) -> None:
|
|
267
|
+
"""
|
|
268
|
+
Copy a page.
|
|
269
|
+
|
|
270
|
+
Creates a copy of a page under a new parent.
|
|
271
|
+
"""
|
|
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
|
+
})
|
|
292
|
+
typer.echo(result)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@app.command()
|
|
296
|
+
async def move(
|
|
297
|
+
page_id: str = typer.Argument(..., help="Page ID to move"),
|
|
298
|
+
destination: str = typer.Argument(..., help="Destination parent ID"),
|
|
299
|
+
) -> None:
|
|
300
|
+
"""
|
|
301
|
+
Move a page.
|
|
302
|
+
|
|
303
|
+
Moves a page to a new parent (database or page).
|
|
304
|
+
"""
|
|
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
|
+
})
|
|
321
|
+
typer.echo(result)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@app.command()
|
|
325
|
+
async def archive(page_id: str) -> None:
|
|
326
|
+
"""
|
|
327
|
+
Archive a page.
|
|
328
|
+
|
|
329
|
+
Archives a page (moves to trash).
|
|
330
|
+
"""
|
|
331
|
+
client = get_client()
|
|
332
|
+
page = await client.pages.get(page_id)
|
|
333
|
+
|
|
334
|
+
await page.update(archived=True)
|
|
335
|
+
|
|
336
|
+
result = format_success({
|
|
337
|
+
"id": page_id,
|
|
338
|
+
"status": "archived",
|
|
339
|
+
})
|
|
340
|
+
typer.echo(result)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@app.command()
|
|
344
|
+
async def restore(page_id: str) -> None:
|
|
345
|
+
"""
|
|
346
|
+
Restore an archived page.
|
|
347
|
+
|
|
348
|
+
Restores a page from the trash/archive.
|
|
349
|
+
"""
|
|
350
|
+
client = get_client()
|
|
351
|
+
page = await client.pages.get(page_id)
|
|
352
|
+
|
|
353
|
+
await page.update(archived=False)
|
|
354
|
+
|
|
355
|
+
result = format_success({
|
|
356
|
+
"id": page_id,
|
|
357
|
+
"status": "restored",
|
|
358
|
+
})
|
|
359
|
+
typer.echo(result)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@app.command("create-from-md")
|
|
363
|
+
async def create_from_md(
|
|
364
|
+
file: str = typer.Option(..., "--file", "-f", help="Path to markdown file"),
|
|
365
|
+
parent: str = typer.Option(..., "--parent", "-p", help="Parent database or page ID"),
|
|
366
|
+
title: str = typer.Option(None, "--title", "-t", help="Custom page title (default: first H1 or filename)"),
|
|
367
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be created without creating"),
|
|
368
|
+
) -> None:
|
|
369
|
+
"""
|
|
370
|
+
Create a page from a markdown file.
|
|
371
|
+
|
|
372
|
+
Parses the markdown file and creates a new Notion page with all blocks.
|
|
373
|
+
"""
|
|
374
|
+
try:
|
|
375
|
+
# Parse markdown file
|
|
376
|
+
md_title, blocks = parse_markdown_file(file)
|
|
377
|
+
|
|
378
|
+
# Use custom title if provided
|
|
379
|
+
page_title = title or md_title
|
|
380
|
+
|
|
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
|
|
399
|
+
|
|
400
|
+
client = get_client()
|
|
401
|
+
|
|
402
|
+
# Resolve parent
|
|
403
|
+
try:
|
|
404
|
+
parent_obj = await client.databases.get(parent)
|
|
405
|
+
except Exception:
|
|
406
|
+
parent_obj = await client.pages.get(parent)
|
|
407
|
+
|
|
408
|
+
# Create page
|
|
409
|
+
page = await client.pages.create(
|
|
410
|
+
parent=parent_obj,
|
|
411
|
+
title=page_title,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
# Add blocks to page
|
|
415
|
+
if blocks:
|
|
416
|
+
# Use BlockCollection to append blocks
|
|
417
|
+
from better_notion._api.collections import BlockCollection
|
|
418
|
+
|
|
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)
|