notionary 0.2.9__py3-none-any.whl → 0.2.11__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 +6 -0
- notionary/database/database_discovery.py +1 -1
- notionary/database/notion_database.py +5 -4
- notionary/database/notion_database_factory.py +10 -5
- notionary/elements/audio_element.py +2 -2
- notionary/elements/bookmark_element.py +2 -2
- notionary/elements/bulleted_list_element.py +2 -2
- notionary/elements/callout_element.py +2 -2
- notionary/elements/code_block_element.py +2 -2
- notionary/elements/column_element.py +51 -44
- notionary/elements/divider_element.py +2 -2
- notionary/elements/embed_element.py +2 -2
- notionary/elements/heading_element.py +3 -3
- notionary/elements/image_element.py +2 -2
- notionary/elements/mention_element.py +2 -2
- notionary/elements/notion_block_element.py +36 -0
- notionary/elements/numbered_list_element.py +2 -2
- notionary/elements/paragraph_element.py +2 -2
- notionary/elements/qoute_element.py +2 -2
- notionary/elements/table_element.py +2 -2
- notionary/elements/text_inline_formatter.py +23 -1
- notionary/elements/todo_element.py +2 -2
- notionary/elements/toggle_element.py +2 -2
- notionary/elements/toggleable_heading_element.py +2 -2
- notionary/elements/video_element.py +2 -2
- notionary/notion_client.py +1 -1
- notionary/page/content/notion_page_content_chunker.py +1 -1
- notionary/page/content/page_content_retriever.py +1 -1
- notionary/page/content/page_content_writer.py +3 -3
- notionary/page/{markdown_to_notion_converter.py → formatting/markdown_to_notion_converter.py} +44 -136
- notionary/page/formatting/spacer_rules.py +483 -0
- notionary/page/metadata/metadata_editor.py +1 -1
- notionary/page/metadata/notion_icon_manager.py +1 -1
- notionary/page/metadata/notion_page_cover_manager.py +1 -1
- notionary/page/notion_page.py +1 -1
- notionary/page/notion_page_factory.py +161 -22
- notionary/page/properites/database_property_service.py +1 -1
- notionary/page/properites/page_property_manager.py +1 -1
- notionary/page/properites/property_formatter.py +1 -1
- notionary/page/properites/property_value_extractor.py +1 -1
- notionary/page/relations/notion_page_relation_manager.py +1 -1
- notionary/page/relations/notion_page_title_resolver.py +1 -1
- notionary/page/relations/page_database_relation.py +1 -1
- notionary/prompting/element_prompt_content.py +1 -0
- notionary/telemetry/__init__.py +7 -0
- notionary/telemetry/telemetry.py +226 -0
- notionary/telemetry/track_usage_decorator.py +76 -0
- notionary/util/__init__.py +5 -0
- notionary/util/logging_mixin.py +3 -0
- notionary/util/singleton.py +18 -0
- {notionary-0.2.9.dist-info → notionary-0.2.11.dist-info}/METADATA +2 -1
- notionary-0.2.11.dist-info/RECORD +67 -0
- {notionary-0.2.9.dist-info → notionary-0.2.11.dist-info}/WHEEL +1 -1
- notionary-0.2.9.dist-info/RECORD +0 -61
- {notionary-0.2.9.dist-info → notionary-0.2.11.dist-info}/licenses/LICENSE +0 -0
- {notionary-0.2.9.dist-info → notionary-0.2.11.dist-info}/top_level.txt +0 -0
notionary/notion_client.py
CHANGED
@@ -7,7 +7,7 @@ import httpx
|
|
7
7
|
from dotenv import load_dotenv
|
8
8
|
from notionary.models.notion_database_response import NotionDatabaseResponse
|
9
9
|
from notionary.models.notion_page_response import NotionPageResponse
|
10
|
-
from notionary.util
|
10
|
+
from notionary.util import LoggingMixin
|
11
11
|
|
12
12
|
|
13
13
|
class HttpMethod(Enum):
|
@@ -6,7 +6,7 @@ from notionary.notion_client import NotionClient
|
|
6
6
|
from notionary.page.notion_to_markdown_converter import (
|
7
7
|
NotionToMarkdownConverter,
|
8
8
|
)
|
9
|
-
from notionary.util
|
9
|
+
from notionary.util import LoggingMixin
|
10
10
|
|
11
11
|
|
12
12
|
class PageContentRetriever(LoggingMixin):
|
@@ -4,7 +4,7 @@ from notionary.elements.divider_element import DividerElement
|
|
4
4
|
from notionary.elements.registry.block_registry import BlockRegistry
|
5
5
|
from notionary.notion_client import NotionClient
|
6
6
|
|
7
|
-
from notionary.page.markdown_to_notion_converter import (
|
7
|
+
from notionary.page.formatting.markdown_to_notion_converter import (
|
8
8
|
MarkdownToNotionConverter,
|
9
9
|
)
|
10
10
|
from notionary.page.notion_to_markdown_converter import (
|
@@ -13,7 +13,7 @@ from notionary.page.notion_to_markdown_converter import (
|
|
13
13
|
from notionary.page.content.notion_page_content_chunker import (
|
14
14
|
NotionPageContentChunker,
|
15
15
|
)
|
16
|
-
from notionary.util
|
16
|
+
from notionary.util import LoggingMixin
|
17
17
|
|
18
18
|
|
19
19
|
class PageContentWriter(LoggingMixin):
|
@@ -46,7 +46,7 @@ class PageContentWriter(LoggingMixin):
|
|
46
46
|
|
47
47
|
# Append divider in markdown format as it will be converted to a Notion divider block
|
48
48
|
if append_divider:
|
49
|
-
markdown_text = markdown_text + "
|
49
|
+
markdown_text = markdown_text + "---\n"
|
50
50
|
|
51
51
|
markdown_text = self._process_markdown_whitespace(markdown_text)
|
52
52
|
|
notionary/page/{markdown_to_notion_converter.py → formatting/markdown_to_notion_converter.py}
RENAMED
@@ -1,21 +1,14 @@
|
|
1
|
-
from typing import Dict, Any, List, Optional, Tuple
|
2
1
|
import re
|
2
|
+
from typing import Dict, Any, List, Optional, Tuple
|
3
3
|
|
4
4
|
from notionary.elements.column_element import ColumnElement
|
5
5
|
from notionary.elements.registry.block_registry import BlockRegistry
|
6
|
-
from notionary.elements.registry.block_registry_builder import
|
7
|
-
|
8
|
-
)
|
6
|
+
from notionary.elements.registry.block_registry_builder import BlockRegistryBuilder
|
7
|
+
from notionary.page.formatting.spacer_rules import SpacerRule, SpacerRuleEngine
|
9
8
|
|
10
9
|
|
11
10
|
class MarkdownToNotionConverter:
|
12
|
-
"""
|
13
|
-
|
14
|
-
SPACER_MARKER = "---spacer---"
|
15
|
-
TOGGLE_ELEMENT_TYPES = ["ToggleElement", "ToggleableHeadingElement"]
|
16
|
-
PIPE_CONTENT_PATTERN = r"^\|\s?(.*)$"
|
17
|
-
HEADING_PATTERN = r"^(#{1,6})\s+(.+)$"
|
18
|
-
DIVIDER_PATTERN = r"^-{3,}$"
|
11
|
+
"""Refactored converter mit expliziten Spacer-Regeln"""
|
19
12
|
|
20
13
|
def __init__(self, block_registry: Optional[BlockRegistry] = None):
|
21
14
|
"""Initialize the converter with an optional custom block registry."""
|
@@ -23,6 +16,13 @@ class MarkdownToNotionConverter:
|
|
23
16
|
block_registry or BlockRegistryBuilder().create_full_registry()
|
24
17
|
)
|
25
18
|
|
19
|
+
# Spacer-Engine mit konfigurierbaren Regeln
|
20
|
+
self._spacer_engine = SpacerRuleEngine()
|
21
|
+
|
22
|
+
# Pattern für andere Verarbeitungsschritte
|
23
|
+
self.TOGGLE_ELEMENT_TYPES = ["ToggleElement", "ToggleableHeadingElement"]
|
24
|
+
self.PIPE_CONTENT_PATTERN = r"^\|\s?(.*)$"
|
25
|
+
|
26
26
|
if self._block_registry.contains(ColumnElement):
|
27
27
|
ColumnElement.set_converter_callback(self.convert)
|
28
28
|
|
@@ -31,153 +31,61 @@ class MarkdownToNotionConverter:
|
|
31
31
|
if not markdown_text:
|
32
32
|
return []
|
33
33
|
|
34
|
-
#
|
35
|
-
processed_markdown = self.
|
36
|
-
print("Processed Markdown:", processed_markdown)
|
34
|
+
# Spacer-Verarbeitung mit expliziten Regeln
|
35
|
+
processed_markdown = self._add_spacers_with_rules(markdown_text)
|
37
36
|
|
38
|
-
#
|
37
|
+
# Rest der Pipeline bleibt gleich
|
39
38
|
all_blocks_with_positions = self._collect_all_blocks_with_positions(
|
40
39
|
processed_markdown
|
41
40
|
)
|
42
|
-
|
43
|
-
# Sort all blocks by their position in the text
|
44
41
|
all_blocks_with_positions.sort(key=lambda x: x[0])
|
45
|
-
|
46
|
-
# Extract just the blocks without position information
|
47
42
|
blocks = [block for _, _, block in all_blocks_with_positions]
|
48
43
|
|
49
|
-
# Process spacing between blocks
|
50
44
|
return self._process_block_spacing(blocks)
|
51
45
|
|
52
|
-
def
|
53
|
-
"""
|
54
|
-
but ignore content inside code blocks and consecutive headings."""
|
46
|
+
def _add_spacers_with_rules(self, markdown_text: str) -> str:
|
47
|
+
"""Fügt Spacer mit expliziten Regeln hinzu"""
|
55
48
|
lines = markdown_text.split("\n")
|
56
49
|
processed_lines = []
|
57
|
-
found_first_heading = False
|
58
|
-
in_code_block = False
|
59
|
-
last_line_was_spacer = False
|
60
|
-
last_non_empty_was_heading = False
|
61
|
-
|
62
|
-
i = 0
|
63
|
-
while i < len(lines):
|
64
|
-
line = lines[i]
|
65
|
-
|
66
|
-
# Check for code block boundaries and handle accordingly
|
67
|
-
if self._is_code_block_marker(line):
|
68
|
-
in_code_block = not in_code_block
|
69
|
-
processed_lines.append(line)
|
70
|
-
if line.strip(): # If not empty
|
71
|
-
last_non_empty_was_heading = False
|
72
|
-
last_line_was_spacer = False
|
73
|
-
i += 1
|
74
|
-
continue
|
75
50
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
51
|
+
# Initialer State
|
52
|
+
state = {
|
53
|
+
"in_code_block": False,
|
54
|
+
"last_line_was_spacer": False,
|
55
|
+
"last_non_empty_was_heading": False,
|
56
|
+
"has_content_before": False,
|
57
|
+
"processed_lines": processed_lines,
|
58
|
+
}
|
84
59
|
|
85
|
-
|
86
|
-
|
87
|
-
line,
|
88
|
-
processed_lines,
|
89
|
-
found_first_heading,
|
90
|
-
last_line_was_spacer,
|
91
|
-
last_non_empty_was_heading,
|
60
|
+
for line_number, line in enumerate(lines):
|
61
|
+
result_lines, state = self._spacer_engine.process_line(
|
62
|
+
line, line_number, state
|
92
63
|
)
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
# Update tracking of consecutive headings and first heading
|
97
|
-
if line.strip(): # Not empty line
|
98
|
-
is_heading = re.match(self.HEADING_PATTERN, line) is not None
|
99
|
-
if is_heading:
|
100
|
-
if not found_first_heading:
|
101
|
-
found_first_heading = True
|
102
|
-
last_non_empty_was_heading = True
|
103
|
-
elif line.strip() != self.SPACER_MARKER: # Not a spacer or heading
|
104
|
-
last_non_empty_was_heading = False
|
105
|
-
|
106
|
-
i += 1
|
64
|
+
processed_lines.extend(result_lines)
|
65
|
+
state["processed_lines"] = processed_lines
|
107
66
|
|
108
67
|
return "\n".join(processed_lines)
|
109
68
|
|
110
|
-
def
|
111
|
-
"""
|
112
|
-
return line.strip().startswith("```")
|
113
|
-
|
114
|
-
def _process_line_for_spacers(
|
115
|
-
self,
|
116
|
-
line: str,
|
117
|
-
processed_lines: List[str],
|
118
|
-
found_first_heading: bool,
|
119
|
-
last_line_was_spacer: bool,
|
120
|
-
last_non_empty_was_heading: bool,
|
121
|
-
) -> Dict[str, bool]:
|
122
|
-
"""
|
123
|
-
Process a single line to add spacers before headings and dividers if needed.
|
69
|
+
def add_custom_spacer_rule(self, rule: SpacerRule, priority: int = -1):
|
70
|
+
"""Fügt eine benutzerdefinierte Spacer-Regel hinzu
|
124
71
|
|
125
72
|
Args:
|
126
|
-
|
127
|
-
|
128
|
-
found_first_heading: Whether the first heading has been found
|
129
|
-
last_line_was_spacer: Whether the last added line was a spacer
|
130
|
-
last_non_empty_was_heading: Whether the last non-empty line was a heading
|
131
|
-
|
132
|
-
Returns:
|
133
|
-
Dictionary with processing results
|
73
|
+
rule: Die hinzuzufügende Regel
|
74
|
+
priority: Position in der Regelliste (-1 = am Ende)
|
134
75
|
"""
|
135
|
-
|
136
|
-
|
137
|
-
is_empty = not line_stripped
|
138
|
-
|
139
|
-
# Skip empty lines
|
140
|
-
if is_empty:
|
141
|
-
processed_lines.append(line)
|
142
|
-
return {"added_spacer": False}
|
143
|
-
|
144
|
-
# Check if line is a heading
|
145
|
-
if re.match(self.HEADING_PATTERN, line):
|
146
|
-
if (
|
147
|
-
found_first_heading
|
148
|
-
and not last_line_was_spacer
|
149
|
-
and not last_non_empty_was_heading
|
150
|
-
):
|
151
|
-
# Add spacer only if:
|
152
|
-
# 1. Not the first heading
|
153
|
-
# 2. Last non-empty line was not a heading
|
154
|
-
# 3. Last line was not already a spacer
|
155
|
-
processed_lines.append(self.SPACER_MARKER)
|
156
|
-
added_spacer = True
|
157
|
-
|
158
|
-
processed_lines.append(line)
|
159
|
-
|
160
|
-
# Check if line is a divider
|
161
|
-
elif re.match(self.DIVIDER_PATTERN, line):
|
162
|
-
if not last_line_was_spacer:
|
163
|
-
# Only add a single spacer line before dividers (no extra line breaks)
|
164
|
-
processed_lines.append(self.SPACER_MARKER)
|
165
|
-
added_spacer = True
|
166
|
-
|
167
|
-
processed_lines.append(line)
|
168
|
-
|
169
|
-
# Check if this line itself is a spacer
|
170
|
-
elif line_stripped == self.SPACER_MARKER:
|
171
|
-
# Never add consecutive spacers
|
172
|
-
if not last_line_was_spacer:
|
173
|
-
processed_lines.append(line)
|
174
|
-
added_spacer = True
|
175
|
-
|
76
|
+
if priority == -1:
|
77
|
+
self._spacer_engine.rules.append(rule)
|
176
78
|
else:
|
177
|
-
|
79
|
+
self._spacer_engine.rules.insert(priority, rule)
|
178
80
|
|
179
|
-
|
81
|
+
def get_spacer_rules_info(self) -> List[Dict[str, str]]:
|
82
|
+
"""Gibt Informationen über alle aktiven Spacer-Regeln zurück"""
|
83
|
+
return [
|
84
|
+
{"name": rule.name, "description": rule.description}
|
85
|
+
for rule in self._spacer_engine.rules
|
86
|
+
]
|
180
87
|
|
88
|
+
# Alle anderen Methoden bleiben unverändert...
|
181
89
|
def _collect_all_blocks_with_positions(
|
182
90
|
self, markdown_text: str
|
183
91
|
) -> List[Tuple[int, int, Dict[str, Any]]]:
|
@@ -412,7 +320,7 @@ class MarkdownToNotionConverter:
|
|
412
320
|
|
413
321
|
def _is_spacer_line(self, line: str) -> bool:
|
414
322
|
"""Check if a line is a spacer marker."""
|
415
|
-
return line.strip() == self.SPACER_MARKER
|
323
|
+
return line.strip() == self._spacer_engine.SPACER_MARKER
|
416
324
|
|
417
325
|
def _process_todo_line(
|
418
326
|
self,
|