notionary 0.2.12__py3-none-any.whl → 0.2.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notionary/__init__.py +0 -4
- notionary/cli/main.py +169 -6
- notionary/cli/onboarding.py +116 -0
- notionary/database/notion_database.py +0 -2
- notionary/database/notion_database_factory.py +0 -3
- notionary/elements/audio_element.py +1 -2
- notionary/elements/bookmark_element.py +1 -2
- notionary/elements/bulleted_list_element.py +1 -2
- notionary/elements/callout_element.py +1 -2
- notionary/elements/code_block_element.py +1 -2
- notionary/elements/column_element.py +1 -2
- notionary/elements/divider_element.py +1 -2
- notionary/elements/embed_element.py +1 -2
- notionary/elements/heading_element.py +1 -2
- notionary/elements/image_element.py +1 -2
- notionary/elements/mention_element.py +1 -2
- notionary/elements/notion_block_element.py +1 -36
- notionary/elements/numbered_list_element.py +1 -2
- notionary/elements/paragraph_element.py +1 -2
- notionary/elements/qoute_element.py +1 -2
- notionary/elements/table_element.py +1 -2
- notionary/elements/todo_element.py +1 -2
- notionary/elements/toggle_element.py +1 -2
- notionary/elements/toggleable_heading_element.py +1 -2
- notionary/elements/video_element.py +1 -2
- notionary/page/notion_page_factory.py +0 -4
- {notionary-0.2.12.dist-info → notionary-0.2.13.dist-info}/METADATA +1 -1
- {notionary-0.2.12.dist-info → notionary-0.2.13.dist-info}/RECORD +32 -35
- notionary/telemetry/__init__.py +0 -7
- notionary/telemetry/telemetry.py +0 -226
- notionary/telemetry/track_usage_decorator.py +0 -76
- {notionary-0.2.12.dist-info → notionary-0.2.13.dist-info}/WHEEL +0 -0
- {notionary-0.2.12.dist-info → notionary-0.2.13.dist-info}/entry_points.txt +0 -0
- {notionary-0.2.12.dist-info → notionary-0.2.13.dist-info}/licenses/LICENSE +0 -0
- {notionary-0.2.12.dist-info → notionary-0.2.13.dist-info}/top_level.txt +0 -0
notionary/__init__.py
CHANGED
notionary/cli/main.py
CHANGED
@@ -6,11 +6,35 @@ Notionary CLI - Integration Key Setup
|
|
6
6
|
import click
|
7
7
|
import os
|
8
8
|
import platform
|
9
|
+
import asyncio
|
10
|
+
import logging
|
9
11
|
from pathlib import Path
|
10
12
|
from dotenv import load_dotenv
|
11
13
|
from rich.console import Console
|
12
14
|
from rich.panel import Panel
|
13
15
|
from rich.prompt import Prompt, Confirm
|
16
|
+
from rich.table import Table
|
17
|
+
from rich import box
|
18
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
|
19
|
+
from notionary.notion_client import NotionClient
|
20
|
+
from notionary.database.database_discovery import DatabaseDiscovery
|
21
|
+
|
22
|
+
# Disable logging for CLI usage
|
23
|
+
def disable_notionary_logging():
|
24
|
+
"""Disable logging for notionary modules when used in CLI"""
|
25
|
+
# Option 1: Set to WARNING level (recommended for CLI)
|
26
|
+
logging.getLogger('notionary').setLevel(logging.WARNING)
|
27
|
+
logging.getLogger('DatabaseDiscovery').setLevel(logging.WARNING)
|
28
|
+
logging.getLogger('NotionClient').setLevel(logging.WARNING)
|
29
|
+
|
30
|
+
def enable_verbose_logging():
|
31
|
+
"""Enable verbose logging for debugging (use with --verbose flag)"""
|
32
|
+
logging.getLogger('notionary').setLevel(logging.DEBUG)
|
33
|
+
logging.getLogger('DatabaseDiscovery').setLevel(logging.DEBUG)
|
34
|
+
logging.getLogger('NotionClient').setLevel(logging.DEBUG)
|
35
|
+
|
36
|
+
# Initialize logging configuration for CLI
|
37
|
+
disable_notionary_logging()
|
14
38
|
|
15
39
|
console = Console()
|
16
40
|
|
@@ -46,12 +70,126 @@ def get_notion_secret() -> str:
|
|
46
70
|
load_dotenv()
|
47
71
|
return os.getenv("NOTION_SECRET", "")
|
48
72
|
|
73
|
+
async def fetch_notion_databases_with_progress():
|
74
|
+
"""Fetch databases using DatabaseDiscovery with progress animation"""
|
75
|
+
try:
|
76
|
+
# Initialize NotionClient and DatabaseDiscovery
|
77
|
+
client = NotionClient()
|
78
|
+
discovery = DatabaseDiscovery(client)
|
79
|
+
|
80
|
+
# Create progress display with custom spinner
|
81
|
+
with Progress(
|
82
|
+
SpinnerColumn(spinner_name="dots12", style="cyan"),
|
83
|
+
TextColumn("[bold blue]Discovering databases..."),
|
84
|
+
TimeElapsedColumn(),
|
85
|
+
console=console,
|
86
|
+
transient=True
|
87
|
+
) as progress:
|
88
|
+
# Add progress task
|
89
|
+
task = progress.add_task("Fetching...", total=None)
|
90
|
+
|
91
|
+
# Fetch databases
|
92
|
+
databases = await discovery._discover(page_size=50)
|
93
|
+
|
94
|
+
# Update progress to show completion
|
95
|
+
progress.update(task, description=f"[bold green]Found {len(databases)} databases!")
|
96
|
+
|
97
|
+
# Brief pause to show completion
|
98
|
+
await asyncio.sleep(0.5)
|
99
|
+
|
100
|
+
return {"databases": databases, "success": True}
|
101
|
+
|
102
|
+
except Exception as e:
|
103
|
+
return {"error": str(e), "success": False}
|
104
|
+
|
105
|
+
def show_databases_overview(api_key: str):
|
106
|
+
"""Show available databases with nice formatting"""
|
107
|
+
console.print("\n[bold blue]🔍 Connecting to Notion...[/bold blue]")
|
108
|
+
|
109
|
+
# Run async function in sync context
|
110
|
+
try:
|
111
|
+
result = asyncio.run(fetch_notion_databases_with_progress())
|
112
|
+
except Exception as e:
|
113
|
+
console.print(Panel.fit(
|
114
|
+
f"[bold red]❌ Unexpected error[/bold red]\n\n"
|
115
|
+
f"[red]{str(e)}[/red]\n\n"
|
116
|
+
"[yellow]Please check:[/yellow]\n"
|
117
|
+
"• Your internet connection\n"
|
118
|
+
"• Your integration key validity\n"
|
119
|
+
"• Try running the command again",
|
120
|
+
title="Connection Error"
|
121
|
+
))
|
122
|
+
return
|
123
|
+
|
124
|
+
if not result["success"]:
|
125
|
+
console.print(Panel.fit(
|
126
|
+
f"[bold red]❌ Could not fetch databases[/bold red]\n\n"
|
127
|
+
f"[red]{result['error']}[/red]\n\n"
|
128
|
+
"[yellow]Common issues:[/yellow]\n"
|
129
|
+
"• Check your integration key\n"
|
130
|
+
"• Make sure your integration has access to databases\n"
|
131
|
+
"• Visit your integration settings to grant access",
|
132
|
+
title="Connection Error"
|
133
|
+
))
|
134
|
+
return
|
135
|
+
|
136
|
+
databases = result["databases"]
|
137
|
+
|
138
|
+
if not databases:
|
139
|
+
console.print(Panel.fit(
|
140
|
+
"[bold yellow]⚠️ No databases found[/bold yellow]\n\n"
|
141
|
+
"Your integration key is valid, but no databases are accessible.\n\n"
|
142
|
+
"[bold blue]To grant access:[/bold blue]\n"
|
143
|
+
"1. Go to any Notion database\n"
|
144
|
+
"2. Click the '...' menu (top right)\n"
|
145
|
+
"3. Go to 'Add connections'\n"
|
146
|
+
"4. Find and select your integration\n\n"
|
147
|
+
"[cyan]https://www.notion.so/help/add-and-manage-connections-with-the-api[/cyan]",
|
148
|
+
title="No Databases Available"
|
149
|
+
))
|
150
|
+
return
|
151
|
+
|
152
|
+
# Create beautiful table
|
153
|
+
table = Table(
|
154
|
+
title=f"📊 Available Databases ({len(databases)} found)",
|
155
|
+
box=box.ROUNDED,
|
156
|
+
title_style="bold green",
|
157
|
+
header_style="bold cyan"
|
158
|
+
)
|
159
|
+
|
160
|
+
table.add_column("#", style="dim", justify="right", width=3)
|
161
|
+
table.add_column("Database Name", style="bold white", min_width=25)
|
162
|
+
table.add_column("ID", style="dim cyan", min_width=36)
|
163
|
+
|
164
|
+
for i, (title, db_id) in enumerate(databases, 1):
|
165
|
+
table.add_row(
|
166
|
+
str(i),
|
167
|
+
title or "Untitled Database",
|
168
|
+
db_id
|
169
|
+
)
|
170
|
+
|
171
|
+
console.print("\n")
|
172
|
+
console.print(table)
|
173
|
+
|
174
|
+
# Success message with next steps
|
175
|
+
console.print(Panel.fit(
|
176
|
+
"[bold green]🎉 Setup Complete![/bold green]\n\n"
|
177
|
+
f"Found [bold cyan]{len(databases)}[/bold cyan] accessible database(s).\n"
|
178
|
+
"You can now use notionary in your Python code!\n\n"
|
179
|
+
"[bold yellow]💡 Tip:[/bold yellow] Run [cyan]notionary db[/cyan] anytime to see this overview again.",
|
180
|
+
title="Ready to Go!"
|
181
|
+
))
|
182
|
+
|
49
183
|
@click.group()
|
50
184
|
@click.version_option() # Automatische Version aus setup.py
|
51
|
-
|
185
|
+
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose logging')
|
186
|
+
def main(verbose):
|
52
187
|
"""
|
53
188
|
Notionary CLI - Notion API Integration
|
54
189
|
"""
|
190
|
+
if verbose:
|
191
|
+
enable_verbose_logging()
|
192
|
+
console.print("[dim]Verbose logging enabled[/dim]")
|
55
193
|
pass
|
56
194
|
|
57
195
|
@main.command()
|
@@ -70,11 +208,19 @@ def init():
|
|
70
208
|
title="Already Configured"
|
71
209
|
))
|
72
210
|
|
73
|
-
# Option to reconfigure
|
74
|
-
|
211
|
+
# Option to reconfigure or show databases
|
212
|
+
choice = Prompt.ask(
|
213
|
+
"\n[yellow]What would you like to do?[/yellow]",
|
214
|
+
choices=["show", "update", "exit"],
|
215
|
+
default="show"
|
216
|
+
)
|
217
|
+
|
218
|
+
if choice == "show":
|
219
|
+
show_databases_overview(existing_key)
|
220
|
+
elif choice == "update":
|
75
221
|
setup_new_key()
|
76
222
|
else:
|
77
|
-
console.print("\n[blue]
|
223
|
+
console.print("\n[blue]Happy coding! 🚀[/blue]")
|
78
224
|
else:
|
79
225
|
# No key found, start setup
|
80
226
|
console.print(Panel.fit(
|
@@ -86,6 +232,23 @@ def init():
|
|
86
232
|
))
|
87
233
|
setup_new_key()
|
88
234
|
|
235
|
+
@main.command()
|
236
|
+
def db() -> None:
|
237
|
+
"""
|
238
|
+
Show available Notion databases
|
239
|
+
"""
|
240
|
+
existing_key = get_notion_secret()
|
241
|
+
|
242
|
+
if not existing_key:
|
243
|
+
console.print(Panel.fit(
|
244
|
+
"[bold red]❌ No Integration Key found![/bold red]\n\n"
|
245
|
+
"Please run [cyan]notionary init[/cyan] first to set up your key.",
|
246
|
+
title="Not Configured"
|
247
|
+
))
|
248
|
+
return
|
249
|
+
|
250
|
+
show_databases_overview(existing_key)
|
251
|
+
|
89
252
|
def setup_new_key():
|
90
253
|
"""Handle the key setup process"""
|
91
254
|
try:
|
@@ -126,7 +289,8 @@ def setup_new_key():
|
|
126
289
|
|
127
290
|
# Save the key
|
128
291
|
if save_integration_key(integration_key):
|
129
|
-
|
292
|
+
# Show databases overview after successful setup
|
293
|
+
show_databases_overview(integration_key)
|
130
294
|
|
131
295
|
except KeyboardInterrupt:
|
132
296
|
console.print("\n[yellow]Setup cancelled.[/yellow]")
|
@@ -170,7 +334,6 @@ def save_integration_key(integration_key: str) -> bool:
|
|
170
334
|
if written_key == integration_key:
|
171
335
|
console.print("\n[bold green]✅ Integration Key saved and verified![/bold green]")
|
172
336
|
console.print(f"[dim]Configuration: {env_file}[/dim]")
|
173
|
-
console.print("\n[blue]Ready to use notionary in your Python code! 🚀[/blue]")
|
174
337
|
return True
|
175
338
|
else:
|
176
339
|
console.print("\n[bold red]❌ Error: Key verification failed![/bold red]")
|
notionary/cli/onboarding.py
CHANGED
@@ -0,0 +1,116 @@
|
|
1
|
+
import asyncio
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from notionary import NotionDatabase
|
4
|
+
|
5
|
+
@dataclass
|
6
|
+
class OnboardingPageResult:
|
7
|
+
url: str
|
8
|
+
tile: str
|
9
|
+
emoji: str
|
10
|
+
|
11
|
+
async def generate_doc_for_database(
|
12
|
+
datbase_name: str,
|
13
|
+
) -> OnboardingPageResult:
|
14
|
+
database = await NotionDatabase.from_database_name(datbase_name)
|
15
|
+
page = await database.create_blank_page()
|
16
|
+
|
17
|
+
page_title = "Welcome to Notionary!"
|
18
|
+
page_icon = "📚"
|
19
|
+
|
20
|
+
markdown_content = """!> [🚀] This page was created fully automatically and serves as a showcase of what is possible with Notionary.
|
21
|
+
|
22
|
+
---
|
23
|
+
|
24
|
+
## 🗃️ Working with Databases
|
25
|
+
|
26
|
+
Discover and manage your Notion databases programmatically:
|
27
|
+
|
28
|
+
```python
|
29
|
+
import asyncio
|
30
|
+
from notionary import NotionDatabase, DatabaseDiscovery
|
31
|
+
|
32
|
+
async def main():
|
33
|
+
# Discover available databases
|
34
|
+
discovery = DatabaseDiscovery()
|
35
|
+
await discovery()
|
36
|
+
|
37
|
+
# Connect to a database by name
|
38
|
+
db = await NotionDatabase.from_database_name("Projects")
|
39
|
+
|
40
|
+
# Create a new page in the database
|
41
|
+
page = await db.create_blank_page()
|
42
|
+
|
43
|
+
# Query pages from database
|
44
|
+
async for page in db.iter_pages():
|
45
|
+
title = await page.get_title()
|
46
|
+
print(f"Page: {title}")
|
47
|
+
|
48
|
+
if __name__ == "__main__":
|
49
|
+
asyncio.run(main())
|
50
|
+
```
|
51
|
+
|
52
|
+
## 📄 Creating and Managing Pages
|
53
|
+
Create and update Notion pages with rich content:
|
54
|
+
```python
|
55
|
+
import asyncio
|
56
|
+
from notionary import NotionPage
|
57
|
+
|
58
|
+
async def main():
|
59
|
+
# Create a page from URL
|
60
|
+
page = NotionPage.from_url("https://www.notion.so/your-page-url")
|
61
|
+
|
62
|
+
# Or find by name
|
63
|
+
page = await NotionPage.from_page_name("My Project Page")
|
64
|
+
|
65
|
+
# Update page metadata
|
66
|
+
await page.set_title("Updated Title")
|
67
|
+
await page.set_emoji_icon("🚀")
|
68
|
+
await page.set_random_gradient_cover()
|
69
|
+
|
70
|
+
# Add markdown content
|
71
|
+
markdown = '''
|
72
|
+
# Project Overview
|
73
|
+
|
74
|
+
!> [💡] This page was created programmatically using Notionary.
|
75
|
+
|
76
|
+
## Features
|
77
|
+
- **Rich** Markdown support
|
78
|
+
- Async functionality
|
79
|
+
- Custom syntax extensions
|
80
|
+
'''
|
81
|
+
|
82
|
+
await page.replace_content(markdown)
|
83
|
+
|
84
|
+
if __name__ == "__main__":
|
85
|
+
asyncio.run(main())
|
86
|
+
```
|
87
|
+
|
88
|
+
## 📊 Tables and Structured Data
|
89
|
+
Create tables for organizing information:
|
90
|
+
FeatureStatusPriorityAPI IntegrationCompleteHighDocumentationIn ProgressMediumDatabase QueriesCompleteHighFile UploadsCompleteMedium
|
91
|
+
|
92
|
+
🎥 Media Embedding
|
93
|
+
Embed videos directly in your pages:
|
94
|
+
@[Caption](https://www.youtube.com/watch?v=dQw4w9WgXcQ) - Never gonna give you up!
|
95
|
+
|
96
|
+
Happy building with Notionary! 🎉"""
|
97
|
+
|
98
|
+
|
99
|
+
await page.set_title(page_title)
|
100
|
+
await page.set_emoji_icon(page_icon)
|
101
|
+
await page.set_random_gradient_cover()
|
102
|
+
await page.append_markdown(markdown_content)
|
103
|
+
|
104
|
+
url = await page.get_url()
|
105
|
+
|
106
|
+
return OnboardingPageResult(
|
107
|
+
url=url,
|
108
|
+
tile=page_title,
|
109
|
+
emoji=page_icon,
|
110
|
+
)
|
111
|
+
|
112
|
+
|
113
|
+
if __name__ == "__main__":
|
114
|
+
print("🚀 Starting Notionary onboarding page generation...")
|
115
|
+
result = asyncio.run(generate_doc_for_database("Wissen & Notizen"))
|
116
|
+
print(f"✅ Onboarding page created: {result.tile} {result.emoji} - {result.url}")
|
@@ -3,7 +3,6 @@ from typing import Any, AsyncGenerator, Dict, List, Optional
|
|
3
3
|
|
4
4
|
from notionary.notion_client import NotionClient
|
5
5
|
from notionary.page.notion_page import NotionPage
|
6
|
-
from notionary.telemetry import NotionaryTelemetry
|
7
6
|
from notionary.util.warn_direct_constructor_usage import warn_direct_constructor_usage
|
8
7
|
from notionary.util import LoggingMixin
|
9
8
|
from notionary.util.page_id_utils import format_uuid
|
@@ -26,7 +25,6 @@ class NotionDatabase(LoggingMixin):
|
|
26
25
|
token: Optional Notion API token
|
27
26
|
"""
|
28
27
|
self.database_id = database_id
|
29
|
-
self._telemetry = NotionaryTelemetry()
|
30
28
|
self._client = NotionClient(token=token)
|
31
29
|
|
32
30
|
@classmethod
|
@@ -10,7 +10,6 @@ from notionary.exceptions.database_exceptions import (
|
|
10
10
|
DatabaseParsingError,
|
11
11
|
NotionDatabaseException,
|
12
12
|
)
|
13
|
-
from notionary.telemetry import track_usage
|
14
13
|
from notionary.util import LoggingMixin
|
15
14
|
from notionary.util import format_uuid
|
16
15
|
from notionary.util import singleton
|
@@ -24,7 +23,6 @@ class NotionDatabaseFactory(LoggingMixin):
|
|
24
23
|
"""
|
25
24
|
|
26
25
|
@classmethod
|
27
|
-
@track_usage('page_factory_method_used', {'method': 'from_page_id'})
|
28
26
|
def from_database_id(
|
29
27
|
cls, database_id: str, token: Optional[str] = None
|
30
28
|
) -> NotionDatabase:
|
@@ -59,7 +57,6 @@ class NotionDatabaseFactory(LoggingMixin):
|
|
59
57
|
raise DatabaseConnectionError(error_msg) from e
|
60
58
|
|
61
59
|
@classmethod
|
62
|
-
@track_usage('page_factory_method_used', {'method': 'from_url'})
|
63
60
|
async def from_database_name(
|
64
61
|
cls, database_name: str, token: Optional[str] = None
|
65
62
|
) -> NotionDatabase:
|
@@ -1,12 +1,11 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List
|
3
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
3
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
4
4
|
from notionary.prompting.element_prompt_content import (
|
5
5
|
ElementPromptBuilder,
|
6
6
|
ElementPromptContent,
|
7
7
|
)
|
8
8
|
|
9
|
-
@auto_track_conversions
|
10
9
|
class AudioElement(NotionBlockElement):
|
11
10
|
"""
|
12
11
|
Handles conversion between Markdown audio embeds and Notion audio blocks.
|
@@ -1,13 +1,12 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List, Tuple
|
3
3
|
|
4
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
4
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
5
5
|
from notionary.prompting.element_prompt_content import (
|
6
6
|
ElementPromptBuilder,
|
7
7
|
ElementPromptContent,
|
8
8
|
)
|
9
9
|
|
10
|
-
@auto_track_conversions
|
11
10
|
class BookmarkElement(NotionBlockElement):
|
12
11
|
"""
|
13
12
|
Handles conversion between Markdown bookmarks and Notion bookmark blocks.
|
@@ -1,13 +1,12 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional
|
3
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
3
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
4
4
|
from notionary.prompting.element_prompt_content import (
|
5
5
|
ElementPromptBuilder,
|
6
6
|
ElementPromptContent,
|
7
7
|
)
|
8
8
|
from notionary.elements.text_inline_formatter import TextInlineFormatter
|
9
9
|
|
10
|
-
@auto_track_conversions
|
11
10
|
class BulletedListElement(NotionBlockElement):
|
12
11
|
"""Class for converting between Markdown bullet lists and Notion bulleted list items."""
|
13
12
|
|
@@ -6,9 +6,8 @@ from notionary.prompting.element_prompt_content import (
|
|
6
6
|
ElementPromptContent,
|
7
7
|
)
|
8
8
|
from notionary.elements.text_inline_formatter import TextInlineFormatter
|
9
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
9
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
10
10
|
|
11
|
-
@auto_track_conversions
|
12
11
|
class CalloutElement(NotionBlockElement):
|
13
12
|
"""
|
14
13
|
Handles conversion between Markdown callouts and Notion callout blocks.
|
@@ -1,12 +1,11 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List, Tuple
|
3
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
3
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
4
4
|
from notionary.prompting.element_prompt_content import (
|
5
5
|
ElementPromptBuilder,
|
6
6
|
ElementPromptContent,
|
7
7
|
)
|
8
8
|
|
9
|
-
@auto_track_conversions
|
10
9
|
class CodeBlockElement(NotionBlockElement):
|
11
10
|
"""
|
12
11
|
Handles conversion between Markdown code blocks and Notion code blocks.
|
@@ -1,14 +1,13 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List, Tuple, Callable
|
3
3
|
|
4
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
4
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
5
5
|
from notionary.page.formatting.spacer_rules import SPACER_MARKER
|
6
6
|
from notionary.prompting.element_prompt_content import (
|
7
7
|
ElementPromptBuilder,
|
8
8
|
ElementPromptContent,
|
9
9
|
)
|
10
10
|
|
11
|
-
@auto_track_conversions
|
12
11
|
class ColumnElement(NotionBlockElement):
|
13
12
|
"""
|
14
13
|
Handles conversion between custom Markdown column syntax and Notion column blocks.
|
@@ -1,13 +1,12 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional
|
3
3
|
|
4
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
4
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
5
5
|
from notionary.prompting.element_prompt_content import (
|
6
6
|
ElementPromptBuilder,
|
7
7
|
ElementPromptContent,
|
8
8
|
)
|
9
9
|
|
10
|
-
@auto_track_conversions
|
11
10
|
class DividerElement(NotionBlockElement):
|
12
11
|
"""
|
13
12
|
Handles conversion between Markdown horizontal dividers and Notion divider blocks.
|
@@ -1,12 +1,11 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List
|
3
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
3
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
4
4
|
from notionary.prompting.element_prompt_content import (
|
5
5
|
ElementPromptBuilder,
|
6
6
|
ElementPromptContent,
|
7
7
|
)
|
8
8
|
|
9
|
-
@auto_track_conversions
|
10
9
|
class EmbedElement(NotionBlockElement):
|
11
10
|
"""
|
12
11
|
Handles conversion between Markdown embeds and Notion embed blocks.
|
@@ -1,14 +1,13 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional
|
3
3
|
|
4
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
4
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
5
5
|
from notionary.prompting.element_prompt_content import (
|
6
6
|
ElementPromptBuilder,
|
7
7
|
ElementPromptContent,
|
8
8
|
)
|
9
9
|
from notionary.elements.text_inline_formatter import TextInlineFormatter
|
10
10
|
|
11
|
-
@auto_track_conversions
|
12
11
|
class HeadingElement(NotionBlockElement):
|
13
12
|
"""Handles conversion between Markdown headings and Notion heading blocks."""
|
14
13
|
|
@@ -1,12 +1,11 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List
|
3
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
3
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
4
4
|
from notionary.prompting.element_prompt_content import (
|
5
5
|
ElementPromptBuilder,
|
6
6
|
ElementPromptContent,
|
7
7
|
)
|
8
8
|
|
9
|
-
@auto_track_conversions
|
10
9
|
class ImageElement(NotionBlockElement):
|
11
10
|
"""
|
12
11
|
Handles conversion between Markdown images and Notion image blocks.
|
@@ -2,13 +2,12 @@ import re
|
|
2
2
|
from typing import Dict, Any, Optional, List
|
3
3
|
from typing_extensions import override
|
4
4
|
|
5
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
5
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
6
6
|
from notionary.prompting.element_prompt_content import (
|
7
7
|
ElementPromptBuilder,
|
8
8
|
ElementPromptContent,
|
9
9
|
)
|
10
10
|
|
11
|
-
@auto_track_conversions
|
12
11
|
class MentionElement(NotionBlockElement):
|
13
12
|
"""
|
14
13
|
Handles conversion between Markdown mentions and Notion mention elements.
|
@@ -3,7 +3,6 @@ from typing import Dict, Any, Optional
|
|
3
3
|
from abc import ABC
|
4
4
|
|
5
5
|
from notionary.prompting.element_prompt_content import ElementPromptContent
|
6
|
-
from notionary.telemetry import track_usage
|
7
6
|
|
8
7
|
|
9
8
|
class NotionBlockElement(ABC):
|
@@ -33,38 +32,4 @@ class NotionBlockElement(ABC):
|
|
33
32
|
|
34
33
|
@classmethod
|
35
34
|
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
36
|
-
"""Returns a dictionary with information for LLM prompts about this element."""
|
37
|
-
|
38
|
-
|
39
|
-
def auto_track_conversions(cls):
|
40
|
-
"""
|
41
|
-
Decorator der sich auch auf Subklassen vererbt.
|
42
|
-
"""
|
43
|
-
conversion_methods = ['markdown_to_notion', 'notion_to_markdown']
|
44
|
-
|
45
|
-
original_init_subclass = getattr(cls, '__init_subclass__', None)
|
46
|
-
|
47
|
-
@classmethod
|
48
|
-
def __init_subclass__(cls_inner, **kwargs):
|
49
|
-
# Original __init_subclass__ aufrufen
|
50
|
-
if original_init_subclass:
|
51
|
-
original_init_subclass(**kwargs)
|
52
|
-
|
53
|
-
# Tracking für Subklasse hinzufügen
|
54
|
-
for method_name in conversion_methods:
|
55
|
-
if hasattr(cls_inner, method_name):
|
56
|
-
original_method = getattr(cls_inner, method_name)
|
57
|
-
|
58
|
-
if isinstance(original_method, classmethod):
|
59
|
-
func = original_method.__func__
|
60
|
-
|
61
|
-
@track_usage(f"{cls_inner.__name__.lower()}_{method_name}")
|
62
|
-
@classmethod
|
63
|
-
@wraps(func)
|
64
|
-
def tracked_method(cls_ref, *args, **kwargs):
|
65
|
-
return func(cls_ref, *args, **kwargs)
|
66
|
-
|
67
|
-
setattr(cls_inner, method_name, tracked_method)
|
68
|
-
|
69
|
-
cls.__init_subclass__ = __init_subclass__
|
70
|
-
return cls
|
35
|
+
"""Returns a dictionary with information for LLM prompts about this element."""
|
@@ -1,13 +1,12 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional
|
3
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
3
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
4
4
|
from notionary.prompting.element_prompt_content import (
|
5
5
|
ElementPromptBuilder,
|
6
6
|
ElementPromptContent,
|
7
7
|
)
|
8
8
|
from notionary.elements.text_inline_formatter import TextInlineFormatter
|
9
9
|
|
10
|
-
@auto_track_conversions
|
11
10
|
class NumberedListElement(NotionBlockElement):
|
12
11
|
"""Class for converting between Markdown numbered lists and Notion numbered list items."""
|
13
12
|
|
@@ -1,13 +1,12 @@
|
|
1
1
|
from typing import Dict, Any, Optional
|
2
2
|
|
3
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
3
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
4
4
|
from notionary.prompting.element_prompt_content import (
|
5
5
|
ElementPromptBuilder,
|
6
6
|
ElementPromptContent,
|
7
7
|
)
|
8
8
|
from notionary.elements.text_inline_formatter import TextInlineFormatter
|
9
9
|
|
10
|
-
@auto_track_conversions
|
11
10
|
class ParagraphElement(NotionBlockElement):
|
12
11
|
"""Handles conversion between Markdown paragraphs and Notion paragraph blocks."""
|
13
12
|
|
@@ -1,12 +1,11 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List, Tuple
|
3
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
3
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
4
4
|
from notionary.prompting.element_prompt_content import (
|
5
5
|
ElementPromptBuilder,
|
6
6
|
ElementPromptContent,
|
7
7
|
)
|
8
8
|
|
9
|
-
@auto_track_conversions
|
10
9
|
class QuoteElement(NotionBlockElement):
|
11
10
|
"""Class for converting between Markdown blockquotes and Notion quote blocks."""
|
12
11
|
|
@@ -1,13 +1,12 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List, Tuple
|
3
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
3
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
4
4
|
from notionary.elements.text_inline_formatter import TextInlineFormatter
|
5
5
|
from notionary.prompting.element_prompt_content import (
|
6
6
|
ElementPromptBuilder,
|
7
7
|
ElementPromptContent,
|
8
8
|
)
|
9
9
|
|
10
|
-
@auto_track_conversions
|
11
10
|
class TableElement(NotionBlockElement):
|
12
11
|
"""
|
13
12
|
Handles conversion between Markdown tables and Notion table blocks.
|
@@ -1,13 +1,12 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional
|
3
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
3
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
4
4
|
from notionary.prompting.element_prompt_content import (
|
5
5
|
ElementPromptBuilder,
|
6
6
|
ElementPromptContent,
|
7
7
|
)
|
8
8
|
from notionary.elements.text_inline_formatter import TextInlineFormatter
|
9
9
|
|
10
|
-
@auto_track_conversions
|
11
10
|
class TodoElement(NotionBlockElement):
|
12
11
|
"""
|
13
12
|
Handles conversion between Markdown todo items and Notion to_do blocks.
|
@@ -1,13 +1,12 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List, Tuple, Callable
|
3
3
|
|
4
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
4
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
5
5
|
from notionary.prompting.element_prompt_content import (
|
6
6
|
ElementPromptBuilder,
|
7
7
|
ElementPromptContent,
|
8
8
|
)
|
9
9
|
|
10
|
-
@auto_track_conversions
|
11
10
|
class ToggleElement(NotionBlockElement):
|
12
11
|
"""
|
13
12
|
Improved ToggleElement class using pipe syntax instead of indentation.
|
@@ -1,14 +1,13 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List, Tuple, Callable
|
3
3
|
|
4
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
4
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
5
5
|
from notionary.prompting.element_prompt_content import (
|
6
6
|
ElementPromptBuilder,
|
7
7
|
ElementPromptContent,
|
8
8
|
)
|
9
9
|
from notionary.elements.text_inline_formatter import TextInlineFormatter
|
10
10
|
|
11
|
-
@auto_track_conversions
|
12
11
|
class ToggleableHeadingElement(NotionBlockElement):
|
13
12
|
"""Handles conversion between Markdown collapsible headings and Notion toggleable heading blocks with pipe syntax."""
|
14
13
|
|
@@ -1,12 +1,11 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List
|
3
|
-
from notionary.elements.notion_block_element import NotionBlockElement
|
3
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
4
4
|
from notionary.prompting.element_prompt_content import (
|
5
5
|
ElementPromptBuilder,
|
6
6
|
ElementPromptContent,
|
7
7
|
)
|
8
8
|
|
9
|
-
@auto_track_conversions
|
10
9
|
class VideoElement(NotionBlockElement):
|
11
10
|
"""
|
12
11
|
Handles conversion between Markdown video embeds and Notion video blocks.
|
@@ -2,7 +2,6 @@ from typing import List, Optional, Dict, Any, Tuple
|
|
2
2
|
from difflib import SequenceMatcher
|
3
3
|
|
4
4
|
from notionary import NotionPage, NotionClient
|
5
|
-
from notionary.telemetry import track_usage
|
6
5
|
from notionary.util import LoggingMixin
|
7
6
|
from notionary.util import format_uuid, extract_and_validate_page_id
|
8
7
|
from notionary.util import singleton
|
@@ -20,7 +19,6 @@ class NotionPageFactory(LoggingMixin):
|
|
20
19
|
EARLY_STOP_THRESHOLD = 0.95
|
21
20
|
|
22
21
|
@classmethod
|
23
|
-
@track_usage('page_factory_method_used', {'method': 'from_page_id'})
|
24
22
|
def from_page_id(cls, page_id: str, token: Optional[str] = None) -> NotionPage:
|
25
23
|
"""Create a NotionPage from a page ID."""
|
26
24
|
|
@@ -36,7 +34,6 @@ class NotionPageFactory(LoggingMixin):
|
|
36
34
|
raise
|
37
35
|
|
38
36
|
@classmethod
|
39
|
-
@track_usage('page_factory_method_used', {'method': 'from_url'})
|
40
37
|
def from_url(cls, url: str, token: Optional[str] = None) -> NotionPage:
|
41
38
|
"""Create a NotionPage from a Notion URL."""
|
42
39
|
|
@@ -56,7 +53,6 @@ class NotionPageFactory(LoggingMixin):
|
|
56
53
|
raise
|
57
54
|
|
58
55
|
@classmethod
|
59
|
-
@track_usage('page_factory_method_used', {'method': 'from_page_name'})
|
60
56
|
async def from_page_name(
|
61
57
|
cls, page_name: str, token: Optional[str] = None
|
62
58
|
) -> NotionPage:
|
@@ -1,32 +1,32 @@
|
|
1
|
-
notionary/__init__.py,sha256=
|
1
|
+
notionary/__init__.py,sha256=U7I4nffaEt1Gfg6N7TFupC_GQVZttLhW5u0qjVX7gP4,717
|
2
2
|
notionary/notion_client.py,sha256=gkREAr8LkUUKK9cOvq72r8jNjlXDleBP2fYm7LjjbjM,7311
|
3
|
-
notionary/cli/main.py,sha256=
|
4
|
-
notionary/cli/onboarding.py,sha256=
|
3
|
+
notionary/cli/main.py,sha256=0tmX9y_xJksKPV8evfUIxp2MTKsHAvNcq76CW4PZCSs,12836
|
4
|
+
notionary/cli/onboarding.py,sha256=I7G6eaEw_WWmbfBKBa4nHgItGyZg6iwtEw9pZylsqa4,3376
|
5
5
|
notionary/database/database_discovery.py,sha256=l9IjthwdA_Y_k_JXcAW-KnvZDwNYylIbsrQ5cpgtb5w,4484
|
6
|
-
notionary/database/notion_database.py,sha256=
|
7
|
-
notionary/database/notion_database_factory.py,sha256=
|
6
|
+
notionary/database/notion_database.py,sha256=vbMu8FRao0nXRkSK68Q6637ypiikyMpP2zOmzGWsgpU,7595
|
7
|
+
notionary/database/notion_database_factory.py,sha256=6AK9c63R8qyQX46VY-eNL1DufHe6pK_Erka0QEBVO90,6594
|
8
8
|
notionary/database/models/page_result.py,sha256=Vmm5_oYpYAkIIJVoTd1ZZGloeC3cmFLMYP255mAmtaw,233
|
9
|
-
notionary/elements/audio_element.py,sha256=
|
10
|
-
notionary/elements/bookmark_element.py,sha256=
|
11
|
-
notionary/elements/bulleted_list_element.py,sha256=
|
12
|
-
notionary/elements/callout_element.py,sha256=
|
13
|
-
notionary/elements/code_block_element.py,sha256=
|
14
|
-
notionary/elements/column_element.py,sha256=
|
15
|
-
notionary/elements/divider_element.py,sha256=
|
16
|
-
notionary/elements/embed_element.py,sha256=
|
17
|
-
notionary/elements/heading_element.py,sha256=
|
18
|
-
notionary/elements/image_element.py,sha256=
|
19
|
-
notionary/elements/mention_element.py,sha256=-
|
20
|
-
notionary/elements/notion_block_element.py,sha256=
|
21
|
-
notionary/elements/numbered_list_element.py,sha256=
|
22
|
-
notionary/elements/paragraph_element.py,sha256=
|
23
|
-
notionary/elements/qoute_element.py,sha256=
|
24
|
-
notionary/elements/table_element.py,sha256=
|
9
|
+
notionary/elements/audio_element.py,sha256=m1zMUYkpQNPCx9S0KpU85ps7pnm9Pzu4lglKYzOozF4,5343
|
10
|
+
notionary/elements/bookmark_element.py,sha256=M_vGJfBVVhUDa7sDfHB622m1Q9wEn4Lw0pCmuDkxpvQ,8143
|
11
|
+
notionary/elements/bulleted_list_element.py,sha256=dyWNu28l_fG6fob5zQzMadnWb5g_B2n7ixCHwaniPdE,2662
|
12
|
+
notionary/elements/callout_element.py,sha256=OVaRLdxNFXTJnMzmfg3SZClOAYfJ4oORSr_Skm5EOPA,4490
|
13
|
+
notionary/elements/code_block_element.py,sha256=hBMn3VpFJLgq12llvdSFkjLX05poMCIZWsBaUF9ZOQA,7527
|
14
|
+
notionary/elements/column_element.py,sha256=VpPe2yVvozMFIladOMxMh0Q1nsND_XysYxpxWoIH8eU,12732
|
15
|
+
notionary/elements/divider_element.py,sha256=sqfs1YRVqsEFlKdBOX9p8K2Ise2lDb19HwcwHM_R7nU,2330
|
16
|
+
notionary/elements/embed_element.py,sha256=PkoaycRel6bAVkP1XHv57vuLVwFmzgeZYKqM5AT8Lg0,4577
|
17
|
+
notionary/elements/heading_element.py,sha256=zgqC6alKoaucpeWDpDJd-TIsbMoJzNI5_BGP-D8Xbsc,3166
|
18
|
+
notionary/elements/image_element.py,sha256=rd9P9BT9YUwofO24JadH7bMtQCJLxVHdjHF1_ykGS-g,4756
|
19
|
+
notionary/elements/mention_element.py,sha256=-AVZ8rn6y-S9Paw-vUDZpa8nhn0dR5L_0qNjd9dQI_s,8225
|
20
|
+
notionary/elements/notion_block_element.py,sha256=utWHp_JZSRQgNIWiZfJfzwl7y2ALuDqGTY-Ve3OoQeg,1285
|
21
|
+
notionary/elements/numbered_list_element.py,sha256=kgJfQQ5FIKZVRjygd6sLJKkE9-IDlx-MFAEaxWVsg84,2661
|
22
|
+
notionary/elements/paragraph_element.py,sha256=oOIRkknpEEAw5Pr81D6c-L6cGRvaZGtggLT3iVYGC4Q,3253
|
23
|
+
notionary/elements/qoute_element.py,sha256=SCvNWht-38EwMIsDg19VPajKNc1IhZBP3oVxLQw_yVA,6120
|
24
|
+
notionary/elements/table_element.py,sha256=6yTY0fNT_Ae2ncwGQE0CSsfCxGNvnDblTTzd3kdz-Sw,11247
|
25
25
|
notionary/elements/text_inline_formatter.py,sha256=q1WePwTxhSkjhTFylcyAbhxaWLo_sjYS3Q_mIPgsKb4,8625
|
26
|
-
notionary/elements/todo_element.py,sha256=
|
27
|
-
notionary/elements/toggle_element.py,sha256=
|
28
|
-
notionary/elements/toggleable_heading_element.py,sha256=
|
29
|
-
notionary/elements/video_element.py,sha256=
|
26
|
+
notionary/elements/todo_element.py,sha256=BKw3KvjdDsZIlSXoqV52z9R-a3KBzkH-9DKo4dN7m9w,4122
|
27
|
+
notionary/elements/toggle_element.py,sha256=sg3LfblBRa0pIrcSBHPRknq6FYWB5-mpDGvA7PBOZ9U,11080
|
28
|
+
notionary/elements/toggleable_heading_element.py,sha256=sKvjD_x61QYOM4l8gfq6VodpLWqxf8FR2JGWXNeee08,9958
|
29
|
+
notionary/elements/video_element.py,sha256=IlB88CmBueYPSFh6p7kxE5zVjcZBQJJ1K953G7kg99M,5725
|
30
30
|
notionary/elements/registry/block_registry.py,sha256=g0id_Q6guzTyNY6HfnB9AjOBvCR4CvtpnUeFAY8kgY0,5027
|
31
31
|
notionary/elements/registry/block_registry_builder.py,sha256=5zRKnw2102rAeHpANs6Csu4DVufOazf1peEovChWcgs,9572
|
32
32
|
notionary/exceptions/database_exceptions.py,sha256=I-Tx6bYRLpi5pjGPtbT-Mqxvz3BFgYTiuZxknJeLxtI,2638
|
@@ -35,7 +35,7 @@ notionary/models/notion_block_response.py,sha256=gzL4C6K9QPcaMS6NbAZaRceSEnMbNwY
|
|
35
35
|
notionary/models/notion_database_response.py,sha256=FMAasQP20S12J_KMdMlNpcHHwxFKX2YtbE4Q9xn-ruQ,1213
|
36
36
|
notionary/models/notion_page_response.py,sha256=r4fwMwwDocj92JdbSmyrzIqBKsnEaz4aDUiPabrg9BM,1762
|
37
37
|
notionary/page/notion_page.py,sha256=CnEr5S425t7r8n4mZERwShlXsXnR2G7bbYjO8yb2oaU,18032
|
38
|
-
notionary/page/notion_page_factory.py,sha256=
|
38
|
+
notionary/page/notion_page_factory.py,sha256=_dsvxn3xmjZFQw3fKIhnTwiIQfk7p8pxVOFj1GMvBZc,11763
|
39
39
|
notionary/page/notion_to_markdown_converter.py,sha256=vUQss0J7LUFLULGvW27PjaTFuWi8OsRQAUBowSYorkM,6408
|
40
40
|
notionary/page/content/notion_page_content_chunker.py,sha256=kWJnV9GLU5YLgSVPKOjwMBbG_CMAmVRkuDtwJYb_UAA,3316
|
41
41
|
notionary/page/content/page_content_retriever.py,sha256=MoRNwVyBacQEPFu-XseahKEFait0q8tjuhFUXHOBrMo,2208
|
@@ -54,17 +54,14 @@ notionary/page/relations/notion_page_title_resolver.py,sha256=LN89y-Tc0Rk81TiTeA
|
|
54
54
|
notionary/page/relations/page_database_relation.py,sha256=nkelofYzfuIFjmM7vR6IGJpWUG9XPmSDnU1WR8WtQrs,2231
|
55
55
|
notionary/prompting/element_prompt_content.py,sha256=tHref-SKA81Ua_IQD2Km7y7BvFtHl74haSIjHNYE3FE,4403
|
56
56
|
notionary/prompting/markdown_syntax_prompt_generator.py,sha256=_1qIYlqSfI6q6Fut10t6gGwTQuS8c3QBcC_5DBme9Mo,5084
|
57
|
-
notionary/telemetry/__init__.py,sha256=qdFGrhOhUb_HPMLS4kLSr1BJNfb5-LzHwLmx-NYCDsw,156
|
58
|
-
notionary/telemetry/telemetry.py,sha256=SEwEsegNhX7xblKnlk9qCDqKOTtf8sGtnNWbLiOVk7g,8182
|
59
|
-
notionary/telemetry/track_usage_decorator.py,sha256=rmxwySb1PfZI1mXXU0G_W9Fpn6ByslYrDjsU4JYnMJ4,2421
|
60
57
|
notionary/util/__init__.py,sha256=ra1jHFFiQNWYDzmVb81OVhtshzkZ0GcLVI8YDODYj3w,235
|
61
58
|
notionary/util/logging_mixin.py,sha256=d5sRSmUtgQeuckdNBkO025IXPGe4oOb-7ueVAIP8amU,1846
|
62
59
|
notionary/util/page_id_utils.py,sha256=EYNMxgf-7ghzL5K8lKZBZfW7g5CsdY0Xuj4IYmU8RPk,1381
|
63
60
|
notionary/util/singleton.py,sha256=CKAvykndwPRZsA3n3MAY_XdCR59MBjjKP0vtm2BcvF0,428
|
64
61
|
notionary/util/warn_direct_constructor_usage.py,sha256=vyJR73F95XVSRWIbyij-82IGOpAne9SBPM25eDpZfSU,1715
|
65
|
-
notionary-0.2.
|
66
|
-
notionary-0.2.
|
67
|
-
notionary-0.2.
|
68
|
-
notionary-0.2.
|
69
|
-
notionary-0.2.
|
70
|
-
notionary-0.2.
|
62
|
+
notionary-0.2.13.dist-info/licenses/LICENSE,sha256=zOm3cRT1qD49eg7vgw95MI79rpUAZa1kRBFwL2FkAr8,1120
|
63
|
+
notionary-0.2.13.dist-info/METADATA,sha256=jXcnz9Gqxx8vFbnLdrLhr_TiByxrWd_OMIxPPqyqHGY,7582
|
64
|
+
notionary-0.2.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
65
|
+
notionary-0.2.13.dist-info/entry_points.txt,sha256=V7X21u3QNm7h7p6Cx0Sx2SO3mtmA7gVwXM8lNYnv9fk,54
|
66
|
+
notionary-0.2.13.dist-info/top_level.txt,sha256=fhONa6BMHQXqthx5PanWGbPL0b8rdFqhrJKVLf_adSs,10
|
67
|
+
notionary-0.2.13.dist-info/RECORD,,
|
notionary/telemetry/__init__.py
DELETED
notionary/telemetry/telemetry.py
DELETED
@@ -1,226 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import uuid
|
3
|
-
import atexit
|
4
|
-
import signal
|
5
|
-
import threading
|
6
|
-
from pathlib import Path
|
7
|
-
from typing import Dict, Any, Optional
|
8
|
-
from posthog import Posthog
|
9
|
-
from dotenv import load_dotenv
|
10
|
-
|
11
|
-
from notionary.util import LoggingMixin
|
12
|
-
from notionary.util import singleton
|
13
|
-
|
14
|
-
load_dotenv()
|
15
|
-
|
16
|
-
@singleton
|
17
|
-
class NotionaryTelemetry(LoggingMixin):
|
18
|
-
"""
|
19
|
-
Anonymous telemetry for Notionary - enabled by default.
|
20
|
-
Disable via: ANONYMIZED_TELEMETRY=false
|
21
|
-
"""
|
22
|
-
|
23
|
-
USER_ID_PATH = str(Path.home() / ".cache" / "notionary" / "telemetry_user_id")
|
24
|
-
PROJECT_API_KEY = (
|
25
|
-
"phc_gItKOx21Tc0l07C1taD0QPpqFnbWgWjVfRjF6z24kke" # write-only so no worries
|
26
|
-
)
|
27
|
-
HOST = "https://eu.i.posthog.com"
|
28
|
-
|
29
|
-
_logged_init_message = False
|
30
|
-
|
31
|
-
def __init__(self):
|
32
|
-
# Default: enabled, disable via ANONYMIZED_TELEMETRY=false
|
33
|
-
telemetry_setting = os.getenv("ANONYMIZED_TELEMETRY", "true").lower()
|
34
|
-
self.enabled = telemetry_setting != "false"
|
35
|
-
|
36
|
-
self._user_id = None
|
37
|
-
self._client = None
|
38
|
-
self._shutdown_lock = threading.Lock()
|
39
|
-
self._is_shutdown = False
|
40
|
-
self._shutdown_registered = False
|
41
|
-
|
42
|
-
if self.enabled:
|
43
|
-
self._initialize_client()
|
44
|
-
self._register_shutdown_handlers()
|
45
|
-
|
46
|
-
def _register_shutdown_handlers(self):
|
47
|
-
"""Register shutdown handlers for clean exit"""
|
48
|
-
with self._shutdown_lock:
|
49
|
-
if self._shutdown_registered:
|
50
|
-
return
|
51
|
-
|
52
|
-
try:
|
53
|
-
# Register atexit handler for normal program termination
|
54
|
-
atexit.register(self._atexit_handler)
|
55
|
-
|
56
|
-
# Register signal handlers for SIGINT (Ctrl+C) and SIGTERM
|
57
|
-
signal.signal(signal.SIGINT, self._signal_handler)
|
58
|
-
signal.signal(signal.SIGTERM, self._signal_handler)
|
59
|
-
|
60
|
-
self._shutdown_registered = True
|
61
|
-
self.logger.debug("Telemetry shutdown handlers registered")
|
62
|
-
|
63
|
-
except Exception as e:
|
64
|
-
self.logger.debug(f"Failed to register shutdown handlers: {e}")
|
65
|
-
|
66
|
-
def _signal_handler(self, signum, frame):
|
67
|
-
"""Handle SIGINT (Ctrl+C) and SIGTERM signals"""
|
68
|
-
signal_name = "SIGINT" if signum == signal.SIGINT else f"SIG{signum}"
|
69
|
-
self.logger.debug(f"Received {signal_name}, shutting down telemetry...")
|
70
|
-
|
71
|
-
self.shutdown(timeout=5.0) # Quick shutdown for signals
|
72
|
-
|
73
|
-
# Let the original signal handler take over (or exit)
|
74
|
-
if signum == signal.SIGINT:
|
75
|
-
# Restore default handler and re-raise
|
76
|
-
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
77
|
-
os.kill(os.getpid(), signal.SIGINT)
|
78
|
-
|
79
|
-
def _atexit_handler(self):
|
80
|
-
"""Handle normal program exit"""
|
81
|
-
self.logger.debug("Normal program exit, shutting down telemetry...")
|
82
|
-
self.shutdown(timeout=10.0)
|
83
|
-
|
84
|
-
@property
|
85
|
-
def user_id(self) -> str:
|
86
|
-
"""Anonymous, persistent user ID"""
|
87
|
-
if self._user_id:
|
88
|
-
return self._user_id
|
89
|
-
|
90
|
-
try:
|
91
|
-
if not os.path.exists(self.USER_ID_PATH):
|
92
|
-
os.makedirs(os.path.dirname(self.USER_ID_PATH), exist_ok=True)
|
93
|
-
with open(self.USER_ID_PATH, "w") as f:
|
94
|
-
new_user_id = str(uuid.uuid4())
|
95
|
-
f.write(new_user_id)
|
96
|
-
self._user_id = new_user_id
|
97
|
-
else:
|
98
|
-
with open(self.USER_ID_PATH, "r") as f:
|
99
|
-
self._user_id = f.read().strip()
|
100
|
-
|
101
|
-
return self._user_id
|
102
|
-
except Exception as e:
|
103
|
-
self.logger.debug(f"Error getting user ID: {e}")
|
104
|
-
return "anonymous_user"
|
105
|
-
|
106
|
-
def capture(self, event_name: str, properties: Optional[Dict[str, Any]] = None):
|
107
|
-
"""
|
108
|
-
Safe event tracking that never affects library functionality
|
109
|
-
|
110
|
-
Args:
|
111
|
-
event_name: Event name (e.g. 'page_factory_used')
|
112
|
-
properties: Event properties as dictionary
|
113
|
-
"""
|
114
|
-
if not self.enabled or not self._client or self._is_shutdown:
|
115
|
-
return
|
116
|
-
|
117
|
-
try:
|
118
|
-
# Add base properties
|
119
|
-
event_properties = {
|
120
|
-
"library": "notionary",
|
121
|
-
"library_version": self._get_notionary_version(),
|
122
|
-
**(properties or {}),
|
123
|
-
}
|
124
|
-
|
125
|
-
self._client.capture(
|
126
|
-
distinct_id=self.user_id, event=event_name, properties=event_properties
|
127
|
-
)
|
128
|
-
|
129
|
-
except Exception:
|
130
|
-
pass
|
131
|
-
|
132
|
-
def flush(self, timeout: float = 5.0):
|
133
|
-
"""
|
134
|
-
Flush events with timeout
|
135
|
-
|
136
|
-
Args:
|
137
|
-
timeout: Maximum time to wait for flush to complete
|
138
|
-
"""
|
139
|
-
if not self.enabled or not self._client or self._is_shutdown:
|
140
|
-
return
|
141
|
-
|
142
|
-
try:
|
143
|
-
# PostHog flush doesn't support timeout directly, so we do it in a thread
|
144
|
-
flush_thread = threading.Thread(target=self._client.flush)
|
145
|
-
flush_thread.daemon = True
|
146
|
-
flush_thread.start()
|
147
|
-
flush_thread.join(timeout=timeout)
|
148
|
-
|
149
|
-
if flush_thread.is_alive():
|
150
|
-
self.logger.warning(f"Telemetry flush timed out after {timeout}s")
|
151
|
-
else:
|
152
|
-
self.logger.debug("Telemetry events flushed successfully")
|
153
|
-
|
154
|
-
except Exception as e:
|
155
|
-
self.logger.debug(f"Error during telemetry flush: {e}")
|
156
|
-
|
157
|
-
def shutdown(self, timeout: float = 10.0):
|
158
|
-
"""
|
159
|
-
Clean shutdown of telemetry with timeout
|
160
|
-
|
161
|
-
Args:
|
162
|
-
timeout: Maximum time to wait for shutdown
|
163
|
-
"""
|
164
|
-
with self._shutdown_lock:
|
165
|
-
if self._is_shutdown:
|
166
|
-
return
|
167
|
-
|
168
|
-
self._is_shutdown = True
|
169
|
-
|
170
|
-
try:
|
171
|
-
if self._client:
|
172
|
-
# First try to flush remaining events
|
173
|
-
self.logger.debug("Flushing telemetry events before shutdown...")
|
174
|
-
self.flush(timeout=timeout * 0.7) # Use 70% of timeout for flush
|
175
|
-
|
176
|
-
# Then shutdown the client
|
177
|
-
shutdown_thread = threading.Thread(target=self._client.shutdown)
|
178
|
-
shutdown_thread.daemon = True
|
179
|
-
shutdown_thread.start()
|
180
|
-
shutdown_thread.join(timeout=timeout * 0.3) # Use 30% for shutdown
|
181
|
-
|
182
|
-
if shutdown_thread.is_alive():
|
183
|
-
self.logger.warning(f"Telemetry client shutdown timed out after {timeout}s")
|
184
|
-
else:
|
185
|
-
self.logger.debug("Telemetry client shut down successfully")
|
186
|
-
|
187
|
-
except Exception as e:
|
188
|
-
self.logger.debug(f"Error during telemetry shutdown: {e}")
|
189
|
-
finally:
|
190
|
-
self._client = None
|
191
|
-
|
192
|
-
def _initialize_client(self):
|
193
|
-
"""Initializes PostHog client and shows startup message"""
|
194
|
-
try:
|
195
|
-
self._client = Posthog(
|
196
|
-
project_api_key=self.PROJECT_API_KEY,
|
197
|
-
host=self.HOST,
|
198
|
-
disable_geoip=True,
|
199
|
-
)
|
200
|
-
if not self._logged_init_message:
|
201
|
-
self.logger.info(
|
202
|
-
"Anonymous telemetry enabled to improve Notionary. "
|
203
|
-
"To disable: export ANONYMIZED_TELEMETRY=false"
|
204
|
-
)
|
205
|
-
self._logged_init_message = True
|
206
|
-
|
207
|
-
self._track_initialization()
|
208
|
-
|
209
|
-
except Exception as e:
|
210
|
-
self.logger.debug(f"Telemetry initialization failed: {e}")
|
211
|
-
self.enabled = False
|
212
|
-
self._client = None
|
213
|
-
|
214
|
-
def _track_initialization(self):
|
215
|
-
"""Tracks library initialization"""
|
216
|
-
self.capture(
|
217
|
-
"notionary_initialized",
|
218
|
-
{
|
219
|
-
"version": self._get_notionary_version(),
|
220
|
-
},
|
221
|
-
)
|
222
|
-
|
223
|
-
def _get_notionary_version(self) -> str:
|
224
|
-
"""Determines the Notionary version"""
|
225
|
-
import notionary
|
226
|
-
return getattr(notionary, "__version__", "0.2.10")
|
@@ -1,76 +0,0 @@
|
|
1
|
-
from functools import wraps
|
2
|
-
from typing import Any, Callable, Dict, Optional
|
3
|
-
from notionary.telemetry import NotionaryTelemetry
|
4
|
-
|
5
|
-
|
6
|
-
def track_usage(event_name: Optional[str] = None, properties: Optional[Dict[str, Any]] = None):
|
7
|
-
"""
|
8
|
-
Simple decorator to track function usage.
|
9
|
-
|
10
|
-
Args:
|
11
|
-
event_name: Custom event name (defaults to function name)
|
12
|
-
properties: Additional properties to track
|
13
|
-
|
14
|
-
Usage:
|
15
|
-
@track_usage()
|
16
|
-
def my_function():
|
17
|
-
pass
|
18
|
-
|
19
|
-
@track_usage('custom_event_name')
|
20
|
-
def my_function():
|
21
|
-
pass
|
22
|
-
|
23
|
-
@track_usage('custom_event', {'feature': 'advanced'})
|
24
|
-
def my_function():
|
25
|
-
pass
|
26
|
-
"""
|
27
|
-
def decorator(func: Callable) -> Callable:
|
28
|
-
@wraps(func)
|
29
|
-
def wrapper(*args, **kwargs):
|
30
|
-
telemetry = NotionaryTelemetry()
|
31
|
-
|
32
|
-
# Generate event name and properties
|
33
|
-
event = event_name or _generate_event_name(func, args)
|
34
|
-
event_properties = _build_properties(func, args, properties)
|
35
|
-
|
36
|
-
# Track and execute
|
37
|
-
telemetry.capture(event, event_properties)
|
38
|
-
return func(*args, **kwargs)
|
39
|
-
|
40
|
-
return wrapper
|
41
|
-
return decorator
|
42
|
-
|
43
|
-
|
44
|
-
def _get_class_name(func: Callable, args: tuple) -> Optional[str]:
|
45
|
-
"""Extract class name from function or arguments."""
|
46
|
-
if args and hasattr(args[0], '__class__'):
|
47
|
-
return args[0].__class__.__name__
|
48
|
-
|
49
|
-
if hasattr(func, '__qualname__') and '.' in func.__qualname__:
|
50
|
-
return func.__qualname__.split('.')[0]
|
51
|
-
|
52
|
-
return None
|
53
|
-
|
54
|
-
|
55
|
-
def _generate_event_name(func: Callable, args: tuple) -> str:
|
56
|
-
"""Generate event name from function and class info."""
|
57
|
-
class_name = _get_class_name(func, args)
|
58
|
-
|
59
|
-
if class_name:
|
60
|
-
return f"{class_name.lower()}_{func.__name__}_used"
|
61
|
-
|
62
|
-
return f"{func.__name__}_used"
|
63
|
-
|
64
|
-
|
65
|
-
def _build_properties(func: Callable, args: tuple, properties: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
66
|
-
"""Build event properties with function and class info."""
|
67
|
-
event_properties = {
|
68
|
-
'function_name': func.__name__,
|
69
|
-
**(properties or {})
|
70
|
-
}
|
71
|
-
|
72
|
-
class_name = _get_class_name(func, args)
|
73
|
-
if class_name:
|
74
|
-
event_properties['class_name'] = class_name
|
75
|
-
|
76
|
-
return event_properties
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|