users.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. from typing import Any, Optional
  2. from uuid import UUID
  3. from shared.api.models import (
  4. WrappedAPIKeyResponse,
  5. WrappedAPIKeysResponse,
  6. WrappedBooleanResponse,
  7. WrappedCollectionsResponse,
  8. WrappedGenericMessageResponse,
  9. WrappedLimitsResponse,
  10. WrappedLoginResponse,
  11. WrappedTokenResponse,
  12. WrappedUserResponse,
  13. WrappedUsersResponse,
  14. )
  15. class UsersSDK:
  16. def __init__(self, client):
  17. self.client = client
  18. async def create(
  19. self,
  20. email: str,
  21. password: str,
  22. name: Optional[str] = None,
  23. bio: Optional[str] = None,
  24. profile_picture: Optional[str] = None,
  25. is_verified: Optional[bool] = None,
  26. ) -> WrappedUserResponse:
  27. """Register a new user.
  28. Args:
  29. email (str): User's email address
  30. password (str): User's password
  31. name (Optional[str]): The name for the new user
  32. bio (Optional[str]): The bio for the new user
  33. profile_picture (Optional[str]): New user profile picture
  34. Returns:
  35. UserResponse: New user information
  36. """
  37. data: dict = {"email": email, "password": password}
  38. if name is not None:
  39. data["name"] = name
  40. if bio is not None:
  41. data["bio"] = bio
  42. if profile_picture is not None:
  43. data["profile_picture"] = profile_picture
  44. if is_verified is not None:
  45. data["is_verified"] = is_verified
  46. response_dict = await self.client._make_request(
  47. "POST",
  48. "users",
  49. json=data,
  50. version="v3",
  51. )
  52. return WrappedUserResponse(**response_dict)
  53. async def send_verification_email(
  54. self, email: str
  55. ) -> WrappedGenericMessageResponse:
  56. """Request that a verification email to a user."""
  57. response_dict = await self.client._make_request(
  58. "POST",
  59. "users/send-verification-email",
  60. json=email,
  61. version="v3",
  62. )
  63. return WrappedGenericMessageResponse(**response_dict)
  64. async def delete(
  65. self, id: str | UUID, password: str
  66. ) -> WrappedBooleanResponse:
  67. """Delete a specific user. Users can only delete their own account
  68. unless they are superusers.
  69. Args:
  70. id (str | UUID): User ID to delete
  71. password (str): User's password
  72. Returns:
  73. dict: Deletion result
  74. """
  75. data: dict[str, Any] = {"password": password}
  76. response_dict = await self.client._make_request(
  77. "DELETE",
  78. f"users/{str(id)}",
  79. json=data,
  80. version="v3",
  81. )
  82. self.client.access_token = None
  83. self.client._refresh_token = None
  84. return WrappedBooleanResponse(**response_dict)
  85. async def verify_email(
  86. self, email: str, verification_code: str
  87. ) -> WrappedGenericMessageResponse:
  88. """Verify a user's email address.
  89. Args:
  90. email (str): User's email address
  91. verification_code (str): Verification code sent to the user's email
  92. Returns:
  93. dict: Verification result
  94. """
  95. data: dict[str, Any] = {
  96. "email": email,
  97. "verification_code": verification_code,
  98. }
  99. response_dict = await self.client._make_request(
  100. "POST",
  101. "users/verify-email",
  102. json=data,
  103. version="v3",
  104. )
  105. return WrappedGenericMessageResponse(**response_dict)
  106. async def login(self, email: str, password: str) -> WrappedLoginResponse:
  107. """Log in a user.
  108. Args:
  109. email (str): User's email address
  110. password (str): User's password
  111. Returns:
  112. WrappedLoginResponse
  113. """
  114. if self.client.api_key:
  115. raise ValueError(
  116. "Cannot log in after setting an API key, please unset your R2R_API_KEY variable or call client.set_api_key(None)"
  117. )
  118. data: dict[str, Any] = {"username": email, "password": password}
  119. response_dict = await self.client._make_request(
  120. "POST",
  121. "users/login",
  122. data=data,
  123. version="v3",
  124. )
  125. login_response = WrappedLoginResponse(**response_dict)
  126. self.client.access_token = login_response.results.access_token.token
  127. self.client._refresh_token = login_response.results.refresh_token.token
  128. user = await self.client._make_request(
  129. "GET",
  130. "users/me",
  131. version="v3",
  132. )
  133. user_response = WrappedUserResponse(**user)
  134. self.client._user_id = user_response.results.id
  135. return login_response
  136. async def logout(self) -> WrappedGenericMessageResponse | None:
  137. """Log out the current user."""
  138. if self.client.access_token:
  139. response_dict = await self.client._make_request(
  140. "POST",
  141. "users/logout",
  142. version="v3",
  143. )
  144. self.client.access_token = None
  145. self.client._refresh_token = None
  146. return WrappedGenericMessageResponse(**response_dict)
  147. self.client.access_token = None
  148. self.client._refresh_token = None
  149. return None
  150. async def refresh_token(self) -> WrappedTokenResponse:
  151. """Refresh the access token using the refresh token."""
  152. if self.client._refresh_token:
  153. response_dict = await self.client._make_request(
  154. "POST",
  155. "users/refresh-token",
  156. json=self.client._refresh_token,
  157. version="v3",
  158. )
  159. self.client.access_token = response_dict["results"]["access_token"][
  160. "token"
  161. ]
  162. self.client._refresh_token = response_dict["results"]["refresh_token"][
  163. "token"
  164. ]
  165. return WrappedTokenResponse(**response_dict)
  166. async def change_password(
  167. self, current_password: str, new_password: str
  168. ) -> WrappedGenericMessageResponse:
  169. """Change the user's password.
  170. Args:
  171. current_password (str): User's current password
  172. new_password (str): User's new password
  173. Returns:
  174. dict: Change password result
  175. """
  176. data: dict[str, Any] = {
  177. "current_password": current_password,
  178. "new_password": new_password,
  179. }
  180. response_dict = await self.client._make_request(
  181. "POST",
  182. "users/change-password",
  183. json=data,
  184. version="v3",
  185. )
  186. return WrappedGenericMessageResponse(**response_dict)
  187. async def request_password_reset(
  188. self, email: str
  189. ) -> WrappedGenericMessageResponse:
  190. """Request a password reset.
  191. Args:
  192. email (str): User's email address
  193. Returns:
  194. dict: Password reset request result
  195. """
  196. response_dict = await self.client._make_request(
  197. "POST",
  198. "users/request-password-reset",
  199. json=email,
  200. version="v3",
  201. )
  202. return WrappedGenericMessageResponse(**response_dict)
  203. async def reset_password(
  204. self, reset_token: str, new_password: str
  205. ) -> WrappedGenericMessageResponse:
  206. """Reset password using a reset token.
  207. Args:
  208. reset_token (str): Password reset token
  209. new_password (str): New password
  210. Returns:
  211. dict: Password reset result
  212. """
  213. data: dict[str, Any] = {
  214. "reset_token": reset_token,
  215. "new_password": new_password,
  216. }
  217. response_dict = await self.client._make_request(
  218. "POST",
  219. "users/reset-password",
  220. json=data,
  221. version="v3",
  222. )
  223. return WrappedGenericMessageResponse(**response_dict)
  224. async def list(
  225. self,
  226. ids: Optional[list[str | UUID]] = None,
  227. offset: Optional[int] = 0,
  228. limit: Optional[int] = 100,
  229. ) -> WrappedUsersResponse:
  230. """List users with pagination and filtering options.
  231. Args:
  232. offset (int, optional): Specifies the number of objects to skip. Defaults to 0.
  233. limit (int, optional): Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.
  234. Returns:
  235. dict: List of users and pagination information
  236. """
  237. params = {
  238. "offset": offset,
  239. "limit": limit,
  240. }
  241. if ids:
  242. params["ids"] = [str(user_id) for user_id in ids] # type: ignore
  243. response_dict = await self.client._make_request(
  244. "GET",
  245. "users",
  246. params=params,
  247. version="v3",
  248. )
  249. return WrappedUsersResponse(**response_dict)
  250. async def retrieve(
  251. self,
  252. id: str | UUID,
  253. ) -> WrappedUserResponse:
  254. """Get a specific user.
  255. Args:
  256. id (str | UUID): User ID to retrieve
  257. Returns:
  258. dict: Detailed user information
  259. """
  260. response_dict = await self.client._make_request(
  261. "GET",
  262. f"users/{str(id)}",
  263. version="v3",
  264. )
  265. return WrappedUserResponse(**response_dict)
  266. async def me(
  267. self,
  268. ) -> WrappedUserResponse:
  269. """Get detailed information about the currently authenticated user.
  270. Returns:
  271. dict: Detailed user information
  272. """
  273. response_dict = await self.client._make_request(
  274. "GET",
  275. "users/me",
  276. version="v3",
  277. )
  278. return WrappedUserResponse(**response_dict)
  279. async def update(
  280. self,
  281. id: str | UUID,
  282. email: Optional[str] = None,
  283. is_superuser: Optional[bool] = None,
  284. name: Optional[str] = None,
  285. bio: Optional[str] = None,
  286. profile_picture: Optional[str] = None,
  287. limits_overrides: dict | None = None,
  288. metadata: dict[str, str | None] | None = None,
  289. ) -> WrappedUserResponse:
  290. """Update user information.
  291. Args:
  292. id (str | UUID): User ID to update
  293. username (Optional[str]): New username
  294. is_superuser (Optional[bool]): Update superuser status
  295. name (Optional[str]): New name
  296. bio (Optional[str]): New bio
  297. profile_picture (Optional[str]): New profile picture
  298. Returns:
  299. dict: Updated user information
  300. """
  301. data: dict = {}
  302. if email is not None:
  303. data["email"] = email
  304. if is_superuser is not None:
  305. data["is_superuser"] = is_superuser
  306. if name is not None:
  307. data["name"] = name
  308. if bio is not None:
  309. data["bio"] = bio
  310. if profile_picture is not None:
  311. data["profile_picture"] = profile_picture
  312. if limits_overrides is not None:
  313. data["limits_overrides"] = limits_overrides
  314. if metadata is not None:
  315. data["metadata"] = metadata
  316. response_dict = await self.client._make_request(
  317. "POST",
  318. f"users/{str(id)}",
  319. json=data,
  320. version="v3",
  321. )
  322. return WrappedUserResponse(**response_dict)
  323. async def list_collections(
  324. self,
  325. id: str | UUID,
  326. offset: Optional[int] = 0,
  327. limit: Optional[int] = 100,
  328. ) -> WrappedCollectionsResponse:
  329. """Get all collections associated with a specific user.
  330. Args:
  331. id (str | UUID): User ID to get collections for
  332. offset (int, optional): Specifies the number of objects to skip. Defaults to 0.
  333. limit (int, optional): Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.
  334. Returns:
  335. dict: List of collections and pagination information
  336. """
  337. params = {
  338. "offset": offset,
  339. "limit": limit,
  340. }
  341. response_dict = await self.client._make_request(
  342. "GET",
  343. f"users/{str(id)}/collections",
  344. params=params,
  345. version="v3",
  346. )
  347. return WrappedCollectionsResponse(**response_dict)
  348. async def add_to_collection(
  349. self,
  350. id: str | UUID,
  351. collection_id: str | UUID,
  352. ) -> WrappedBooleanResponse:
  353. """Add a user to a collection.
  354. Args:
  355. id (str | UUID): User ID to add
  356. collection_id (str | UUID): Collection ID to add user to
  357. """
  358. response_dict = await self.client._make_request(
  359. "POST",
  360. f"users/{str(id)}/collections/{str(collection_id)}",
  361. version="v3",
  362. )
  363. return WrappedBooleanResponse(**response_dict)
  364. async def remove_from_collection(
  365. self,
  366. id: str | UUID,
  367. collection_id: str | UUID,
  368. ) -> WrappedBooleanResponse:
  369. """Remove a user from a collection.
  370. Args:
  371. id (str | UUID): User ID to remove
  372. collection_id (str | UUID): Collection ID to remove user from
  373. Returns:
  374. bool: True if successful
  375. """
  376. response_dict = await self.client._make_request(
  377. "DELETE",
  378. f"users/{str(id)}/collections/{str(collection_id)}",
  379. version="v3",
  380. )
  381. return WrappedBooleanResponse(**response_dict)
  382. async def create_api_key(
  383. self,
  384. id: str | UUID,
  385. name: Optional[str] = None,
  386. description: Optional[str] = None,
  387. ) -> WrappedAPIKeyResponse:
  388. """Create a new API key for the specified user.
  389. Args:
  390. id (str | UUID): User ID to create API key for
  391. name (Optional[str]): Name of the API key
  392. description (Optional[str]): Description of the API key
  393. Returns:
  394. dict: { "message": "API key created successfully", "api_key": "key_id.raw_api_key" }
  395. """
  396. data: dict[str, Any] = {}
  397. if name:
  398. data["name"] = name
  399. if description:
  400. data["description"] = description
  401. response_dict = await self.client._make_request(
  402. "POST",
  403. f"users/{str(id)}/api-keys",
  404. json=data,
  405. version="v3",
  406. )
  407. return WrappedAPIKeyResponse(**response_dict)
  408. async def list_api_keys(
  409. self,
  410. id: str | UUID,
  411. ) -> WrappedAPIKeysResponse:
  412. """List all API keys for the specified user.
  413. Args:
  414. id (str | UUID): User ID to list API keys for
  415. Returns:
  416. WrappedAPIKeysResponse
  417. """
  418. resp_dict = await self.client._make_request(
  419. "GET",
  420. f"users/{str(id)}/api-keys",
  421. version="v3",
  422. )
  423. return WrappedAPIKeysResponse(**resp_dict)
  424. async def delete_api_key(
  425. self,
  426. id: str | UUID,
  427. key_id: str | UUID,
  428. ) -> WrappedBooleanResponse:
  429. """Delete a specific API key for the specified user.
  430. Args:
  431. id (str | UUID): User ID
  432. key_id (str | UUID): API key ID to delete
  433. Returns:
  434. dict: { "message": "API key deleted successfully" }
  435. """
  436. response_dict = await self.client._make_request(
  437. "DELETE",
  438. f"users/{str(id)}/api-keys/{str(key_id)}",
  439. version="v3",
  440. )
  441. return WrappedBooleanResponse(**response_dict)
  442. async def get_limits(self) -> WrappedLimitsResponse:
  443. response_dict = await self.client._make_request(
  444. "GET",
  445. f"users/{str(self.client._user_id)}/limits",
  446. version="v3",
  447. )
  448. return WrappedLimitsResponse(**response_dict)
  449. async def oauth_google_authorize(self) -> WrappedGenericMessageResponse:
  450. """Get Google OAuth 2.0 authorization URL from the server.
  451. Returns:
  452. WrappedGenericMessageResponse
  453. """
  454. response_dict = await self.client._make_request(
  455. "GET",
  456. "users/oauth/google/authorize",
  457. version="v3",
  458. )
  459. return WrappedGenericMessageResponse(**response_dict)
  460. async def oauth_github_authorize(self) -> WrappedGenericMessageResponse:
  461. """Get GitHub OAuth 2.0 authorization URL from the server.
  462. Returns:
  463. WrappedGenericMessageResponse
  464. """
  465. response_dict = await self.client._make_request(
  466. "GET",
  467. "users/oauth/github/authorize",
  468. version="v3",
  469. )
  470. return WrappedGenericMessageResponse(**response_dict)
  471. async def oauth_google_callback(
  472. self, code: str, state: str
  473. ) -> WrappedLoginResponse:
  474. """Exchange `code` and `state` with the Google OAuth 2.0 callback
  475. route."""
  476. response_dict = await self.client._make_request(
  477. "GET",
  478. "users/oauth/google/callback",
  479. params={"code": code, "state": state},
  480. version="v3",
  481. )
  482. return WrappedLoginResponse(**response_dict)
  483. async def oauth_github_callback(
  484. self, code: str, state: str
  485. ) -> WrappedLoginResponse:
  486. """Exchange `code` and `state` with the GitHub OAuth 2.0 callback
  487. route."""
  488. response_dict = await self.client._make_request(
  489. "GET",
  490. "users/oauth/github/callback",
  491. params={"code": code, "state": state},
  492. version="v3",
  493. )
  494. return WrappedLoginResponse(**response_dict)