notionary 0.1.11__py3-none-any.whl → 0.1.13__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 (55) hide show
  1. notionary/__init__.py +21 -6
  2. notionary/{core/converters → converters}/elements/audio_element.py +7 -5
  3. notionary/{core/converters → converters}/elements/bookmark_element.py +1 -1
  4. notionary/{core/converters → converters}/elements/callout_element.py +2 -2
  5. notionary/{core/converters → converters}/elements/code_block_element.py +1 -1
  6. notionary/{core/converters → converters}/elements/column_element.py +1 -1
  7. notionary/{core/converters → converters}/elements/divider_element.py +1 -1
  8. notionary/{core/converters → converters}/elements/embed_element.py +3 -5
  9. notionary/{core/converters → converters}/elements/heading_element.py +2 -2
  10. notionary/{core/converters → converters}/elements/image_element.py +1 -1
  11. notionary/{core/converters → converters}/elements/list_element.py +2 -2
  12. notionary/{core/converters → converters}/elements/paragraph_element.py +2 -2
  13. notionary/{core/converters → converters}/elements/qoute_element.py +1 -1
  14. notionary/{core/converters → converters}/elements/table_element.py +2 -2
  15. notionary/{core/converters → converters}/elements/todo_lists.py +2 -2
  16. notionary/{core/converters → converters}/elements/toggle_element.py +24 -21
  17. notionary/{core/converters → converters}/elements/video_element.py +1 -1
  18. notionary/{core/converters → converters}/markdown_to_notion_converter.py +72 -111
  19. notionary/{core/converters → converters}/notion_to_markdown_converter.py +2 -2
  20. notionary/{core/converters → converters}/registry/block_element_registry.py +5 -5
  21. notionary/{core/converters → converters}/registry/block_element_registry_builder.py +18 -18
  22. notionary/database/database_discovery.py +142 -0
  23. notionary/{core/database → database}/database_info_service.py +1 -1
  24. notionary/{core/database/notion_database_manager.py → database/notion_database.py} +33 -57
  25. notionary/{core/database/notion_database_manager_factory.py → database/notion_database_factory.py} +18 -16
  26. notionary/{core/notion_client.py → notion_client.py} +4 -2
  27. notionary/page/content/notion_page_content_chunker.py +84 -0
  28. notionary/{core/page → page}/content/page_content_manager.py +29 -13
  29. notionary/{core/page → page}/metadata/metadata_editor.py +59 -46
  30. notionary/{core/page → page}/metadata/notion_icon_manager.py +10 -12
  31. notionary/{core/page → page}/metadata/notion_page_cover_manager.py +16 -21
  32. notionary/page/notion_page.py +504 -0
  33. notionary/page/notion_page_factory.py +256 -0
  34. notionary/{core/page → page}/properites/database_property_service.py +115 -99
  35. notionary/{core/page → page}/properites/page_property_manager.py +81 -52
  36. notionary/{core/page → page}/properites/property_formatter.py +1 -1
  37. notionary/{core/page → page}/properites/property_operation_result.py +43 -30
  38. notionary/{core/page → page}/properites/property_value_extractor.py +26 -8
  39. notionary/{core/page → page}/relations/notion_page_relation_manager.py +72 -53
  40. notionary/{core/page → page}/relations/notion_page_title_resolver.py +12 -12
  41. notionary/{core/page → page}/relations/page_database_relation.py +15 -15
  42. notionary/{core/page → page}/relations/relation_operation_result.py +50 -41
  43. notionary/util/page_id_utils.py +14 -8
  44. {notionary-0.1.11.dist-info → notionary-0.1.13.dist-info}/METADATA +1 -1
  45. notionary-0.1.13.dist-info/RECORD +56 -0
  46. notionary/core/database/notion_database_schema.py +0 -104
  47. notionary/core/page/notion_page_manager.py +0 -322
  48. notionary-0.1.11.dist-info/RECORD +0 -54
  49. /notionary/{core/converters → converters}/__init__.py +0 -0
  50. /notionary/{core/converters → converters}/elements/notion_block_element.py +0 -0
  51. /notionary/{core/converters → converters}/elements/text_inline_formatter.py +0 -0
  52. /notionary/{core/database → database}/models/page_result.py +0 -0
  53. {notionary-0.1.11.dist-info → notionary-0.1.13.dist-info}/WHEEL +0 -0
  54. {notionary-0.1.11.dist-info → notionary-0.1.13.dist-info}/licenses/LICENSE +0 -0
  55. {notionary-0.1.11.dist-info → notionary-0.1.13.dist-info}/top_level.txt +0 -0
