notionary 0.2.16__py3-none-any.whl → 0.2.18__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 (137) hide show
  1. notionary/__init__.py +10 -5
  2. notionary/base_notion_client.py +18 -7
  3. notionary/blocks/__init__.py +55 -24
  4. notionary/blocks/audio/__init__.py +7 -0
  5. notionary/blocks/audio/audio_element.py +152 -0
  6. notionary/blocks/audio/audio_markdown_node.py +29 -0
  7. notionary/blocks/audio/audio_models.py +59 -0
  8. notionary/blocks/bookmark/__init__.py +7 -0
  9. notionary/blocks/{bookmark_element.py → bookmark/bookmark_element.py} +20 -65
  10. notionary/blocks/bookmark/bookmark_markdown_node.py +43 -0
  11. notionary/blocks/bulleted_list/__init__.py +7 -0
  12. notionary/blocks/{bulleted_list_element.py → bulleted_list/bulleted_list_element.py} +7 -3
  13. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +33 -0
  14. notionary/blocks/bulleted_list/bulleted_list_models.py +0 -0
  15. notionary/blocks/callout/__init__.py +7 -0
  16. notionary/blocks/callout/callout_element.py +132 -0
  17. notionary/blocks/callout/callout_markdown_node.py +31 -0
  18. notionary/blocks/callout/callout_models.py +0 -0
  19. notionary/blocks/code/__init__.py +7 -0
  20. notionary/blocks/{code_block_element.py → code/code_element.py} +72 -40
  21. notionary/blocks/code/code_markdown_node.py +43 -0
  22. notionary/blocks/code/code_models.py +0 -0
  23. notionary/blocks/column/__init__.py +5 -0
  24. notionary/blocks/{column_element.py → column/column_element.py} +24 -55
  25. notionary/blocks/column/column_models.py +0 -0
  26. notionary/blocks/divider/__init__.py +7 -0
  27. notionary/blocks/{divider_element.py → divider/divider_element.py} +11 -3
  28. notionary/blocks/divider/divider_markdown_node.py +24 -0
  29. notionary/blocks/divider/divider_models.py +0 -0
  30. notionary/blocks/document/__init__.py +7 -0
  31. notionary/blocks/document/document_element.py +102 -0
  32. notionary/blocks/document/document_markdown_node.py +31 -0
  33. notionary/blocks/document/document_models.py +0 -0
  34. notionary/blocks/embed/__init__.py +7 -0
  35. notionary/blocks/{embed_element.py → embed/embed_element.py} +50 -32
  36. notionary/blocks/embed/embed_markdown_node.py +30 -0
  37. notionary/blocks/embed/embed_models.py +0 -0
  38. notionary/blocks/heading/__init__.py +7 -0
  39. notionary/blocks/{heading_element.py → heading/heading_element.py} +25 -17
  40. notionary/blocks/heading/heading_markdown_node.py +29 -0
  41. notionary/blocks/heading/heading_models.py +0 -0
  42. notionary/blocks/image/__init__.py +7 -0
  43. notionary/blocks/{image_element.py → image/image_element.py} +62 -42
  44. notionary/blocks/image/image_markdown_node.py +33 -0
  45. notionary/blocks/image/image_models.py +0 -0
  46. notionary/blocks/markdown_builder.py +356 -0
  47. notionary/blocks/markdown_node.py +29 -0
  48. notionary/blocks/mention/__init__.py +7 -0
  49. notionary/blocks/{mention_element.py → mention/mention_element.py} +6 -2
  50. notionary/blocks/mention/mention_markdown_node.py +38 -0
  51. notionary/blocks/mention/mention_models.py +0 -0
  52. notionary/blocks/numbered_list/__init__.py +7 -0
  53. notionary/blocks/{numbered_list_element.py → numbered_list/numbered_list_element.py} +10 -6
  54. notionary/blocks/numbered_list/numbered_list_markdown_node.py +29 -0
  55. notionary/blocks/numbered_list/numbered_list_models.py +0 -0
  56. notionary/blocks/paragraph/__init__.py +7 -0
  57. notionary/blocks/{paragraph_element.py → paragraph/paragraph_element.py} +7 -3
  58. notionary/blocks/paragraph/paragraph_markdown_node.py +25 -0
  59. notionary/blocks/paragraph/paragraph_models.py +0 -0
  60. notionary/blocks/quote/__init__.py +7 -0
  61. notionary/blocks/quote/quote_element.py +92 -0
  62. notionary/blocks/quote/quote_markdown_node.py +23 -0
  63. notionary/blocks/quote/quote_models.py +0 -0
  64. notionary/blocks/registry/block_registry.py +17 -3
  65. notionary/blocks/registry/block_registry_builder.py +90 -178
  66. notionary/blocks/shared/__init__.py +0 -0
  67. notionary/blocks/shared/block_client.py +256 -0
  68. notionary/blocks/shared/models.py +710 -0
  69. notionary/blocks/{notion_block_element.py → shared/notion_block_element.py} +8 -5
  70. notionary/blocks/{text_inline_formatter.py → shared/text_inline_formatter.py} +14 -14
  71. notionary/blocks/shared/text_inline_formatter_new.py +139 -0
  72. notionary/blocks/table/__init__.py +7 -0
  73. notionary/blocks/{table_element.py → table/table_element.py} +23 -11
  74. notionary/blocks/table/table_markdown_node.py +40 -0
  75. notionary/blocks/table/table_models.py +0 -0
  76. notionary/blocks/todo/__init__.py +7 -0
  77. notionary/blocks/{todo_element.py → todo/todo_element.py} +8 -4
  78. notionary/blocks/todo/todo_markdown_node.py +31 -0
  79. notionary/blocks/todo/todo_models.py +0 -0
  80. notionary/blocks/toggle/__init__.py +4 -0
  81. notionary/blocks/{toggle_element.py → toggle/toggle_element.py} +7 -3
  82. notionary/blocks/toggle/toggle_markdown_node.py +35 -0
  83. notionary/blocks/toggle/toggle_models.py +0 -0
  84. notionary/blocks/toggleable_heading/__init__.py +9 -0
  85. notionary/blocks/{toggleable_heading_element.py → toggleable_heading/toggleable_heading_element.py} +8 -4
  86. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +43 -0
  87. notionary/blocks/toggleable_heading/toggleable_heading_models.py +0 -0
  88. notionary/blocks/video/__init__.py +7 -0
  89. notionary/blocks/{video_element.py → video/video_element.py} +82 -57
  90. notionary/blocks/video/video_markdown_node.py +30 -0
  91. notionary/database/__init__.py +4 -0
  92. notionary/database/database.py +481 -0
  93. notionary/database/{filter_builder.py → database_filter_builder.py} +27 -29
  94. notionary/database/{notion_database_provider.py → database_provider.py} +4 -4
  95. notionary/database/notion_database.py +45 -18
  96. notionary/file_upload/__init__.py +7 -0
  97. notionary/file_upload/client.py +254 -0
  98. notionary/file_upload/models.py +60 -0
  99. notionary/file_upload/notion_file_upload.py +387 -0
  100. notionary/page/content/markdown_whitespace_processor.py +80 -0
  101. notionary/page/content/notion_text_length_utils.py +87 -0
  102. notionary/page/content/page_content_retriever.py +2 -2
  103. notionary/page/content/page_content_writer.py +97 -148
  104. notionary/page/formatting/line_processor.py +153 -0
  105. notionary/page/formatting/markdown_to_notion_converter.py +103 -424
  106. notionary/page/notion_page.py +13 -14
  107. notionary/page/notion_to_markdown_converter.py +9 -13
  108. notionary/telemetry/views.py +15 -6
  109. notionary/user/__init__.py +11 -0
  110. notionary/user/base_notion_user.py +52 -0
  111. notionary/user/client.py +129 -0
  112. notionary/user/models.py +83 -0
  113. notionary/user/notion_bot_user.py +227 -0
  114. notionary/user/notion_user.py +256 -0
  115. notionary/user/notion_user_manager.py +173 -0
  116. notionary/user/notion_user_provider.py +1 -0
  117. notionary/util/__init__.py +3 -5
  118. notionary/util/factory_decorator.py +0 -33
  119. notionary/util/factory_only.py +37 -0
  120. notionary/util/fuzzy.py +74 -0
  121. notionary/util/logging_mixin.py +12 -12
  122. notionary/workspace.py +38 -3
  123. {notionary-0.2.16.dist-info → notionary-0.2.18.dist-info}/METADATA +2 -1
  124. notionary-0.2.18.dist-info/RECORD +149 -0
  125. notionary/blocks/audio_element.py +0 -144
  126. notionary/blocks/callout_element.py +0 -122
  127. notionary/blocks/notion_block_client.py +0 -26
  128. notionary/blocks/qoute_element.py +0 -169
  129. notionary/page/content/notion_page_content_chunker.py +0 -84
  130. notionary/page/formatting/spacer_rules.py +0 -483
  131. notionary/util/fuzzy_matcher.py +0 -82
  132. notionary-0.2.16.dist-info/RECORD +0 -71
  133. /notionary/{elements/__init__.py → blocks/bookmark/bookmark_models.py} +0 -0
  134. /notionary/database/{database_exceptions.py → exceptions.py} +0 -0
  135. /notionary/util/{singleton_decorator.py → singleton.py} +0 -0
  136. {notionary-0.2.16.dist-info → notionary-0.2.18.dist-info}/LICENSE +0 -0
  137. {notionary-0.2.16.dist-info → notionary-0.2.18.dist-info}/WHEEL +0 -0
