notionary 0.1.29__py3-none-any.whl → 0.2.0__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 (59) hide show
  1. notionary/__init__.py +5 -5
  2. notionary/database/notion_database.py +50 -59
  3. notionary/database/notion_database_factory.py +16 -20
  4. notionary/elements/audio_element.py +1 -1
  5. notionary/elements/bookmark_element.py +1 -1
  6. notionary/elements/bulleted_list_element.py +2 -8
  7. notionary/elements/callout_element.py +1 -1
  8. notionary/elements/code_block_element.py +1 -1
  9. notionary/elements/divider_element.py +1 -1
  10. notionary/elements/embed_element.py +1 -1
  11. notionary/elements/heading_element.py +2 -8
  12. notionary/elements/image_element.py +1 -1
  13. notionary/elements/mention_element.py +1 -1
  14. notionary/elements/notion_block_element.py +1 -1
  15. notionary/elements/numbered_list_element.py +2 -7
  16. notionary/elements/paragraph_element.py +1 -1
  17. notionary/elements/qoute_element.py +1 -1
  18. notionary/elements/registry/{block_element_registry.py → block_registry.py} +70 -26
  19. notionary/elements/registry/{block_element_registry_builder.py → block_registry_builder.py} +48 -32
  20. notionary/elements/table_element.py +1 -1
  21. notionary/elements/text_inline_formatter.py +13 -9
  22. notionary/elements/todo_element.py +1 -1
  23. notionary/elements/toggle_element.py +1 -1
  24. notionary/elements/toggleable_heading_element.py +1 -1
  25. notionary/elements/video_element.py +1 -1
  26. notionary/models/notion_block_response.py +264 -0
  27. notionary/models/notion_database_response.py +63 -0
  28. notionary/models/notion_page_response.py +100 -0
  29. notionary/notion_client.py +38 -5
  30. notionary/page/content/page_content_retriever.py +68 -0
  31. notionary/page/content/page_content_writer.py +103 -0
  32. notionary/page/markdown_to_notion_converter.py +5 -5
  33. notionary/page/metadata/metadata_editor.py +91 -63
  34. notionary/page/metadata/notion_icon_manager.py +55 -28
  35. notionary/page/metadata/notion_page_cover_manager.py +23 -20
  36. notionary/page/notion_page.py +223 -218
  37. notionary/page/notion_page_factory.py +102 -151
  38. notionary/page/notion_to_markdown_converter.py +5 -5
  39. notionary/page/properites/database_property_service.py +11 -55
  40. notionary/page/properites/page_property_manager.py +44 -67
  41. notionary/page/properites/property_value_extractor.py +3 -3
  42. notionary/page/relations/notion_page_relation_manager.py +165 -213
  43. notionary/page/relations/notion_page_title_resolver.py +59 -41
  44. notionary/page/relations/page_database_relation.py +7 -9
  45. notionary/{elements/prompts → prompting}/element_prompt_content.py +19 -4
  46. notionary/prompting/markdown_syntax_prompt_generator.py +92 -0
  47. notionary/util/logging_mixin.py +17 -8
  48. notionary/util/warn_direct_constructor_usage.py +54 -0
  49. {notionary-0.1.29.dist-info → notionary-0.2.0.dist-info}/METADATA +2 -1
  50. notionary-0.2.0.dist-info/RECORD +60 -0
  51. {notionary-0.1.29.dist-info → notionary-0.2.0.dist-info}/WHEEL +1 -1
  52. notionary/database/database_info_service.py +0 -43
  53. notionary/elements/prompts/synthax_prompt_builder.py +0 -150
  54. notionary/page/content/page_content_manager.py +0 -211
  55. notionary/page/properites/property_operation_result.py +0 -116
  56. notionary/page/relations/relation_operation_result.py +0 -144
  57. notionary-0.1.29.dist-info/RECORD +0 -58
  58. {notionary-0.1.29.dist-info → notionary-0.2.0.dist-info}/licenses/LICENSE +0 -0
  59. {notionary-0.1.29.dist-info → notionary-0.2.0.dist-info}/top_level.txt +0 -0