@@ -1,40 +1,53 @@
1
1
  from typing import Dict, Any, List, Optional
2
- from notionary.core.notion_client import NotionClient
3
- from notionary.core.page.metadata.metadata_editor import MetadataEditor
4
- from notionary.core.page.properites.property_operation_result import PropertyOperationResult
5
- from notionary.core.page.relations.notion_page_title_resolver import NotionPageTitleResolver
6
- from notionary.core.page.properites.database_property_service import DatabasePropertyService
7
- from notionary.core.page.relations.page_database_relation import PageDatabaseRelation
8
- from notionary.core.page.properites.property_value_extractor import PropertyValueExtractor
2
+ from notionary.notion_client import NotionClient
3
+ from notionary.page.metadata.metadata_editor import MetadataEditor
4
+ from notionary.page.properites.property_operation_result import (
5
+ PropertyOperationResult,
6
+ )
7
+ from notionary.page.relations.notion_page_title_resolver import (
8
+ NotionPageTitleResolver,
9
+ )
10
+ from notionary.page.properites.database_property_service import (
11
+ DatabasePropertyService,
12
+ )
13
+ from notionary.page.relations.page_database_relation import PageDatabaseRelation
14
+ from notionary.page.properites.property_value_extractor import (
15
+ PropertyValueExtractor,
16
+ )
9
17
  from notionary.util.logging_mixin import LoggingMixin
10
18
 
19
+
11
20
  class PagePropertyManager(LoggingMixin):
12
21
  """Verwaltet den Zugriff auf und die Änderung von Seiteneigenschaften."""
13
-
14
- def __init__(self, page_id: str, client: NotionClient,
15
- metadata_editor: MetadataEditor,
16
- db_relation: PageDatabaseRelation):
22
+
23
+ def __init__(
24
+ self,
25
+ page_id: str,
26
+ client: NotionClient,
27
+ metadata_editor: MetadataEditor,
28
+ db_relation: PageDatabaseRelation,
29
+ ):
17
30
  self._page_id = page_id
18
31
  self._client = client
19
32
  self._page_data = None
20
33
  self._metadata_editor = metadata_editor
21
34
  self._db_relation = db_relation
22
35
  self._db_property_service = None
23
-
36
+
24
37
  self._extractor = PropertyValueExtractor(self.logger)
25
38
  self._title_resolver = NotionPageTitleResolver(client)
26
-
39
+
27
40
  async def get_properties(self) -> Dict[str, Any]:
28
41
  """Retrieves all properties of the page."""
29
42
  page_data = await self._get_page_data()
30
43
  if page_data and "properties" in page_data:
31
44
  return page_data["properties"]
32
45
  return {}
33
-
46
+
34
47
  async def get_property_value(self, property_name: str, relation_getter=None) -> Any:
35
48
  """
36
49
  Get the value of a specific property.
37
-
50
+
38
51
  Args:
39
52
  property_name: Name of the property to get
40
53
  relation_getter: Optional callback function to get relation values
@@ -42,105 +55,121 @@ class PagePropertyManager(LoggingMixin):
42
55
  properties = await self.get_properties()
43
56
  if property_name not in properties:
44
57
  return None
45
-
58
+
46
59
  prop_data = properties[property_name]
47
60
  return await self._extractor.extract(property_name, prop_data, relation_getter)
48
-
49
61
 
50
- async def set_property_by_name(self, property_name: str, value: Any) -> PropertyOperationResult:
62
+ async def set_property_by_name(
63
+ self, property_name: str, value: Any
64
+ ) -> PropertyOperationResult:
51
65
  """
52
66
  Set a property value by name, automatically detecting the property type.
53
-
67
+
54
68
  Args:
55
69
  property_name: Name of the property
56
70
  value: Value to set
57
-
71
+
58
72
  Returns:
59
- PropertyOperationResult: Result of the operation with status, error messages,
73
+ PropertyOperationResult: Result of the operation with status, error messages,
60
74
  and available options if applicable
