users.py 17 KB

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