@@ -1,483 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Dict, Any, List, Optional, Tuple
4
- from abc import ABC, abstractmethod
5
- from dataclasses import dataclass
6
- from enum import Enum
7
- import re
8
-
9
- SPACER_MARKER = "---spacer---"
10
-
11
-
12
- class LineType(Enum):
13
- """Enum for different line types"""
14
-
15
- EMPTY = "empty"
16
- HEADING = "heading"
17
- DIVIDER = "divider"
18
- CODE_BLOCK_MARKER = "code_block_marker"
19
- SPACER_MARKER = SPACER_MARKER
20
- PIPE_SYNTAX = "pipe_syntax"
21
- TODO_ITEM = "todo_item"
22
- REGULAR_CONTENT = "regular_content"
23
-
24
-
25
- @dataclass
26
- class LineContext:
27
- """Context of a line for spacer rule application"""
28
-
29
- line: str
30
- line_number: int
31
- line_type: LineType
32
- is_empty: bool
33
- content: str
34
- in_code_block: bool
35
- last_line_was_spacer: bool
36
- last_non_empty_was_heading: bool
37
- has_content_before: bool
38
- processed_lines: List[str]
39
-
40
-
41
- @dataclass
42
- class SpacerDecision:
43
- """Decision about inserting a spacer"""
44
-
45
- should_add_spacer: bool
46
- reason: str
47
- rule_name: str
48
-
49
-
50
- @dataclass
51
- class ProcessingResult:
52
- """Result of processing a single line"""
53
-
54
- output_lines: List[str]
55
- new_state: Dict[str, Any]
56
- spacer_added: bool = False
57
-
58
-
59
- class SpacerRule(ABC):
60
- """Abstract base class for spacer rules"""
61
-
62
- @property
63
- @abstractmethod
64
- def name(self) -> str:
65
- """Name of the rule for debugging"""
66
- pass
67
-
68
- @property
69
- @abstractmethod
70
- def description(self) -> str:
71
- """Description of what the rule does"""
72
- pass
73
-
74
- @abstractmethod
75
- def applies_to(self, context: LineContext) -> bool:
76
- """Checks if this rule is relevant for the context"""
77
- pass
78
-
79
- @abstractmethod
80
- def should_add_spacer(self, context: LineContext) -> SpacerDecision:
81
- """Decides whether a spacer should be added"""
82
- pass
83
-
84
-
85
- class HeadingSpacerRule(SpacerRule):
86
- """Rule: Add spacer before headings (except after other headings)"""
87
-
88
- @property
89
- def name(self) -> str:
90
- return "HeadingSpacerRule"
91
-
92
- @property
93
- def description(self) -> str:
94
- return "Adds spacer before headings, except when the previous line was already a heading"
95
-
96
- def applies_to(self, context: LineContext) -> bool:
97
- return context.line_type == LineType.HEADING
98
-
99
- def should_add_spacer(self, context: LineContext) -> SpacerDecision:
100
- # Rule: Insert spacer before heading when:
101
- # 1. There is content before this heading
102
- # 2. The last line was not a spacer
103
- # 3. The last non-empty line was not a heading
104
-
105
- if not context.has_content_before:
106
- return SpacerDecision(False, "No content before this heading", self.name)
107
-
108
- if context.last_line_was_spacer:
109
- return SpacerDecision(
110
- False, "Previous line was already a spacer", self.name
111
- )
112
-
113
- if context.last_non_empty_was_heading:
114
- return SpacerDecision(
115
- False,
116
- "Previous non-empty line was a heading (consecutive headings)",
117
- self.name,
118
- )
119
-
120
- return SpacerDecision(
121
- True,
122
- "Adding spacer before heading to separate from previous content",
123
- self.name,
124
- )
125
-
126
-
127
- class DividerSpacerRule(SpacerRule):
128
- """Rule: Add spacer before dividers"""
129
-
130
- @property
131
- def name(self) -> str:
132
- return "DividerSpacerRule"
133
-
134
- @property
135
- def description(self) -> str:
136
- return "Adds spacer before dividers (---) to create visual distance"
137
-
138
- def applies_to(self, context: LineContext) -> bool:
139
- return context.line_type == LineType.DIVIDER
140
-
141
- def should_add_spacer(self, context: LineContext) -> SpacerDecision:
142
- # Rule: Insert spacer before divider except when last line was already a spacer
143
-
144
- if context.last_line_was_spacer:
145
- return SpacerDecision(
146
- False, "Previous line was already a spacer", self.name
147
- )
148
-
149
- return SpacerDecision(
150
- True, "Adding spacer before divider for visual separation", self.name
151
- )
152
-
153
-
154
- class ConsecutiveSpacerRule(SpacerRule):
155
- """Rule: Prevent consecutive spacers"""
156
-
157
- @property
158
- def name(self) -> str:
159
- return "ConsecutiveSpacerRule"
160
-
161
- @property
162
- def description(self) -> str:
163
- return "Prevents consecutive spacer markers"
164
-
165
- def applies_to(self, context: LineContext) -> bool:
166
- return context.line_type == LineType.SPACER_MARKER
167
-
168
- def should_add_spacer(self, context: LineContext) -> SpacerDecision:
169
- # Rule: Never allow consecutive spacers
170
-
171
- if context.last_line_was_spacer:
172
- return SpacerDecision(False, "Preventing consecutive spacers", self.name)
173
-
174
- return SpacerDecision(True, "Adding spacer marker", self.name)
175
-
176
-
177
- class CodeBlockSpacerRule(SpacerRule):
178
- """Rule: No spacers inside code blocks"""
179
-
180
- @property
181
- def name(self) -> str:
182
- return "CodeBlockSpacerRule"
183
-
184
- @property
185
- def description(self) -> str:
186
- return "Prevents spacer processing inside code blocks"
187
-
188
- def applies_to(self, context: LineContext) -> bool:
189
- return context.in_code_block and context.line_type != LineType.CODE_BLOCK_MARKER
190
-
191
- def should_add_spacer(self, context: LineContext) -> SpacerDecision:
192
- return SpacerDecision(
193
- False, "Inside code block - no spacer processing", self.name
194
- )
195
-
196
-
197
- class StateBuilder:
198
- """Builder for creating and updating state"""
199
-
200
- def __init__(self, initial_state: Dict[str, Any]):
201
- self._state = initial_state.copy()
202
-
203
- def toggle_code_block(self) -> StateBuilder:
204
- """Toggle the code block state"""
205
- self._state["in_code_block"] = not self._state.get("in_code_block", False)
206
- return self
207
-
208
- def set_last_line_was_spacer(self, value: bool) -> StateBuilder:
209
- """Set whether the last line was a spacer"""
210
- self._state["last_line_was_spacer"] = value
211
- return self
212
-
213
- def update_content_tracking(
214
- self, line_type: LineType, has_content: bool
215
- ) -> StateBuilder:
216
- """Update content tracking state"""
217
- if has_content:
218
- self._state["last_non_empty_was_heading"] = line_type == LineType.HEADING
219
- self._state["has_content_before"] = True
220
- return self
221
-
222
- def build(self) -> Dict[str, Any]:
223
- """Build the final state"""
224
- return self._state
225
-
226
-
227
- class LineProcessor(ABC):
228
- """Abstract processor for different line types"""
229
-
230
- @abstractmethod
231
- def can_process(self, line_type: LineType) -> bool:
232
- """Check if this processor can handle the line type"""
233
- pass
234
-
235
- @abstractmethod
236
- def process(self, context: LineContext, state: Dict[str, Any]) -> ProcessingResult:
237
- """Process the line and return the result"""
238
- pass
239
-
240
-
241
- class EmptyLineProcessor(LineProcessor):
242
- """Processor for empty lines"""
243
-
244
- def can_process(self, line_type: LineType) -> bool:
245
- return line_type == LineType.EMPTY
246
-
247
- def process(self, context: LineContext, state: Dict[str, Any]) -> ProcessingResult:
248
- new_state = StateBuilder(state).set_last_line_was_spacer(False).build()
249
-
250
- return ProcessingResult(output_lines=[context.line], new_state=new_state)
251
-
252
-
253
- class CodeBlockMarkerProcessor(LineProcessor):
254
- """Processor for code block markers"""
255
-
256
- def can_process(self, line_type: LineType) -> bool:
257
- return line_type == LineType.CODE_BLOCK_MARKER
258
-
259
- def process(self, context: LineContext, state: Dict[str, Any]) -> ProcessingResult:
260
- new_state = (
261
- StateBuilder(state)
262
- .toggle_code_block()
263
- .set_last_line_was_spacer(False)
264
- .update_content_tracking(context.line_type, bool(context.content))
265
- .build()
266
- )
267
-
268
- return ProcessingResult(output_lines=[context.line], new_state=new_state)
269
-
270
-
271
- class SpacerMarkerProcessor(LineProcessor):
272
- """Processor for spacer marker lines"""
273
-
274
- def __init__(self, spacer_marker: str, rules: List[SpacerRule]):
275
- self.spacer_marker = spacer_marker
276
- self.rules = rules
277
-
278
- def can_process(self, line_type: LineType) -> bool:
279
- return line_type == LineType.SPACER_MARKER
280
-
281
- def process(self, context: LineContext, state: Dict[str, Any]) -> ProcessingResult:
282
- # Apply spacer rules
283
- spacer_decision = self._get_spacer_decision(context)
284
-
285
- output_lines = []
286
- spacer_added = False
287
-
288
- if spacer_decision.should_add_spacer:
289
- output_lines.append(context.line)
290
- spacer_added = True
291
-
292
- new_state = StateBuilder(state).set_last_line_was_spacer(spacer_added).build()
293
-
294
- return ProcessingResult(
295
- output_lines=output_lines, new_state=new_state, spacer_added=spacer_added
296
- )
297
-
298
- def _get_spacer_decision(self, context: LineContext) -> SpacerDecision:
299
- """Get spacer decision from rules"""
300
- for rule in self.rules:
301
- if rule.applies_to(context):
302
- return rule.should_add_spacer(context)
303
-
304
- # Default: don't add spacer
305
- return SpacerDecision(False, "No applicable rule found", "DefaultRule")
306
-
307
-
308
- class RegularContentProcessor(LineProcessor):
309
- """Processor for regular content lines"""
310
-
311
- def __init__(self, spacer_marker: str, rules: List[SpacerRule]):
312
- self.spacer_marker = spacer_marker
313
- self.rules = rules
314
-
315
- def can_process(self, line_type: LineType) -> bool:
316
- return line_type in [
317
- LineType.HEADING,
318
- LineType.DIVIDER,
319
- LineType.TODO_ITEM,
320
- LineType.REGULAR_CONTENT,
321
- LineType.PIPE_SYNTAX,
322
- ]
323
-
324
- def process(self, context: LineContext, state: Dict[str, Any]) -> ProcessingResult:
325
- output_lines = []
326
- spacer_added = False
327
-
328
- # Check if we should add a spacer before this line
329
- spacer_decision = self._get_spacer_decision(context)
330
-
331
- if spacer_decision.should_add_spacer:
332
- output_lines.append(self.spacer_marker)
333
- spacer_added = True
334
-
335
- # Add the original line
336
- output_lines.append(context.line)
337
-
338
- # Build new state
339
- new_state = (
340
- StateBuilder(state)
341
- .set_last_line_was_spacer(spacer_added)
342
- .update_content_tracking(context.line_type, bool(context.content))
343
- .build()
344
- )
345
-
346
- return ProcessingResult(
347
- output_lines=output_lines, new_state=new_state, spacer_added=spacer_added
348
- )
349
-
350
- def _get_spacer_decision(self, context: LineContext) -> SpacerDecision:
351
- """Get spacer decision from rules"""
352
- for rule in self.rules:
353
- if rule.applies_to(context):
354
- return rule.should_add_spacer(context)
355
-
356
- # Default: don't add spacer
357
- return SpacerDecision(False, "No applicable rule found", "DefaultRule")
358
-
359
-
360
- class LineProcessorFactory:
361
- """Factory for creating line processors"""
362
-
363
- def __init__(self, spacer_marker: str, rules: List[SpacerRule]):
364
- self.processors = [
365
- EmptyLineProcessor(),
366
- CodeBlockMarkerProcessor(),
367
- SpacerMarkerProcessor(spacer_marker, rules),
368
- RegularContentProcessor(spacer_marker, rules),
369
- ]
370
-
371
- def get_processor(self, line_type: LineType) -> Optional[LineProcessor]:
372
- """Get appropriate processor for the line type"""
373
- for processor in self.processors:
374
- if processor.can_process(line_type):
375
- return processor
376
- return None
377
-
378
-
379
- class ContextFactory:
380
- """Factory for creating line contexts"""
381
-
382
- @staticmethod
383
- def create_context(
384
- line: str, line_number: int, line_type: LineType, state: Dict[str, Any]
385
- ) -> LineContext:
386
- """Create a LineContext from line and state"""
387
- return LineContext(
388
- line=line,
389
- line_number=line_number,
390
- line_type=line_type,
391
- is_empty=not line.strip(),
392
- content=line.strip(),
393
- in_code_block=state.get("in_code_block", False),
394
- last_line_was_spacer=state.get("last_line_was_spacer", False),
395
- last_non_empty_was_heading=state.get("last_non_empty_was_heading", False),
396
- has_content_before=state.get("has_content_before", False),
397
- processed_lines=state.get("processed_lines", []),
398
- )
399
-
400
-
401
- class SpacerRuleEngine:
402
- """Refactored engine with reduced complexity"""
403
-
404
- def __init__(self, rules: Optional[List[SpacerRule]] = None):
405
- self.rules = rules or self._get_default_rules()
406
- self.SPACER_MARKER = SPACER_MARKER
407
-
408
- # Initialize factories
409
- self.processor_factory = LineProcessorFactory(
410
- self.SPACER_MARKER,
411
- self.rules,
412
- )
413
- self.context_factory = ContextFactory()
414
-
415
- def process_line(
416
- self, line: str, line_number: int, context_state: Dict[str, Any]
417
- ) -> Tuple[List[str], Dict[str, Any]]:
418
- """
419
- Processes a line and returns the resulting lines + new state
420
-
421
- Returns:
422
- Tuple[List[str], Dict[str, Any]]: (processed_lines, new_state)
423
- """
424
- # Step 1: Determine line type (single responsibility)
425
- line_type = self._determine_line_type(
426
- line, context_state.get("in_code_block", False)
427
- )
428
-
429
- # Step 2: Create context (factory pattern)
430
- context = self.context_factory.create_context(
431
- line, line_number, line_type, context_state
432
- )
433
-
434
- # Step 3: Get appropriate processor (strategy pattern)
435
- processor = self.processor_factory.get_processor(line_type)
436
- if not processor:
437
- # Fallback to original line
438
- return [line], context_state.copy()
439
-
440
- # Step 4: Process line (delegation)
441
- result = processor.process(context, context_state)
442
-
443
- return result.output_lines, result.new_state
444
-
445
- def _get_default_rules(self) -> List[SpacerRule]:
446
- """Default rule set"""
447
- return [
448
- CodeBlockSpacerRule(), # Highest priority - code blocks
449
- ConsecutiveSpacerRule(), # Prevent duplicate spacers
450
- HeadingSpacerRule(), # Spacer before headings
451
- DividerSpacerRule(), # Spacer before dividers
452
- ]
453
-
454
- def _determine_line_type(self, line: str, in_code_block: bool) -> LineType:
455
- """Determines the type of a line"""
456
- content = line.strip()
457
-
458
- # Guard clauses for early returns
459
- if not content:
460
- return LineType.EMPTY
461
-
462
- if content.startswith("```"):
463
- return LineType.CODE_BLOCK_MARKER
464
-
465
- if in_code_block:
466
- return LineType.REGULAR_CONTENT
467
-
468
- if content == self.SPACER_MARKER:
469
- return LineType.SPACER_MARKER
470
-
471
- # Pattern matching with early returns
472
- patterns = [
473
- (r"^\|\s?(.*)$", LineType.PIPE_SYNTAX),
474
- (r"^(#{1,6})\s+(.+)$", LineType.HEADING),
475
- (r"^-{3,}$", LineType.DIVIDER),
476
- (r"^\s*[-*+]\s+\[[ x]\]", LineType.TODO_ITEM),
477
- ]
478
-
479
- for pattern, line_type in patterns:
480
- if re.match(pattern, content if pattern.startswith("^#{") else line):
481
- return line_type
482
-
483
- return LineType.REGULAR_CONTENT
@@ -1,82 +0,0 @@
1
- from __future__ import annotations
2
- import difflib
3
- from typing import List, Any, TypeVar, Callable, Optional
4
- from dataclasses import dataclass
5
-
6
- T = TypeVar("T")
7
-
8
-
9
- @dataclass
10
- class MatchResult:
11
- """Result of a fuzzy match operation."""
12
-
13
- item: Any
14
- similarity: float
15
- matched_text: str
16
-
17
-
18
- class FuzzyMatcher:
19
- """Utility class for fuzzy string matching operations."""
20
-
21
- @staticmethod
22
- def calculate_similarity(query: str, target: str) -> float:
23
- """Calculate similarity between two strings using difflib."""
24
- return difflib.SequenceMatcher(
25
- None, query.lower().strip(), target.lower().strip()
26
- ).ratio()
27
-
28
- @classmethod
29
- def find_best_matches(
30
- cls,
31
- query: str,
32
- items: List[T],
33
- text_extractor: Callable[[T], str],
34
- min_similarity: float = 0.0,
35
- limit: Optional[int] = None,
36
- ) -> List[MatchResult[T]]:
37
- """
38
- Find best fuzzy matches from a list of items.
39
-
40
- Args:
41
- query: The search query
42
- items: List of items to search through
43
- text_extractor: Function to extract text from each item
44
- min_similarity: Minimum similarity threshold (0.0 to 1.0)
45
- limit: Maximum number of results to return
46
-
47
- Returns:
48
- List of MatchResult objects sorted by similarity (highest first)
49
- """
50
- results = []
51
-
52
- for item in items:
53
- text = text_extractor(item)
54
- similarity = cls.calculate_similarity(query, text)
55
-
56
- if similarity >= min_similarity:
57
- results.append(
58
- MatchResult(item=item, similarity=similarity, matched_text=text)
59
- )
60
-
61
- # Sort by similarity (highest first)
62
- results.sort(key=lambda x: x.similarity, reverse=True)
63
-
64
- # Apply limit if specified
65
- if limit:
66
- results = results[:limit]
67
-
68
- return results
69
-
70
- @classmethod
71
- def find_best_match(
72
- cls,
73
- query: str,
74
- items: List[T],
75
- text_extractor: Callable[[T], str],
76
- min_similarity: float = 0.0,
77
- ) -> Optional[MatchResult[T]]:
78
- """Find the single best fuzzy match."""
79
- matches = cls.find_best_matches(
80
- query, items, text_extractor, min_similarity, limit=1
81
- )
82
- return matches[0] if matches else None
@@ -1,71 +0,0 @@
1
- notionary/__init__.py,sha256=4eO6Jx57VRR_Ejo9w7IJeET8SZOvxFl_1lOB39o39No,250
2
- notionary/base_notion_client.py,sha256=hJnN8CZe7CUunMBljGdKKN44xMlJQAIAhQyT-3WEIK8,6722
3
- notionary/blocks/__init__.py,sha256=MFBxK3zZ28tV_u8XT20Q6HY39KENCfJDfDflLTYVt4E,2019
4
- notionary/blocks/audio_element.py,sha256=rQbWz8akbobci8CFvnFuuHoDNJCG7mcuSXdB8hHjqLU,5355
5
- notionary/blocks/bookmark_element.py,sha256=gW6uKCkuWFpHEzq-g1CbvKvma6hyTMUH2XMczI0U-5M,8080
6
- notionary/blocks/bulleted_list_element.py,sha256=Uv_ohhF0MTwQ29w_RUX91NFuz57Dtr4vQpV8seRAgy0,2599
7
- notionary/blocks/callout_element.py,sha256=Cya-1HIRBiCiyMgQq6PqXU4_iGj2O3qAPirhtC2QrTY,4446
8
- notionary/blocks/code_block_element.py,sha256=w0AN5m1qEFEEMDZ5dicCUhh4RwQpjByzDW3PuHgvgt0,7466
9
- notionary/blocks/column_element.py,sha256=qzbrTcWWOhGts2fAWHTwQUW8Ca6yoQEMZol9ZxUDCCI,12669
10
- notionary/blocks/divider_element.py,sha256=eCX2OupttnjGUaIaF59RhULKqh8R6d8KPnpctMMaXJs,2267
11
- notionary/blocks/embed_element.py,sha256=MFHh3ZFNntvaJ1NiEs0bzpbmJTRm0Axqdtf5oputbi0,4516
12
- notionary/blocks/heading_element.py,sha256=aALMpclbPTvKfJOICJdgP0y-y7w5jhv7rUQl04TQpeg,3051
13
- notionary/blocks/image_element.py,sha256=SEZ31_uDBRy6_lpn8E_GMX5uzI7-c-pJB9idUGZiTrE,4695
14
- notionary/blocks/mention_element.py,sha256=G04nnc54YzUP8qu_aAx4-z56fspVrqCc4IHqSw4C5fk,8122
15
- notionary/blocks/notion_block_client.py,sha256=mLkJ9mbfTZB7oml2hjXxxmr9XUCfM3u_8xjwKDi77oA,911
16
- notionary/blocks/notion_block_element.py,sha256=r27KYICQvdmOg3AyzHE6ouWjX8WuJmX1bERCgkBdaGE,1263
17
- notionary/blocks/numbered_list_element.py,sha256=BL_mui9vJ0usOFbRrNZRP_IY8QLG3vGFRYiPPsq_OJw,2596
18
- notionary/blocks/paragraph_element.py,sha256=-zCwJOanOVjv07DRArD13yRYaxfL8sCob6oN3PKvzNc,3188
19
- notionary/blocks/prompts/element_prompt_builder.py,sha256=rYMKPmpEFyk26JFZlwcTzMHATpvHnn4Dn284vewFog0,2953
20
- notionary/blocks/prompts/element_prompt_content.py,sha256=ItnhGwKsHGnnY9E_LGgZZeTCT9ZfnkJY8xad4wFViWk,1567
21
- notionary/blocks/qoute_element.py,sha256=pkeT6N7PZrepIod8WLrY1DMe2DW6fM98Y4zXiiACenw,6059
22
- notionary/blocks/registry/block_registry.py,sha256=fEKe03bJYkzy19cbkeO2d30nvSThG6TWHXHxf_-aUbg,4961
23
- notionary/blocks/registry/block_registry_builder.py,sha256=FA_0WOajaeVaqdphNh8EyN0p_7ItzFqEufYa6YVBLeY,8731
24
- notionary/blocks/table_element.py,sha256=DzXbSVm3KwTfnLF2cp765gj-VC50zWvj_0RU_WcQDJw,11184
25
- notionary/blocks/text_inline_formatter.py,sha256=aKnaR1LvmbBkRdJVId8xtMkrbw1xaw6e4ZLUH97XLfU,8583
26
- notionary/blocks/todo_element.py,sha256=6ndhgGJNiy7eb-Ll--Va7zEqQySxFAFYpzY4PWJbGUQ,4059
27
- notionary/blocks/toggle_element.py,sha256=2gofKL4ndVkRxkuH-iYVx0YFUc649gpQQbZtwh2BpY8,11017
28
- notionary/blocks/toggleable_heading_element.py,sha256=fkXvKtgCg6PuHqrHq7LupmqzpasJ1IyVf2RBLYTiVIo,9893
29
- notionary/blocks/video_element.py,sha256=C19XxFRyAUEbhhC9xvhAAGN8YBYP6ON1vm_x7b_gUrY,5664
30
- notionary/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- notionary/database/client.py,sha256=ZcfydeYlpgGJt6wV1ib33KeXUiL-cGNJ1qraQZ4RVRc,4775
32
- notionary/database/database_exceptions.py,sha256=jwFdxoIQHLO3mO3p5t890--1FjbTX60fNyqBAe-sszo,452
33
- notionary/database/factory.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- notionary/database/filter_builder.py,sha256=4EJnWUF73l5oi-HnvMu-mI1OncLzEs2o2mr_xG75quk,6315
35
- notionary/database/models/page_result.py,sha256=Vmm5_oYpYAkIIJVoTd1ZZGloeC3cmFLMYP255mAmtaw,233
36
- notionary/database/notion_database.py,sha256=jA3_x-pMJdcI6-ZDiSrx43ywaFaw0MLRW8wYb7DOlvQ,15755
37
- notionary/database/notion_database_provider.py,sha256=GVfS8fgf5RhX15y8gpvRjBkQbv--8WFgKBkI_Z5LRaU,9009
38
- notionary/elements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- notionary/models/notion_block_response.py,sha256=gzL4C6K9QPcaMS6NbAZaRceSEnMbNwYBVVzxysza5VU,6002
40
- notionary/models/notion_database_response.py,sha256=3kvADIP1dSxgITSK4n8Ex3QpF8n_Lxnu_IXbPVGcq4o,7648
41
- notionary/models/notion_page_response.py,sha256=7ZwDYhlyK-avix_joQpGuNQZopjlQFI8jS3nvNNumoc,1544
42
- notionary/models/search_response.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
- notionary/page/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
- notionary/page/client.py,sha256=XQ72lOEwn-gO8fmhKSKHqSHs3hRmoKH0TkJ3TtblcAg,4030
45
- notionary/page/content/notion_page_content_chunker.py,sha256=kWJnV9GLU5YLgSVPKOjwMBbG_CMAmVRkuDtwJYb_UAA,3316
46
- notionary/page/content/page_content_retriever.py,sha256=iNazSf0uv_gi0J816-SZn4Lw4qbAxRHG90k9Jy_qw2Q,1587
47
- notionary/page/content/page_content_writer.py,sha256=VVvK-Z8NvyIhi7Crcm9mZQuuD_L72NsqSQg9gf33Zwk,7369
48
- notionary/page/formatting/markdown_to_notion_converter.py,sha256=9RyGON8VrJv6XifdQdOt5zKgKT3irc974zcbGDBhmLY,17328
49
- notionary/page/formatting/spacer_rules.py,sha256=j2RHvdXT3HxXPVBEuCtulyy9cPxsEcOmj71pJqV-D3M,15677
50
- notionary/page/markdown_syntax_prompt_generator.py,sha256=uHCPNV9aQi3GzLVimyUKwza29hfxu6DTMVIa_QevJbk,4987
51
- notionary/page/notion_page.py,sha256=PAwixEuzn5mnkIU4AtOKHiaaG98fmVqFhPe4Mjnb9bA,19110
52
- notionary/page/notion_to_markdown_converter.py,sha256=_MJWWwsBvgZ3a8tLZ23ZCIA_G9Qfvt2JG1FqVTlRxHs,6308
53
- notionary/page/properites/property_value_extractor.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- notionary/page/property_formatter.py,sha256=_978ViH83gfcr-XtDscWTfyBI2srGW2hzC-gzgp5NR8,3788
55
- notionary/page/search_filter_builder.py,sha256=wZpW_KHmPXql3sNIyQd9EzZ2-ERy2i0vYNdoLkoBUfc,4597
56
- notionary/page/utils.py,sha256=2nfBrWeczBdPH13R3q8dKP4OY4MwEdfKbcs2UJ9kg1o,2041
57
- notionary/telemetry/__init__.py,sha256=Y7KyXeN4PiA6GtzV3NnwoH4hJnPwdjikWP22ckPYuHM,511
58
- notionary/telemetry/service.py,sha256=DD7RbkSN0HWRK2YpOJTgFD7PeXGhSe9KrLkhiVIaC7Y,4763
59
- notionary/telemetry/views.py,sha256=BXVa25h0A4leGaz5U9F-T5ebShkojD-DvYElWkrP6U4,1762
60
- notionary/util/__init__.py,sha256=JAOxIchioEx4h_KcRs8mqgBjPveoNmzqkNzdQZnokNk,438
61
- notionary/util/factory_decorator.py,sha256=3SD63EPxXMmKQ8iF7sF88xUFMG8dy14L2DJZ7XdcYm4,1110
62
- notionary/util/fuzzy_matcher.py,sha256=RYR86hMTp8lrWl3PeOa3RpDpzh04HJ30qrIlrq6_qDo,2442
63
- notionary/util/logging_mixin.py,sha256=d5sRSmUtgQeuckdNBkO025IXPGe4oOb-7ueVAIP8amU,1846
64
- notionary/util/page_id_utils.py,sha256=AA00kRO-g3Cc50tf_XW_tb5RBuPKLuBxRa0D8LYhLXg,736
65
- notionary/util/singleton_decorator.py,sha256=CKAvykndwPRZsA3n3MAY_XdCR59MBjjKP0vtm2BcvF0,428
66
- notionary/util/singleton_metaclass.py,sha256=uNeHiqS6TwhljvG1RE4NflIp2HyMuMmrCg2xI-vxmHE,809
67
- notionary/workspace.py,sha256=kW9fbVUSECivlvABBwnks2nALfk09V6g6Oc2Eq_pK5U,2511
68
- notionary-0.2.16.dist-info/LICENSE,sha256=zOm3cRT1qD49eg7vgw95MI79rpUAZa1kRBFwL2FkAr8,1120
69
- notionary-0.2.16.dist-info/METADATA,sha256=qy_xKJWjzGlY0zZcJ4T2hZtFgjMmgZhRSEqaQc8Hlqo,6824
70
- notionary-0.2.16.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
71
- notionary-0.2.16.dist-info/RECORD,,
File without changes