61
75
  """
62
76
  property_type = await self.get_property_type(property_name)
63
-
77
+
64
78
  if property_type == "relation":
65
- result = PropertyOperationResult.from_relation_type_error(property_name, value)
79
+ result = PropertyOperationResult.from_relation_type_error(
80
+ property_name, value
81
+ )
66
82
  self.logger.warning(result.error)
67
83
  return result
68
-
84
+
69
85
  if not await self._db_relation.is_database_page():
70
- api_response = await self._metadata_editor.set_property_by_name(property_name, value)
86
+ api_response = await self._metadata_editor.set_property_by_name(
87
+ property_name, value
88
+ )
71
89
  if api_response:
72
90
  await self.invalidate_cache()
73
- return PropertyOperationResult.from_success(property_name, value, api_response)
91
+ return PropertyOperationResult.from_success(
92
+ property_name, value, api_response
93
+ )
74
94
  return PropertyOperationResult.from_no_api_response(property_name, value)
75
-
95
+
76
96
  db_service = await self._init_db_property_service()
77
-
97
+
78
98
  if not db_service:
79
- api_response = await self._metadata_editor.set_property_by_name(property_name, value)
99
+ api_response = await self._metadata_editor.set_property_by_name(
100
+ property_name, value
101
+ )
80
102
  if api_response:
81
103
  await self.invalidate_cache()
82
- return PropertyOperationResult.from_success(property_name, value, api_response)
104
+ return PropertyOperationResult.from_success(
105
+ property_name, value, api_response
106
+ )
83
107
  return PropertyOperationResult.from_no_api_response(property_name, value)
84
-
85
- is_valid, error_message, available_options = await db_service.validate_property_value(
86
- property_name, value
108
+
109
+ is_valid, error_message, available_options = (
110
+ await db_service.validate_property_value(property_name, value)
87
111
  )
88
-
112
+
89
113
  if not is_valid:
90
114
  if available_options:
91
115
  options_str = "', '".join(available_options)
92
116
  detailed_error = f"{error_message}\nAvailable options for '{property_name}': '{options_str}'"
93
117
  self.logger.warning(detailed_error)
94
118
  else:
95
- self.logger.warning("%s\nNo valid options available for '%s'", error_message, property_name)
96
-
119
+ self.logger.warning(
120
+ "%s\nNo valid options available for '%s'",
121
+ error_message,
122
+ property_name,
123
+ )
124
+
97
125
  return PropertyOperationResult.from_error(
98
- property_name,
99
- error_message,
100
- value,
101
- available_options
126
+ property_name, error_message, value, available_options
102
127
  )
103
-
104
- api_response = await self._metadata_editor.set_property_by_name(property_name, value)
128
+
129
+ api_response = await self._metadata_editor.set_property_by_name(
130
+ property_name, value
131
+ )
105
132
  if api_response:
106
133
  await self.invalidate_cache()
107
- return PropertyOperationResult.from_success(property_name, value, api_response)
108
-
134
+ return PropertyOperationResult.from_success(
135
+ property_name, value, api_response
136
+ )
137
+
109
138
  return PropertyOperationResult.from_no_api_response(property_name, value)
110
-
139
+
111
140
  async def get_property_type(self, property_name: str) -> Optional[str]:
112
141
  """Gets the type of a specific property."""
113
142
  db_service = await self._init_db_property_service()
114
143
  if db_service:
115
144
  return await db_service.get_property_type(property_name)
116
145
  return None
117
-
146
+
118
147
  async def get_available_options_for_property(self, property_name: str) -> List[str]:
119
148
  """Gets the available option names for a property."""
120
149
  db_service = await self._init_db_property_service()
121
150
  if db_service:
122
151
  return await db_service.get_option_names(property_name)
123
152
  return []
124
-
153
+
125
154
  async def _get_page_data(self, force_refresh=False) -> Dict[str, Any]:
126
155
  """Gets the page data and caches it for future use."""
127
156
  if self._page_data is None or force_refresh:
128
157
  self._page_data = await self._client.get_page(self._page_id)
129
158
  return self._page_data
130
-
159
+
131
160
  async def invalidate_cache(self) -> None:
132
161
  """Forces a refresh of the cached page data on next access."""
133
162
  self._page_data = None
134
-
163
+
135
164
  async def _init_db_property_service(self) -> Optional[DatabasePropertyService]:
136
165
  """Lazily initializes the database property service if needed."""
137
166
  if self._db_property_service is not None:
138
167
  return self._db_property_service
139
-
168
+
140
169
  database_id = await self._db_relation.get_parent_database_id()
141
170
  if not database_id:
142
171
  return None
143
-
172
+
144
173
  self._db_property_service = DatabasePropertyService(database_id, self._client)
145
174
  await self._db_property_service.load_schema()
146
- return self._db_property_service
175
+ return self._db_property_service
@@ -93,4 +93,4 @@ class NotionPropertyFormatter(LoggingMixin):
93
93
  self.logger.warning("Unknown property type: %s", property_type)
94
94
  return None
95
95
 
96
- return formatter(value)
96
+ return formatter(value)
@@ -1,11 +1,12 @@
1
1
  from typing import Any, Dict, List, Optional
2
2
  from dataclasses import dataclass
3
3
 
4
+
4
5
  @dataclass
5
6
  class PropertyOperationResult:
6
7
  """