@@ -24,14 +24,19 @@ class ElementPromptContent:
24
24
  avoid: Optional[str] = None
25
25
  """Optional field listing scenarios when this element should be avoided."""
26
26
 
27
+ is_standard_markdown: bool = False
28
+ """Indicates whether this element follows standard Markdown syntax (and does not require full examples)."""
29
+
27
30
  def __post_init__(self):
28
31
  """Validates that the content meets minimum requirements."""
29
32
  if not self.description:
30
33
  raise ValueError("Description is required")
31
34
  if not self.syntax:
32
35
  raise ValueError("Syntax is required")
33
- if not self.examples:
34
- raise ValueError("At least one example is required")
36
+ if not self.examples and not self.is_standard_markdown:
37
+ raise ValueError(
38
+ "At least one example is required unless it's standard markdown."
39
+ )
35
40
  if not self.when_to_use:
36
41
  raise ValueError("Usage guidelines are required")
37
42
 
@@ -48,6 +53,7 @@ class ElementPromptBuilder:
48
53
  self._examples: List[str] = []
49
54
  self._when_to_use: Optional[str] = None
50
55
  self._avoid: Optional[str] = None
56
+ self._is_standard_markdown = False
51
57
 
52
58
  def with_description(self, description: str) -> Self:
53
59
  """Set the description of the element."""
@@ -79,6 +85,12 @@ class ElementPromptBuilder:
79
85
  self._avoid = avoid
80
86
  return self
81
87
 
88
+ def with_standard_markdown(self) -> Self:
89
+ """Indicate that this element follows standard Markdown syntax."""
90
+ self._examples = []
91
+ self._is_standard_markdown = True
92
+ return self
93
+
82
94
  def build(self) -> ElementPromptContent:
