notionary 0.2.16__py3-none-any.whl → 0.2.17__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 +9 -5
- notionary/base_notion_client.py +18 -7
- notionary/blocks/__init__.py +2 -0
- notionary/blocks/document_element.py +194 -0
- notionary/database/__init__.py +4 -0
- notionary/database/database.py +481 -0
- notionary/database/{filter_builder.py → database_filter_builder.py} +27 -29
- notionary/database/{notion_database_provider.py → database_provider.py} +4 -4
- notionary/database/notion_database.py +45 -18
- notionary/file_upload/__init__.py +7 -0
- notionary/file_upload/client.py +254 -0
- notionary/file_upload/models.py +60 -0
- notionary/file_upload/notion_file_upload.py +387 -0
- notionary/page/notion_page.py +4 -3
- notionary/telemetry/views.py +15 -6
- notionary/user/__init__.py +11 -0
- notionary/user/base_notion_user.py +52 -0
- notionary/user/client.py +129 -0
- notionary/user/models.py +83 -0
- notionary/user/notion_bot_user.py +227 -0
- notionary/user/notion_user.py +256 -0
- notionary/user/notion_user_manager.py +173 -0
- notionary/user/notion_user_provider.py +1 -0
- notionary/util/__init__.py +3 -5
- notionary/util/{factory_decorator.py → factory_only.py} +9 -5
- notionary/util/fuzzy.py +74 -0
- notionary/util/logging_mixin.py +12 -12
- notionary/workspace.py +38 -2
- {notionary-0.2.16.dist-info → notionary-0.2.17.dist-info}/METADATA +2 -1
- {notionary-0.2.16.dist-info → notionary-0.2.17.dist-info}/RECORD +34 -20
- notionary/util/fuzzy_matcher.py +0 -82
- /notionary/database/{database_exceptions.py → exceptions.py} +0 -0
- /notionary/util/{singleton_decorator.py → singleton.py} +0 -0
- {notionary-0.2.16.dist-info → notionary-0.2.17.dist-info}/LICENSE +0 -0
- {notionary-0.2.16.dist-info → notionary-0.2.17.dist-info}/WHEEL +0 -0
notionary/user/models.py
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Literal, Optional
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
|
6
|
+
class PersonUser(BaseModel):
|
7
|
+
"""Person user details"""
|
8
|
+
|
9
|
+
email: Optional[str] = None
|
10
|
+
|
11
|
+
|
12
|
+
class BotOwner(BaseModel):
|
13
|
+
"""Bot owner information - simplified structure"""
|
14
|
+
|
15
|
+
type: Literal["workspace", "user"]
|
16
|
+
workspace: Optional[bool] = None
|
17
|
+
|
18
|
+
|
19
|
+
class WorkspaceLimits(BaseModel):
|
20
|
+
"""Workspace limits for bot users"""
|
21
|
+
|
22
|
+
max_file_upload_size_in_bytes: int
|
23
|
+
|
24
|
+
|
25
|
+
class BotUser(BaseModel):
|
26
|
+
"""Bot user details"""
|
27
|
+
|
28
|
+
owner: Optional[BotOwner] = None
|
29
|
+
workspace_name: Optional[str] = None
|
30
|
+
workspace_limits: Optional[WorkspaceLimits] = None
|
31
|
+
|
32
|
+
|
33
|
+
class NotionUserResponse(BaseModel):
|
34
|
+
"""
|
35
|
+
Represents a Notion user object as returned by the Users API.
|
36
|
+
Can represent both person and bot users.
|
37
|
+
"""
|
38
|
+
|
39
|
+
object: Literal["user"]
|
40
|
+
id: str
|
41
|
+
type: Optional[Literal["person", "bot"]] = None
|
42
|
+
name: Optional[str] = None
|
43
|
+
avatar_url: Optional[str] = None
|
44
|
+
|
45
|
+
# Person-specific fields
|
46
|
+
person: Optional[PersonUser] = None
|
47
|
+
|
48
|
+
# Bot-specific fields
|
49
|
+
bot: Optional[BotUser] = None
|
50
|
+
|
51
|
+
|
52
|
+
class NotionBotUserResponse(NotionUserResponse):
|
53
|
+
"""
|
54
|
+
Specialized response for bot user (from /users/me endpoint)
|
55
|
+
"""
|
56
|
+
|
57
|
+
# Bot users should have these fields, but they can still be None
|
58
|
+
type: Literal["bot"]
|
59
|
+
bot: Optional[BotUser] = None
|
60
|
+
|
61
|
+
|
62
|
+
class NotionUsersListResponse(BaseModel):
|
63
|
+
"""
|
64
|
+
Response model for paginated users list from /v1/users endpoint.
|
65
|
+
Follows Notion's standard pagination pattern.
|
66
|
+
"""
|
67
|
+
|
68
|
+
object: Literal["list"]
|
69
|
+
results: list[NotionUserResponse]
|
70
|
+
next_cursor: Optional[str] = None
|
71
|
+
has_more: bool
|
72
|
+
type: Literal["user"]
|
73
|
+
user: dict = {}
|
74
|
+
|
75
|
+
|
76
|
+
@dataclass
|
77
|
+
class WorkspaceInfo:
|
78
|
+
"""Dataclass to hold workspace information for bot users."""
|
79
|
+
|
80
|
+
name: Optional[str] = None
|
81
|
+
limits: Optional[WorkspaceLimits] = None
|
82
|
+
owner_type: Optional[str] = None
|
83
|
+
is_workspace_owned: bool = False
|
@@ -0,0 +1,227 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Optional, List
|
3
|
+
from notionary.user.base_notion_user import BaseNotionUser
|
4
|
+
from notionary.user.client import NotionUserClient
|
5
|
+
from notionary.user.models import (
|
6
|
+
NotionBotUserResponse,
|
7
|
+
WorkspaceLimits,
|
8
|
+
)
|
9
|
+
from notionary.util import factory_only
|
10
|
+
from notionary.util.fuzzy import find_best_match
|
11
|
+
|
12
|
+
|
13
|
+
class NotionBotUser(BaseNotionUser):
|
14
|
+
"""
|
15
|
+
Manager for Notion bot users.
|
16
|
+
Handles bot-specific operations and workspace information.
|
17
|
+
"""
|
18
|
+
|
19
|
+
NO_USERS_FOUND_MSG = "No users found in workspace"
|
20
|
+
NO_BOT_USERS_FOUND_MSG = "No bot users found in workspace"
|
21
|
+
|
22
|
+
@factory_only("from_current_integration", "from_bot_response", "from_name")
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
user_id: str,
|
26
|
+
name: Optional[str] = None,
|
27
|
+
avatar_url: Optional[str] = None,
|
28
|
+
workspace_name: Optional[str] = None,
|
29
|
+
workspace_limits: Optional[WorkspaceLimits] = None,
|
30
|
+
owner_type: Optional[str] = None,
|
31
|
+
token: Optional[str] = None,
|
32
|
+
):
|
33
|
+
"""Initialize bot user with bot-specific properties."""
|
34
|
+
super().__init__(user_id, name, avatar_url, token)
|
35
|
+
self._workspace_name = workspace_name
|
36
|
+
self._workspace_limits = workspace_limits
|
37
|
+
self._owner_type = owner_type
|
38
|
+
|
39
|
+
@classmethod
|
40
|
+
async def from_current_integration(
|
41
|
+
cls, token: Optional[str] = None
|
42
|
+
) -> Optional[NotionBotUser]:
|
43
|
+
"""
|
44
|
+
Get the current bot user (from the API token).
|
45
|
+
|
46
|
+
Args:
|
47
|
+
token: Optional Notion API token
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
Optional[NotionBotUser]: Bot user instance or None if failed
|
51
|
+
"""
|
52
|
+
client = NotionUserClient(token=token)
|
53
|
+
bot_response = await client.get_bot_user()
|
54
|
+
|
55
|
+
if bot_response is None:
|
56
|
+
cls.logger.error("Failed to load bot user data")
|
57
|
+
return None
|
58
|
+
|
59
|
+
return cls._create_from_response(bot_response, token)
|
60
|
+
|
61
|
+
@classmethod
|
62
|
+
async def from_name(
|
63
|
+
cls, name: str, token: Optional[str] = None, min_similarity: float = 0.6
|
64
|
+
) -> Optional[NotionBotUser]:
|
65
|
+
"""
|
66
|
+
Create a NotionBotUser by finding a bot user with fuzzy matching on the name.
|
67
|
+
Uses Notion's list users API and fuzzy matching to find the best result.
|
68
|
+
"""
|
69
|
+
client = NotionUserClient(token=token)
|
70
|
+
|
71
|
+
try:
|
72
|
+
# Get all users from workspace
|
73
|
+
all_users_response = await client.get_all_users()
|
74
|
+
|
75
|
+
if not all_users_response:
|
76
|
+
cls.logger.warning(cls.NO_USERS_FOUND_MSG)
|
77
|
+
raise ValueError(cls.NO_USERS_FOUND_MSG)
|
78
|
+
|
79
|
+
# Filter to only bot users
|
80
|
+
bot_users = [
|
81
|
+
user for user in all_users_response if user.type == "bot" and user.name
|
82
|
+
]
|
83
|
+
|
84
|
+
if not bot_users:
|
85
|
+
cls.logger.warning(cls.NO_BOT_USERS_FOUND_MSG)
|
86
|
+
raise ValueError(cls.NO_BOT_USERS_FOUND_MSG)
|
87
|
+
|
88
|
+
cls.logger.debug(
|
89
|
+
"Found %d bot users for fuzzy matching: %s",
|
90
|
+
len(bot_users),
|
91
|
+
[user.name for user in bot_users[:5]], # Log first 5 names
|
92
|
+
)
|
93
|
+
|
94
|
+
# Use fuzzy matching to find best match
|
95
|
+
best_match = find_best_match(
|
96
|
+
query=name,
|
97
|
+
items=bot_users,
|
98
|
+
text_extractor=lambda user: user.name or "",
|
99
|
+
min_similarity=min_similarity,
|
100
|
+
)
|
101
|
+
|
102
|
+
if not best_match:
|
103
|
+
available_names = [user.name for user in bot_users[:5]]
|
104
|
+
cls.logger.warning(
|
105
|
+
"No sufficiently similar bot user found for '%s' (min: %.3f). Available: %s",
|
106
|
+
name,
|
107
|
+
min_similarity,
|
108
|
+
available_names,
|
109
|
+
)
|
110
|
+
raise ValueError(f"No sufficiently similar bot user found for '{name}'")
|
111
|
+
|
112
|
+
cls.logger.info(
|
113
|
+
"Found best match: '%s' with similarity %.3f for query '%s'",
|
114
|
+
best_match.matched_text,
|
115
|
+
best_match.similarity,
|
116
|
+
name,
|
117
|
+
)
|
118
|
+
|
119
|
+
# Create NotionBotUser from the matched user response
|
120
|
+
return cls._create_from_response(best_match.item, token)
|
121
|
+
|
122
|
+
except Exception as e:
|
123
|
+
cls.logger.error("Error finding bot user by name '%s': %s", name, str(e))
|
124
|
+
raise
|
125
|
+
|
126
|
+
@classmethod
|
127
|
+
def from_bot_response(
|
128
|
+
cls, bot_response: NotionBotUserResponse, token: Optional[str] = None
|
129
|
+
) -> NotionBotUser:
|
130
|
+
"""
|
131
|
+
Create a NotionBotUser from an existing bot API response.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
bot_response: Bot user response from Notion API
|
135
|
+
token: Optional Notion API token
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
NotionBotUser: Bot user instance
|
139
|
+
"""
|
140
|
+
return cls._create_from_response(bot_response, token)
|
141
|
+
|
142
|
+
@property
|
143
|
+
def workspace_name(self) -> Optional[str]:
|
144
|
+
"""Get the workspace name."""
|
145
|
+
return self._workspace_name
|
146
|
+
|
147
|
+
@property
|
148
|
+
def workspace_limits(self) -> Optional[WorkspaceLimits]:
|
149
|
+
"""Get the workspace limits."""
|
150
|
+
return self._workspace_limits
|
151
|
+
|
152
|
+
@property
|
153
|
+
def owner_type(self) -> Optional[str]:
|
154
|
+
"""Get the owner type ('workspace' or 'user')."""
|
155
|
+
return self._owner_type
|
156
|
+
|
157
|
+
@property
|
158
|
+
def user_type(self) -> str:
|
159
|
+
"""Get the user type."""
|
160
|
+
return "bot"
|
161
|
+
|
162
|
+
@property
|
163
|
+
def is_person(self) -> bool:
|
164
|
+
"""Check if this is a person user."""
|
165
|
+
return False
|
166
|
+
|
167
|
+
@property
|
168
|
+
def is_bot(self) -> bool:
|
169
|
+
"""Check if this is a bot user."""
|
170
|
+
return True
|
171
|
+
|
172
|
+
@property
|
173
|
+
def is_workspace_integration(self) -> bool:
|
174
|
+
"""Check if this is a workspace-owned integration."""
|
175
|
+
return self._owner_type == "workspace"
|
176
|
+
|
177
|
+
@property
|
178
|
+
def is_user_integration(self) -> bool:
|
179
|
+
"""Check if this is a user-owned integration."""
|
180
|
+
return self._owner_type == "user"
|
181
|
+
|
182
|
+
@property
|
183
|
+
def max_file_upload_size(self) -> Optional[int]:
|
184
|
+
"""The maximum file upload size in bytes."""
|
185
|
+
return (
|
186
|
+
self._workspace_limits.max_file_upload_size_in_bytes
|
187
|
+
if self._workspace_limits
|
188
|
+
else None
|
189
|
+
)
|
190
|
+
|
191
|
+
def __str__(self) -> str:
|
192
|
+
"""String representation of the bot user."""
|
193
|
+
workspace = self._workspace_name or "Unknown Workspace"
|
194
|
+
return f"NotionBotUser(name='{self.get_display_name()}', workspace='{workspace}', id='{self._user_id[:8]}...')"
|
195
|
+
|
196
|
+
@classmethod
|
197
|
+
def _create_from_response(
|
198
|
+
cls, bot_response: NotionBotUserResponse, token: Optional[str]
|
199
|
+
) -> NotionBotUser:
|
200
|
+
"""Create NotionBotUser instance from API response."""
|
201
|
+
workspace_name = None
|
202
|
+
workspace_limits = None
|
203
|
+
owner_type = None
|
204
|
+
|
205
|
+
if bot_response.bot:
|
206
|
+
workspace_name = bot_response.bot.workspace_name
|
207
|
+
workspace_limits = bot_response.bot.workspace_limits
|
208
|
+
owner_type = bot_response.bot.owner.type if bot_response.bot.owner else None
|
209
|
+
|
210
|
+
instance = cls(
|
211
|
+
user_id=bot_response.id,
|
212
|
+
name=bot_response.name,
|
213
|
+
avatar_url=bot_response.avatar_url,
|
214
|
+
workspace_name=workspace_name,
|
215
|
+
workspace_limits=workspace_limits,
|
216
|
+
owner_type=owner_type,
|
217
|
+
token=token,
|
218
|
+
)
|
219
|
+
|
220
|
+
cls.logger.info(
|
221
|
+
"Created bot user: '%s' (ID: %s, Workspace: %s)",
|
222
|
+
bot_response.name or "Unknown Bot",
|
223
|
+
bot_response.id,
|
224
|
+
workspace_name or "Unknown",
|
225
|
+
)
|
226
|
+
|
227
|
+
return instance
|
@@ -0,0 +1,256 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Optional, List
|
4
|
+
from notionary.user.base_notion_user import BaseNotionUser
|
5
|
+
from notionary.user.client import NotionUserClient
|
6
|
+
from notionary.user.models import (
|
7
|
+
NotionUserResponse,
|
8
|
+
)
|
9
|
+
from notionary.util import factory_only
|
10
|
+
from notionary.util.fuzzy import find_best_matches
|
11
|
+
|
12
|
+
|
13
|
+
class NotionUser(BaseNotionUser):
|
14
|
+
"""
|
15
|
+
Manager for Notion person users.
|
16
|
+
Handles person-specific operations and information.
|
17
|
+
"""
|
18
|
+
|
19
|
+
NO_USERS_FOUND_MSG = "No users found in workspace"
|
20
|
+
NO_PERSON_USERS_FOUND_MSG = "No person users found in workspace"
|
21
|
+
|
22
|
+
@factory_only("from_user_id", "from_user_response", "from_name")
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
user_id: str,
|
26
|
+
name: Optional[str] = None,
|
27
|
+
avatar_url: Optional[str] = None,
|
28
|
+
email: Optional[str] = None,
|
29
|
+
token: Optional[str] = None,
|
30
|
+
):
|
31
|
+
"""Initialize person user with person-specific properties."""
|
32
|
+
super().__init__(user_id, name, avatar_url, token)
|
33
|
+
self._email = email
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
async def from_user_id(
|
37
|
+
cls, user_id: str, token: Optional[str] = None
|
38
|
+
) -> Optional[NotionUser]:
|
39
|
+
"""
|
40
|
+
Create a NotionUser from a user ID.
|
41
|
+
"""
|
42
|
+
client = NotionUserClient(token=token)
|
43
|
+
user_response = await client.get_user(user_id)
|
44
|
+
|
45
|
+
if user_response is None:
|
46
|
+
cls.logger.error("Failed to load user data for ID: %s", user_id)
|
47
|
+
return None
|
48
|
+
|
49
|
+
# Ensure this is actually a person user
|
50
|
+
if user_response.type != "person":
|
51
|
+
cls.logger.error(
|
52
|
+
"User %s is not a person user (type: %s)", user_id, user_response.type
|
53
|
+
)
|
54
|
+
return None
|
55
|
+
|
56
|
+
return cls._create_from_response(user_response, token)
|
57
|
+
|
58
|
+
@classmethod
|
59
|
+
async def from_name(
|
60
|
+
cls, name: str, token: Optional[str] = None, min_similarity: float = 0.6
|
61
|
+
) -> Optional[NotionUser]:
|
62
|
+
"""
|
63
|
+
Create a NotionUser by finding a person user with fuzzy matching on the name.
|
64
|
+
"""
|
65
|
+
from notionary.util import find_best_match
|
66
|
+
|
67
|
+
client = NotionUserClient(token=token)
|
68
|
+
|
69
|
+
try:
|
70
|
+
# Get all users from workspace
|
71
|
+
all_users_response = await client.get_all_users()
|
72
|
+
|
73
|
+
if not all_users_response:
|
74
|
+
cls.logger.warning(cls.NO_USERS_FOUND_MSG)
|
75
|
+
raise ValueError(cls.NO_USERS_FOUND_MSG)
|
76
|
+
|
77
|
+
person_users = [
|
78
|
+
user
|
79
|
+
for user in all_users_response
|
80
|
+
if user.type == "person" and user.name
|
81
|
+
]
|
82
|
+
|
83
|
+
if not person_users:
|
84
|
+
cls.logger.warning(cls.NO_PERSON_USERS_FOUND_MSG)
|
85
|
+
raise ValueError(cls.NO_PERSON_USERS_FOUND_MSG)
|
86
|
+
|
87
|
+
cls.logger.debug(
|
88
|
+
"Found %d person users for fuzzy matching: %s",
|
89
|
+
len(person_users),
|
90
|
+
[user.name for user in person_users[:5]],
|
91
|
+
)
|
92
|
+
|
93
|
+
# Use fuzzy matching to find best match
|
94
|
+
best_match = find_best_match(
|
95
|
+
query=name,
|
96
|
+
items=person_users,
|
97
|
+
text_extractor=lambda user: user.name or "",
|
98
|
+
min_similarity=min_similarity,
|
99
|
+
)
|
100
|
+
|
101
|
+
if not best_match:
|
102
|
+
available_names = [user.name for user in person_users[:5]]
|
103
|
+
cls.logger.warning(
|
104
|
+
"No sufficiently similar person user found for '%s' (min: %.3f). Available: %s",
|
105
|
+
name,
|
106
|
+
min_similarity,
|
107
|
+
available_names,
|
108
|
+
)
|
109
|
+
raise ValueError(
|
110
|
+
f"No sufficiently similar person user found for '{name}'"
|
111
|
+
)
|
112
|
+
|
113
|
+
cls.logger.info(
|
114
|
+
"Found best match: '%s' with similarity %.3f for query '%s'",
|
115
|
+
best_match.matched_text,
|
116
|
+
best_match.similarity,
|
117
|
+
name,
|
118
|
+
)
|
119
|
+
|
120
|
+
# Create NotionUser from the matched user response
|
121
|
+
return cls._create_from_response(best_match.item, token)
|
122
|
+
|
123
|
+
except Exception as e:
|
124
|
+
cls.logger.error("Error finding user by name '%s': %s", name, str(e))
|
125
|
+
raise
|
126
|
+
|
127
|
+
@classmethod
|
128
|
+
def from_user_response(
|
129
|
+
cls, user_response: NotionUserResponse, token: Optional[str] = None
|
130
|
+
) -> NotionUser:
|
131
|
+
"""
|
132
|
+
Create a NotionUser from an existing API response.
|
133
|
+
"""
|
134
|
+
if user_response.type != "person":
|
135
|
+
raise ValueError(f"Cannot create NotionUser from {user_response.type} user")
|
136
|
+
|
137
|
+
return cls._create_from_response(user_response, token)
|
138
|
+
|
139
|
+
@classmethod
|
140
|
+
async def search_users_by_name(
|
141
|
+
cls,
|
142
|
+
name: str,
|
143
|
+
token: Optional[str] = None,
|
144
|
+
min_similarity: float = 0.3,
|
145
|
+
limit: Optional[int] = 5,
|
146
|
+
) -> List[NotionUser]:
|
147
|
+
"""
|
148
|
+
Search for multiple person users by name using fuzzy matching.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
name: The name to search for
|
152
|
+
token: Optional Notion API token
|
153
|
+
min_similarity: Minimum similarity threshold (0.0 to 1.0), default 0.3
|
154
|
+
limit: Maximum number of results to return, default 5
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
List[NotionUser]: List of matching users sorted by similarity (best first)
|
158
|
+
"""
|
159
|
+
client = NotionUserClient(token=token)
|
160
|
+
|
161
|
+
try:
|
162
|
+
# Get all users from workspace
|
163
|
+
all_users_response = await client.get_all_users()
|
164
|
+
|
165
|
+
if not all_users_response:
|
166
|
+
cls.logger.warning(cls.NO_USERS_FOUND_MSG)
|
167
|
+
return []
|
168
|
+
|
169
|
+
# Filter to only person users (not bots)
|
170
|
+
person_users = [
|
171
|
+
user
|
172
|
+
for user in all_users_response
|
173
|
+
if user.type == "person" and user.name
|
174
|
+
]
|
175
|
+
|
176
|
+
if not person_users:
|
177
|
+
cls.logger.warning(cls.NO_PERSON_USERS_FOUND_MSG)
|
178
|
+
return []
|
179
|
+
|
180
|
+
# Use fuzzy matching to find all matches
|
181
|
+
matches = find_best_matches(
|
182
|
+
query=name,
|
183
|
+
items=person_users,
|
184
|
+
text_extractor=lambda user: user.name or "",
|
185
|
+
min_similarity=min_similarity,
|
186
|
+
limit=limit,
|
187
|
+
)
|
188
|
+
|
189
|
+
cls.logger.info(
|
190
|
+
"Found %d matching users for query '%s'", len(matches), name
|
191
|
+
)
|
192
|
+
|
193
|
+
# Convert to NotionUser instances
|
194
|
+
result_users = []
|
195
|
+
for match in matches:
|
196
|
+
try:
|
197
|
+
user = cls._create_from_response(match.item, token)
|
198
|
+
result_users.append(user)
|
199
|
+
except Exception as e:
|
200
|
+
cls.logger.warning(
|
201
|
+
"Failed to create user from match '%s': %s",
|
202
|
+
match.matched_text,
|
203
|
+
str(e),
|
204
|
+
)
|
205
|
+
continue
|
206
|
+
|
207
|
+
return result_users
|
208
|
+
|
209
|
+
except Exception as e:
|
210
|
+
cls.logger.error("Error searching users by name '%s': %s", name, str(e))
|
211
|
+
return []
|
212
|
+
|
213
|
+
@property
|
214
|
+
def email(self) -> Optional[str]:
|
215
|
+
"""
|
216
|
+
Get the user email (requires proper integration capabilities).
|
217
|
+
"""
|
218
|
+
return self._email
|
219
|
+
|
220
|
+
@property
|
221
|
+
def user_type(self) -> str:
|
222
|
+
"""Get the user type."""
|
223
|
+
return "person"
|
224
|
+
|
225
|
+
@property
|
226
|
+
def is_person(self) -> bool:
|
227
|
+
"""Check if this is a person user."""
|
228
|
+
return True
|
229
|
+
|
230
|
+
@property
|
231
|
+
def is_bot(self) -> bool:
|
232
|
+
"""Check if this is a bot user."""
|
233
|
+
return False
|
234
|
+
|
235
|
+
@classmethod
|
236
|
+
def _create_from_response(
|
237
|
+
cls, user_response: NotionUserResponse, token: Optional[str]
|
238
|
+
) -> NotionUser:
|
239
|
+
"""Create NotionUser instance from API response."""
|
240
|
+
email = user_response.person.email if user_response.person else None
|
241
|
+
|
242
|
+
instance = cls(
|
243
|
+
user_id=user_response.id,
|
244
|
+
name=user_response.name,
|
245
|
+
avatar_url=user_response.avatar_url,
|
246
|
+
email=email,
|
247
|
+
token=token,
|
248
|
+
)
|
249
|
+
|
250
|
+
cls.logger.info(
|
251
|
+
"Created person user: '%s' (ID: %s)",
|
252
|
+
user_response.name or "Unknown",
|
253
|
+
user_response.id,
|
254
|
+
)
|
255
|
+
|
256
|
+
return instance
|