notionary 0.2.15__tar.gz → 0.2.17__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.
- {notionary-0.2.15 → notionary-0.2.17}/PKG-INFO +3 -1
- notionary-0.2.17/notionary/__init__.py +17 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/base_notion_client.py +19 -8
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/__init__.py +2 -0
- notionary-0.2.17/notionary/blocks/document_element.py +194 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/registry/block_registry.py +27 -3
- notionary-0.2.17/notionary/database/__init__.py +4 -0
- notionary-0.2.17/notionary/database/database.py +481 -0
- notionary-0.2.15/notionary/database/filter_builder.py → notionary-0.2.17/notionary/database/database_filter_builder.py +27 -29
- notionary-0.2.15/notionary/database/notion_database_provider.py → notionary-0.2.17/notionary/database/database_provider.py +6 -10
- {notionary-0.2.15 → notionary-0.2.17}/notionary/database/notion_database.py +73 -32
- notionary-0.2.17/notionary/file_upload/__init__.py +7 -0
- notionary-0.2.17/notionary/file_upload/client.py +254 -0
- notionary-0.2.17/notionary/file_upload/models.py +60 -0
- notionary-0.2.17/notionary/file_upload/notion_file_upload.py +387 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/notion_page.py +5 -6
- notionary-0.2.17/notionary/telemetry/__init__.py +19 -0
- notionary-0.2.17/notionary/telemetry/service.py +136 -0
- notionary-0.2.17/notionary/telemetry/views.py +73 -0
- notionary-0.2.17/notionary/user/__init__.py +11 -0
- notionary-0.2.17/notionary/user/base_notion_user.py +52 -0
- notionary-0.2.17/notionary/user/client.py +129 -0
- notionary-0.2.17/notionary/user/models.py +83 -0
- notionary-0.2.17/notionary/user/notion_bot_user.py +227 -0
- notionary-0.2.17/notionary/user/notion_user.py +256 -0
- notionary-0.2.17/notionary/user/notion_user_manager.py +173 -0
- notionary-0.2.17/notionary/user/notion_user_provider.py +1 -0
- notionary-0.2.17/notionary/util/__init__.py +14 -0
- notionary-0.2.15/notionary/util/factory_decorator.py → notionary-0.2.17/notionary/util/factory_only.py +9 -5
- notionary-0.2.17/notionary/util/fuzzy.py +74 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/util/logging_mixin.py +12 -12
- {notionary-0.2.15 → notionary-0.2.17}/notionary/workspace.py +38 -2
- {notionary-0.2.15 → notionary-0.2.17}/pyproject.toml +4 -1
- notionary-0.2.15/notionary/__init__.py +0 -13
- notionary-0.2.15/notionary/page/__init__.py +0 -0
- notionary-0.2.15/notionary/util/__init__.py +0 -14
- notionary-0.2.15/notionary/util/fuzzy_matcher.py +0 -82
- {notionary-0.2.15 → notionary-0.2.17}/LICENSE +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/README.md +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/audio_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/bookmark_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/bulleted_list_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/callout_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/code_block_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/column_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/divider_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/embed_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/heading_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/image_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/mention_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/notion_block_client.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/notion_block_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/numbered_list_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/paragraph_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/prompts/element_prompt_builder.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/prompts/element_prompt_content.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/qoute_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/registry/block_registry_builder.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/table_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/text_inline_formatter.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/todo_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/toggle_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/toggleable_heading_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/blocks/video_element.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/database/client.py +0 -0
- /notionary-0.2.15/notionary/database/database_exceptions.py → /notionary-0.2.17/notionary/database/exceptions.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/database/factory.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/database/models/page_result.py +0 -0
- {notionary-0.2.15/notionary/database → notionary-0.2.17/notionary/elements}/__init__.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/models/notion_block_response.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/models/notion_database_response.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/models/notion_page_response.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/models/search_response.py +0 -0
- {notionary-0.2.15/notionary/elements → notionary-0.2.17/notionary/page}/__init__.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/client.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/content/notion_page_content_chunker.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/content/page_content_retriever.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/content/page_content_writer.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/formatting/markdown_to_notion_converter.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/formatting/spacer_rules.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/markdown_syntax_prompt_generator.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/notion_to_markdown_converter.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/properites/property_value_extractor.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/property_formatter.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/search_filter_builder.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/page/utils.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/util/page_id_utils.py +0 -0
- /notionary-0.2.15/notionary/util/singleton_decorator.py → /notionary-0.2.17/notionary/util/singleton.py +0 -0
- {notionary-0.2.15 → notionary-0.2.17}/notionary/util/singleton_metaclass.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: notionary
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.17
|
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,7 +13,9 @@ 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)
|
16
17
|
Requires-Dist: httpx (>=0.28.0)
|
18
|
+
Requires-Dist: posthog (>=6.3.1,<7.0.0)
|
17
19
|
Requires-Dist: pydantic (>=2.11.4)
|
18
20
|
Requires-Dist: python-dotenv (>=1.1.0)
|
19
21
|
Project-URL: Homepage, https://github.com/mathisarends/notionary
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from .database import NotionDatabase, DatabaseFilterBuilder
|
2
|
+
from .page.notion_page import NotionPage
|
3
|
+
from .workspace import NotionWorkspace
|
4
|
+
from .user import NotionUser, NotionUserManager, NotionBotUser
|
5
|
+
from .file_upload import NotionFileUpload, NotionFileUploadClient
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
"NotionDatabase",
|
9
|
+
"DatabaseFilterBuilder",
|
10
|
+
"NotionPage",
|
11
|
+
"NotionWorkspace",
|
12
|
+
"NotionUser",
|
13
|
+
"NotionUserManager",
|
14
|
+
"NotionBotUser",
|
15
|
+
"NotionFileUpload",
|
16
|
+
"NotionFileUploadClient",
|
17
|
+
]
|
@@ -93,14 +93,17 @@ class BaseNotionClient(LoggingMixin, ABC):
|
|
93
93
|
self._is_initialized = False
|
94
94
|
self.logger.debug("NotionClient closed")
|
95
95
|
|
96
|
-
async def get(
|
96
|
+
async def get(
|
97
|
+
self, endpoint: str, params: Optional[Dict[str, Any]] = None
|
98
|
+
) -> Optional[Dict[str, Any]]:
|
97
99
|
"""
|
98
100
|
Sends a GET request to the specified Notion API endpoint.
|
99
101
|
|
100
102
|
Args:
|
101
103
|
endpoint: The API endpoint (without base URL)
|
104
|
+
params: Query parameters to include in the request
|
102
105
|
"""
|
103
|
-
return await self._make_request(HttpMethod.GET, endpoint)
|
106
|
+
return await self._make_request(HttpMethod.GET, endpoint, params=params)
|
104
107
|
|
105
108
|
async def post(
|
106
109
|
self, endpoint: str, data: Optional[Dict[str, Any]] = None
|
@@ -141,6 +144,7 @@ class BaseNotionClient(LoggingMixin, ABC):
|
|
141
144
|
method: Union[HttpMethod, str],
|
142
145
|
endpoint: str,
|
143
146
|
data: Optional[Dict[str, Any]] = None,
|
147
|
+
params: Optional[Dict[str, Any]] = None,
|
144
148
|
) -> Optional[Dict[str, Any]]:
|
145
149
|
"""
|
146
150
|
Executes an HTTP request and returns the data or None on error.
|
@@ -149,6 +153,7 @@ class BaseNotionClient(LoggingMixin, ABC):
|
|
149
153
|
method: HTTP method to use
|
150
154
|
endpoint: API endpoint
|
151
155
|
data: Request body data (for POST/PATCH)
|
156
|
+
params: Query parameters (for GET requests)
|
152
157
|
"""
|
153
158
|
await self.ensure_initialized()
|
154
159
|
|
@@ -160,15 +165,21 @@ class BaseNotionClient(LoggingMixin, ABC):
|
|
160
165
|
try:
|
161
166
|
self.logger.debug("Sending %s request to %s", method_str.upper(), url)
|
162
167
|
|
168
|
+
request_kwargs = {}
|
169
|
+
|
170
|
+
# Add query parameters for GET requests
|
171
|
+
if params:
|
172
|
+
request_kwargs["params"] = params
|
173
|
+
|
163
174
|
if (
|
164
175
|
method_str in [HttpMethod.POST.value, HttpMethod.PATCH.value]
|
165
176
|
and data is not None
|
166
177
|
):
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
178
|
+
request_kwargs["json"] = data
|
179
|
+
|
180
|
+
response: httpx.Response = await getattr(self.client, method_str)(
|
181
|
+
url, **request_kwargs
|
182
|
+
)
|
172
183
|
|
173
184
|
response.raise_for_status()
|
174
185
|
result_data = response.json()
|
@@ -194,7 +205,7 @@ class BaseNotionClient(LoggingMixin, ABC):
|
|
194
205
|
token = next(
|
195
206
|
(
|
196
207
|
os.getenv(var)
|
197
|
-
for var in ("NOTION_SECRET", "
|
208
|
+
for var in ("NOTION_SECRET", "NOTION_INTEGRATION_KEY", "NOTION_TOKEN")
|
198
209
|
if os.getenv(var)
|
199
210
|
),
|
200
211
|
None,
|
@@ -26,6 +26,7 @@ from .divider_element import DividerElement
|
|
26
26
|
from .heading_element import HeadingElement
|
27
27
|
from .mention_element import MentionElement
|
28
28
|
from .qoute_element import QuoteElement
|
29
|
+
from .document_element import DocumentElement
|
29
30
|
|
30
31
|
from .registry.block_registry import BlockRegistry
|
31
32
|
from .registry.block_registry_builder import BlockRegistryBuilder
|
@@ -55,6 +56,7 @@ __all__ = [
|
|
55
56
|
"BookmarkElement",
|
56
57
|
"MentionElement",
|
57
58
|
"QuoteElement",
|
59
|
+
"DocumentElement",
|
58
60
|
"BlockRegistry",
|
59
61
|
"BlockRegistryBuilder",
|
60
62
|
"NotionBlockClient",
|
@@ -0,0 +1,194 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Dict, Any, Optional, List
|
3
|
+
|
4
|
+
from notionary.blocks import NotionBlockElement
|
5
|
+
from notionary.blocks import ElementPromptContent, ElementPromptBuilder
|
6
|
+
|
7
|
+
|
8
|
+
class DocumentElement(NotionBlockElement):
|
9
|
+
"""
|
10
|
+
Handles conversion between Markdown document embeds and Notion file blocks.
|
11
|
+
|
12
|
+
Markdown document syntax (custom format):
|
13
|
+
- %[Caption](https://example.com/document.pdf) - Basic document with caption
|
14
|
+
- %[](https://example.com/document.pdf) - Document without caption
|
15
|
+
- %[Meeting Notes](https://drive.google.com/file/d/123/view) - Google Drive document
|
16
|
+
- %[Report](https://company.sharepoint.com/document.docx) - SharePoint document
|
17
|
+
|
18
|
+
Supports various document URLs including PDFs, Word docs, Excel files, PowerPoint,
|
19
|
+
Google Drive files, and other document formats that Notion can display.
|
20
|
+
"""
|
21
|
+
|
22
|
+
PATTERN = re.compile(
|
23
|
+
r"^%\[(.*?)\]" # %[Caption] part
|
24
|
+
+ r'\((https?://[^\s"]+)' # (URL part
|
25
|
+
+ r"\)$" # closing parenthesis
|
26
|
+
)
|
27
|
+
|
28
|
+
DOCUMENT_EXTENSIONS = [
|
29
|
+
".pdf",
|
30
|
+
".doc",
|
31
|
+
".docx",
|
32
|
+
".xls",
|
33
|
+
".xlsx",
|
34
|
+
".ppt",
|
35
|
+
".pptx",
|
36
|
+
".txt",
|
37
|
+
".rtf",
|
38
|
+
".odt",
|
39
|
+
".ods",
|
40
|
+
".odp",
|
41
|
+
".pages",
|
42
|
+
".numbers",
|
43
|
+
".key",
|
44
|
+
".epub",
|
45
|
+
".mobi",
|
46
|
+
]
|
47
|
+
|
48
|
+
@classmethod
|
49
|
+
def match_markdown(cls, text: str) -> bool:
|
50
|
+
"""Check if text is a markdown document embed."""
|
51
|
+
text = text.strip()
|
52
|
+
return text.startswith("%[") and bool(cls.PATTERN.match(text))
|
53
|
+
|
54
|
+
@classmethod
|
55
|
+
def match_notion(cls, block: Dict[str, Any]) -> bool:
|
56
|
+
"""Check if block is a Notion file (document)."""
|
57
|
+
return block.get("type") == "file"
|
58
|
+
|
59
|
+
@classmethod
|
60
|
+
def is_document_url(cls, url: str) -> bool:
|
61
|
+
"""Check if URL points to a document file."""
|
62
|
+
url_lower = url.lower()
|
63
|
+
|
64
|
+
# Check for common document file extensions
|
65
|
+
if any(url_lower.endswith(ext) for ext in cls.DOCUMENT_EXTENSIONS):
|
66
|
+
return True
|
67
|
+
|
68
|
+
# Check for common document hosting services
|
69
|
+
document_services = [
|
70
|
+
"drive.google.com",
|
71
|
+
"docs.google.com",
|
72
|
+
"sheets.google.com",
|
73
|
+
"slides.google.com",
|
74
|
+
"sharepoint.com",
|
75
|
+
"onedrive.com",
|
76
|
+
"dropbox.com",
|
77
|
+
"box.com",
|
78
|
+
"scribd.com",
|
79
|
+
"slideshare.net",
|
80
|
+
]
|
81
|
+
|
82
|
+
return any(service in url_lower for service in document_services)
|
83
|
+
|
84
|
+
@classmethod
|
85
|
+
def markdown_to_notion(cls, text: str) -> Optional[Dict[str, Any]]:
|
86
|
+
"""Convert markdown document embed to Notion file block."""
|
87
|
+
doc_match = cls.PATTERN.match(text.strip())
|
88
|
+
if not doc_match:
|
89
|
+
return None
|
90
|
+
|
91
|
+
caption = doc_match.group(1)
|
92
|
+
url = doc_match.group(2)
|
93
|
+
|
94
|
+
if not url:
|
95
|
+
return None
|
96
|
+
|
97
|
+
# Verify this looks like a document URL
|
98
|
+
if not cls.is_document_url(url):
|
99
|
+
# Still proceed - user might know better than our detection
|
100
|
+
pass
|
101
|
+
|
102
|
+
# Prepare the file block
|
103
|
+
file_block = {
|
104
|
+
"type": "file",
|
105
|
+
"file": {"type": "external", "external": {"url": url}},
|
106
|
+
}
|
107
|
+
|
108
|
+
# Add caption if provided
|
109
|
+
if caption:
|
110
|
+
file_block["file"]["caption"] = [
|
111
|
+
{"type": "text", "text": {"content": caption}}
|
112
|
+
]
|
113
|
+
|
114
|
+
return file_block
|
115
|
+
|
116
|
+
@classmethod
|
117
|
+
def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
|
118
|
+
"""Convert Notion file block to markdown document embed."""
|
119
|
+
if block.get("type") != "file":
|
120
|
+
return None
|
121
|
+
|
122
|
+
file_data = block.get("file", {})
|
123
|
+
|
124
|
+
# Handle both external and file (uploaded) documents
|
125
|
+
if file_data.get("type") == "external":
|
126
|
+
url = file_data.get("external", {}).get("url", "")
|
127
|
+
elif file_data.get("type") == "file":
|
128
|
+
url = file_data.get("file", {}).get("url", "")
|
129
|
+
elif file_data.get("type") == "file_upload":
|
130
|
+
# Handle file uploads with special notion:// syntax
|
131
|
+
file_upload_id = file_data.get("file_upload", {}).get("id", "")
|
132
|
+
if file_upload_id:
|
133
|
+
url = f"notion://file_upload/{file_upload_id}"
|
134
|
+
else:
|
135
|
+
return None
|
136
|
+
else:
|
137
|
+
return None
|
138
|
+
|
139
|
+
if not url:
|
140
|
+
return None
|
141
|
+
|
142
|
+
# Extract caption if available
|
143
|
+
caption = ""
|
144
|
+
caption_rich_text = file_data.get("caption", [])
|
145
|
+
if caption_rich_text:
|
146
|
+
caption = cls._extract_text_content(caption_rich_text)
|
147
|
+
|
148
|
+
return f"%[{caption}]({url})"
|
149
|
+
|
150
|
+
@classmethod
|
151
|
+
def is_multiline(cls) -> bool:
|
152
|
+
"""Document embeds are single-line elements."""
|
153
|
+
return False
|
154
|
+
|
155
|
+
@classmethod
|
156
|
+
def _extract_text_content(cls, rich_text: List[Dict[str, Any]]) -> str:
|
157
|
+
"""Extract plain text content from Notion rich_text elements."""
|
158
|
+
result = ""
|
159
|
+
for text_obj in rich_text:
|
160
|
+
if text_obj.get("type") == "text":
|
161
|
+
result += text_obj.get("text", {}).get("content", "")
|
162
|
+
elif "plain_text" in text_obj:
|
163
|
+
result += text_obj.get("plain_text", "")
|
164
|
+
return result
|
165
|
+
|
166
|
+
@classmethod
|
167
|
+
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
168
|
+
"""Returns information for LLM prompts about this element."""
|
169
|
+
return (
|
170
|
+
ElementPromptBuilder()
|
171
|
+
.with_description(
|
172
|
+
"Embeds document files from external sources like PDFs, Word docs, Excel files, or cloud storage services."
|
173
|
+
)
|
174
|
+
.with_usage_guidelines(
|
175
|
+
"Use document embeds when you want to include reference materials, reports, presentations, or any "
|
176
|
+
"file-based content directly in your document. Documents can be viewed inline or downloaded by users. "
|
177
|
+
"Perfect for sharing contracts, reports, manuals, or any important files."
|
178
|
+
)
|
179
|
+
.with_syntax("%[Caption](https://example.com/document.pdf)")
|
180
|
+
.with_examples(
|
181
|
+
[
|
182
|
+
"%[Project Proposal](https://drive.google.com/file/d/1a2b3c4d5e/view)",
|
183
|
+
"%[Q4 Financial Report](https://company.sharepoint.com/reports/q4-2024.xlsx)",
|
184
|
+
"%[User Manual](https://cdn.company.com/docs/manual-v2.1.pdf)",
|
185
|
+
"%[Meeting Minutes](https://docs.google.com/document/d/1x2y3z4/edit)",
|
186
|
+
"%[](https://example.com/contract.pdf)",
|
187
|
+
]
|
188
|
+
)
|
189
|
+
.with_avoidance_guidelines(
|
190
|
+
"Only use for actual document files. For web pages or articles, use bookmark or embed elements instead. "
|
191
|
+
"Ensure document URLs are accessible to your intended audience."
|
192
|
+
)
|
193
|
+
.build()
|
194
|
+
)
|
@@ -8,6 +8,12 @@ from notionary.page.markdown_syntax_prompt_generator import (
|
|
8
8
|
from notionary.blocks.text_inline_formatter import TextInlineFormatter
|
9
9
|
|
10
10
|
from notionary.blocks import NotionBlockElement
|
11
|
+
from notionary.telemetry import (
|
12
|
+
ProductTelemetry,
|
13
|
+
NotionMarkdownSyntaxPromptEvent,
|
14
|
+
MarkdownToNotionConversionEvent,
|
15
|
+
NotionToMarkdownConversionEvent,
|
16
|
+
)
|
11
17
|
|
12
18
|
|
13
19
|
class BlockRegistry:
|
@@ -28,6 +34,8 @@ class BlockRegistry:
|
|
28
34
|
for element in elements:
|
29
35
|
self.register(element)
|
30
36
|
|
37
|
+
self.telemetry = ProductTelemetry()
|
38
|
+
|
31
39
|
def register(self, element_class: Type[NotionBlockElement]) -> bool:
|
32
40
|
"""
|
33
41
|
Register an element class.
|
@@ -57,13 +65,13 @@ class BlockRegistry:
|
|
57
65
|
|
58
66
|
def contains(self, element_class: Type[NotionBlockElement]) -> bool:
|
59
67
|
"""
|
60
|
-
|
68
|
+
Checks if a specific element is contained in the registry.
|
61
69
|
|
62
70
|
Args:
|
63
|
-
element_class:
|
71
|
+
element_class: The element class to check.
|
64
72
|
|
65
73
|
Returns:
|
66
|
-
bool: True
|
74
|
+
bool: True if the element is contained, otherwise False.
|
67
75
|
"""
|
68
76
|
return element_class in self._elements
|
69
77
|
|
@@ -77,14 +85,28 @@ class BlockRegistry:
|
|
77
85
|
def markdown_to_notion(self, text: str) -> Optional[Dict[str, Any]]:
|
78
86
|
"""Convert markdown to Notion block using registered elements."""
|
79
87
|
handler = self.find_markdown_handler(text)
|
88
|
+
|
80
89
|
if handler:
|
90
|
+
self.telemetry.capture(
|
91
|
+
MarkdownToNotionConversionEvent(
|
92
|
+
handler_element_name=handler.__name__,
|
93
|
+
)
|
94
|
+
)
|
95
|
+
|
81
96
|
return handler.markdown_to_notion(text)
|
82
97
|
return None
|
83
98
|
|
84
99
|
def notion_to_markdown(self, block: Dict[str, Any]) -> Optional[str]:
|
85
100
|
"""Convert Notion block to markdown using registered elements."""
|
86
101
|
handler = self._find_notion_handler(block)
|
102
|
+
|
87
103
|
if handler:
|
104
|
+
self.telemetry.capture(
|
105
|
+
NotionToMarkdownConversionEvent(
|
106
|
+
handler_element_name=handler.__name__,
|
107
|
+
)
|
108
|
+
)
|
109
|
+
|
88
110
|
return handler.notion_to_markdown(block)
|
89
111
|
return None
|
90
112
|
|
@@ -106,6 +128,8 @@ class BlockRegistry:
|
|
106
128
|
if "TextInlineFormatter" not in formatter_names:
|
107
129
|
element_classes = element_classes + [TextInlineFormatter]
|
108
130
|
|
131
|
+
self.telemetry.capture(NotionMarkdownSyntaxPromptEvent())
|
132
|
+
|
109
133
|
return MarkdownSyntaxPromptGenerator.generate_system_prompt(element_classes)
|
110
134
|
|
111
135
|
def _find_notion_handler(
|