notionary 0.2.12__py3-none-any.whl → 0.2.14__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 (86) hide show
  1. notionary/__init__.py +3 -20
  2. notionary/{notion_client.py → base_notion_client.py} +92 -98
  3. notionary/blocks/__init__.py +61 -0
  4. notionary/{elements → blocks}/audio_element.py +6 -4
  5. notionary/{elements → blocks}/bookmark_element.py +3 -6
  6. notionary/{elements → blocks}/bulleted_list_element.py +5 -7
  7. notionary/{elements → blocks}/callout_element.py +5 -8
  8. notionary/{elements → blocks}/code_block_element.py +4 -6
  9. notionary/{elements → blocks}/column_element.py +3 -6
  10. notionary/{elements → blocks}/divider_element.py +3 -6
  11. notionary/{elements → blocks}/embed_element.py +4 -6
  12. notionary/{elements → blocks}/heading_element.py +5 -9
  13. notionary/{elements → blocks}/image_element.py +4 -6
  14. notionary/{elements → blocks}/mention_element.py +3 -7
  15. notionary/blocks/notion_block_client.py +26 -0
  16. notionary/blocks/notion_block_element.py +34 -0
  17. notionary/{elements → blocks}/numbered_list_element.py +4 -7
  18. notionary/{elements → blocks}/paragraph_element.py +4 -7
  19. notionary/{prompting/element_prompt_content.py → blocks/prompts/element_prompt_builder.py} +1 -40
  20. notionary/blocks/prompts/element_prompt_content.py +41 -0
  21. notionary/{elements → blocks}/qoute_element.py +4 -6
  22. notionary/{elements → blocks}/registry/block_registry.py +4 -26
  23. notionary/{elements → blocks}/registry/block_registry_builder.py +26 -25
  24. notionary/{elements → blocks}/table_element.py +6 -8
  25. notionary/{elements → blocks}/text_inline_formatter.py +1 -4
  26. notionary/{elements → blocks}/todo_element.py +6 -8
  27. notionary/{elements → blocks}/toggle_element.py +3 -6
  28. notionary/{elements → blocks}/toggleable_heading_element.py +5 -8
  29. notionary/{elements → blocks}/video_element.py +4 -6
  30. notionary/cli/main.py +245 -53
  31. notionary/cli/onboarding.py +117 -0
  32. notionary/database/__init__.py +0 -0
  33. notionary/database/client.py +132 -0
  34. notionary/database/database_exceptions.py +13 -0
  35. notionary/database/factory.py +0 -0
  36. notionary/database/filter_builder.py +175 -0
  37. notionary/database/notion_database.py +339 -128
  38. notionary/database/notion_database_provider.py +230 -0
  39. notionary/elements/__init__.py +0 -0
  40. notionary/models/notion_database_response.py +294 -13
  41. notionary/models/notion_page_response.py +9 -31
  42. notionary/models/search_response.py +0 -0
  43. notionary/page/__init__.py +0 -0
  44. notionary/page/client.py +110 -0
  45. notionary/page/content/page_content_retriever.py +5 -20
  46. notionary/page/content/page_content_writer.py +3 -4
  47. notionary/page/formatting/markdown_to_notion_converter.py +1 -3
  48. notionary/{prompting → page}/markdown_syntax_prompt_generator.py +1 -2
  49. notionary/page/notion_page.py +354 -317
  50. notionary/page/notion_to_markdown_converter.py +1 -4
  51. notionary/page/properites/property_value_extractor.py +0 -64
  52. notionary/page/{properites/property_formatter.py → property_formatter.py} +7 -4
  53. notionary/page/search_filter_builder.py +131 -0
  54. notionary/page/utils.py +60 -0
  55. notionary/util/__init__.py +12 -3
  56. notionary/util/factory_decorator.py +33 -0
  57. notionary/util/fuzzy_matcher.py +82 -0
  58. notionary/util/page_id_utils.py +0 -21
  59. notionary/util/singleton_metaclass.py +22 -0
  60. notionary/workspace.py +69 -0
  61. {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/METADATA +4 -1
  62. notionary-0.2.14.dist-info/RECORD +72 -0
  63. notionary/database/database_discovery.py +0 -142
  64. notionary/database/notion_database_factory.py +0 -193
  65. notionary/elements/notion_block_element.py +0 -70
  66. notionary/exceptions/database_exceptions.py +0 -76
  67. notionary/exceptions/page_creation_exception.py +0 -9
  68. notionary/page/metadata/metadata_editor.py +0 -150
  69. notionary/page/metadata/notion_icon_manager.py +0 -77
  70. notionary/page/metadata/notion_page_cover_manager.py +0 -56
  71. notionary/page/notion_page_factory.py +0 -332
  72. notionary/page/properites/database_property_service.py +0 -302
  73. notionary/page/properites/page_property_manager.py +0 -152
  74. notionary/page/relations/notion_page_relation_manager.py +0 -350
  75. notionary/page/relations/notion_page_title_resolver.py +0 -104
  76. notionary/page/relations/page_database_relation.py +0 -68
  77. notionary/telemetry/__init__.py +0 -7
  78. notionary/telemetry/telemetry.py +0 -226
  79. notionary/telemetry/track_usage_decorator.py +0 -76
  80. notionary/util/warn_direct_constructor_usage.py +0 -54
  81. notionary-0.2.12.dist-info/RECORD +0 -70
  82. /notionary/util/{singleton.py → singleton_decorator.py} +0 -0
  83. {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/WHEEL +0 -0
  84. {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/entry_points.txt +0 -0
  85. {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/licenses/LICENSE +0 -0
  86. {notionary-0.2.12.dist-info → notionary-0.2.14.dist-info}/top_level.txt +0 -0
@@ -1,302 +0,0 @@
1
- from typing import Dict, List, Optional, Any, Tuple
2
- from notionary.notion_client import NotionClient
3
- from notionary.util import LoggingMixin
4
-
5
-
6
- class DatabasePropertyService(LoggingMixin):
7
- """
8
- Service for working with Notion database properties and options.
9
- Provides specialized methods for retrieving property information and validating values.
10
- """
11
-
12
- def __init__(self, database_id: str, client: NotionClient):
13
- """
14
- Initialize the database property service.
15
-
16
- Args:
17
- database_id: ID of the Notion database
18
- client: Instance of NotionClient
19
- """
20
- self._database_id = database_id
21
- self._client = client
22
- self._schema = None
23
-
24
- async def load_schema(self, force_refresh: bool = False) -> bool:
25
- """
26
- Loads the database schema.
27
-
28
- Args:
29
- force_refresh: Whether to force a refresh of the schema
30
-
31
- Returns:
32
- True if schema loaded successfully, False otherwise.
33
- """
34
- if self._schema is not None and not force_refresh:
35
- return True
36
-
37
- try:
38
- database = await self._client.get_database(self._database_id)
39
-
40
- self._schema = database.properties
41
- self.logger.debug("Loaded schema for database %s", self._database_id)
42
- return True
43
-
44
- except Exception as e:
45
- self.logger.error(
46
- "Error loading database schema for %s: %s", self._database_id, str(e)
47
- )
48
- return False
49
-
50
- async def _ensure_schema_loaded(self) -> None:
51
- """
52
- Ensures the schema is loaded before accessing it.
53
- """
54
- if self._schema is None:
55
- await self.load_schema()
56
-
57
- async def get_schema(self) -> Dict[str, Any]:
58
- """
59
- Gets the database schema.
60
-
61
- Returns:
62
- Dict[str, Any]: The database schema
63
- """
64
- await self._ensure_schema_loaded()
65
- return self._schema or {}
66
-
67
- async def get_property_types(self) -> Dict[str, str]:
68
- """
69
- Gets all property types for the database.
70
-
71
- Returns:
72
- Dict[str, str]: Dictionary mapping property names to their types
73
- """
74
- await self._ensure_schema_loaded()
75
-
76
- if not self._schema:
77
- return {}
78
-
79
- return {
80
- prop_name: prop_data.get("type", "unknown")
81
- for prop_name, prop_data in self._schema.items()
82
- }
83
-
84
- async def get_property_schema(self, property_name: str) -> Optional[Dict[str, Any]]:
85
- """
86
- Gets the schema for a specific property.
87
-
88
- Args:
89
- property_name: The name of the property
90
-
91
- Returns:
92
- Optional[Dict[str, Any]]: The property schema or None if not found
93
- """
94
- await self._ensure_schema_loaded()
95
-
96
- if not self._schema or property_name not in self._schema:
97
- return None
98
-
99
- return self._schema[property_name]
100
-
101
- async def get_property_type(self, property_name: str) -> Optional[str]:
102
- """
103
- Gets the type of a specific property.
104
-
105
- Args:
106
- property_name: The name of the property
107
-
108
- Returns:
109
- Optional[str]: The property type or None if not found
110
- """
111
- property_schema = await self.get_property_schema(property_name)
112
-
113
- if not property_schema:
114
- return None
115
-
116
- return property_schema.get("type")
117
-
118
- async def property_exists(self, property_name: str) -> bool:
119
- """
120
- Checks if a property exists in the database.
121
-
122
- Args:
123
- property_name: The name of the property
124
-
125
- Returns:
126
- bool: True if the property exists, False otherwise
127
- """
128
- property_schema = await self.get_property_schema(property_name)
129
- return property_schema is not None
130
-
131
- async def get_property_options(self, property_name: str) -> List[Dict[str, Any]]:
132
- """
133
- Gets the available options for a property (select, multi_select, status).
134
-
135
- Args:
136
- property_name: The name of the property
137
-
138
- Returns:
139
- List[Dict[str, Any]]: List of available options with their metadata
140
- """
141
- property_schema = await self.get_property_schema(property_name)
142
-
143
- if not property_schema:
144
- return []
145
-
146
- property_type = property_schema.get("type")
147
-
148
- if property_type in ["select", "multi_select", "status"]:
149
- return property_schema.get(property_type, {}).get("options", [])
150
-
151
- return []
152
-
153
- async def get_option_names(self, property_name: str) -> List[str]:
154
- """
155
- Gets the available option names for a property (select, multi_select, status).
156
-
157
- Args:
158
- property_name: The name of the property
159
-
160
- Returns:
161
- List[str]: List of available option names
162
- """
163
- options = await self.get_property_options(property_name)
164
- return [option.get("name", "") for option in options]
165
-
166
- async def get_relation_details(
167
- self, property_name: str
168
- ) -> Optional[Dict[str, Any]]:
169
- """
170
- Gets details about a relation property, including the related database.
171
-
172
- Args:
173
- property_name: The name of the property
174
-
175
- Returns:
176
- Optional[Dict[str, Any]]: The relation details or None if not a relation
177
- """
178
- property_schema = await self.get_property_schema(property_name)
179
-
180
- if not property_schema or property_schema.get("type") != "relation":
181
- return None
182
-
183
- return property_schema.get("relation", {})
184
-
185
- async def get_relation_options(
186
- self, property_name: str, limit: int = 100
187
- ) -> List[Dict[str, Any]]:
188
- """
189
- Gets available options for a relation property by querying the related database.
190
-
191
- Args:
192
- property_name: The name of the relation property
193
- limit: Maximum number of options to retrieve
194
-
195
- Returns:
196
- List[Dict[str, Any]]: List of pages from the related database
197
- """
198
- relation_details = await self.get_relation_details(property_name)
199
-
200
- if not relation_details or "database_id" not in relation_details:
201
- return []
202
-
203
- related_db_id = relation_details["database_id"]
204
-
205
- try:
206
- # Query the related database to get options
207
- query_result = await self._client.post(
208
- f"databases/{related_db_id}/query",
209
- {
210
- "page_size": limit,
211
- },
212
- )
213
-
214
- if not query_result or "results" not in query_result:
215
- return []
216
-
217
- # Extract relevant information from each page
218
- options = []
219
- for page in query_result["results"]:
220
- page_id = page.get("id")
221
- title = self._extract_title_from_page(page)
222
-
223
- if page_id and title:
224
- options.append({"id": page_id, "name": title})
225
-
226
- return options
227
- except Exception as e:
228
- self.logger.error(f"Error getting relation options: {str(e)}")
229
- return []
230
-
231
- def _extract_title_from_page(self, page: Dict[str, Any]) -> Optional[str]:
232
- """
233
- Extracts the title from a page object.
234
-
235
- Args:
236
- page: The page object from Notion API
237
-
238
- Returns:
239
- Optional[str]: The page title or None if not found
240
- """
241
- if "properties" not in page:
242
- return None
243
-
244
- properties = page["properties"]
245
-
246
- # Look for a title property
247
- for prop_data in properties.values():
248
- if prop_data.get("type") == "title" and "title" in prop_data:
249
- title_parts = prop_data["title"]
250
- return "".join(
251
- [text_obj.get("plain_text", "") for text_obj in title_parts]
252
- )
253
-
254
- return None
255
-
256
- async def validate_property_value(
257
- self, property_name: str, value: Any
258
- ) -> Tuple[bool, Optional[str], Optional[List[str]]]:
259
- """
260
- Validates a value for a property.
261
-
262
- Args:
263
- property_name: The name of the property
264
- value: The value to validate
265
-
266
- Returns:
267
- Tuple[bool, Optional[str], Optional[List[str]]]:
268
- - Boolean indicating if valid
269
- - Error message if invalid
270
- - Available options if applicable
271
- """
272
- property_schema = await self.get_property_schema(property_name)
273
-
274
- if not property_schema:
275
- return False, f"Property '{property_name}' does not exist", None
276
-
277
- property_type = property_schema.get("type")
278
-
279
- # Validate select, multi_select, status properties
280
- if property_type in ["select", "status"]:
281
- options = await self.get_option_names(property_name)
282
-
283
- if isinstance(value, str) and value not in options:
284
- return (
285
- False,
286
- f"Invalid {property_type} option. Value '{value}' is not in the available options.",
287
- options,
288
- )
289
-
290
- elif property_type == "multi_select":
291
- options = await self.get_option_names(property_name)
292
-
293
- if isinstance(value, list):
294
- invalid_values = [val for val in value if val not in options]
295
- if invalid_values:
296
- return (
297
- False,
298
- f"Invalid multi_select options: {', '.join(invalid_values)}",
299
- options,
300
- )
301
-
302
- return True, None, None
@@ -1,152 +0,0 @@
1
- from typing import Dict, Any, List, Optional
2
- from notionary.models.notion_page_response import NotionPageResponse
3
- from notionary.notion_client import NotionClient
4
- from notionary.page.metadata.metadata_editor import MetadataEditor
5
- from notionary.page.properites.database_property_service import (
6
- DatabasePropertyService,
7
- )
8
- from notionary.page.relations.page_database_relation import PageDatabaseRelation
9
- from notionary.page.properites.property_value_extractor import (
10
- PropertyValueExtractor,
11
- )
12
- from notionary.util import LoggingMixin
13
-
14
-
15
- class PagePropertyManager(LoggingMixin):
16
- """Verwaltet den Zugriff auf und die Änderung von Seiteneigenschaften."""
17
-
18
- def __init__(
19
- self,
20
- page_id: str,
21
- client: NotionClient,
22
- metadata_editor: MetadataEditor,
23
- db_relation: PageDatabaseRelation,
24
- ):
25
- self._page_id = page_id
26
- self._client = client
27
- self._page_data = None
28
- self._metadata_editor = metadata_editor
29
- self._db_relation = db_relation
30
- self._db_property_service = None
31
-
32
- self._extractor = PropertyValueExtractor()
33
-
34
- async def get_property_value(self, property_name: str, relation_getter=None) -> Any:
35
- """
36
- Get the value of a specific property.
37
-
38
- Args:
39
- property_name: Name of the property to get
40
- relation_getter: Optional callback function to get relation values
41
- """
42
- properties = await self._get_properties()
43
- if property_name not in properties:
44
- return None
45
-
46
- prop_data = properties[property_name]
47
- return await self._extractor.extract(property_name, prop_data, relation_getter)
48
-
49
- async def set_property_by_name(
50
- self, property_name: str, value: Any
51
- ) -> Optional[Any]:
52
- """
53
- Set a property value by name, automatically detecting the property type.
54
-
55
- Args:
56
- property_name: Name of the property
57
- value: Value to set
58
-
59
- Returns:
60
- Optional[Any]: The new value if successful, None if failed
61
- """
62
- property_type = await self.get_property_type(property_name)
63
-
64
- if property_type == "relation":
65
- self.logger.warning(
66
- "Property '%s' is of type 'relation'. Relations must be set using the RelationManager.",
67
- property_name,
68
- )
69
- return None
70
-
71
- is_db_page = await self._db_relation.is_database_page()
72
- db_service = None
73
-
74
- if is_db_page:
75
- db_service = await self._init_db_property_service()
76
-
77
- if db_service:
78
- is_valid, error_message, available_options = (
79
- await db_service.validate_property_value(property_name, value)
80
- )
81
-
82
- if not is_valid:
83
- if available_options:
84
- options_str = "', '".join(available_options)
85
- self.logger.warning(
86
- "%s\nAvailable options for '%s': '%s'",
87
- error_message,
88
- property_name,
89
- options_str,
90
- )
91
- else:
92
- self.logger.warning(
93
- "%s\nNo valid options available for '%s'",
94
- error_message,
95
- property_name,
96
- )
97
- return None
98
-
99
- api_response = await self._metadata_editor.set_property_by_name(
100
- property_name, value
101
- )
102
-
103
- if api_response:
104
- await self.invalidate_cache()
105
- return value
106
-
107
- self.logger.warning(
108
- "Failed to set property '%s' (no API response)", property_name
109
- )
110
- return None
111
-
112
- async def get_property_type(self, property_name: str) -> Optional[str]:
113
- """Gets the type of a specific property."""
114
- db_service = await self._init_db_property_service()
115
- if db_service:
116
- return await db_service.get_property_type(property_name)
117
- return None
118
-
119
- async def get_available_options_for_property(self, property_name: str) -> List[str]:
120
- """Gets the available option names for a property."""
121
- db_service = await self._init_db_property_service()
122
- if db_service:
123
- return await db_service.get_option_names(property_name)
124
- return []
125
-
126
- async def _get_page_data(self, force_refresh=False) -> NotionPageResponse:
127
- """Gets the page data and caches it for future use."""
128
- if self._page_data is None or force_refresh:
129
- self._page_data = await self._client.get_page(self._page_id)
130
- return self._page_data
131
-
132
- async def invalidate_cache(self) -> None:
133
- """Forces a refresh of the cached page data on next access."""
134
- self._page_data = None
135
-
136
- async def _init_db_property_service(self) -> Optional[DatabasePropertyService]:
137
- """Lazily initializes the database property service if needed."""
138
- if self._db_property_service is not None:
139
- return self._db_property_service
140
-
141
- database_id = await self._db_relation.get_parent_database_id()
142
- if not database_id:
143
- return None
144
-
145
- self._db_property_service = DatabasePropertyService(database_id, self._client)
146
- await self._db_property_service.load_schema()
147
- return self._db_property_service
148
-
149
- async def _get_properties(self) -> Dict[str, Any]:
150
- """Retrieves all properties of the page."""
151
- page_data = await self._get_page_data()
152
- return page_data.properties if page_data.properties else {}