users.py 15 KB

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