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.
Files changed (56) hide show
  1. notionary/__init__.py +6 -0
  2. notionary/database/database_discovery.py +1 -1
  3. notionary/database/notion_database.py +5 -4
  4. notionary/database/notion_database_factory.py +10 -5
  5. notionary/elements/audio_element.py +2 -2
  6. notionary/elements/bookmark_element.py +2 -2
  7. notionary/elements/bulleted_list_element.py +2 -2
  8. notionary/elements/callout_element.py +2 -2
  9. notionary/elements/code_block_element.py +2 -2
  10. notionary/elements/column_element.py +51 -44
  11. notionary/elements/divider_element.py +2 -2
  12. notionary/elements/embed_element.py +2 -2
  13. notionary/elements/heading_element.py +3 -3
  14. notionary/elements/image_element.py +2 -2
  15. notionary/elements/mention_element.py +2 -2
  16. notionary/elements/notion_block_element.py +36 -0
  17. notionary/elements/numbered_list_element.py +2 -2
  18. notionary/elements/paragraph_element.py +2 -2
  19. notionary/elements/qoute_element.py +2 -2
  20. notionary/elements/table_element.py +2 -2
  21. notionary/elements/text_inline_formatter.py +23 -1
  22. notionary/elements/todo_element.py +2 -2
  23. notionary/elements/toggle_element.py +2 -2
  24. notionary/elements/toggleable_heading_element.py +2 -2
  25. notionary/elements/video_element.py +2 -2
  26. notionary/notion_client.py +1 -1
  27. notionary/page/content/notion_page_content_chunker.py +1 -1
  28. notionary/page/content/page_content_retriever.py +1 -1
  29. notionary/page/content/page_content_writer.py +3 -3
  30. notionary/page/{markdown_to_notion_converter.py → formatting/markdown_to_notion_converter.py} +44 -136
  31. notionary/page/formatting/spacer_rules.py +483 -0
  32. notionary/page/metadata/metadata_editor.py +1 -1
  33. notionary/page/metadata/notion_icon_manager.py +1 -1
  34. notionary/page/metadata/notion_page_cover_manager.py +1 -1
  35. notionary/page/notion_page.py +1 -1
  36. notionary/page/notion_page_factory.py +161 -22
  37. notionary/page/properites/database_property_service.py +1 -1
  38. notionary/page/properites/page_property_manager.py +1 -1
  39. notionary/page/properites/property_formatter.py +1 -1
  40. notionary/page/properites/property_value_extractor.py +1 -1
  41. notionary/page/relations/notion_page_relation_manager.py +1 -1
  42. notionary/page/relations/notion_page_title_resolver.py +1 -1
  43. notionary/page/relations/page_database_relation.py +1 -1
  44. notionary/prompting/element_prompt_content.py +1 -0
  45. notionary/telemetry/__init__.py +7 -0
  46. notionary/telemetry/telemetry.py +226 -0
  47. notionary/telemetry/track_usage_decorator.py +76 -0
  48. notionary/util/__init__.py +5 -0
  49. notionary/util/logging_mixin.py +3 -0
  50. notionary/util/singleton.py +18 -0
  51. {notionary-0.2.9.dist-info → notionary-0.2.11.dist-info}/METADATA +2 -1
  52. notionary-0.2.11.dist-info/RECORD +67 -0
  53. {notionary-0.2.9.dist-info → notionary-0.2.11.dist-info}/WHEEL +1 -1
  54. notionary-0.2.9.dist-info/RECORD +0 -61
  55. {notionary-0.2.9.dist-info → notionary-0.2.11.dist-info}/licenses/LICENSE +0 -0
  56. {notionary-0.2.9.dist-info → notionary-0.2.11.dist-info}/top_level.txt +0 -0
@@ -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.logging_mixin import LoggingMixin
10
+ from notionary.util import LoggingMixin
11
11
 
12
12
 
13
13
  class HttpMethod(Enum):
@@ -1,6 +1,6 @@
1
1
  import re
2
2
  from typing import Any, Dict, List
3
- from notionary.util.logging_mixin import LoggingMixin
3
+ from notionary.util import LoggingMixin
4
4
 
5
5
 
6
6
  class NotionPageContentChunker(LoggingMixin):
@@ -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.logging_mixin import LoggingMixin
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.logging_mixin import LoggingMixin
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 + "\n---"
49
+ markdown_text = markdown_text + "---\n"
50
50
 
51
51
  markdown_text = self._process_markdown_whitespace(markdown_text)
52
52
 
@@ -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
- BlockRegistryBuilder,
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
- """Converts Markdown text to Notion API block format with support for pipe syntax for nested structures."""
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
- # Preprocess markdown to add spacers before headings and dividers
35
- processed_markdown = self._add_spacers_before_elements(markdown_text)
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
- # Collect all blocks with their positions in the text
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 _add_spacers_before_elements(self, markdown_text: str) -> str:
53
- """Add spacer markers before every heading (except the first one) and before every divider,
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
- # Skip processing markdown inside code blocks
77
- if in_code_block:
78
- processed_lines.append(line)
79
- if line.strip(): # If not empty
80
- last_non_empty_was_heading = False
81
- last_line_was_spacer = False
82
- i += 1
83
- continue
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
- # Process line with context about consecutive headings
86
- result = self._process_line_for_spacers(
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
- last_line_was_spacer = result["added_spacer"]
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 _is_code_block_marker(self, line: str) -> bool:
111
- """Check if a line is a code block marker (start or end)."""
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
- line: The line to process
127
- processed_lines: List of already processed lines to append to
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
- added_spacer = False
136
- line_stripped = line.strip()
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
- processed_lines.append(line)
79
+ self._spacer_engine.rules.insert(priority, rule)
178
80
 
179
- return {"added_spacer": added_spacer}
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,