users.py 14 KB

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