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,142 +0,0 @@
1
- from typing import (
2
- AsyncGenerator,
3
- Dict,
4
- List,
5
- Optional,
6
- Any,
7
- Tuple,
8
- )
9
- from notionary.notion_client import NotionClient
10
- from notionary.util import LoggingMixin
11
-
12
-
13
- class DatabaseDiscovery(LoggingMixin):
14
- """
15
- A utility class that discovers Notion databases accessible to your integration.
16
- Focused on efficiently retrieving essential database information.
17
- """
18
-
19
- def __init__(self, client: Optional[NotionClient] = None) -> None:
20
- """
21
- Initialize the database discovery with a NotionClient.
22
-
23
- Args:
24
- client: NotionClient instance for API communication
25
- """
26
- self._client = client if client else NotionClient()
27
- self.logger.info("DatabaseDiscovery initialized")
28
-
29
- async def __call__(self, page_size: int = 100) -> List[Tuple[str, str]]:
30
- """
31
- Discover databases and print the results in a nicely formatted way.
32
-
33
- This is a convenience method that discovers databases and handles
34
- the formatting and printing of results.
35
-
36
- Args:
37
- page_size: The number of databases to fetch per request
38
-
39
- Returns:
40
- The same list of databases as discover() for further processing
41
- """
42
- databases = await self._discover(page_size)
43
-
44
- if not databases:
45
- print("\n⚠️ No databases found!")
46
- print("Please ensure your Notion integration has access to databases.")
47
- print(
48
- "You need to share the databases with your integration in Notion settings."
49
- )
50
- return databases
51
-
52
- print(f"✅ Found {len(databases)} databases:")
53
-
54
- for i, (title, db_id) in enumerate(databases, 1):
55
- print(f"{i}. {title} (ID: {db_id})")
56
-
57
- return databases
58
-
59
- async def _discover(self, page_size: int = 100) -> List[Tuple[str, str]]:
60
- """
61
- Discover all accessible databases and return their titles and IDs.
62
-
63
- Args:
64
- page_size: The number of databases to fetch per request
65
-
66
- Returns:
67
- List of tuples containing (database_title, database_id)
68
- """
69
- databases = []
70
-
71
- async for database in self._iter_databases(page_size):
72
- db_id = database.get("id")
73
- if not db_id:
74
- continue
75
-
76
- title = self._extract_database_title(database)
77
- databases.append((title, db_id))
78
-
79
- return databases
80
-
81
- async def _iter_databases(
82
- self, page_size: int = 100
83
- ) -> AsyncGenerator[Dict[str, Any], None]:
84
- """
85
- Asynchronous generator that yields Notion databases one by one.
86
-
87
- Uses the Notion API to provide paginated access to all databases
88
- without loading all of them into memory at once.
89
-
90
- Args:
91
- page_size: The number of databases to fetch per request
92
-
93
- Yields:
94
- Individual database objects from the Notion API
95
- """
96
- start_cursor: Optional[str] = None
97
-
98
- while True:
99
- body: Dict[str, Any] = {
100
- "filter": {"value": "database", "property": "object"},
101
- "page_size": page_size,
102
- }
103
-
104
- if start_cursor:
105
- body["start_cursor"] = start_cursor
106
-
107
- result = await self._client.post("search", data=body)
108
-
109
- if not result or "results" not in result:
110
- self.logger.error("Error fetching databases")
111
- return
112
-
113
- for database in result["results"]:
114
- yield database
115
-
116
- if not result.get("has_more") or not result.get("next_cursor"):
117
- return
118
-
119
- start_cursor = result["next_cursor"]
120
-
121
- def _extract_database_title(self, database: Dict[str, Any]) -> str:
122
- """
123
- Extract the database title from a Notion API response.
124
-
125
- Args:
126
- database: The database object from the Notion API
127
-
128
- Returns:
129
- The extracted title or "Untitled" if no title is found
130
- """
131
- if "title" not in database:
132
- return "Untitled"
133
-
134
- title_parts = []
135
- for text_obj in database["title"]:
136
- if "plain_text" in text_obj:
137
- title_parts.append(text_obj["plain_text"])
138
-
139
- if not title_parts:
140
- return "Untitled"
141
-
142
- return "".join(title_parts)
@@ -1,193 +0,0 @@
1
- from typing import List, Optional, Dict, Any
2
- from difflib import SequenceMatcher
3
-
4
- from notionary.database.notion_database import NotionDatabase
5
- from notionary.notion_client import NotionClient
6
- from notionary.exceptions.database_exceptions import (
7
- DatabaseConnectionError,
8
- DatabaseInitializationError,
9
- DatabaseNotFoundException,
10
- DatabaseParsingError,
11
- NotionDatabaseException,
12
- )
13
- from notionary.telemetry import track_usage
14
- from notionary.util import LoggingMixin
15
- from notionary.util import format_uuid
16
- from notionary.util import singleton
17
-
18
-
19
- @singleton
20
- class NotionDatabaseFactory(LoggingMixin):
21
- """
22
- Factory class for creating NotionDatabaseManager instances.
23
- Provides methods for creating managers by database ID or name.
24
- """
25
-
26
- @classmethod
27
- @track_usage('page_factory_method_used', {'method': 'from_page_id'})
28
- def from_database_id(
29
- cls, database_id: str, token: Optional[str] = None
30
- ) -> NotionDatabase:
31
- """
32
- Create a NotionDatabaseManager from a database ID.
33
-
34
- Args:
35
- database_id: The ID of the Notion database
36
- token: Optional Notion API token (uses environment variable if not provided)
37
-
38
- Returns:
39
- An initialized NotionDatabaseManager instance
40
- """
41
-
42
- try:
43
- formatted_id = format_uuid(database_id) or database_id
44
-
45
- manager = NotionDatabase(formatted_id, token)
46
-
47
- cls.logger.info(
48
- "Successfully created database manager for ID: %s", formatted_id
49
- )
50
- return manager
51
-
52
- except DatabaseInitializationError:
53
- raise
54
- except NotionDatabaseException:
55
- raise
56
- except Exception as e:
57
- error_msg = f"Error connecting to database {database_id}: {str(e)}"
58
- cls.logger.error(error_msg)
59
- raise DatabaseConnectionError(error_msg) from e
60
-
61
- @classmethod
62
- @track_usage('page_factory_method_used', {'method': 'from_url'})
63
- async def from_database_name(
64
- cls, database_name: str, token: Optional[str] = None
65
- ) -> NotionDatabase:
66
- """
67
- Create a NotionDatabaseManager by finding a database with a matching name.
68
- Uses fuzzy matching to find the closest match to the given name.
69
-
70
- Args:
71
- database_name: The name of the Notion database to search for
72
- token: Optional Notion API token (uses environment variable if not provided)
73
-
74
- Returns:
75
- An initialized NotionDatabaseManager instance
76
- """
77
- cls.logger.debug("Searching for database with name: %s", database_name)
78
-
79
- client = NotionClient(token=token)
80
-
81
- try:
82
- cls.logger.debug("Using search endpoint to find databases")
83
-
84
- search_payload = {
85
- "filter": {"property": "object", "value": "database"},
86
- "page_size": 100,
87
- }
88
-
89
- response = await client.post("search", search_payload)
90
-
91
- if not response or "results" not in response:
92
- error_msg = "Failed to fetch databases using search endpoint"
93
- cls.logger.error(error_msg)
94
- raise DatabaseConnectionError(error_msg)
95
-
96
- databases = response.get("results", [])
97
-
98
- if not databases:
99
- error_msg = "No databases found"
100
- cls.logger.warning(error_msg)
101
- raise DatabaseNotFoundException(database_name, error_msg)
102
-
103
- cls.logger.debug(
104
- "Found %d databases, searching for best match", len(databases)
105
- )
106
-
107
- best_match = None
108
- best_score = 0
109
-
110
- for db in databases:
111
- title = cls._extract_title_from_database(db)
112
-
113
- score = SequenceMatcher(
114
- None, database_name.lower(), title.lower()
115
- ).ratio()
116
-
117
- if score > best_score:
118
- best_score = score
119
- best_match = db
120
-
121
- if best_score < 0.6 or not best_match:
122
- error_msg = f"No good database name match found for '{database_name}'. Best match had score {best_score:.2f}"
123
- cls.logger.warning(error_msg)
124
- raise DatabaseNotFoundException(database_name, error_msg)
125
-
126
- database_id = best_match.get("id")
127
-
128
- if not database_id:
129
- error_msg = "Best match database has no ID"
130
- cls.logger.error(error_msg)
131
- raise DatabaseParsingError(error_msg)
132
-
133
- matched_name = cls._extract_title_from_database(best_match)
134
-
135
- cls.logger.info(
136
- "Found matching database: '%s' (ID: %s) with score: %.2f",
137
- matched_name,
138
- database_id,
139
- best_score,
140
- )
141
-
142
- manager = NotionDatabase(database_id, token)
143
-
144
- cls.logger.info(
145
- "Successfully created database manager for '%s'", matched_name
146
- )
147
- await client.close()
148
- return manager
149
-
150
- except NotionDatabaseException:
151
- await client.close()
152
- raise
153
- except Exception as e:
154
- error_msg = f"Error finding database by name: {str(e)}"
155
- cls.logger.error(error_msg)
156
- await client.close()
157
- raise DatabaseConnectionError(error_msg) from e
158
-
159
- @classmethod
160
- def _extract_title_from_database(cls, database: Dict[str, Any]) -> str:
161
- """
162
- Extract the title from a database object.
163
- """
164
- try:
165
- if "title" in database:
166
- return cls._extract_text_from_rich_text(database["title"])
167
-
168
- if "properties" in database and "title" in database["properties"]:
169
- title_prop = database["properties"]["title"]
170
- if "title" in title_prop:
171
- return cls._extract_text_from_rich_text(title_prop["title"])
172
-
173
- return "Untitled"
174
-
175
- except Exception as e:
176
- error_msg = f"Error extracting database title: {str(e)}"
177
- cls.class_logger().warning(error_msg)
178
- raise DatabaseParsingError(error_msg) from e
179
-
180
- @classmethod
181
- def _extract_text_from_rich_text(cls, rich_text: List[Dict[str, Any]]) -> str:
182
- """
183
- Extract plain text from a rich text array.
184
- """
185
- if not rich_text:
186
- return ""
187
-
188
- text_parts = []
189
- for text_obj in rich_text:
190
- if "plain_text" in text_obj:
191
- text_parts.append(text_obj["plain_text"])
192
-
193
- return "".join(text_parts)
@@ -1,70 +0,0 @@
1
- from functools import wraps
2
- from typing import Dict, Any, Optional
3
- from abc import ABC
4
-
5
- from notionary.prompting.element_prompt_content import ElementPromptContent
6
- from notionary.telemetry import track_usage
7
-
8
-
9
- class NotionBlockElement(ABC):
10
- """Base class for elements that can be converted between Markdown and Notion."""
11
-
12
- @classmethod
13
- def markdown_to_notion(cls, text: str) -> Optional[Dict[str, Any]]:
14
- """Convert markdown to Notion block."""
15
-
16
- @classmethod
17
- def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
18
- """Convert Notion block to markdown."""
19
-
20
- @classmethod
21
- def match_markdown(cls, text: str) -> bool:
22
- """Check if this element can handle the given markdown text."""
23
- return bool(cls.markdown_to_notion(text)) # Now calls the class's version
24
-
25
- @classmethod
26
- def match_notion(cls, block: Dict[str, Any]) -> bool:
27
- """Check if this element can handle the given Notion block."""
28
- return bool(cls.notion_to_markdown(block)) # Now calls the class's version
29
-
30
- @classmethod
31
- def is_multiline(cls) -> bool:
32
- return False
33
-
34
- @classmethod
35
- def get_llm_prompt_content(cls) -> ElementPromptContent:
36
- """Returns a dictionary with information for LLM prompts about this element."""
37
-
38
-
39
- def auto_track_conversions(cls):
40
- """
41
- Decorator der sich auch auf Subklassen vererbt.
42
- """
43
- conversion_methods = ['markdown_to_notion', 'notion_to_markdown']
44
-
45
- original_init_subclass = getattr(cls, '__init_subclass__', None)
46
-
47
- @classmethod
48
- def __init_subclass__(cls_inner, **kwargs):
49
- # Original __init_subclass__ aufrufen
50
- if original_init_subclass:
51
- original_init_subclass(**kwargs)
52
-
53
- # Tracking für Subklasse hinzufügen
54
- for method_name in conversion_methods:
55
- if hasattr(cls_inner, method_name):
56
- original_method = getattr(cls_inner, method_name)
57
-
58
- if isinstance(original_method, classmethod):
59
- func = original_method.__func__
60
-
61
- @track_usage(f"{cls_inner.__name__.lower()}_{method_name}")
62
- @classmethod
63
- @wraps(func)
64
- def tracked_method(cls_ref, *args, **kwargs):
65
- return func(cls_ref, *args, **kwargs)
66
-
67
- setattr(cls_inner, method_name, tracked_method)
68
-
69
- cls.__init_subclass__ = __init_subclass__
70
- return cls
@@ -1,76 +0,0 @@
1
- from typing import Optional
2
-
3
-
4
- class NotionDatabaseException(Exception):
5
- """Base exception for all Notion database operations."""
6
-
7
- pass
8
-
9
-
10
- class DatabaseNotFoundException(NotionDatabaseException):
11
- """Exception raised when a database is not found."""
12
-
13
- def __init__(self, identifier: str, message: str = None):
14
- self.identifier = identifier
15
- self.message = message or f"Database not found: {identifier}"
16
- super().__init__(self.message)
17
-
18
-
19
- class DatabaseInitializationError(NotionDatabaseException):
20
- """Exception raised when a database manager fails to initialize."""
21
-
22
- def __init__(self, database_id: str, message: str = None):
23
- self.database_id = database_id
24
- self.message = (
25
- message or f"Failed to initialize database manager for ID: {database_id}"
26
- )
27
- super().__init__(self.message)
28
-
29
-
30
- class DatabaseConnectionError(NotionDatabaseException):
31
- """Exception raised when there's an error connecting to Notion API."""
32
-
33
- def __init__(self, message: str = None):
34
- self.message = message or "Error connecting to Notion API"
35
- super().__init__(self.message)
36
-
37
-
38
- class DatabaseParsingError(NotionDatabaseException):
39
- """Exception raised when there's an error parsing database data."""
40
-
41
- def __init__(self, message: str = None):
42
- self.message = message or "Error parsing database data"
43
- super().__init__(self.message)
44
-
45
-
46
- class PageNotFoundException(NotionDatabaseException):
47
- """Raised when a page is not found or cannot be accessed."""
48
-
49
- def __init__(self, page_id: str, message: Optional[str] = None):
50
- self.page_id = page_id
51
- self.message = message or f"Page not found: {page_id}"
52
- super().__init__(self.message)
53
-
54
-
55
- class PageOperationError(NotionDatabaseException):
56
- """Raised when an operation on a page fails."""
57
-
58
- def __init__(self, page_id: str, operation: str, message: Optional[str] = None):
59
- self.page_id = page_id
60
- self.operation = operation
61
- self.message = message or f"Failed to {operation} page {page_id}"
62
- super().__init__(self.message)
63
-
64
-
65
- class PropertyError(NotionDatabaseException):
66
- """Raised when there's an error with database properties."""
67
-
68
- def __init__(
69
- self, property_name: Optional[str] = None, message: Optional[str] = None
70
- ):
71
- self.property_name = property_name
72
- self.message = (
73
- message
74
- or f"Error with property{' ' + property_name if property_name else ''}"
75
- )
76
- super().__init__(self.message)
@@ -1,9 +0,0 @@
1
- from typing import Optional
2
-
3
-
4
- class PageCreationException(Exception):
5
- """Exception raised when page creation in Notion fails."""
6
-
7
- def __init__(self, message: str, response: Optional[dict] = None):
8
- super().__init__(message)
9
- self.response = response
@@ -1,150 +0,0 @@
1
- from typing import Any, Dict, Optional
2
- from notionary.notion_client import NotionClient
3
- from notionary.page.properites.property_formatter import NotionPropertyFormatter
4
- from notionary.util import LoggingMixin
5
-
6
-
7
- class MetadataEditor(LoggingMixin):
8
- """
9
- Manages and edits the metadata and properties of a Notion page.
10
- """
11
-
12
- def __init__(self, page_id: str, client: NotionClient):
13
- """
14
- Initialize the metadata editor.
15
-
16
- Args:
17
- page_id: The ID of the Notion page
18
- client: The Notion API client
19
- """
20
- self.page_id = page_id
21
- self._client = client
22
- self._property_formatter = NotionPropertyFormatter()
23
-
24
- async def set_title(self, title: str) -> Optional[str]:
25
- """
26
- Sets the title of the page.
27
-
28
- Args:
29
- title: The new title for the page.
30
-
31
- Returns:
32
- Optional[str]: The new title if successful, None otherwise.
33
- """
34
- try:
35
- data = {
36
- "properties": {
37
- "title": {"title": [{"type": "text", "text": {"content": title}}]}
38
- }
39
- }
40
-
41
- result = await self._client.patch_page(self.page_id, data)
42
-
43
- if result:
44
- return title
45
- return None
46
- except Exception as e:
47
- self.logger.error("Error setting page title: %s", str(e))
48
- return None
49
-
50
- async def set_property_by_name(
51
- self, property_name: str, value: Any
52
- ) -> Optional[str]:
53
- """
54
- Sets a property value based on the property name, automatically detecting the property type.
55
-
56
- Args:
57
- property_name: The name of the property in Notion
58
- value: The value to set
59
-
60
- Returns:
61
- Optional[str]: The property name if successful, None if operation fails
62
- """
63
- property_schema = await self._get_property_schema()
64
-
65
- if property_name not in property_schema:
66
- self.logger.warning(
67
- "Property '%s' not found in database schema", property_name
68
- )
69
- return None
70
-
71
- property_type = property_schema[property_name]["type"]
72
- return await self._set_property(property_name, value, property_type)
73
-
74
- async def _set_property(
75
- self, property_name: str, property_value: Any, property_type: str
76
- ) -> Optional[str]:
77
- """
78
- Generic method to set any property on a Notion page.
79
-
80
- Args:
81
- property_name: The name of the property in Notion
82
- property_value: The value to set
83
- property_type: The type of property ('select', 'multi_select', 'status', 'relation', etc.)
84
-
85
- Returns:
86
- Optional[str]: The property name if successful, None if operation fails
87
- """
88
- property_payload = self._property_formatter.format_value(
89
- property_type, property_value
90
- )
91
-
92
- if not property_payload:
93
- self.logger.warning(
94
- "Could not create payload for property type: %s", property_type
95
- )
96
- return None
97
-
98
- try:
99
- result = await self._client.patch_page(
100
- self.page_id, {"properties": {property_name: property_payload}}
101
- )
102
-
103
- if result:
104
- return property_name
105
- return None
106
- except Exception as e:
107
- self.logger.error("Error setting property '%s': %s", property_name, str(e))
108
- return None
109
-
110
- async def _get_property_schema(self) -> Dict[str, Dict[str, Any]]:
111
- """
112
- Retrieves the schema for all properties of the page.
113
-
114
- Returns:
115
- Dict[str, Dict[str, Any]]: A dictionary mapping property names to their schema
116
- """
117
- page_data = await self._client.get_page(self.page_id)
118
- property_schema = {}
119
-
120
- # Property types that can have options
121
- option_types = {
122
- "select": "select",
123
- "multi_select": "multi_select",
124
- "status": "status",
125
- }
126
-
127
- for prop_name, prop_data in page_data.properties.items():
128
- prop_type = prop_data.get("type")
129
-
130
- schema_entry = {
131
- "id": prop_data.get("id"),
132
- "type": prop_type,
133
- "name": prop_name,
134
- }
135
-
136
- # Check if this property type can have options
137
- if prop_type in option_types:
138
- option_key = option_types[prop_type]
139
- try:
140
- prop_type_data = prop_data.get(option_key, {})
141
- if isinstance(prop_type_data, dict):
142
- schema_entry["options"] = prop_type_data.get("options", [])
143
- except Exception as e:
144
- self.logger.warning(
145
- "Error processing property schema for '%s': %s", prop_name, e
146
- )
147
-
148
- property_schema[prop_name] = schema_entry
149
-
150
- return property_schema