83
95
  """
84
96
  Build and validate the ElementPromptContent object.
@@ -93,8 +105,10 @@ class ElementPromptBuilder:
93
105
  raise ValueError("Description is required")
94
106
  if not self._syntax:
95
107
  raise ValueError("Syntax is required")
96
- if not self._examples:
97
- raise ValueError("At least one example is required")
108
+ if not self._examples and not self._is_standard_markdown:
109
+ raise ValueError(
110
+ "At least one example is required unless it's standard markdown."
111
+ )
98
112
  if not self._when_to_use:
99
113
  raise ValueError("Usage guidelines are required")
100
114
 
@@ -104,4 +118,5 @@ class ElementPromptBuilder:
104
118
  examples=self._examples,
105
119
  when_to_use=self._when_to_use,
106
120
  avoid=self._avoid,
121
+ is_standard_markdown=self._is_standard_markdown,
107
122
  )
@@ -0,0 +1,92 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Type, List
4
+ from notionary.elements.notion_block_element import NotionBlockElement
5
+
6
+
7
+ class MarkdownSyntaxPromptGenerator:
8
+ """
9
+ Generator for LLM system prompts that describe Notion-Markdown syntax.
10
+
11
+ This class extracts information about supported Markdown patterns
12
+ and formats them optimally for LLMs.
13
+ """
14
+
15
+ def __init__(self):
16
+ # Lade das Template aus der Markdown-Datei
17
+ self.SYSTEM_PROMPT_TEMPLATE = self._load_template()
18
+
19
+ def _load_template(self) -> str:
20
+ """
21
+ Lädt das Prompt-Template aus der Markdown-Datei.
22
+ """
23
+ current_dir = Path(__file__).parent
24
+ template_path = current_dir / "res/notion_syntax_prompt.md"
25
+
26
+ try:
27
+ with open(template_path, "r", encoding="utf-8") as file:
28
+ return file.read()
29
+ except FileNotFoundError:
30
+ raise FileNotFoundError(f"Template file not found at {template_path}")
31
+ except Exception as e:
32
+ raise RuntimeError(f"Error loading template file: {e}")
33
+
34
+ @staticmethod
35
+ def generate_element_doc(element_class: Type[NotionBlockElement]) -> str:
36
+ """
37
+ Generates documentation for a specific NotionBlockElement in a compact format.
38
+ Uses the element's get_llm_prompt_content method if available.
39
+ """
40
+ class_name = element_class.__name__
41
+ element_name = class_name.replace("Element", "")
42
+
43
+ content = element_class.get_llm_prompt_content()
44
+
45
+ doc_parts = [
46
+ f"## {element_name}",
47
+ f"{content.description}",
48
+ f"**Syntax:** {content.syntax}",
49
+ ]
50
+
51
+ if content.examples:
52
+ doc_parts.append("**Examples:**")
53
+ for example in content.examples:
54
+ doc_parts.append(example)
55
+
56
+ doc_parts.append(f"**When to use:** {content.when_to_use}")
57
+
58
+ if content.avoid:
59
+ doc_parts.append(f"**Avoid:** {content.avoid}")
60
+
61
+ return "\n".join([part for part in doc_parts if part])
62
+
63
+ @classmethod
64
+ def generate_element_docs(
65
+ cls, element_classes: List[Type[NotionBlockElement]]
66
+ ) -> str:
67
+ """
68
+ Generates complete documentation for all provided element classes.
69
+ """
70
+ docs = [
71
+ "# Markdown Syntax for Notion Blocks",
72
+ "The following Markdown patterns are supported for creating Notion blocks:",
73
+ ]
74
+
75
+ # Generate docs for each element
76
+ for element in element_classes:
77
+ docs.append("\n" + cls.generate_element_doc(element))
78
+
79
+ return "\n".join(docs)
80
+
81
+ @classmethod
82
+ def generate_system_prompt(
83
+ cls,
84
+ element_classes: List[Type[NotionBlockElement]],
85
+ ) -> str:
86
+ """
87
+ Generates a complete system prompt for LLMs.
88
+ """
89
+ # Erstelle eine Instanz, um das Template zu laden
90
+ instance = cls()
91
+ element_docs = cls.generate_element_docs(element_classes)
92
+ return instance.SYSTEM_PROMPT_TEMPLATE.format(element_docs=element_docs)
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  import inspect
3
+ from typing import Optional, ClassVar
3
4
 
4
5
 
5
6
  def setup_logging():
@@ -10,19 +11,27 @@ def setup_logging():
10
11
 
11
12
 
12
13
  class LoggingMixin:
14
+ # Class attribute with proper typing
15
+ logger: ClassVar[logging.Logger] = None
16
+
17
+ def __init_subclass__(cls, **kwargs):
18
+ """
19
+ This method is called when a class inherits from LoggingMixin.
20
+ It automatically sets up the logger as a class attribute.
21
+ """
22
+ super().__init_subclass__(**kwargs)
23
+ cls.logger = logging.getLogger(cls.__name__)
24
+
13
25
  @property
14
- def logger(self):
26
+ def instance_logger(self) -> logging.Logger:
27
+ """Instance logger - for instance methods"""
15
28
  if not hasattr(self, "_logger"):
16
29
  self._logger = logging.getLogger(self.__class__.__name__)
17
30
  return self._logger
18
31
 
19
- @classmethod
20
- def class_logger(cls):
21
- """Class logger - für Klassenmethoden"""
22
- return logging.getLogger(cls.__name__)
23
-
24
32
  @staticmethod
25
- def static_logger():
33
+ def static_logger() -> logging.Logger:
34
+ """Static logger - for static methods"""
26
35
  stack = inspect.stack()
27
36
  for frame_info in stack[1:]:
28
37
  class_name = LoggingMixin._get_class_name_from_frame(frame_info.frame)
@@ -31,7 +40,7 @@ class LoggingMixin:
31
40
  return logging.getLogger("UnknownStaticContext")
32
41
 
33
42
  @staticmethod
34
- def _get_class_name_from_frame(frame):
43
+ def _get_class_name_from_frame(frame) -> Optional[str]:
35
44
  local_vars = frame.f_locals
36
45
  if "self" in local_vars:
37
46
  return local_vars["self"].__class__.__name__
@@ -0,0 +1,54 @@
1
+ import functools
2
+ import inspect
3
+ from typing import Callable, Any, TypeVar, cast
4
+
5
+ F = TypeVar("F", bound=Callable[..., Any])
6
+
7
+
8
+ def warn_direct_constructor_usage(func: F) -> F:
9
+ """
10
+ Method decorator that logs a warning when the constructor is called directly
11
+ instead of through a factory method.
12
+
13
+ This is an advisory decorator - it only logs a warning and doesn't
14
+ prevent direct constructor usage.
15
+ """
16
+
17
+ @functools.wraps(func)
18
+ def wrapper(self, *args, **kwargs):
19
+ # Get the call stack
20
+ stack = inspect.stack()
21
+
22
+ self._from_factory = False
23
+
24
+ search_depth = min(6, len(stack))
25
+
26
+ for i in range(1, search_depth):
27
+ if i >= len(stack):
28
+ break
29
+
30
+ caller_frame = stack[i]
31
+ caller_name = caller_frame.function
32
+
33
+ # Debug logging might be helpful during development
34
+ # print(f"Frame {i}: {caller_name}")
35
+
36
+ # If called from a factory method, mark it and break
37
+ if caller_name.startswith("create_from_") or caller_name.startswith(
38
+ "from_"
39
+ ):
40
+ self._from_factory = True
41
+ break
42
+
43
+ # If not from factory, log warning
44
+ if not self._from_factory and hasattr(self, "logger"):
45
+ self.logger.warning(
46
+ "Advisory: Direct constructor usage is discouraged. "
47
+ "Consider using factory methods like create_from_page_id(), "
48
+ "create_from_url(), or create_from_page_name() instead."
49
+ )
50
+
51
+ # Call the original __init__
52
+ return func(self, *args, **kwargs)
53
+
54
+ return cast(F, wrapper)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: notionary
3
- Version: 0.1.29
3
+ Version: 0.2.0
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
@@ -12,6 +12,7 @@ Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: httpx>=0.28.0
14
14
  Requires-Dist: python-dotenv>=1.1.0
15
+ Requires-Dist: pydantic>=2.11.4
15
16
  Dynamic: author
16
17
  Dynamic: author-email
17
18
  Dynamic: classifier
@@ -0,0 +1,60 @@
1
+ notionary/__init__.py,sha256=hPvZ-iqt5R_dAs9KaRBhC5eXzuQ5uvt-9EaU2O_7bZw,691
2
+ notionary/notion_client.py,sha256=sJJMB36DqL0abcG-5w_plUDeS-zn1x0LpCgVNYeKqx0,7413
3
+ notionary/database/database_discovery.py,sha256=qDGFhXG9s-_6CXdRg8tMiwX4dvX7jLjgAUFPSNlYtlI,4506
4
+ notionary/database/notion_database.py,sha256=zbHPejETr101pprd7kewZ555d_TONN_wJi7b9Eyfoyg,7634
5
+ notionary/database/notion_database_factory.py,sha256=FmijGYz6A4mCWVionOg9sxgFXfb9he52xdgNswJw24k,6584
6
+ notionary/database/models/page_result.py,sha256=Vmm5_oYpYAkIIJVoTd1ZZGloeC3cmFLMYP255mAmtaw,233
7
+ notionary/elements/audio_element.py,sha256=7bEpFl9jA6S1UZlEXsmFzEUVoViEp1o_7zZIC-S7750,5345
8
+ notionary/elements/bookmark_element.py,sha256=msCtZvuPkIj1kiShNwE8i1GDYwamFb5mwRyZm4XyVY4,8145
9
+ notionary/elements/bulleted_list_element.py,sha256=obsb3JqUNET3uS5OZM3yzDqxSzJzUuEob-Fzx0UIg9Y,2664
10
+ notionary/elements/callout_element.py,sha256=ZsRvRtVy9kxdTwgrB5JGjZ4qcCiwcC0WimWJ_cW0aLY,4492
11
+ notionary/elements/code_block_element.py,sha256=IbwpptMLtHDFO0Hyvt2o0p5AZ0S4vhxfzoBhTqKexSY,6240
12
+ notionary/elements/divider_element.py,sha256=0e10YK-CC8uGuL7921dEIjeJK9ha-WhRIYRf2fFuxVQ,2211
13
+ notionary/elements/embed_element.py,sha256=Zcc18Kl8SGoG98P2aYE0TkBviRvSz-sYOdjMEs-tvgk,4579
14
+ notionary/elements/heading_element.py,sha256=kqgjyfaawEODir2tzDyf7-7wm38DbqoZnsH5k94GsA0,3013
15
+ notionary/elements/image_element.py,sha256=cwdovaWK8e4uZJU97l_fJ2etAxAgM2rG2EE34t4eag8,4758
16
+ notionary/elements/mention_element.py,sha256=L4t6eAY3RcbOqIiwVT_CAqwatDtP4tBs9FaqRhaCbpQ,8227
17
+ notionary/elements/notion_block_element.py,sha256=BVrZH09vyojuacs3KGReVx3W0Ee6di_5o9E8N5sex28,1258
18
+ notionary/elements/numbered_list_element.py,sha256=LHZ3aQjz8mHQKOd_oGgbaaj5Hv9_ZQVomj3GaTP8r1E,2663
19
+ notionary/elements/paragraph_element.py,sha256=RfnC-whzmE2eysbTtTNsswmWBqxqK0lUdDlinHKsFMg,3255
20
+ notionary/elements/qoute_element.py,sha256=NsMus2tiAKr8e2HBnHAZ442w40_qxL96z0-BzwR0uYU,6122
21
+ notionary/elements/table_element.py,sha256=5ghOVjo5ocEGaQPPzbdbzcF8TQ3kLRJ2AYdsA2uHDJk,11249
22
+ notionary/elements/text_inline_formatter.py,sha256=KvvTqctFNlzBo-OMoShAMnu-oK_AeiKYqslYQ-2dUFY,7963
23
+ notionary/elements/todo_element.py,sha256=ND3oOzSnd0l1AUGTcG2NiHW50ZbI4-atjtNorLV5m2U,4124
24
+ notionary/elements/toggle_element.py,sha256=h9vYkkAIUHzn-0mu31qC6UPdlk_0EFIsU5A4T_A2ZI8,11082
25
+ notionary/elements/toggleable_heading_element.py,sha256=XdaPsd8anufwAACL8J-Egd_RcqPqZ1gFlzeol1GOyyc,9960
26
+ notionary/elements/video_element.py,sha256=y0OmOYXdQBc2rSYAHRmA4l4rzNqPnyhuXbEipcgzQgY,5727
27
+ notionary/elements/registry/block_registry.py,sha256=giWGcdgc3Z60wvfUr-FS6UMc-k-Q6DlXO8T0gl4fVC8,5027
28
+ notionary/elements/registry/block_registry_builder.py,sha256=5dQhWiJ7jsyKUin1y7r-1Cmp0oOEAIfh6g91w8O4ydI,9319
29
+ notionary/exceptions/database_exceptions.py,sha256=I-Tx6bYRLpi5pjGPtbT-Mqxvz3BFgYTiuZxknJeLxtI,2638
30
+ notionary/exceptions/page_creation_exception.py,sha256=4v7IuZD6GsQLrqhDLriGjuG3ML638gAO53zDCrLePuU,281
31
+ notionary/models/notion_block_response.py,sha256=gzL4C6K9QPcaMS6NbAZaRceSEnMbNwYBVVzxysza5VU,6002
32
+ notionary/models/notion_database_response.py,sha256=Ij8XZniAi2BGjKn2fzT7auCAYAnTzL-jPTUjj5uH7i0,1240
33
+ notionary/models/notion_page_response.py,sha256=r4fwMwwDocj92JdbSmyrzIqBKsnEaz4aDUiPabrg9BM,1762
34
+ notionary/page/markdown_to_notion_converter.py,sha256=EuqUGNv2HZu67INOnGheeJkt7WHTWGuLnhEG72_Wv5Y,15833
35
+ notionary/page/notion_page.py,sha256=Ap5h-6Ef9K8gtgrMM-msaYRM4q7OgFvzZS_xSjLdBiU,18049
36
+ notionary/page/notion_page_factory.py,sha256=2A3M5Ub_kV2-q7PPRqDgfwBjhkGCwtL5i3Kr2RfvvVo,7213
37
+ notionary/page/notion_to_markdown_converter.py,sha256=vUQss0J7LUFLULGvW27PjaTFuWi8OsRQAUBowSYorkM,6408
38
+ notionary/page/content/notion_page_content_chunker.py,sha256=xRks74Dqec-De6-AVTxMPnXs-MSJBzSm1HfJfaHiKr8,3330
39
+ notionary/page/content/page_content_retriever.py,sha256=btVWarx06KZ2A2ZRxNpNEvkeYwyyI2tnM8dKWSkiQGQ,2235
40
+ notionary/page/content/page_content_writer.py,sha256=czBzNCGcwdpqNLSQPyna1s8Y7pjyPzDgJC3UUK5PLGA,3793
41
+ notionary/page/metadata/metadata_editor.py,sha256=HI7m8Zn_Lz6x36rBnW1EnbicVS-4Q8NmCJYKN-OlY-c,5130
42
+ notionary/page/metadata/notion_icon_manager.py,sha256=6a9GS5sT0trfuAb0hlF2Cw_Wc1oM59a1QA4kO9asvMA,2576
43
+ notionary/page/metadata/notion_page_cover_manager.py,sha256=gHQSA8EtO4gbkMt_C3nKc0DF44SY_4ycd57cJSihdqk,2215
44
+ notionary/page/properites/database_property_service.py,sha256=-UlA3NlquQAVyDy4Eyshy9J70UpIvv7K_PBPgvxVdCo,9909
45
+ notionary/page/properites/page_property_manager.py,sha256=UVVcSwm3C9y-ggBdaca0o3Ib16YCm6do2ZDKLk8kdBA,5743
46
+ notionary/page/properites/property_formatter.py,sha256=d_Nr5XQxgjB6VIS0u3ey14MOUKY416o_BvdXjbkUNAQ,3667
47
+ notionary/page/properites/property_value_extractor.py,sha256=TZIbJXWcA1UQ7FutbGlJ96YoTdJp4I_Mz5qlTMgGFuU,2356
48
+ notionary/page/relations/notion_page_relation_manager.py,sha256=tfkvLHClaYel_uEad1PIZ7yzhb2tXS-QrLn1CBvUuuw,11069
49
+ notionary/page/relations/notion_page_title_resolver.py,sha256=dIjiEeHjjNT-DrIhz1nynkfHkMpUuJJFOEjb25Wy7f4,3575
50
+ notionary/page/relations/page_database_relation.py,sha256=8lEp8fQjPwjWhA8nZu3k8mW6EEc54ki1Uwf4iUV1DOU,2245
51
+ notionary/prompting/element_prompt_content.py,sha256=tHref-SKA81Ua_IQD2Km7y7BvFtHl74haSIjHNYE3FE,4403
52
+ notionary/prompting/markdown_syntax_prompt_generator.py,sha256=xKzTF62SFKzadyC7FHcOxWueRzkKiJ054pBHu9B4aLg,3155
53
+ notionary/util/logging_mixin.py,sha256=b6wHj0IoVSWXbHh0yynfJlwvIR33G2qmaGNzrqyb7Gs,1825
54
+ notionary/util/page_id_utils.py,sha256=EYNMxgf-7ghzL5K8lKZBZfW7g5CsdY0Xuj4IYmU8RPk,1381
55
+ notionary/util/warn_direct_constructor_usage.py,sha256=vyJR73F95XVSRWIbyij-82IGOpAne9SBPM25eDpZfSU,1715
56
+ notionary-0.2.0.dist-info/licenses/LICENSE,sha256=zOm3cRT1qD49eg7vgw95MI79rpUAZa1kRBFwL2FkAr8,1120
57
+ notionary-0.2.0.dist-info/METADATA,sha256=LJEmoURn20Z9L2j8sK3GartZp5tQHhdQpt5tZ4dVSWA,8374
58
+ notionary-0.2.0.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
59
+ notionary-0.2.0.dist-info/top_level.txt,sha256=fhONa6BMHQXqthx5PanWGbPL0b8rdFqhrJKVLf_adSs,10
60
+ notionary-0.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.0)
2
+ Generator: setuptools (80.4.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,43 +0,0 @@
1
- from typing import Optional
2
- from notionary.notion_client import NotionClient
3
-
4
-
5
- class DatabaseInfoService:
6
- """Service für den Zugriff auf Datenbankinformationen"""
7
-
8
- def __init__(self, client: NotionClient, database_id: str):
9
- self._client = client
10
- self.database_id = database_id
11
- self._title = None
12
-
13
- async def fetch_database_title(self) -> str:
14
- """
15
- Fetch the database title from the Notion API.
16
-
17
- Returns:
18
- The database title or "Untitled" if no title is found
19
- """
20
- db_details = await self._client.get(f"databases/{self.database_id}")
21
- if not db_details:
22
- return "Untitled"
23
-
24
- title = "Untitled"
25
- if "title" in db_details:
26
- title_parts = []
27
- for text_obj in db_details["title"]:
28
- if "plain_text" in text_obj:
29
- title_parts.append(text_obj["plain_text"])
30
-
31
- if title_parts:
32
- title = "".join(title_parts)
33
-
34
- return title
35
-
36
- @property
37
- def title(self) -> Optional[str]:
38
- return self._title
39
-
40
- async def load_title(self) -> str:
41
- """Lädt den Titel der Datenbank und speichert ihn im Cache"""
42
- self._title = await self.fetch_database_title()
43
- return self._title
@@ -1,150 +0,0 @@
1
- from typing import Type, List
2
- from notionary.elements.notion_block_element import NotionBlockElement
3
-
4
-
5
- class MarkdownSyntaxPromptBuilder:
6
- """
7
- Generator for LLM system prompts that describe Notion-Markdown syntax.
8
-
9
- This class extracts information about supported Markdown patterns
10
- and formats them optimally for LLMs.
11
- """
12
-
13
- SYSTEM_PROMPT_TEMPLATE = """You are a knowledgeable assistant that helps users create content for Notion pages.
14
- Notion supports standard Markdown with some special extensions for creating rich content.
15
-
16
- # Understanding Notion Blocks
17
- Notion documents are composed of individual blocks. Each block has a specific type (paragraph, heading, list item, etc.) and format.
18
- The Markdown syntax you use directly maps to these Notion blocks.
19
-
20
- ## Inline Formatting
21
- Inline formatting can be used within most block types to style your text. You can combine multiple formatting options.
22
- **Syntax:** **bold**, *italic*, `code`, ~~strikethrough~~, __underline__, [text](url)
23
- **Examples:**
24
- - This text has a **bold** word.
25
- - This text has an *italic* word.
26
- - This text has `code` formatting.
27
- - This text has ~~strikethrough~~ formatting.
28
- - This text has __underlined__ formatting.
29
- - This has a [hyperlink](https://example.com).
30
- - You can **combine *different* formatting** styles.
31
-
32
- **When to use:** Use inline formatting to highlight important words, provide emphasis, show code or paths, or add hyperlinks. This helps create visual hierarchy and improves scanability.
33
-
34
- ## Spacers and Block Separation
35
- There are two ways to create visual separation between blocks:
36
-
37
- 1. **Empty Lines**: Simply add a blank line between blocks
38
- **Syntax:** Press Enter twice between blocks
39
- **Example:**
40
- First paragraph.
41
-
42
- Second paragraph after an empty line.
43
-
44
- 2. **HTML Comment Spacer**: For more deliberate spacing between logical sections
45
- **Syntax:** <!-- spacer -->
46
- **Example:**
47
- ## First Section
48
- Content here.
49
- <!-- spacer -->
50
- ## Second Section
51
- More content here.
52
-
53
- **When to use:** Use empty lines for basic separation between blocks. Use the HTML comment spacer (<!-- spacer -->) to create more obvious visual separation between major logical sections of your document.
54
-
55
- {element_docs}
56
-
57
- CRITICAL USAGE GUIDELINES:
58
-
59
- 1. Do NOT start content with a level 1 heading (# Heading). In Notion, the page title is already displayed in the metadata, so starting with an H1 heading is redundant. Begin with H2 (## Heading) or lower for section headings.
60
-
61
- 2. INLINE FORMATTING - VERY IMPORTANT:
62
- ✅ You can use inline formatting within almost any block type.
63
- ✅ Combine **bold**, *italic*, `code`, and other formatting as needed.
64
- ✅ Format text to create visual hierarchy and emphasize important points.
65
- ❌ DO NOT overuse formatting - be strategic with formatting for best readability.
66
-
67
- 3. BACKTICK HANDLING - EXTREMELY IMPORTANT:
68
- ❌ NEVER wrap entire content or responses in triple backticks (```).
69
- ❌ DO NOT use triple backticks (```) for anything except CODE BLOCKS or DIAGRAMS.
70
- ❌ DO NOT use triple backticks to mark or highlight regular text or examples.
71
- ✅ USE triple backticks ONLY for actual programming code, pseudocode, or specialized notation.
72
- ✅ For inline code, use single backticks (`code`).
73
- ✅ When showing Markdown syntax examples, use inline code formatting with single backticks.
74
-
75
- 4. BLOCK SEPARATION - IMPORTANT:
76
- ✅ Use empty lines between different blocks to ensure proper rendering in Notion.
77
- ✅ For major logical sections, add the HTML comment spacer: <!-- spacer -->
78
- ✅ This spacer creates better visual breaks between key sections of your document.
79
- ⚠️ While headings can sometimes work without an empty line before the following paragraph, including empty lines between all block types ensures consistent rendering.
80
-
81
- 5. TOGGLE BLOCKS - NOTE:
82
- ✅ For toggle blocks and collapsible headings, use pipe prefixes (|) for content.
83
- ✅ Each line within a toggle should start with a pipe character followed by a space.
84
- ❌ Do not use the pipe character for any other blocks.
85
-
86
- 6. CONTENT FORMATTING - CRITICAL:
87
- ❌ DO NOT include introductory phrases like "I understand that..." or "Here's the content...".
88
- ✅ Provide ONLY the requested content directly without any prefacing text or meta-commentary.
89
- ✅ Generate just the content itself, formatted according to these guidelines.
90
- """
91
-
92
- @staticmethod
93
- def generate_element_doc(element_class: Type[NotionBlockElement]) -> str:
94
- """
95
- Generates documentation for a specific NotionBlockElement in a compact format.
96
- Uses the element's get_llm_prompt_content method if available.
97
- """
98
- class_name = element_class.__name__
99
- element_name = class_name.replace("Element", "")
100
-
101
- # Check if the class has the get_llm_prompt_content method
102
- if not hasattr(element_class, "get_llm_prompt_content") or not callable(
103
- getattr(element_class, "get_llm_prompt_content")
104
- ):
105
- return f"## {element_name}"
106
-
107
- # Get the element content
108
- content = element_class.get_llm_prompt_content()
109
-
110
- doc_parts = [
111
- f"## {element_name}",
112
- f"{content.description}",
113
- f"**Syntax:** {content.syntax}",
114
- f"**Example:** {content.examples[0]}" if content.examples else "",
115
- f"**When to use:** {content.when_to_use}",
116
- ]
117
-
118
- if content.avoid:
119
- doc_parts.append(f"**Avoid:** {content.avoid}")
120
-
121
- return "\n".join([part for part in doc_parts if part])
122
-
123
- @classmethod
124
- def generate_element_docs(
125
- cls, element_classes: List[Type[NotionBlockElement]]
126
- ) -> str:
127
- """
128
- Generates complete documentation for all provided element classes.
129
- """
130
- docs = [
131
- "# Markdown Syntax for Notion Blocks",
132
- "The following Markdown patterns are supported for creating Notion blocks:",
133
- ]
134
-
135
- # Generate docs for each element
136
- for element in element_classes:
137
- docs.append("\n" + cls.generate_element_doc(element))
138
-
139
- return "\n".join(docs)
140
-
141
- @classmethod
142
- def generate_system_prompt(
143
- cls,
144
- element_classes: List[Type[NotionBlockElement]],
145
- ) -> str:
146
- """
147
- Generates a complete system prompt for LLMs.
148
- """
149
- element_docs = cls.generate_element_docs(element_classes)
150
- return cls.SYSTEM_PROMPT_TEMPLATE.format(element_docs=element_docs)