7
8
  Result of a property operation in Notion.
8
-
9
+
9
10
  Attributes:
10
11
  success: Whether the operation was successful
11
12
  property_name: Name of the affected property
@@ -14,90 +15,102 @@ class PropertyOperationResult:
14
15
  available_options: Available options for select-like properties
15
16
  api_response: The original API response
16
17
  """
18
+
17
19
  success: bool
18
20
  property_name: str
19
21
  value: Optional[Any] = None
20
22
  error: Optional[str] = None
21
23
  available_options: Optional[List[str]] = None
22
24
  api_response: Optional[Dict[str, Any]] = None
23
-
25
+
24
26
  # Common error messages
25
27
  NO_API_RESPONSE = "Failed to set property (no API response)"
26
28
  RELATION_TYPE_ERROR = "Property '{}' is of type 'relation'. Relations must be set using the RelationManager."
27
-
29
+
28
30
  @classmethod
29
- def from_success(cls, property_name: str, value: Any, api_response: Dict[str, Any]) -> "PropertyOperationResult":
31
+ def from_success(
32
+ cls, property_name: str, value: Any, api_response: Dict[str, Any]
33
+ ) -> "PropertyOperationResult":
30
34
  """Creates a success result."""
31
35
  return cls(
32
- success=True,
33
- property_name=property_name,
34
- value=value,
35
- api_response=api_response
36
+ success=True,
37
+ property_name=property_name,
38
+ value=value,
39
+ api_response=api_response,
36
40
  )
37
-
41
+
38
42
  @classmethod
39
- def from_error(cls, property_name: str,
40
- error: str,
41
- value: Optional[Any] = None,
42
- available_options: Optional[List[str]] = None) -> "PropertyOperationResult":
43
+ def from_error(
44
+ cls,
45
+ property_name: str,
46
+ error: str,
47
+ value: Optional[Any] = None,
48
+ available_options: Optional[List[str]] = None,
49
+ ) -> "PropertyOperationResult":
43
50
  """Creates an error result."""
44
51
  return cls(
45
52
  success=False,
46
53
  property_name=property_name,
47
54
  value=value,
48
55
  error=error,
49
- available_options=available_options or []
56
+ available_options=available_options or [],
50
57
  )
51
-
58
+
52
59
  @classmethod
53
- def from_api_error(cls, property_name: str, api_response: Dict[str, Any]) -> "PropertyOperationResult":
60
+ def from_api_error(
61
+ cls, property_name: str, api_response: Dict[str, Any]
62
+ ) -> "PropertyOperationResult":
54
63
  """Creates a result from an API error response."""
55
64
  return cls(
56
65
  success=False,
57
66
  property_name=property_name,
58
67
  error=api_response.get("message", "Unknown API error"),
59
- api_response=api_response
68
+ api_response=api_response,
60
69
  )
61
-
70
+
62
71
  @classmethod
63
- def from_no_api_response(cls, property_name: str, value: Optional[Any] = None) -> "PropertyOperationResult":
72
+ def from_no_api_response(
73
+ cls, property_name: str, value: Optional[Any] = None
74
+ ) -> "PropertyOperationResult":
64
75
  """Creates a standardized result for missing API responses."""
65
76
  return cls.from_error(property_name, cls.NO_API_RESPONSE, value)
66
77
 
67
78
  @classmethod
68
- def from_relation_type_error(cls, property_name: str, value: Optional[Any] = None) -> "PropertyOperationResult":
79
+ def from_relation_type_error(
80
+ cls, property_name: str, value: Optional[Any] = None
81
+ ) -> "PropertyOperationResult":
69
82
  """Creates a standardized error result for relation type properties."""
70
83
  error_msg = cls.RELATION_TYPE_ERROR.format(property_name)
71
84
  return cls.from_error(property_name, error_msg, value)
72
-
85
+
73
86
  def to_dict(self) -> Dict[str, Any]:
74
87
  """Converts the result to a dictionary."""
75
88
  result = {
76
89
  "success": self.success,
77
90
  "property": self.property_name,
78
91
  }
79
-
92
+
80
93
  if self.value is not None:
81
94
  result["value"] = self.value
82
-
95
+
83
96
  if not self.success:
84
97
  result["error"] = self.error
85
-
98
+
86
99
  if self.available_options:
87
100
  result["available_options"] = self.available_options
88
-
101
+
89
102
  if self.api_response:
90
103
  result["api_response"] = self.api_response
91
-
104
+
92
105
  return result
93
-
106
+
94
107
  def __str__(self) -> str:
95
108
  """String representation of the result."""
96
109
  if self.success:
97
110
  return f"Success: Property '{self.property_name}' set to '{self.value}'"
98
-
111
+
99
112
  if self.available_options:
100
113
  options = "', '".join(self.available_options)
101
114
  return f"Error: {self.error}\nAvailable options for '{self.property_name}': '{options}'"
102
-
103
- return f"Error: {self.error}"
115
+
116
+ return f"Error: {self.error}"
@@ -10,19 +10,33 @@ class PropertyValueExtractor:
10
10
  self,
11
11
  property_name: str,
12
12
  prop_data: dict,
13
- relation_resolver: Callable[[str], Awaitable[Any]]
13
+ relation_resolver: Callable[[str], Awaitable[Any]],
14
14
  ) -> Any:
15
15
  prop_type = prop_data.get("type")
16
16
  if not prop_type:
17
17
  return None
18
18
 
19
19
  handlers: dict[str, Callable[[], Awaitable[Any] | Any]] = {
20
- "title": lambda: "".join(t.get("plain_text", "") for t in prop_data.get("title", [])),
21
- "rich_text": lambda: "".join(t.get("plain_text", "") for t in prop_data.get("rich_text", [])),
20
+ "title": lambda: "".join(
21
+ t.get("plain_text", "") for t in prop_data.get("title", [])
22
+ ),
23
+ "rich_text": lambda: "".join(
24
+ t.get("plain_text", "") for t in prop_data.get("rich_text", [])
25
+ ),
22
26
  "number": lambda: prop_data.get("number"),
23
- "select": lambda: prop_data.get("select", {}).get("name") if prop_data.get("select") else None,
24
- "multi_select": lambda: [o.get("name") for o in prop_data.get("multi_select", [])],
25
- "status": lambda: prop_data.get("status", {}).get("name") if prop_data.get("status") else None,
27
+ "select": lambda: (
28
+ prop_data.get("select", {}).get("name")
29
+ if prop_data.get("select")
30
+ else None
31
+ ),
32
+ "multi_select": lambda: [
33
+ o.get("name") for o in prop_data.get("multi_select", [])
34
+ ],
35
+ "status": lambda: (
36
+ prop_data.get("status", {}).get("name")
37
+ if prop_data.get("status")
38
+ else None
39
+ ),
26
40
  "date": lambda: prop_data.get("date"),
27
41
  "checkbox": lambda: prop_data.get("checkbox"),
28
42
  "url": lambda: prop_data.get("url"),
@@ -31,9 +45,13 @@ class PropertyValueExtractor:
31
45
  "relation": lambda: relation_resolver(property_name),
32
46
  "people": lambda: [p.get("id") for p in prop_data.get("people", [])],
33
47
  "files": lambda: [
34
- f.get("external", {}).get("url") if f.get("type") == "external" else f.get("name")
48
+ (
49
+ f.get("external", {}).get("url")
50
+ if f.get("type") == "external"
51
+ else f.get("name")
52
+ )
35
53
  for f in prop_data.get("files", [])
36
- ]
54
+ ],
37
55
  }
38
56
 
39
57
  handler = handlers.get(prop_type)