Skip to content

Commit fb914c8

Browse files
committed
Seemingly retrievev all user information and bot informaiton from api
1 parent d025dbd commit fb914c8

File tree

9 files changed

+505
-140
lines changed

9 files changed

+505
-140
lines changed

notionary/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
"NotionWorkspace",
1111
"NotionUser",
1212
"NotionUserManager",
13-
]
13+
]

notionary/base_notion_client.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,19 @@ async def close(self) -> None:
9393
self._is_initialized = False
9494
self.logger.debug("NotionClient closed")
9595

96-
async def get(self, endpoint: str) -> Optional[Dict[str, Any]]:
96+
async def get(
97+
self,
98+
endpoint: str,
99+
params: Optional[Dict[str, Any]] = None
100+
) -> Optional[Dict[str, Any]]:
97101
"""
98102
Sends a GET request to the specified Notion API endpoint.
99103
100104
Args:
101105
endpoint: The API endpoint (without base URL)
106+
params: Query parameters to include in the request
102107
"""
103-
return await self._make_request(HttpMethod.GET, endpoint)
108+
return await self._make_request(HttpMethod.GET, endpoint, params=params)
104109

105110
async def post(
106111
self, endpoint: str, data: Optional[Dict[str, Any]] = None
@@ -141,6 +146,7 @@ async def _make_request(
141146
method: Union[HttpMethod, str],
142147
endpoint: str,
143148
data: Optional[Dict[str, Any]] = None,
149+
params: Optional[Dict[str, Any]] = None,
144150
) -> Optional[Dict[str, Any]]:
145151
"""
146152
Executes an HTTP request and returns the data or None on error.
@@ -149,6 +155,7 @@ async def _make_request(
149155
method: HTTP method to use
150156
endpoint: API endpoint
151157
data: Request body data (for POST/PATCH)
158+
params: Query parameters (for GET requests)
152159
"""
153160
await self.ensure_initialized()
154161

@@ -160,15 +167,21 @@ async def _make_request(
160167
try:
161168
self.logger.debug("Sending %s request to %s", method_str.upper(), url)
162169

170+
request_kwargs = {}
171+
172+
# Add query parameters for GET requests
173+
if params:
174+
request_kwargs["params"] = params
175+
163176
if (
164177
method_str in [HttpMethod.POST.value, HttpMethod.PATCH.value]
165178
and data is not None
166179
):
167-
response: httpx.Response = await getattr(self.client, method_str)(
168-
url, json=data
169-
)
170-
else:
171-
response: httpx.Response = await getattr(self.client, method_str)(url)
180+
request_kwargs["json"] = data
181+
182+
response: httpx.Response = await getattr(self.client, method_str)(
183+
url, **request_kwargs
184+
)
172185

173186
response.raise_for_status()
174187
result_data = response.json()
@@ -203,4 +216,4 @@ def _find_token(self) -> Optional[str]:
203216
self.logger.debug("Found token in environment variable.")
204217
return token
205218
self.logger.warning("No Notion API token found in environment variables")
206-
return None
219+
return None

notionary/user/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
from .notion_user import NotionUser, NotionUserManager
1+
from .notion_user import NotionUser
2+
from .notion_user_manager import NotionUserManager
23
from .client import NotionUserClient
34

45
__all__ = [
56
"NotionUser",
6-
"NotionUserManager",
7+
"NotionUserManager",
78
"NotionUserClient",
8-
]
9+
]

notionary/user/client.py

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
from typing import Optional
1+
from typing import Optional, List
22
from notionary.base_notion_client import BaseNotionClient
33
from notionary.user.models import (
4-
NotionUserResponse,
54
NotionBotUserResponse,
5+
NotionUserResponse,
6+
NotionUsersListResponse,
67
)
78
from notionary.util import singleton
89

@@ -12,26 +13,20 @@ class NotionUserClient(BaseNotionClient):
1213
"""
1314
Client for Notion user-specific operations.
1415
Inherits base HTTP functionality from BaseNotionClient.
15-
16+
1617
Note: The Notion API only supports individual user queries and bot user info.
17-
There is NO endpoint to list all workspace users via the standard API.
18+
List users endpoint is available but only returns workspace members (no guests).
1819
"""
1920

2021
async def get_user(self, user_id: str) -> Optional[NotionUserResponse]:
2122
"""
2223
Retrieve a user by their ID.
23-
24-
Args:
25-
user_id: The ID of the user to retrieve
26-
27-
Returns:
28-
Optional[NotionUserResponse]: The user object or None if failed
2924
"""
3025
response = await self.get(f"users/{user_id}")
3126
if response is None:
3227
self.logger.error("Failed to fetch user %s - API returned None", user_id)
3328
return None
34-
29+
3530
try:
3631
return NotionUserResponse.model_validate(response)
3732
except Exception as e:
@@ -41,27 +36,78 @@ async def get_user(self, user_id: str) -> Optional[NotionUserResponse]:
4136
async def get_bot_user(self) -> Optional[NotionBotUserResponse]:
4237
"""
4338
Retrieve your token's bot user information.
44-
45-
Returns:
46-
Optional[NotionBotUserResponse]: The bot user object with full details or None if failed
4739
"""
4840
response = await self.get("users/me")
4941
if response is None:
5042
self.logger.error("Failed to fetch bot user - API returned None")
5143
return None
52-
44+
5345
try:
5446
return NotionBotUserResponse.model_validate(response)
5547
except Exception as e:
5648
self.logger.error("Failed to validate bot user response: %s", e)
5749
return None
5850

51+
async def list_users(
52+
self,
53+
page_size: int = 100,
54+
start_cursor: Optional[str] = None
55+
) -> Optional[NotionUsersListResponse]:
56+
"""
57+
List all users in the workspace (paginated).
58+
59+
Note: Guests are not included in the response.
60+
"""
61+
params = {"page_size": min(page_size, 100)} # API max is 100
62+
if start_cursor:
63+
params["start_cursor"] = start_cursor
64+
65+
response = await self.get("users", params=params)
66+
if response is None:
67+
self.logger.error("Failed to fetch users list - API returned None")
68+
return None
69+
70+
try:
71+
return NotionUsersListResponse.model_validate(response)
72+
except Exception as e:
73+
self.logger.error("Failed to validate users list response: %s", e)
74+
return None
75+
76+
async def get_all_users(self) -> List[NotionUserResponse]:
77+
"""
78+
Get all users in the workspace by handling pagination automatically.
79+
"""
80+
all_users = []
81+
start_cursor = None
82+
83+
while True:
84+
try:
85+
response = await self.list_users(
86+
page_size=100,
87+
start_cursor=start_cursor
88+
)
89+
90+
if not response or not response.results:
91+
break
92+
93+
all_users.extend(response.results)
94+
95+
# Check if there are more pages
96+
if not response.has_more or not response.next_cursor:
97+
break
98+
99+
start_cursor = response.next_cursor
100+
101+
except Exception as e:
102+
self.logger.error("Error fetching all users: %s", str(e))
103+
break
104+
105+
self.logger.info("Retrieved %d total users from workspace", len(all_users))
106+
return all_users
107+
59108
async def get_workspace_name(self) -> Optional[str]:
60109
"""
61110
Get the workspace name from the bot user.
62-
63-
Returns:
64-
Optional[str]: Workspace name if available
65111
"""
66112
try:
67113
bot_user = await self.get_bot_user()
@@ -75,9 +121,6 @@ async def get_workspace_name(self) -> Optional[str]:
75121
async def get_workspace_limits(self) -> Optional[dict]:
76122
"""
77123
Get workspace limits from the bot user.
78-
79-
Returns:
80-
Optional[dict]: Workspace limits if available
81124
"""
82125
try:
83126
bot_user = await self.get_bot_user()

notionary/user/models.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from typing import Literal, Optional, Dict, Any, Union
1+
from dataclasses import dataclass
2+
from typing import Literal, Optional
23
from pydantic import BaseModel
34

4-
55
class PersonUser(BaseModel):
66
"""Person user details"""
77
email: Optional[str] = None
@@ -49,4 +49,24 @@ class NotionBotUserResponse(NotionUserResponse):
4949
"""
5050
# Bot users should have these fields, but they can still be None
5151
type: Literal["bot"]
52-
bot: Optional[BotUser] = None
52+
bot: Optional[BotUser] = None
53+
54+
class NotionUsersListResponse(BaseModel):
55+
"""
56+
Response model for paginated users list from /v1/users endpoint.
57+
Follows Notion's standard pagination pattern.
58+
"""
59+
60+
object: Literal["list"]
61+
results: list[NotionUserResponse]
62+
next_cursor: Optional[str] = None
63+
has_more: bool
64+
type: Literal["user"]
65+
user: dict = {}
66+
67+
# TODO: This is not used so use it
68+
@dataclass
69+
class WorkspaceInfo:
70+
"""Dataclass to hold workspace information for bot users."""
71+
name: Optional[str] = None
72+
limits: Optional[WorkspaceLimits] = None

0 commit comments

Comments
 (0)