users.py 15 KB

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