notionary 0.2.22__py3-none-any.whl → 0.2.23__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/blocks/child_database/child_database_element.py +2 -4
- notionary/blocks/rich_text/text_inline_formatter.py +1 -1
- notionary/comments/__init__.py +26 -0
- notionary/comments/client.py +211 -0
- notionary/comments/models.py +129 -0
- notionary/page/client.py +1 -6
- notionary/page/notion_page.py +55 -11
- notionary/page/page_context.py +0 -1
- notionary/shared/__init__.py +5 -0
- notionary/{blocks/rich_text → shared}/name_to_id_resolver.py +0 -2
- {notionary-0.2.22.dist-info → notionary-0.2.23.dist-info}/METADATA +1 -3
- {notionary-0.2.22.dist-info → notionary-0.2.23.dist-info}/RECORD +14 -10
- {notionary-0.2.22.dist-info → notionary-0.2.23.dist-info}/LICENSE +0 -0
- {notionary-0.2.22.dist-info → notionary-0.2.23.dist-info}/WHEEL +0 -0
@@ -8,6 +8,7 @@ from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformati
|
|
8
8
|
from notionary.blocks.models import Block, BlockType
|
9
9
|
from notionary.util import LoggingMixin
|
10
10
|
|
11
|
+
|
11
12
|
class ChildDatabaseElement(BaseBlockElement, LoggingMixin):
|
12
13
|
"""
|
13
14
|
Handles conversion between Markdown database references and Notion child database blocks.
|
@@ -23,14 +24,11 @@ class ChildDatabaseElement(BaseBlockElement, LoggingMixin):
|
|
23
24
|
return block.type == BlockType.CHILD_DATABASE and block.child_database
|
24
25
|
|
25
26
|
@classmethod
|
26
|
-
async def markdown_to_notion(
|
27
|
-
cls, text: str
|
28
|
-
) -> Optional[str]:
|
27
|
+
async def markdown_to_notion(cls, text: str) -> Optional[str]:
|
29
28
|
"""
|
30
29
|
Convert markdown database syntax to actual Notion database.
|
31
30
|
Returns the database_id if successful, None otherwise.
|
32
31
|
"""
|
33
|
-
cls.logger.warning("Creating database from markdown is not supported via the block api. Call the create_child_page method in NotionPage instead.s")
|
34
32
|
return None
|
35
33
|
|
36
34
|
@classmethod
|
@@ -10,7 +10,7 @@ from notionary.blocks.rich_text.rich_text_models import (
|
|
10
10
|
MentionTemplateMention,
|
11
11
|
)
|
12
12
|
from notionary.blocks.types import BlockColor
|
13
|
-
from notionary.
|
13
|
+
from notionary.shared import NameIdResolver
|
14
14
|
|
15
15
|
|
16
16
|
class TextInlineFormatter:
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from .client import CommentClient
|
2
|
+
from .models import (
|
3
|
+
Comment,
|
4
|
+
CommentAttachment,
|
5
|
+
CommentAttachmentExternal,
|
6
|
+
CommentAttachmentFile,
|
7
|
+
CommentDisplayName,
|
8
|
+
CommentListResponse,
|
9
|
+
CommentParent,
|
10
|
+
FileWithExpiry,
|
11
|
+
UserRef,
|
12
|
+
)
|
13
|
+
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"CommentClient",
|
17
|
+
"Comment",
|
18
|
+
"CommentAttachment",
|
19
|
+
"CommentAttachmentExternal",
|
20
|
+
"CommentAttachmentFile",
|
21
|
+
"CommentDisplayName",
|
22
|
+
"CommentListResponse",
|
23
|
+
"CommentParent",
|
24
|
+
"FileWithExpiry",
|
25
|
+
"UserRef",
|
26
|
+
]
|
@@ -0,0 +1,211 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Any, AsyncGenerator, Optional
|
4
|
+
|
5
|
+
from notionary.base_notion_client import BaseNotionClient
|
6
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
7
|
+
from notionary.comments.models import Comment, CommentListResponse
|
8
|
+
|
9
|
+
|
10
|
+
class CommentClient(BaseNotionClient):
|
11
|
+
"""
|
12
|
+
Client for Notion comment operations.
|
13
|
+
Uses Pydantic models for typed responses.
|
14
|
+
|
15
|
+
Notes / API constraints:
|
16
|
+
- Listing returns only *unresolved* comments. Resolved comments are not returned.
|
17
|
+
- You can create:
|
18
|
+
1) a top-level comment on a page
|
19
|
+
2) a reply in an existing discussion (requires discussion_id)
|
20
|
+
You cannot start a brand-new inline thread via API.
|
21
|
+
- Read/Insert comment capabilities must be enabled for the integration.
|
22
|
+
"""
|
23
|
+
|
24
|
+
async def retrieve_comment(self, comment_id: str) -> Comment:
|
25
|
+
"""
|
26
|
+
Retrieve a single Comment object by its ID.
|
27
|
+
|
28
|
+
Requires the integration to have "Read comment" capability enabled.
|
29
|
+
Raises 403 (restricted_resource) without it.
|
30
|
+
"""
|
31
|
+
resp = await self.get(f"comments/{comment_id}")
|
32
|
+
if resp is None:
|
33
|
+
raise RuntimeError("Failed to retrieve comment.")
|
34
|
+
return Comment.model_validate(resp)
|
35
|
+
|
36
|
+
async def list_all_comments_for_page(
|
37
|
+
self, *, page_id: str, page_size: int = 100
|
38
|
+
) -> list[Comment]:
|
39
|
+
"""Returns all unresolved comments for a page (handles pagination)."""
|
40
|
+
results: list[Comment] = []
|
41
|
+
cursor: str | None = None
|
42
|
+
while True:
|
43
|
+
page = await self.list_comments(
|
44
|
+
block_id=page_id, start_cursor=cursor, page_size=page_size
|
45
|
+
)
|
46
|
+
results.extend(page.results)
|
47
|
+
if not page.has_more:
|
48
|
+
break
|
49
|
+
cursor = page.next_cursor
|
50
|
+
return results
|
51
|
+
|
52
|
+
async def list_comments(
|
53
|
+
self,
|
54
|
+
*,
|
55
|
+
block_id: str,
|
56
|
+
start_cursor: Optional[str] = None,
|
57
|
+
page_size: Optional[int] = None,
|
58
|
+
) -> CommentListResponse:
|
59
|
+
"""
|
60
|
+
List unresolved comments for a page or block.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
block_id: Page ID or block ID to list comments for.
|
64
|
+
start_cursor: Pagination cursor.
|
65
|
+
page_size: Max items per page (<= 100).
|
66
|
+
|
67
|
+
Returns:
|
68
|
+
CommentListResponse with results, next_cursor, has_more, etc.
|
69
|
+
"""
|
70
|
+
params: dict[str, str | int] = {"block_id": block_id}
|
71
|
+
if start_cursor:
|
72
|
+
params["start_cursor"] = start_cursor
|
73
|
+
if page_size:
|
74
|
+
params["page_size"] = page_size
|
75
|
+
|
76
|
+
resp = await self.get("comments", params=params)
|
77
|
+
if resp is None:
|
78
|
+
raise RuntimeError("Failed to list comments.")
|
79
|
+
return CommentListResponse.model_validate(resp)
|
80
|
+
|
81
|
+
async def iter_comments(
|
82
|
+
self,
|
83
|
+
*,
|
84
|
+
block_id: str,
|
85
|
+
page_size: int = 100,
|
86
|
+
) -> AsyncGenerator[Comment, None]:
|
87
|
+
"""
|
88
|
+
Async generator over all unresolved comments for a given page/block.
|
89
|
+
Handles pagination under the hood.
|
90
|
+
"""
|
91
|
+
cursor: Optional[str] = None
|
92
|
+
while True:
|
93
|
+
page = await self.list_comments(
|
94
|
+
block_id=block_id, start_cursor=cursor, page_size=page_size
|
95
|
+
)
|
96
|
+
for item in page.results:
|
97
|
+
yield item
|
98
|
+
if not page.has_more:
|
99
|
+
break
|
100
|
+
cursor = page.next_cursor
|
101
|
+
|
102
|
+
async def create_comment_on_page(
|
103
|
+
self,
|
104
|
+
*,
|
105
|
+
page_id: str,
|
106
|
+
text: str,
|
107
|
+
display_name: Optional[dict] = None,
|
108
|
+
attachments: Optional[list[dict]] = None,
|
109
|
+
) -> Comment:
|
110
|
+
"""
|
111
|
+
Create a top-level comment on a page.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
page_id: Target page ID.
|
115
|
+
text: Plain text content for the comment (rich_text will be constructed).
|
116
|
+
display_name: Optional "Comment Display Name" object to override author label.
|
117
|
+
attachments: Optional list of "Comment Attachment" objects (max 3).
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
The created Comment object.
|
121
|
+
"""
|
122
|
+
body: dict = {
|
123
|
+
"parent": {"page_id": page_id},
|
124
|
+
"rich_text": [{"type": "text", "text": {"content": text}}],
|
125
|
+
}
|
126
|
+
if display_name:
|
127
|
+
body["display_name"] = display_name
|
128
|
+
if attachments:
|
129
|
+
body["attachments"] = attachments
|
130
|
+
|
131
|
+
resp = await self.post("comments", data=body)
|
132
|
+
if resp is None:
|
133
|
+
raise RuntimeError("Failed to create page comment.")
|
134
|
+
return Comment.model_validate(resp)
|
135
|
+
|
136
|
+
async def create_comment(
|
137
|
+
self,
|
138
|
+
*,
|
139
|
+
page_id: Optional[str] = None,
|
140
|
+
discussion_id: Optional[str] = None,
|
141
|
+
content: Optional[str] = None,
|
142
|
+
rich_text: Optional[list[RichTextObject]] = None,
|
143
|
+
display_name: Optional[dict[str, Any]] = None,
|
144
|
+
attachments: Optional[list[dict[str, Any]]] = None,
|
145
|
+
) -> Comment:
|
146
|
+
"""
|
147
|
+
Create a comment on a page OR reply to an existing discussion.
|
148
|
+
|
149
|
+
Rules:
|
150
|
+
- Exactly one of page_id or discussion_id must be provided.
|
151
|
+
- Provide either rich_text OR content (plain text). If both given, rich_text wins.
|
152
|
+
- Up to 3 attachments allowed by Notion.
|
153
|
+
"""
|
154
|
+
# validate parent
|
155
|
+
if (page_id is None) == (discussion_id is None):
|
156
|
+
raise ValueError("Specify exactly one parent: page_id OR discussion_id")
|
157
|
+
|
158
|
+
# build rich_text if only content is provided
|
159
|
+
rt = rich_text if rich_text else None
|
160
|
+
if rt is None:
|
161
|
+
if not content:
|
162
|
+
raise ValueError("Provide either 'rich_text' or 'content'.")
|
163
|
+
rt = [{"type": "text", "text": {"content": content}}]
|
164
|
+
|
165
|
+
body: dict[str, Any] = {"rich_text": rt}
|
166
|
+
if page_id:
|
167
|
+
body["parent"] = {"page_id": page_id}
|
168
|
+
else:
|
169
|
+
body["discussion_id"] = discussion_id
|
170
|
+
|
171
|
+
if display_name:
|
172
|
+
body["display_name"] = display_name
|
173
|
+
if attachments:
|
174
|
+
body["attachments"] = attachments
|
175
|
+
|
176
|
+
resp = await self.post("comments", data=body)
|
177
|
+
if resp is None:
|
178
|
+
raise RuntimeError("Failed to create comment.")
|
179
|
+
return Comment.model_validate(resp)
|
180
|
+
|
181
|
+
# ---------- Convenience wrappers ----------
|
182
|
+
|
183
|
+
async def create_comment_on_page(
|
184
|
+
self,
|
185
|
+
*,
|
186
|
+
page_id: str,
|
187
|
+
text: str,
|
188
|
+
display_name: Optional[dict] = None,
|
189
|
+
attachments: Optional[list[dict]] = None,
|
190
|
+
) -> Comment:
|
191
|
+
return await self.create_comment(
|
192
|
+
page_id=page_id,
|
193
|
+
content=text,
|
194
|
+
display_name=display_name,
|
195
|
+
attachments=attachments,
|
196
|
+
)
|
197
|
+
|
198
|
+
async def reply_to_discussion(
|
199
|
+
self,
|
200
|
+
*,
|
201
|
+
discussion_id: str,
|
202
|
+
text: str,
|
203
|
+
display_name: Optional[dict] = None,
|
204
|
+
attachments: Optional[list[dict]] = None,
|
205
|
+
) -> Comment:
|
206
|
+
return await self.create_comment(
|
207
|
+
discussion_id=discussion_id,
|
208
|
+
content=text,
|
209
|
+
display_name=display_name,
|
210
|
+
attachments=attachments,
|
211
|
+
)
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# notionary/comments/models.py
|
2
|
+
from __future__ import annotations
|
3
|
+
|
4
|
+
from datetime import datetime
|
5
|
+
from typing import Literal, Optional, Union
|
6
|
+
|
7
|
+
from pydantic import BaseModel, Field, ConfigDict
|
8
|
+
|
9
|
+
from notionary.blocks.rich_text import RichTextObject
|
10
|
+
|
11
|
+
|
12
|
+
class UserRef(BaseModel):
|
13
|
+
"""Minimal Notion user reference."""
|
14
|
+
|
15
|
+
model_config = ConfigDict(extra="ignore")
|
16
|
+
object: Literal["user"] = "user"
|
17
|
+
id: str
|
18
|
+
|
19
|
+
|
20
|
+
class CommentParent(BaseModel):
|
21
|
+
"""
|
22
|
+
Parent of a comment. Can be page_id or block_id.
|
23
|
+
Notion responds with the active one; the other remains None.
|
24
|
+
"""
|
25
|
+
|
26
|
+
model_config = ConfigDict(extra="ignore")
|
27
|
+
type: Literal["page_id", "block_id"]
|
28
|
+
page_id: Optional[str] = None
|
29
|
+
block_id: Optional[str] = None
|
30
|
+
|
31
|
+
|
32
|
+
class FileWithExpiry(BaseModel):
|
33
|
+
"""File object with temporary URL (common Notion pattern)."""
|
34
|
+
|
35
|
+
model_config = ConfigDict(extra="ignore")
|
36
|
+
url: str
|
37
|
+
expiry_time: Optional[datetime] = None
|
38
|
+
|
39
|
+
|
40
|
+
class CommentAttachmentFile(BaseModel):
|
41
|
+
"""Attachment stored by Notion with expiring download URL."""
|
42
|
+
|
43
|
+
model_config = ConfigDict(extra="ignore")
|
44
|
+
type: Literal["file"] = "file"
|
45
|
+
name: Optional[str] = None
|
46
|
+
file: FileWithExpiry
|
47
|
+
|
48
|
+
|
49
|
+
class CommentAttachmentExternal(BaseModel):
|
50
|
+
"""External attachment referenced by URL."""
|
51
|
+
|
52
|
+
model_config = ConfigDict(extra="ignore")
|
53
|
+
type: Literal["external"] = "external"
|
54
|
+
name: Optional[str] = None
|
55
|
+
external: dict # {"url": "..."} – kept generic
|
56
|
+
|
57
|
+
|
58
|
+
CommentAttachment = Union[CommentAttachmentFile, CommentAttachmentExternal]
|
59
|
+
|
60
|
+
|
61
|
+
# ---------------------------
|
62
|
+
# Display name override (optional)
|
63
|
+
# ---------------------------
|
64
|
+
|
65
|
+
|
66
|
+
class CommentDisplayName(BaseModel):
|
67
|
+
"""
|
68
|
+
Optional display name override for comments created by an integration.
|
69
|
+
Example: {"type": "integration", "resolved_name": "int"}.
|
70
|
+
"""
|
71
|
+
|
72
|
+
model_config = ConfigDict(extra="ignore")
|
73
|
+
type: str
|
74
|
+
resolved_name: Optional[str] = None
|
75
|
+
|
76
|
+
|
77
|
+
# ---------------------------
|
78
|
+
# Core Comment object
|
79
|
+
# ---------------------------
|
80
|
+
|
81
|
+
|
82
|
+
class Comment(BaseModel):
|
83
|
+
"""
|
84
|
+
Notion Comment object as returned by:
|
85
|
+
- GET /v1/comments/{comment_id} (retrieve)
|
86
|
+
- GET /v1/comments?block_id=... (list -> in results[])
|
87
|
+
- POST /v1/comments (create)
|
88
|
+
"""
|
89
|
+
|
90
|
+
model_config = ConfigDict(extra="ignore")
|
91
|
+
|
92
|
+
object: Literal["comment"] = "comment"
|
93
|
+
id: str
|
94
|
+
|
95
|
+
parent: CommentParent
|
96
|
+
discussion_id: str
|
97
|
+
|
98
|
+
created_time: datetime
|
99
|
+
last_edited_time: datetime
|
100
|
+
|
101
|
+
created_by: UserRef
|
102
|
+
|
103
|
+
rich_text: list[RichTextObject] = Field(default_factory=list)
|
104
|
+
|
105
|
+
# Optional fields that may appear depending on capabilities/payload
|
106
|
+
display_name: Optional[CommentDisplayName] = None
|
107
|
+
attachments: Optional[list[CommentAttachment]] = None
|
108
|
+
|
109
|
+
|
110
|
+
# ---------------------------
|
111
|
+
# List envelope (for list-comments)
|
112
|
+
# ---------------------------
|
113
|
+
|
114
|
+
|
115
|
+
class CommentListResponse(BaseModel):
|
116
|
+
"""
|
117
|
+
Envelope for GET /v1/comments?block_id=...
|
118
|
+
"""
|
119
|
+
|
120
|
+
model_config = ConfigDict(extra="ignore")
|
121
|
+
|
122
|
+
object: Literal["list"] = "list"
|
123
|
+
results: list[Comment] = Field(default_factory=list)
|
124
|
+
next_cursor: Optional[str] = None
|
125
|
+
has_more: bool = False
|
126
|
+
|
127
|
+
# Notion includes these two fields on the list envelope.
|
128
|
+
type: Optional[Literal["comment"]] = None
|
129
|
+
comment: Optional[dict] = None
|
notionary/page/client.py
CHANGED
@@ -42,18 +42,13 @@ class NotionPageClient(BaseNotionClient):
|
|
42
42
|
)
|
43
43
|
|
44
44
|
properties: dict[str, Any] = {
|
45
|
-
"title": {
|
46
|
-
"title": [
|
47
|
-
{"type": "text", "text": {"content": title}}
|
48
|
-
]
|
49
|
-
}
|
45
|
+
"title": {"title": [{"type": "text", "text": {"content": title}}]}
|
50
46
|
}
|
51
47
|
|
52
48
|
payload = {"parent": parent, "properties": properties}
|
53
49
|
response = await self.post("pages", payload)
|
54
50
|
return NotionPageResponse.model_validate(response)
|
55
51
|
|
56
|
-
|
57
52
|
async def patch_page(
|
58
53
|
self, page_id: str, data: Optional[dict[str, Any]] = None
|
59
54
|
) -> NotionPageResponse:
|
notionary/page/notion_page.py
CHANGED
@@ -5,6 +5,7 @@ import random
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
6
6
|
|
7
7
|
from notionary.blocks.client import NotionBlockClient
|
8
|
+
from notionary.comments import CommentClient, Comment
|
8
9
|
from notionary.blocks.syntax_prompt_builder import SyntaxPromptBuilder
|
9
10
|
from notionary.blocks.models import DatabaseParent
|
10
11
|
from notionary.blocks.registry.block_registry import BlockRegistry
|
@@ -24,6 +25,7 @@ from notionary.util.fuzzy import find_best_match
|
|
24
25
|
if TYPE_CHECKING:
|
25
26
|
from notionary import NotionDatabase
|
26
27
|
|
28
|
+
|
27
29
|
class NotionPage(LoggingMixin):
|
28
30
|
"""
|
29
31
|
Managing content and metadata of a Notion page.
|
@@ -56,8 +58,9 @@ class NotionPage(LoggingMixin):
|
|
56
58
|
|
57
59
|
self._client = NotionPageClient(token=token)
|
58
60
|
self._block_client = NotionBlockClient(token=token)
|
61
|
+
self._comment_client = CommentClient(token=token)
|
59
62
|
self._page_data = None
|
60
|
-
|
63
|
+
|
61
64
|
self.block_element_registry = BlockRegistry.create_registry()
|
62
65
|
|
63
66
|
self._page_content_writer = PageContentWriter(
|
@@ -213,6 +216,41 @@ class NotionPage(LoggingMixin):
|
|
213
216
|
markdown_syntax_builder = SyntaxPromptBuilder()
|
214
217
|
return markdown_syntax_builder.build_concise_reference()
|
215
218
|
|
219
|
+
async def get_comments(self) -> list[Comment]:
|
220
|
+
return await self._comment_client.list_all_comments_for_page(page_id=self._page_id)
|
221
|
+
|
222
|
+
async def post_comment(
|
223
|
+
self,
|
224
|
+
content: str,
|
225
|
+
*,
|
226
|
+
discussion_id: Optional[str] = None,
|
227
|
+
rich_text: Optional[list[dict[str, Any]]] = None,
|
228
|
+
) -> Optional[Comment]:
|
229
|
+
"""
|
230
|
+
Post a comment on this page.
|
231
|
+
|
232
|
+
Args:
|
233
|
+
content: The plain text content of the comment
|
234
|
+
discussion_id: Optional discussion ID to reply to an existing discussion
|
235
|
+
rich_text: Optional rich text formatting for the comment content
|
236
|
+
|
237
|
+
Returns:
|
238
|
+
Comment: The created comment object, or None if creation failed
|
239
|
+
"""
|
240
|
+
try:
|
241
|
+
# Use the comment client to create the comment
|
242
|
+
comment = await self._comment_client.create_comment(
|
243
|
+
page_id=self._page_id,
|
244
|
+
content=content,
|
245
|
+
discussion_id=discussion_id,
|
246
|
+
rich_text=rich_text,
|
247
|
+
)
|
248
|
+
self.logger.info(f"Successfully posted comment on page '{self._title}'")
|
249
|
+
return comment
|
250
|
+
except Exception as e:
|
251
|
+
self.logger.error(f"Failed to post comment on page '{self._title}': {str(e)}")
|
252
|
+
return None
|
253
|
+
|
216
254
|
async def set_title(self, title: str) -> str:
|
217
255
|
"""
|
218
256
|
Set the title of the page.
|
@@ -320,27 +358,33 @@ class NotionPage(LoggingMixin):
|
|
320
358
|
|
321
359
|
self.logger.error(f"Error updating page emoji: {str(e)}")
|
322
360
|
return None
|
323
|
-
|
361
|
+
|
324
362
|
async def create_child_database(self, title: str) -> NotionDatabase:
|
325
363
|
from notionary import NotionDatabase
|
364
|
+
|
326
365
|
database_client = NotionDatabaseClient(token=self._client.token)
|
327
|
-
|
328
|
-
create_database_response =
|
366
|
+
|
367
|
+
create_database_response = await database_client.create_database(
|
329
368
|
title=title,
|
330
369
|
parent_page_id=self._page_id,
|
331
370
|
)
|
332
|
-
|
333
|
-
return await NotionDatabase.from_database_id(
|
334
|
-
|
371
|
+
|
372
|
+
return await NotionDatabase.from_database_id(
|
373
|
+
id=create_database_response.id, token=self._client.token
|
374
|
+
)
|
375
|
+
|
335
376
|
async def create_child_page(self, title: str) -> NotionPage:
|
336
377
|
from notionary import NotionPage
|
378
|
+
|
337
379
|
child_page_response = await self._client.create_page(
|
338
380
|
parent_page_id=self._page_id,
|
339
381
|
title=title,
|
340
382
|
)
|
341
|
-
|
342
|
-
return await NotionPage.from_page_id(
|
343
|
-
|
383
|
+
|
384
|
+
return await NotionPage.from_page_id(
|
385
|
+
page_id=child_page_response.id, token=self._client.token
|
386
|
+
)
|
387
|
+
|
344
388
|
async def set_external_icon(self, url: str) -> Optional[str]:
|
345
389
|
"""
|
346
390
|
Sets the page icon to an external image.
|
@@ -636,4 +680,4 @@ class NotionPage(LoggingMixin):
|
|
636
680
|
"""Extract parent database ID from page response."""
|
637
681
|
parent = page_response.parent
|
638
682
|
if isinstance(parent, DatabaseParent):
|
639
|
-
return parent.database_id
|
683
|
+
return parent.database_id
|
notionary/page/page_context.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: notionary
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.23
|
4
4
|
Summary: Python library for programmatic Notion workspace management - databases, pages, and content with advanced Markdown support
|
5
5
|
License: MIT
|
6
6
|
Author: Mathis Arends
|
@@ -13,9 +13,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
16
|
-
Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
|
17
16
|
Requires-Dist: httpx (>=0.28.0)
|
18
|
-
Requires-Dist: isort (>=6.0.1,<7.0.0)
|
19
17
|
Requires-Dist: posthog (>=6.3.1,<7.0.0)
|
20
18
|
Requires-Dist: pydantic (>=2.11.4)
|
21
19
|
Requires-Dist: python-dotenv (>=1.1.0)
|
@@ -24,7 +24,7 @@ notionary/blocks/callout/callout_element.py,sha256=6Ss1jv1cqAM4GuNGvuy7dCFjpp-dL
|
|
24
24
|
notionary/blocks/callout/callout_markdown_node.py,sha256=TMd2wQ0BfoQ0mgHSy5MrqCvpJvovSw7zsLrXnRrJoQs,948
|
25
25
|
notionary/blocks/callout/callout_models.py,sha256=KabWhRCkW1KmTGqt23QTc5iCeVczIzRHIVevEagcKE0,860
|
26
26
|
notionary/blocks/child_database/__init__.py,sha256=FzjjHOS9Je2oXuxLK9S9Oq56-EDEZRTJBfDzkacFpY0,368
|
27
|
-
notionary/blocks/child_database/child_database_element.py,sha256=
|
27
|
+
notionary/blocks/child_database/child_database_element.py,sha256=1pxtoB_XTIWorYp984TpsPMZy8A5IdQ15nJALyzme-c,2279
|
28
28
|
notionary/blocks/child_database/child_database_models.py,sha256=SP6S9tKTetKNdCkYY3QCxeXd2lq1DyfdNvDhKlhD22Q,264
|
29
29
|
notionary/blocks/child_page/__init__.py,sha256=qaHvJqF8Bfzj0NVE1Q5uN_4o3Jl4RuNYspMZ6E7bUuk,182
|
30
30
|
notionary/blocks/child_page/child_page_element.py,sha256=iZIJ91CQ9ZztivtSNKKA8-OD6YIGZvGsxeuD8vZ1PLg,3389
|
@@ -89,9 +89,8 @@ notionary/blocks/registry/__init__.py,sha256=Het-sHkwxg8c0A1GlPvpm6Ca-GwfN-II55Y
|
|
89
89
|
notionary/blocks/registry/block_registry.py,sha256=B01_6jwrIExvMpgxFHDD8P3mXtrkK0SDriNhV7o7NWY,3195
|
90
90
|
notionary/blocks/registry/block_registry_builder.py,sha256=a-oID7OpW4b1bEhhsQOOnwK8evlJem-MralvdhaMYno,8866
|
91
91
|
notionary/blocks/rich_text/__init__.py,sha256=UuOn0MmGKNqK3fLBnEcH2surwbDkZ4GF8Gpn4TLNwLc,773
|
92
|
-
notionary/blocks/rich_text/name_to_id_resolver.py,sha256=crAiY1mzE8RZVvWfkGcEPdGGvRmXa51WTDxca10oWA0,6373
|
93
92
|
notionary/blocks/rich_text/rich_text_models.py,sha256=QPCHA-vo8DoH7sLAVzOXervoeeV39JRuJkR-IGVA63Y,6278
|
94
|
-
notionary/blocks/rich_text/text_inline_formatter.py,sha256=
|
93
|
+
notionary/blocks/rich_text/text_inline_formatter.py,sha256=8J8y27OuMLX-IvSJs0Ogmvw0gw1bgQD9n55nRjSldbg,18379
|
95
94
|
notionary/blocks/syntax_prompt_builder.py,sha256=VwvpR8JyyKR13_ni0jUt--F7w6DoD_nzJB2LbwiJXJc,4626
|
96
95
|
notionary/blocks/table/__init__.py,sha256=Vrs6w_P63cFptV-gVuDpFbU5Rg-bDf3Tf_jUk0n-s-I,511
|
97
96
|
notionary/blocks/table/table_element.py,sha256=eeqFHlyQ1dG1ZxB3zdrdvF5qXPsLqSEVyNQJ70W9Rwg,8002
|
@@ -117,6 +116,9 @@ notionary/blocks/video/__init__.py,sha256=z_lz1agYtSRv9A9bFRwPlpzmpfgf49w9uJMVkH
|
|
117
116
|
notionary/blocks/video/video_element.py,sha256=nDYcr1HwCZYVBSLPEo1qHuqU9AQ8yzS9OHQTIe3rVyA,4417
|
118
117
|
notionary/blocks/video/video_element_models.py,sha256=CYK2tq0qhrOxjO_ctOgRvEHM9hLjQCBXrP7wcTNlUPw,229
|
119
118
|
notionary/blocks/video/video_markdown_node.py,sha256=u5OaoxLzg10PAdvo70OYgi1d-eFtTK_kUdYLAKQWdtM,1151
|
119
|
+
notionary/comments/__init__.py,sha256=G0OWsaN2VgbykvhLlNSweL_f0bl16j9NpidH2UNbFcQ,529
|
120
|
+
notionary/comments/client.py,sha256=c8mgHKVjMxC-ssuu4nv34S3788icCWSZQS-a1AZXM48,7309
|
121
|
+
notionary/comments/models.py,sha256=O2-yg-0Xrs8xrzLUa4793FlyNn3HbTRsv4dqaskI6ps,3409
|
120
122
|
notionary/database/__init__.py,sha256=4tdML0fBzkOCpiWT6q-L--5NELFLbTPD0IUA_E8yZno,155
|
121
123
|
notionary/database/client.py,sha256=mN_8XHvIQkRxiWbgBfvncgw4IRiCFX3bVLMLBvtk1Ig,5398
|
122
124
|
notionary/database/database.py,sha256=sdo830KVInR4enaEaeMHaPadNfYnPLy5R3OW2f74w28,16558
|
@@ -136,12 +138,12 @@ notionary/markdown/markdown_builder.py,sha256=q9J7FduIxrf8hSPu_5P4ZwYaGVZdvpe-qa
|
|
136
138
|
notionary/markdown/markdown_document_model.py,sha256=i9zMINcTlTwlqpHV9zeQfMVlni2VAl6BMjAdKSdoUeg,6409
|
137
139
|
notionary/markdown/markdown_node.py,sha256=u8ApehnTCo1KnTFbtYzGuTAyUQrYPc3RSMJaUMyg0LI,717
|
138
140
|
notionary/models/notion_database_response.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
139
|
-
notionary/page/client.py,sha256=
|
141
|
+
notionary/page/client.py,sha256=IOsjWOsmODxOq-7OF0y-gcuC2HUbMIT0SvJZHrH9weQ,4520
|
140
142
|
notionary/page/models.py,sha256=pD8FlK8KtnUrIITTd2jg_yJICakgPE4ysZiS9KiMpr0,7088
|
141
|
-
notionary/page/notion_page.py,sha256=
|
143
|
+
notionary/page/notion_page.py,sha256=4X13kJWzJIoB7pFrfNbrIvLCQ2j4HKPOesblr6FM6d8,23903
|
142
144
|
notionary/page/page_content_deleting_service.py,sha256=ZxGUMmT7k85IPLALfZkIJ5oQA3tMHWVW4acjdp5cB0k,4540
|
143
145
|
notionary/page/page_content_writer.py,sha256=6WMDTbgjYRgJhmCrMnK4XokgVDhRWSRm55_1xceo0CA,6856
|
144
|
-
notionary/page/page_context.py,sha256=
|
146
|
+
notionary/page/page_context.py,sha256=fIeCF9gjpiAtRK0GZ-3CUZ5Ina8xIVCvmOnt4MtS3ek,1938
|
145
147
|
notionary/page/property_formatter.py,sha256=_978ViH83gfcr-XtDscWTfyBI2srGW2hzC-gzgp5NR8,3788
|
146
148
|
notionary/page/reader/handler/__init__.py,sha256=ROMH1KvGGdQ0ANLhmIP_4LtMks9jsTTwnZKP8W0uwls,644
|
147
149
|
notionary/page/reader/handler/base_block_renderer.py,sha256=HZwv727bvu6suT1-uzK4y-4ci54VCdi0FF_yOJYBB1g,1513
|
@@ -174,6 +176,8 @@ notionary/page/writer/markdown_to_notion_formatting_post_processor.py,sha256=E41
|
|
174
176
|
notionary/page/writer/markdown_to_notion_post_processor.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
175
177
|
notionary/page/writer/markdown_to_notion_text_length_post_processor.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
176
178
|
notionary/page/writer/notion_text_length_processor.py,sha256=WfXGouHs2KUFH1ASqloRkV6y7q2jS4vqbiEEMrmd7Qw,5744
|
179
|
+
notionary/shared/__init__.py,sha256=7dKlDC1BpKy7LxceHTKVamLc9X0mc6stO5MOxBxidWY,87
|
180
|
+
notionary/shared/name_to_id_resolver.py,sha256=SlxB2vmq1cMIZnL0o3U27wjYQSXeQkKURmKFy-BAqk4,6318
|
177
181
|
notionary/telemetry/__init__.py,sha256=uzPrvkPIpbzPSHCu-tObCTmZA18l3VWqk42L8WLT-XM,511
|
178
182
|
notionary/telemetry/service.py,sha256=FujT1ZuSHzTO0Rxa6oKlycE_Y-Qpe5lc59EGzzDf-jc,4725
|
179
183
|
notionary/telemetry/views.py,sha256=FgFZGYaxP7pRYx-9wg18skMh_MJAwf4W3rCfe9JOZe4,1796
|
@@ -194,7 +198,7 @@ notionary/util/page_id_utils.py,sha256=AA00kRO-g3Cc50tf_XW_tb5RBuPKLuBxRa0D8LYhL
|
|
194
198
|
notionary/util/singleton.py,sha256=CKAvykndwPRZsA3n3MAY_XdCR59MBjjKP0vtm2BcvF0,428
|
195
199
|
notionary/util/singleton_metaclass.py,sha256=DMvrh0IbAV8nIG1oX-2Yz57Uk1YHB647DNxoI3pAT3s,809
|
196
200
|
notionary/workspace.py,sha256=QP4WcOIdQnlVr4M7hpGaFT-Fori_QRhsV-SBC2lnwTU,3809
|
197
|
-
notionary-0.2.
|
198
|
-
notionary-0.2.
|
199
|
-
notionary-0.2.
|
200
|
-
notionary-0.2.
|
201
|
+
notionary-0.2.23.dist-info/LICENSE,sha256=zOm3cRT1qD49eg7vgw95MI79rpUAZa1kRBFwL2FkAr8,1120
|
202
|
+
notionary-0.2.23.dist-info/METADATA,sha256=asanJv6EUiN6zgUVVplKEM7MUrafPfkGdxV4EQNGQ-Y,9035
|
203
|
+
notionary-0.2.23.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
204
|
+
notionary-0.2.23.dist-info/RECORD,,
|
File without changes
|
File without changes
|