notionary 0.2.13__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.
- notionary/__init__.py +3 -16
- notionary/{notion_client.py → base_notion_client.py} +92 -98
- notionary/blocks/__init__.py +61 -0
- notionary/{elements → blocks}/audio_element.py +6 -3
- notionary/{elements → blocks}/bookmark_element.py +3 -5
- notionary/{elements → blocks}/bulleted_list_element.py +5 -6
- notionary/{elements → blocks}/callout_element.py +4 -6
- notionary/{elements → blocks}/code_block_element.py +4 -5
- notionary/{elements → blocks}/column_element.py +3 -5
- notionary/{elements → blocks}/divider_element.py +3 -5
- notionary/{elements → blocks}/embed_element.py +4 -5
- notionary/{elements → blocks}/heading_element.py +4 -7
- notionary/{elements → blocks}/image_element.py +4 -5
- notionary/{elements → blocks}/mention_element.py +3 -6
- notionary/blocks/notion_block_client.py +26 -0
- notionary/{elements → blocks}/notion_block_element.py +2 -3
- notionary/{elements → blocks}/numbered_list_element.py +4 -6
- notionary/{elements → blocks}/paragraph_element.py +4 -6
- notionary/{prompting/element_prompt_content.py → blocks/prompts/element_prompt_builder.py} +1 -40
- notionary/blocks/prompts/element_prompt_content.py +41 -0
- notionary/{elements → blocks}/qoute_element.py +4 -5
- notionary/{elements → blocks}/registry/block_registry.py +4 -26
- notionary/{elements → blocks}/registry/block_registry_builder.py +26 -25
- notionary/{elements → blocks}/table_element.py +5 -6
- notionary/{elements → blocks}/text_inline_formatter.py +1 -4
- notionary/{elements → blocks}/todo_element.py +5 -6
- notionary/{elements → blocks}/toggle_element.py +3 -5
- notionary/{elements → blocks}/toggleable_heading_element.py +4 -6
- notionary/{elements → blocks}/video_element.py +4 -5
- notionary/cli/main.py +157 -128
- notionary/cli/onboarding.py +10 -9
- notionary/database/__init__.py +0 -0
- notionary/database/client.py +132 -0
- notionary/database/database_exceptions.py +13 -0
- notionary/database/factory.py +0 -0
- notionary/database/filter_builder.py +175 -0
- notionary/database/notion_database.py +339 -126
- notionary/database/notion_database_provider.py +230 -0
- notionary/elements/__init__.py +0 -0
- notionary/models/notion_database_response.py +294 -13
- notionary/models/notion_page_response.py +9 -31
- notionary/models/search_response.py +0 -0
- notionary/page/__init__.py +0 -0
- notionary/page/client.py +110 -0
- notionary/page/content/page_content_retriever.py +5 -20
- notionary/page/content/page_content_writer.py +3 -4
- notionary/page/formatting/markdown_to_notion_converter.py +1 -3
- notionary/{prompting → page}/markdown_syntax_prompt_generator.py +1 -2
- notionary/page/notion_page.py +354 -317
- notionary/page/notion_to_markdown_converter.py +1 -4
- notionary/page/properites/property_value_extractor.py +0 -64
- notionary/page/{properites/property_formatter.py → property_formatter.py} +7 -4
- notionary/page/search_filter_builder.py +131 -0
- notionary/page/utils.py +60 -0
- notionary/util/__init__.py +12 -3
- notionary/util/factory_decorator.py +33 -0
- notionary/util/fuzzy_matcher.py +82 -0
- notionary/util/page_id_utils.py +0 -21
- notionary/util/singleton_metaclass.py +22 -0
- notionary/workspace.py +69 -0
- {notionary-0.2.13.dist-info → notionary-0.2.14.dist-info}/METADATA +4 -1
- notionary-0.2.14.dist-info/RECORD +72 -0
- notionary/database/database_discovery.py +0 -142
- notionary/database/notion_database_factory.py +0 -190
- notionary/exceptions/database_exceptions.py +0 -76
- notionary/exceptions/page_creation_exception.py +0 -9
- notionary/page/metadata/metadata_editor.py +0 -150
- notionary/page/metadata/notion_icon_manager.py +0 -77
- notionary/page/metadata/notion_page_cover_manager.py +0 -56
- notionary/page/notion_page_factory.py +0 -328
- notionary/page/properites/database_property_service.py +0 -302
- notionary/page/properites/page_property_manager.py +0 -152
- notionary/page/relations/notion_page_relation_manager.py +0 -350
- notionary/page/relations/notion_page_title_resolver.py +0 -104
- notionary/page/relations/page_database_relation.py +0 -68
- notionary/util/warn_direct_constructor_usage.py +0 -54
- notionary-0.2.13.dist-info/RECORD +0 -67
- /notionary/util/{singleton.py → singleton_decorator.py} +0 -0
- {notionary-0.2.13.dist-info → notionary-0.2.14.dist-info}/WHEEL +0 -0
- {notionary-0.2.13.dist-info → notionary-0.2.14.dist-info}/entry_points.txt +0 -0
- {notionary-0.2.13.dist-info → notionary-0.2.14.dist-info}/licenses/LICENSE +0 -0
- {notionary-0.2.13.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,190 +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.util import LoggingMixin
|
14
|
-
from notionary.util import format_uuid
|
15
|
-
from notionary.util import singleton
|
16
|
-
|
17
|
-
|
18
|
-
@singleton
|
19
|
-
class NotionDatabaseFactory(LoggingMixin):
|
20
|
-
"""
|
21
|
-
Factory class for creating NotionDatabaseManager instances.
|
22
|
-
Provides methods for creating managers by database ID or name.
|
23
|
-
"""
|
24
|
-
|
25
|
-
@classmethod
|
26
|
-
def from_database_id(
|
27
|
-
cls, database_id: str, token: Optional[str] = None
|
28
|
-
) -> NotionDatabase:
|
29
|
-
"""
|
30
|
-
Create a NotionDatabaseManager from a database ID.
|
31
|
-
|
32
|
-
Args:
|
33
|
-
database_id: The ID of the Notion database
|
34
|
-
token: Optional Notion API token (uses environment variable if not provided)
|
35
|
-
|
36
|
-
Returns:
|
37
|
-
An initialized NotionDatabaseManager instance
|
38
|
-
"""
|
39
|
-
|
40
|
-
try:
|
41
|
-
formatted_id = format_uuid(database_id) or database_id
|
42
|
-
|
43
|
-
manager = NotionDatabase(formatted_id, token)
|
44
|
-
|
45
|
-
cls.logger.info(
|
46
|
-
"Successfully created database manager for ID: %s", formatted_id
|
47
|
-
)
|
48
|
-
return manager
|
49
|
-
|
50
|
-
except DatabaseInitializationError:
|
51
|
-
raise
|
52
|
-
except NotionDatabaseException:
|
53
|
-
raise
|
54
|
-
except Exception as e:
|
55
|
-
error_msg = f"Error connecting to database {database_id}: {str(e)}"
|
56
|
-
cls.logger.error(error_msg)
|
57
|
-
raise DatabaseConnectionError(error_msg) from e
|
58
|
-
|
59
|
-
@classmethod
|
60
|
-
async def from_database_name(
|
61
|
-
cls, database_name: str, token: Optional[str] = None
|
62
|
-
) -> NotionDatabase:
|
63
|
-
"""
|
64
|
-
Create a NotionDatabaseManager by finding a database with a matching name.
|
65
|
-
Uses fuzzy matching to find the closest match to the given name.
|
66
|
-
|
67
|
-
Args:
|
68
|
-
database_name: The name of the Notion database to search for
|
69
|
-
token: Optional Notion API token (uses environment variable if not provided)
|
70
|
-
|
71
|
-
Returns:
|
72
|
-
An initialized NotionDatabaseManager instance
|
73
|
-
"""
|
74
|
-
cls.logger.debug("Searching for database with name: %s", database_name)
|
75
|
-
|
76
|
-
client = NotionClient(token=token)
|
77
|
-
|
78
|
-
try:
|
79
|
-
cls.logger.debug("Using search endpoint to find databases")
|
80
|
-
|
81
|
-
search_payload = {
|
82
|
-
"filter": {"property": "object", "value": "database"},
|
83
|
-
"page_size": 100,
|
84
|
-
}
|
85
|
-
|
86
|
-
response = await client.post("search", search_payload)
|
87
|
-
|
88
|
-
if not response or "results" not in response:
|
89
|
-
error_msg = "Failed to fetch databases using search endpoint"
|
90
|
-
cls.logger.error(error_msg)
|
91
|
-
raise DatabaseConnectionError(error_msg)
|
92
|
-
|
93
|
-
databases = response.get("results", [])
|
94
|
-
|
95
|
-
if not databases:
|
96
|
-
error_msg = "No databases found"
|
97
|
-
cls.logger.warning(error_msg)
|
98
|
-
raise DatabaseNotFoundException(database_name, error_msg)
|
99
|
-
|
100
|
-
cls.logger.debug(
|
101
|
-
"Found %d databases, searching for best match", len(databases)
|
102
|
-
)
|
103
|
-
|
104
|
-
best_match = None
|
105
|
-
best_score = 0
|
106
|
-
|
107
|
-
for db in databases:
|
108
|
-
title = cls._extract_title_from_database(db)
|
109
|
-
|
110
|
-
score = SequenceMatcher(
|
111
|
-
None, database_name.lower(), title.lower()
|
112
|
-
).ratio()
|
113
|
-
|
114
|
-
if score > best_score:
|
115
|
-
best_score = score
|
116
|
-
best_match = db
|
117
|
-
|
118
|
-
if best_score < 0.6 or not best_match:
|
119
|
-
error_msg = f"No good database name match found for '{database_name}'. Best match had score {best_score:.2f}"
|
120
|
-
cls.logger.warning(error_msg)
|
121
|
-
raise DatabaseNotFoundException(database_name, error_msg)
|
122
|
-
|
123
|
-
database_id = best_match.get("id")
|
124
|
-
|
125
|
-
if not database_id:
|
126
|
-
error_msg = "Best match database has no ID"
|
127
|
-
cls.logger.error(error_msg)
|
128
|
-
raise DatabaseParsingError(error_msg)
|
129
|
-
|
130
|
-
matched_name = cls._extract_title_from_database(best_match)
|
131
|
-
|
132
|
-
cls.logger.info(
|
133
|
-
"Found matching database: '%s' (ID: %s) with score: %.2f",
|
134
|
-
matched_name,
|
135
|
-
database_id,
|
136
|
-
best_score,
|
137
|
-
)
|
138
|
-
|
139
|
-
manager = NotionDatabase(database_id, token)
|
140
|
-
|
141
|
-
cls.logger.info(
|
142
|
-
"Successfully created database manager for '%s'", matched_name
|
143
|
-
)
|
144
|
-
await client.close()
|
145
|
-
return manager
|
146
|
-
|
147
|
-
except NotionDatabaseException:
|
148
|
-
await client.close()
|
149
|
-
raise
|
150
|
-
except Exception as e:
|
151
|
-
error_msg = f"Error finding database by name: {str(e)}"
|
152
|
-
cls.logger.error(error_msg)
|
153
|
-
await client.close()
|
154
|
-
raise DatabaseConnectionError(error_msg) from e
|
155
|
-
|
156
|
-
@classmethod
|
157
|
-
def _extract_title_from_database(cls, database: Dict[str, Any]) -> str:
|
158
|
-
"""
|
159
|
-
Extract the title from a database object.
|
160
|
-
"""
|
161
|
-
try:
|
162
|
-
if "title" in database:
|
163
|
-
return cls._extract_text_from_rich_text(database["title"])
|
164
|
-
|
165
|
-
if "properties" in database and "title" in database["properties"]:
|
166
|
-
title_prop = database["properties"]["title"]
|
167
|
-
if "title" in title_prop:
|
168
|
-
return cls._extract_text_from_rich_text(title_prop["title"])
|
169
|
-
|
170
|
-
return "Untitled"
|
171
|
-
|
172
|
-
except Exception as e:
|
173
|
-
error_msg = f"Error extracting database title: {str(e)}"
|
174
|
-
cls.class_logger().warning(error_msg)
|
175
|
-
raise DatabaseParsingError(error_msg) from e
|
176
|
-
|
177
|
-
@classmethod
|
178
|
-
def _extract_text_from_rich_text(cls, rich_text: List[Dict[str, Any]]) -> str:
|
179
|
-
"""
|
180
|
-
Extract plain text from a rich text array.
|
181
|
-
"""
|
182
|
-
if not rich_text:
|
183
|
-
return ""
|
184
|
-
|
185
|
-
text_parts = []
|
186
|
-
for text_obj in rich_text:
|
187
|
-
if "plain_text" in text_obj:
|
188
|
-
text_parts.append(text_obj["plain_text"])
|
189
|
-
|
190
|
-
return "".join(text_parts)
|
@@ -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,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
|
@@ -1,77 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
from typing import Optional
|
3
|
-
|
4
|
-
from notionary.models.notion_page_response import EmojiIcon, ExternalIcon, FileIcon
|
5
|
-
from notionary.notion_client import NotionClient
|
6
|
-
from notionary.util import LoggingMixin
|
7
|
-
|
8
|
-
|
9
|
-
class NotionPageIconManager(LoggingMixin):
|
10
|
-
def __init__(self, page_id: str, client: NotionClient):
|
11
|
-
self.page_id = page_id
|
12
|
-
self._client = client
|
13
|
-
|
14
|
-
async def set_emoji_icon(self, emoji: str) -> Optional[str]:
|
15
|
-
"""
|
16
|
-
Sets the page icon to an emoji.
|
17
|
-
|
18
|
-
Args:
|
19
|
-
emoji (str): The emoji character to set as the icon.
|
20
|
-
|
21
|
-
Returns:
|
22
|
-
Optional[str]: The emoji that was set as the icon, or None if the operation failed.
|
23
|
-
"""
|
24
|
-
icon = {"type": "emoji", "emoji": emoji}
|
25
|
-
page_response = await self._client.patch_page(
|
26
|
-
page_id=self.page_id, data={"icon": icon}
|
27
|
-
)
|
28
|
-
|
29
|
-
if page_response and page_response.icon and page_response.icon.type == "emoji":
|
30
|
-
return page_response.icon.emoji
|
31
|
-
return None
|
32
|
-
|
33
|
-
async def set_external_icon(self, external_icon_url: str) -> Optional[str]:
|
34
|
-
"""
|
35
|
-
Sets the page icon to an external image.
|
36
|
-
|
37
|
-
Args:
|
38
|
-
url (str): The URL of the external image to set as the icon.
|
39
|
-
|
40
|
-
Returns:
|
41
|
-
Optional[str]: The URL of the external image that was set as the icon,
|
42
|
-
or None if the operation failed.
|
43
|
-
"""
|
44
|
-
icon = {"type": "external", "external": {"url": external_icon_url}}
|
45
|
-
page_response = await self._client.patch_page(
|
46
|
-
page_id=self.page_id, data={"icon": icon}
|
47
|
-
)
|
48
|
-
|
49
|
-
if (
|
50
|
-
page_response
|
51
|
-
and page_response.icon
|
52
|
-
and page_response.icon.type == "external"
|
53
|
-
):
|
54
|
-
return page_response.icon.external.url
|
55
|
-
return None
|
56
|
-
|
57
|
-
async def get_icon(self) -> Optional[str]:
|
58
|
-
"""
|
59
|
-
Retrieves the page icon - either emoji or external URL.
|
60
|
-
|
61
|
-
Returns:
|
62
|
-
Optional[str]: Emoji character or URL if set, None if no icon.
|
63
|
-
"""
|
64
|
-
page_response = await self._client.get_page(self.page_id)
|
65
|
-
if not page_response or not page_response.icon:
|
66
|
-
return None
|
67
|
-
|
68
|
-
icon = page_response.icon
|
69
|
-
|
70
|
-
if isinstance(icon, EmojiIcon):
|
71
|
-
return icon.emoji
|
72
|
-
if isinstance(icon, ExternalIcon):
|
73
|
-
return icon.external.url
|
74
|
-
if isinstance(icon, FileIcon):
|
75
|
-
return icon.file.url
|
76
|
-
|
77
|
-
return None
|