notionary 0.2.10__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 -140
- 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.10.dist-info → notionary-0.2.11.dist-info}/METADATA +2 -1
- notionary-0.2.11.dist-info/RECORD +67 -0
- {notionary-0.2.10.dist-info → notionary-0.2.11.dist-info}/WHEEL +1 -1
- notionary-0.2.10.dist-info/RECORD +0 -61
- {notionary-0.2.10.dist-info → notionary-0.2.11.dist-info}/licenses/LICENSE +0 -0
- {notionary-0.2.10.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):
|
@@ -44,7 +44,7 @@ class PageContentWriter(LoggingMixin):
|
|
44
44
|
)
|
45
45
|
append_divider = False
|
46
46
|
|
47
|
-
# Append divider in markdown format as it will be converted to a Notion divider block
|
47
|
+
# Append divider in markdown format as it will be converted to a Notion divider block
|
48
48
|
if append_divider:
|
49
49
|
markdown_text = markdown_text + "---\n"
|
50
50
|
|
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,157 +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
|
-
last_line_was_spacer,
|
90
|
-
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
|
91
63
|
)
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
# Update tracking of consecutive headings and first heading
|
96
|
-
if line.strip(): # Not empty line
|
97
|
-
is_heading = re.match(self.HEADING_PATTERN, line) is not None
|
98
|
-
if is_heading:
|
99
|
-
if not found_first_heading:
|
100
|
-
found_first_heading = True
|
101
|
-
last_non_empty_was_heading = True
|
102
|
-
elif line.strip() != self.SPACER_MARKER: # Not a spacer or heading
|
103
|
-
last_non_empty_was_heading = False
|
104
|
-
|
105
|
-
i += 1
|
64
|
+
processed_lines.extend(result_lines)
|
65
|
+
state["processed_lines"] = processed_lines
|
106
66
|
|
107
67
|
return "\n".join(processed_lines)
|
108
68
|
|
109
|
-
def
|
110
|
-
"""
|
111
|
-
return line.strip().startswith("```")
|
112
|
-
|
113
|
-
def _process_line_for_spacers(
|
114
|
-
self,
|
115
|
-
line: str,
|
116
|
-
processed_lines: List[str],
|
117
|
-
last_line_was_spacer: bool,
|
118
|
-
last_non_empty_was_heading: bool,
|
119
|
-
) -> Dict[str, bool]:
|
120
|
-
"""
|
121
|
-
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
|
122
71
|
|
123
72
|
Args:
|
124
|
-
|
125
|
-
|
126
|
-
found_first_heading: Whether the first heading has been found
|
127
|
-
last_line_was_spacer: Whether the last added line was a spacer
|
128
|
-
last_non_empty_was_heading: Whether the last non-empty line was a heading
|
129
|
-
|
130
|
-
Returns:
|
131
|
-
Dictionary with processing results
|
73
|
+
rule: Die hinzuzufügende Regel
|
74
|
+
priority: Position in der Regelliste (-1 = am Ende)
|
132
75
|
"""
|
133
|
-
|
134
|
-
|
135
|
-
is_empty = not line_stripped
|
136
|
-
|
137
|
-
# Skip empty lines
|
138
|
-
if is_empty:
|
139
|
-
processed_lines.append(line)
|
140
|
-
return {"added_spacer": False}
|
141
|
-
|
142
|
-
# Check if line is a heading
|
143
|
-
if re.match(self.HEADING_PATTERN, line):
|
144
|
-
# Check if there's content before this heading (excluding spacers)
|
145
|
-
has_content_before = any(
|
146
|
-
processed_line.strip() and processed_line.strip() != self.SPACER_MARKER
|
147
|
-
for processed_line in processed_lines
|
148
|
-
)
|
149
|
-
|
150
|
-
if (
|
151
|
-
has_content_before
|
152
|
-
and not last_line_was_spacer
|
153
|
-
and not last_non_empty_was_heading
|
154
|
-
):
|
155
|
-
# Add spacer if:
|
156
|
-
# 1. There's content before this heading
|
157
|
-
# 2. Last line was not already a spacer
|
158
|
-
# 3. Last non-empty line was not a heading
|
159
|
-
processed_lines.append(self.SPACER_MARKER)
|
160
|
-
added_spacer = True
|
161
|
-
|
162
|
-
processed_lines.append(line)
|
163
|
-
|
164
|
-
# Check if line is a divider
|
165
|
-
elif re.match(self.DIVIDER_PATTERN, line):
|
166
|
-
if not last_line_was_spacer:
|
167
|
-
# Only add a single spacer line before dividers (no extra line breaks)
|
168
|
-
processed_lines.append(self.SPACER_MARKER)
|
169
|
-
added_spacer = True
|
170
|
-
|
171
|
-
processed_lines.append(line)
|
172
|
-
|
173
|
-
# Check if this line itself is a spacer
|
174
|
-
elif line_stripped == self.SPACER_MARKER:
|
175
|
-
# Never add consecutive spacers
|
176
|
-
if not last_line_was_spacer:
|
177
|
-
processed_lines.append(line)
|
178
|
-
added_spacer = True
|
179
|
-
|
76
|
+
if priority == -1:
|
77
|
+
self._spacer_engine.rules.append(rule)
|
180
78
|
else:
|
181
|
-
|
79
|
+
self._spacer_engine.rules.insert(priority, rule)
|
182
80
|
|
183
|
-
|
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
|
+
]
|
184
87
|
|
88
|
+
# Alle anderen Methoden bleiben unverändert...
|
185
89
|
def _collect_all_blocks_with_positions(
|
186
90
|
self, markdown_text: str
|
187
91
|
) -> List[Tuple[int, int, Dict[str, Any]]]:
|
@@ -416,7 +320,7 @@ class MarkdownToNotionConverter:
|
|
416
320
|
|
417
321
|
def _is_spacer_line(self, line: str) -> bool:
|
418
322
|
"""Check if a line is a spacer marker."""
|
419
|
-
return line.strip() == self.SPACER_MARKER
|
323
|
+
return line.strip() == self._spacer_engine.SPACER_MARKER
|
420
324
|
|
421
325
|
def _process_todo_line(
|
422
326
|
self,
|