notionary 0.2.10__tar.gz → 0.2.12__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.
Files changed (77) hide show
  1. {notionary-0.2.10 → notionary-0.2.12}/PKG-INFO +3 -1
  2. {notionary-0.2.10 → notionary-0.2.12}/notionary/__init__.py +6 -0
  3. notionary-0.2.12/notionary/cli/main.py +184 -0
  4. notionary-0.2.12/notionary/cli/onboarding.py +0 -0
  5. {notionary-0.2.10 → notionary-0.2.12}/notionary/database/database_discovery.py +1 -1
  6. {notionary-0.2.10 → notionary-0.2.12}/notionary/database/notion_database.py +5 -4
  7. {notionary-0.2.10 → notionary-0.2.12}/notionary/database/notion_database_factory.py +10 -5
  8. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/audio_element.py +2 -2
  9. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/bookmark_element.py +2 -2
  10. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/bulleted_list_element.py +2 -2
  11. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/callout_element.py +2 -2
  12. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/code_block_element.py +2 -2
  13. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/column_element.py +51 -44
  14. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/divider_element.py +2 -2
  15. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/embed_element.py +2 -2
  16. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/heading_element.py +3 -3
  17. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/image_element.py +2 -2
  18. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/mention_element.py +2 -2
  19. notionary-0.2.12/notionary/elements/notion_block_element.py +70 -0
  20. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/numbered_list_element.py +2 -2
  21. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/paragraph_element.py +2 -2
  22. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/qoute_element.py +2 -2
  23. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/table_element.py +2 -2
  24. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/text_inline_formatter.py +23 -1
  25. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/todo_element.py +2 -2
  26. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/toggle_element.py +2 -2
  27. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/toggleable_heading_element.py +2 -2
  28. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/video_element.py +2 -2
  29. {notionary-0.2.10 → notionary-0.2.12}/notionary/notion_client.py +1 -1
  30. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/content/notion_page_content_chunker.py +1 -1
  31. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/content/page_content_retriever.py +1 -1
  32. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/content/page_content_writer.py +3 -3
  33. {notionary-0.2.10/notionary/page → notionary-0.2.12/notionary/page/formatting}/markdown_to_notion_converter.py +44 -140
  34. notionary-0.2.12/notionary/page/formatting/spacer_rules.py +483 -0
  35. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/metadata/metadata_editor.py +1 -1
  36. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/metadata/notion_icon_manager.py +1 -1
  37. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/metadata/notion_page_cover_manager.py +1 -1
  38. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/notion_page.py +1 -1
  39. notionary-0.2.12/notionary/page/notion_page_factory.py +332 -0
  40. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/properites/database_property_service.py +1 -1
  41. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/properites/page_property_manager.py +1 -1
  42. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/properites/property_formatter.py +1 -1
  43. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/properites/property_value_extractor.py +1 -1
  44. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/relations/notion_page_relation_manager.py +1 -1
  45. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/relations/notion_page_title_resolver.py +1 -1
  46. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/relations/page_database_relation.py +1 -1
  47. {notionary-0.2.10 → notionary-0.2.12}/notionary/prompting/element_prompt_content.py +1 -0
  48. notionary-0.2.12/notionary/telemetry/__init__.py +7 -0
  49. notionary-0.2.12/notionary/telemetry/telemetry.py +226 -0
  50. notionary-0.2.12/notionary/telemetry/track_usage_decorator.py +76 -0
  51. notionary-0.2.12/notionary/util/__init__.py +5 -0
  52. {notionary-0.2.10 → notionary-0.2.12}/notionary/util/logging_mixin.py +3 -0
  53. notionary-0.2.12/notionary/util/singleton.py +18 -0
  54. {notionary-0.2.10 → notionary-0.2.12}/notionary.egg-info/PKG-INFO +3 -1
  55. {notionary-0.2.10 → notionary-0.2.12}/notionary.egg-info/SOURCES.txt +10 -1
  56. notionary-0.2.12/notionary.egg-info/entry_points.txt +2 -0
  57. {notionary-0.2.10 → notionary-0.2.12}/notionary.egg-info/requires.txt +2 -0
  58. {notionary-0.2.10 → notionary-0.2.12}/setup.py +13 -2
  59. notionary-0.2.10/notionary/elements/notion_block_element.py +0 -34
  60. notionary-0.2.10/notionary/page/notion_page_factory.py +0 -193
  61. {notionary-0.2.10 → notionary-0.2.12}/LICENSE +0 -0
  62. {notionary-0.2.10 → notionary-0.2.12}/README.md +0 -0
  63. {notionary-0.2.10 → notionary-0.2.12}/notionary/database/models/page_result.py +0 -0
  64. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/registry/block_registry.py +0 -0
  65. {notionary-0.2.10 → notionary-0.2.12}/notionary/elements/registry/block_registry_builder.py +0 -0
  66. {notionary-0.2.10 → notionary-0.2.12}/notionary/exceptions/database_exceptions.py +0 -0
  67. {notionary-0.2.10 → notionary-0.2.12}/notionary/exceptions/page_creation_exception.py +0 -0
  68. {notionary-0.2.10 → notionary-0.2.12}/notionary/models/notion_block_response.py +0 -0
  69. {notionary-0.2.10 → notionary-0.2.12}/notionary/models/notion_database_response.py +0 -0
  70. {notionary-0.2.10 → notionary-0.2.12}/notionary/models/notion_page_response.py +0 -0
  71. {notionary-0.2.10 → notionary-0.2.12}/notionary/page/notion_to_markdown_converter.py +0 -0
  72. {notionary-0.2.10 → notionary-0.2.12}/notionary/prompting/markdown_syntax_prompt_generator.py +0 -0
  73. {notionary-0.2.10 → notionary-0.2.12}/notionary/util/page_id_utils.py +0 -0
  74. {notionary-0.2.10 → notionary-0.2.12}/notionary/util/warn_direct_constructor_usage.py +0 -0
  75. {notionary-0.2.10 → notionary-0.2.12}/notionary.egg-info/dependency_links.txt +0 -0
  76. {notionary-0.2.10 → notionary-0.2.12}/notionary.egg-info/top_level.txt +0 -0
  77. {notionary-0.2.10 → notionary-0.2.12}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: notionary
3
- Version: 0.2.10
3
+ Version: 0.2.12
4
4
  Summary: A toolkit to convert between Markdown and Notion blocks
5
5
  Home-page: https://github.com/mathisarends/notionary
6
6
  Author: Mathis Arends
@@ -13,6 +13,8 @@ License-File: LICENSE
13
13
  Requires-Dist: httpx>=0.28.0
14
14
  Requires-Dist: python-dotenv>=1.1.0
15
15
  Requires-Dist: pydantic>=2.11.4
16
+ Requires-Dist: posthog>=3.0.0
17
+ Requires-Dist: click>=8.0.0
16
18
  Dynamic: author
17
19
  Dynamic: author-email
18
20
  Dynamic: classifier
@@ -1,3 +1,5 @@
1
+ __version__ = "0.2.10"
2
+
1
3
  from .notion_client import NotionClient
2
4
 
3
5
  from .database.notion_database import NotionDatabase
@@ -12,6 +14,10 @@ from .elements.registry.block_registry_builder import (
12
14
  BlockRegistryBuilder,
13
15
  )
14
16
 
17
+ from .telemetry.telemetry import NotionaryTelemetry
18
+
19
+ _telemetry = NotionaryTelemetry()
20
+
15
21
  __all__ = [
16
22
  "NotionClient",
17
23
  "NotionDatabase",
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Notionary CLI - Integration Key Setup
4
+ """
5
+
6
+ import click
7
+ import os
8
+ import platform
9
+ from pathlib import Path
10
+ from dotenv import load_dotenv
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+ from rich.prompt import Prompt, Confirm
14
+
15
+ console = Console()
16
+
17
+ def get_paste_tips():
18
+ """Get platform-specific paste tips"""
19
+ system = platform.system().lower()
20
+
21
+ if system == "darwin": # macOS
22
+ return [
23
+ "• Terminal: [cyan]Cmd+V[/cyan]",
24
+ "• iTerm2: [cyan]Cmd+V[/cyan]",
25
+ ]
26
+ elif system == "windows":
27
+ return [
28
+ "• PowerShell: [cyan]Right-click[/cyan] or [cyan]Shift+Insert[/cyan]",
29
+ "• cmd: [cyan]Right-click[/cyan]",
30
+ ]
31
+ else: # Linux and others
32
+ return [
33
+ "• Terminal: [cyan]Ctrl+Shift+V[/cyan] or [cyan]Right-click[/cyan]",
34
+ "• Some terminals: [cyan]Shift+Insert[/cyan]",
35
+ ]
36
+
37
+ def show_paste_tips():
38
+ """Show platform-specific paste tips"""
39
+ console.print("\n[bold yellow]💡 Paste Tips:[/bold yellow]")
40
+ for tip in get_paste_tips():
41
+ console.print(tip)
42
+ console.print()
43
+
44
+ def get_notion_secret() -> str:
45
+ """Get NOTION_SECRET using the same logic as NotionClient"""
46
+ load_dotenv()
47
+ return os.getenv("NOTION_SECRET", "")
48
+
49
+ @click.group()
50
+ @click.version_option() # Automatische Version aus setup.py
51
+ def main():
52
+ """
53
+ Notionary CLI - Notion API Integration
54
+ """
55
+ pass
56
+
57
+ @main.command()
58
+ def init():
59
+ """
60
+ Setup your Notion Integration Key
61
+ """
62
+ # Check if key already exists
63
+ existing_key = get_notion_secret()
64
+
65
+ if existing_key:
66
+ console.print(Panel.fit(
67
+ "[bold green]✅ You're all set![/bold green]\n"
68
+ f"Your Notion Integration Key is already configured.\n"
69
+ f"Key: [dim]{existing_key[:8]}...[/dim]",
70
+ title="Already Configured"
71
+ ))
72
+
73
+ # Option to reconfigure
74
+ if Confirm.ask("\n[yellow]Would you like to update your key?[/yellow]"):
75
+ setup_new_key()
76
+ else:
77
+ console.print("\n[blue]No changes made. Happy coding! 🚀[/blue]")
78
+ else:
79
+ # No key found, start setup
80
+ console.print(Panel.fit(
81
+ "[bold green]🚀 Notionary Setup[/bold green]\n"
82
+ "Enter your Notion Integration Key to get started...\n\n"
83
+ "[bold blue]🔗 Create an Integration Key or get an existing one:[/bold blue]\n"
84
+ "[cyan]https://www.notion.so/profile/integrations[/cyan]",
85
+ title="Initialization"
86
+ ))
87
+ setup_new_key()
88
+
89
+ def setup_new_key():
90
+ """Handle the key setup process"""
91
+ try:
92
+ # Show Integration Key creation link
93
+ console.print("\n[bold blue]🔗 Create an Integration Key:[/bold blue]")
94
+ console.print("[cyan]https://www.notion.so/profile/integrations[/cyan]")
95
+ console.print()
96
+
97
+ # Get integration key
98
+ integration_key = Prompt.ask(
99
+ "[bold cyan]Notion Integration Key[/bold cyan]"
100
+ )
101
+
102
+ # Input validation
103
+ if not integration_key or not integration_key.strip():
104
+ console.print("[bold red]❌ Integration Key cannot be empty![/bold red]")
105
+ return
106
+
107
+ # Trim whitespace
108
+ integration_key = integration_key.strip()
109
+
110
+ # Check for common paste issues
111
+ if integration_key in ["^V", "^v", "^C", "^c"]:
112
+ console.print("[bold red]❌ Paste didn't work! Try:[/bold red]")
113
+ show_paste_tips()
114
+ return
115
+
116
+ # Show masked feedback that paste worked
117
+ masked_key = "•" * len(integration_key)
118
+ console.print(f"[dim]Received: {masked_key} ({len(integration_key)} characters)[/dim]")
119
+
120
+ # Basic validation for Notion keys
121
+ if not integration_key.startswith('ntn_') or len(integration_key) < 30:
122
+ console.print("[bold yellow]⚠️ Warning: This doesn't look like a valid Notion Integration Key[/bold yellow]")
123
+ console.print("[dim]Notion keys usually start with 'ntn_' and are about 50+ characters long[/dim]")
124
+ if not Confirm.ask("Continue anyway?"):
125
+ return
126
+
127
+ # Save the key
128
+ if save_integration_key(integration_key):
129
+ return # Success!
130
+
131
+ except KeyboardInterrupt:
132
+ console.print("\n[yellow]Setup cancelled.[/yellow]")
133
+ except Exception as e:
134
+ console.print(f"\n[bold red]❌ Error during setup: {e}[/bold red]")
135
+ raise click.Abort()
136
+
137
+ def save_integration_key(integration_key: str) -> bool:
138
+ """Save the integration key to .env file"""
139
+ try:
140
+ # .env Datei im aktuellen Verzeichnis erstellen/aktualisieren
141
+ env_file = Path.cwd() / ".env"
142
+
143
+ # Bestehende .env lesen falls vorhanden
144
+ existing_lines = []
145
+ if env_file.exists():
146
+ with open(env_file, 'r', encoding='utf-8') as f:
147
+ existing_lines = [line.rstrip() for line in f.readlines()]
148
+
149
+ # NOTION_SECRET Zeile hinzufügen/ersetzen
150
+ updated_lines = []
151
+ notion_secret_found = False
152
+
153
+ for line in existing_lines:
154
+ if line.startswith('NOTION_SECRET='):
155
+ updated_lines.append(f'NOTION_SECRET={integration_key}')
156
+ notion_secret_found = True
157
+ else:
158
+ updated_lines.append(line)
159
+
160
+ # Falls NOTION_SECRET noch nicht existiert, hinzufügen
161
+ if not notion_secret_found:
162
+ updated_lines.append(f'NOTION_SECRET={integration_key}')
163
+
164
+ # .env Datei schreiben
165
+ with open(env_file, 'w', encoding='utf-8') as f:
166
+ f.write('\n'.join(updated_lines) + '\n')
167
+
168
+ # Verification
169
+ written_key = get_notion_secret()
170
+ if written_key == integration_key:
171
+ console.print("\n[bold green]✅ Integration Key saved and verified![/bold green]")
172
+ console.print(f"[dim]Configuration: {env_file}[/dim]")
173
+ console.print("\n[blue]Ready to use notionary in your Python code! 🚀[/blue]")
174
+ return True
175
+ else:
176
+ console.print("\n[bold red]❌ Error: Key verification failed![/bold red]")
177
+ return False
178
+
179
+ except Exception as e:
180
+ console.print(f"\n[bold red]❌ Error saving key: {e}[/bold red]")
181
+ return False
182
+
183
+ if __name__ == '__main__':
184
+ main()
File without changes
@@ -7,7 +7,7 @@ from typing import (
7
7
  Tuple,
8
8
  )
9
9
  from notionary.notion_client import NotionClient
10
- from notionary.util.logging_mixin import LoggingMixin
10
+ from notionary.util import LoggingMixin
11
11
 
12
12
 
13
13
  class DatabaseDiscovery(LoggingMixin):
@@ -1,11 +1,11 @@
1
1
  from __future__ import annotations
2
- import json
3
2
  from typing import Any, AsyncGenerator, Dict, List, Optional
4
3
 
5
4
  from notionary.notion_client import NotionClient
6
5
  from notionary.page.notion_page import NotionPage
6
+ from notionary.telemetry import NotionaryTelemetry
7
7
  from notionary.util.warn_direct_constructor_usage import warn_direct_constructor_usage
8
- from notionary.util.logging_mixin import LoggingMixin
8
+ from notionary.util import LoggingMixin
9
9
  from notionary.util.page_id_utils import format_uuid
10
10
 
11
11
 
@@ -26,10 +26,11 @@ class NotionDatabase(LoggingMixin):
26
26
  token: Optional Notion API token
27
27
  """
28
28
  self.database_id = database_id
29
+ self._telemetry = NotionaryTelemetry()
29
30
  self._client = NotionClient(token=token)
30
31
 
31
32
  @classmethod
32
- async def from_database_id(
33
+ def from_database_id(
33
34
  cls, database_id: str, token: Optional[str] = None
34
35
  ) -> NotionDatabase:
35
36
  """
@@ -38,7 +39,7 @@ class NotionDatabase(LoggingMixin):
38
39
  """
39
40
  from notionary.database.notion_database_factory import NotionDatabaseFactory
40
41
 
41
- return await NotionDatabaseFactory.from_database_id(database_id, token)
42
+ return NotionDatabaseFactory.from_database_id(database_id, token)
42
43
 
43
44
  @classmethod
44
45
  async def from_database_name(
@@ -1,4 +1,3 @@
1
- import logging
2
1
  from typing import List, Optional, Dict, Any
3
2
  from difflib import SequenceMatcher
4
3
 
@@ -11,10 +10,13 @@ from notionary.exceptions.database_exceptions import (
11
10
  DatabaseParsingError,
12
11
  NotionDatabaseException,
13
12
  )
14
- from notionary.util.logging_mixin import LoggingMixin
15
- from notionary.util.page_id_utils import format_uuid
13
+ from notionary.telemetry import track_usage
14
+ from notionary.util import LoggingMixin
15
+ from notionary.util import format_uuid
16
+ from notionary.util import singleton
16
17
 
17
18
 
19
+ @singleton
18
20
  class NotionDatabaseFactory(LoggingMixin):
19
21
  """
20
22
  Factory class for creating NotionDatabaseManager instances.
@@ -22,7 +24,8 @@ class NotionDatabaseFactory(LoggingMixin):
22
24
  """
23
25
 
24
26
  @classmethod
25
- async def from_database_id(
27
+ @track_usage('page_factory_method_used', {'method': 'from_page_id'})
28
+ def from_database_id(
26
29
  cls, database_id: str, token: Optional[str] = None
27
30
  ) -> NotionDatabase:
28
31
  """
@@ -35,6 +38,7 @@ class NotionDatabaseFactory(LoggingMixin):
35
38
  Returns:
36
39
  An initialized NotionDatabaseManager instance
37
40
  """
41
+
38
42
  try:
39
43
  formatted_id = format_uuid(database_id) or database_id
40
44
 
@@ -55,6 +59,7 @@ class NotionDatabaseFactory(LoggingMixin):
55
59
  raise DatabaseConnectionError(error_msg) from e
56
60
 
57
61
  @classmethod
62
+ @track_usage('page_factory_method_used', {'method': 'from_url'})
58
63
  async def from_database_name(
59
64
  cls, database_name: str, token: Optional[str] = None
60
65
  ) -> NotionDatabase:
@@ -72,7 +77,7 @@ class NotionDatabaseFactory(LoggingMixin):
72
77
  cls.logger.debug("Searching for database with name: %s", database_name)
73
78
 
74
79
  client = NotionClient(token=token)
75
-
80
+
76
81
  try:
77
82
  cls.logger.debug("Using search endpoint to find databases")
78
83
 
@@ -1,12 +1,12 @@
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, auto_track_conversions
4
4
  from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
8
8
 
9
-
9
+ @auto_track_conversions
10
10
  class AudioElement(NotionBlockElement):
11
11
  """
12
12
  Handles conversion between Markdown audio embeds and Notion audio blocks.
@@ -1,13 +1,13 @@
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, auto_track_conversions
5
5
  from notionary.prompting.element_prompt_content import (
6
6
  ElementPromptBuilder,
7
7
  ElementPromptContent,
8
8
  )
9
9
 
10
-
10
+ @auto_track_conversions
11
11
  class BookmarkElement(NotionBlockElement):
12
12
  """
13
13
  Handles conversion between Markdown bookmarks and Notion bookmark blocks.
@@ -1,13 +1,13 @@
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, auto_track_conversions
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
-
10
+ @auto_track_conversions
11
11
  class BulletedListElement(NotionBlockElement):
12
12
  """Class for converting between Markdown bullet lists and Notion bulleted list items."""
13
13
 
@@ -6,9 +6,9 @@ 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
10
-
9
+ from notionary.elements.notion_block_element import NotionBlockElement, auto_track_conversions
11
10
 
11
+ @auto_track_conversions
12
12
  class CalloutElement(NotionBlockElement):
13
13
  """
14
14
  Handles conversion between Markdown callouts and Notion callout blocks.
@@ -1,12 +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, auto_track_conversions
4
4
  from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
8
8
 
9
-
9
+ @auto_track_conversions
10
10
  class CodeBlockElement(NotionBlockElement):
11
11
  """
12
12
  Handles conversion between Markdown code blocks and Notion code blocks.
@@ -1,10 +1,14 @@
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
5
- from notionary.prompting.element_prompt_content import ElementPromptBuilder, ElementPromptContent
6
-
7
-
4
+ from notionary.elements.notion_block_element import NotionBlockElement, auto_track_conversions
5
+ from notionary.page.formatting.spacer_rules import SPACER_MARKER
6
+ from notionary.prompting.element_prompt_content import (
7
+ ElementPromptBuilder,
8
+ ElementPromptContent,
9
+ )
10
+
11
+ @auto_track_conversions
8
12
  class ColumnElement(NotionBlockElement):
9
13
  """
10
14
  Handles conversion between custom Markdown column syntax and Notion column blocks.
@@ -252,7 +256,7 @@ class ColumnElement(NotionBlockElement):
252
256
  """
253
257
  if not (in_column and column_content):
254
258
  return
255
-
259
+
256
260
  processed_content = ColumnElement._preprocess_column_content(column_content)
257
261
 
258
262
  column_blocks = converter_callback("\n".join(processed_content))
@@ -265,43 +269,45 @@ class ColumnElement(NotionBlockElement):
265
269
  def is_multiline(cls) -> bool:
266
270
  """Column blocks span multiple lines."""
267
271
  return True
268
-
272
+
269
273
  @staticmethod
270
274
  def _preprocess_column_content(lines: List[str]) -> List[str]:
271
275
  """
272
276
  Preprocess column content to handle special cases like first headings.
273
-
277
+
274
278
  This removes any spacer markers that might have been added before the first
275
279
  heading in a column, as each column should have its own heading context.
276
-
280
+
277
281
  Args:
278
282
  lines: The lines of content for the column
279
-
283
+
280
284
  Returns:
281
285
  Processed lines ready for conversion
282
286
  """
283
- from notionary.page.markdown_to_notion_converter import MarkdownToNotionConverter
284
-
285
287
  processed_lines = []
286
288
  found_first_heading = False
287
- spacer_marker = MarkdownToNotionConverter.SPACER_MARKER
288
-
289
+
289
290
  i = 0
290
291
  while i < len(lines):
291
292
  line = lines[i]
292
-
293
+
293
294
  # Check if this is a heading line
294
295
  if re.match(r"^(#{1,6})\s+(.+)$", line.strip()):
295
296
  # If it's the first heading, look ahead to check for spacer
296
- if not found_first_heading and i > 0 and processed_lines and processed_lines[-1] == spacer_marker:
297
+ if (
298
+ not found_first_heading
299
+ and i > 0
300
+ and processed_lines
301
+ and processed_lines[-1] == SPACER_MARKER
302
+ ):
297
303
  # Remove spacer before first heading in column
298
304
  processed_lines.pop()
299
-
305
+
300
306
  found_first_heading = True
301
-
307
+
302
308
  processed_lines.append(line)
303
309
  i += 1
304
-
310
+
305
311
  return processed_lines
306
312
 
307
313
  @classmethod
@@ -331,30 +337,31 @@ class ColumnElement(NotionBlockElement):
331
337
  ":::\n"
332
338
  ":::"
333
339
  )
334
- .with_examples([
335
- "::: columns\n"
336
- "::: column\n"
337
- "## Features\n"
338
- "- Fast response time\n"
339
- "- Intuitive interface\n"
340
- "- Regular updates\n"
341
- ":::\n"
342
- "::: column\n"
343
- "## Benefits\n"
344
- "- Increased productivity\n"
345
- "- Better collaboration\n"
346
- "- Simplified workflows\n"
347
- ":::\n"
348
- ":::",
349
-
350
- "::: columns\n"
351
- "::: column\n"
352
- "![Image placeholder](/api/placeholder/400/320)\n"
353
- ":::\n"
354
- "::: column\n"
355
- "This text appears next to the image, creating a media-with-caption style layout.\n"
356
- ":::\n"
357
- ":::"
358
- ])
340
+ .with_examples(
341
+ [
342
+ "::: columns\n"
343
+ "::: column\n"
344
+ "## Features\n"
345
+ "- Fast response time\n"
346
+ "- Intuitive interface\n"
347
+ "- Regular updates\n"
348
+ ":::\n"
349
+ "::: column\n"
350
+ "## Benefits\n"
351
+ "- Increased productivity\n"
352
+ "- Better collaboration\n"
353
+ "- Simplified workflows\n"
354
+ ":::\n"
355
+ ":::",
356
+ "::: columns\n"
357
+ "::: column\n"
358
+ "![Image placeholder](/api/placeholder/400/320)\n"
359
+ ":::\n"
360
+ "::: column\n"
361
+ "This text appears next to the image, creating a media-with-caption style layout.\n"
362
+ ":::\n"
363
+ ":::",
364
+ ]
365
+ )
359
366
  .build()
360
- )
367
+ )
@@ -1,13 +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, auto_track_conversions
5
5
  from notionary.prompting.element_prompt_content import (
6
6
  ElementPromptBuilder,
7
7
  ElementPromptContent,
8
8
  )
9
9
 
10
-
10
+ @auto_track_conversions
11
11
  class DividerElement(NotionBlockElement):
12
12
  """
13
13
  Handles conversion between Markdown horizontal dividers and Notion divider blocks.
@@ -1,12 +1,12 @@
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, auto_track_conversions
4
4
  from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
8
8
 
9
-
9
+ @auto_track_conversions
10
10
  class EmbedElement(NotionBlockElement):
11
11
  """
12
12
  Handles conversion between Markdown embeds and Notion embed blocks.
@@ -1,14 +1,14 @@
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, auto_track_conversions
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
-
11
+ @auto_track_conversions
12
12
  class HeadingElement(NotionBlockElement):
13
13
  """Handles conversion between Markdown headings and Notion heading blocks."""
14
14
 
@@ -91,4 +91,4 @@ class HeadingElement(NotionBlockElement):
91
91
  .with_syntax("## Your Heading Text")
92
92
  .with_standard_markdown()
93
93
  .build()
94
- )
94
+ )
@@ -1,12 +1,12 @@
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, auto_track_conversions
4
4
  from notionary.prompting.element_prompt_content import (
5
5
  ElementPromptBuilder,
6
6
  ElementPromptContent,
7
7
  )
8
8
 
9
-
9
+ @auto_track_conversions
10
10
  class ImageElement(NotionBlockElement):
11
11
  """
12
12
  Handles conversion between Markdown images and Notion image blocks.
@@ -2,13 +2,13 @@ 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, auto_track_conversions
6
6
  from notionary.prompting.element_prompt_content import (
7
7
  ElementPromptBuilder,
8
8
  ElementPromptContent,
9
9
  )
10
10
 
11
-
11
+ @auto_track_conversions
12
12
  class MentionElement(NotionBlockElement):
13
13
  """
14
14
  Handles conversion between Markdown mentions and Notion mention elements.