users.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. from __future__ import annotations # for Python 3.10+
  2. from typing import Optional
  3. from uuid import UUID
  4. from typing_extensions import deprecated
  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. return await self.client._make_request(
  82. "DELETE",
  83. f"users/{str(id)}",
  84. json=data,
  85. version="v3",
  86. )
  87. async def verify_email(
  88. self, email: str, verification_code: str
  89. ) -> WrappedGenericMessageResponse:
  90. """
  91. Verify a user's email address.
  92. Args:
  93. email (str): User's email address
  94. verification_code (str): Verification code sent to the user's email
  95. Returns:
  96. dict: Verification result
  97. """
  98. data = {"email": email, "verification_code": verification_code}
  99. return await self.client._make_request(
  100. "POST",
  101. "users/verify-email",
  102. json=data,
  103. version="v3",
  104. )
  105. async def login(self, email: str, password: str) -> dict[str, Token]:
  106. """
  107. Log in a user.
  108. Args:
  109. email (str): User's email address
  110. password (str): User's password
  111. Returns:
  112. dict[str, Token]: Access and refresh tokens
  113. """
  114. data = {"username": email, "password": password}
  115. response = await self.client._make_request(
  116. "POST",
  117. "users/login",
  118. data=data,
  119. version="v3",
  120. )
  121. self.client.access_token = response["results"]["access_token"]["token"]
  122. self.client._refresh_token = response["results"]["refresh_token"][
  123. "token"
  124. ]
  125. return response
  126. # FIXME: What is going on here...
  127. async def login_with_token(self, access_token: str) -> dict[str, Token]:
  128. """
  129. Log in using an existing access token.
  130. Args:
  131. access_token (str): Existing access token
  132. Returns:
  133. dict[str, Token]: Token information
  134. """
  135. self.client.access_token = access_token
  136. try:
  137. await self.client._make_request(
  138. "GET",
  139. "users/me",
  140. version="v3",
  141. )
  142. return {
  143. "access_token": Token(
  144. token=access_token, token_type="access_token"
  145. ),
  146. }
  147. except Exception:
  148. self.access_token = None
  149. self.client._refresh_token = None
  150. raise ValueError("Invalid token provided")
  151. async def logout(self) -> WrappedGenericMessageResponse:
  152. """Log out the current user."""
  153. response = await self.client._make_request(
  154. "POST",
  155. "users/logout",
  156. version="v3",
  157. )
  158. self.client.access_token = None
  159. self.client._refresh_token = None
  160. return response
  161. async def refresh_token(self) -> WrappedTokenResponse:
  162. """Refresh the access token using the refresh token."""
  163. response = await self.client._make_request(
  164. "POST",
  165. "users/refresh-token",
  166. json=self.client._refresh_token,
  167. version="v3",
  168. )
  169. self.client.access_token = response["results"]["access_token"]["token"]
  170. self.client._refresh_token = response["results"]["refresh_token"][
  171. "token"
  172. ]
  173. return response
  174. async def change_password(
  175. self, current_password: str, new_password: str
  176. ) -> WrappedGenericMessageResponse:
  177. """
  178. Change the user's password.
  179. Args:
  180. current_password (str): User's current password
  181. new_password (str): User's new password
  182. Returns:
  183. dict: Change password result
  184. """
  185. data = {
  186. "current_password": current_password,
  187. "new_password": new_password,
  188. }
  189. return await self.client._make_request(
  190. "POST",
  191. "users/change-password",
  192. json=data,
  193. version="v3",
  194. )
  195. async def request_password_reset(
  196. self, email: str
  197. ) -> WrappedGenericMessageResponse:
  198. """
  199. Request a password reset.
  200. Args:
  201. email (str): User's email address
  202. Returns:
  203. dict: Password reset request result
  204. """
  205. return await self.client._make_request(
  206. "POST",
  207. "users/request-password-reset",
  208. json=email,
  209. version="v3",
  210. )
  211. async def reset_password(
  212. self, reset_token: str, new_password: str
  213. ) -> WrappedGenericMessageResponse:
  214. """
  215. Reset password using a reset token.
  216. Args:
  217. reset_token (str): Password reset token
  218. new_password (str): New password
  219. Returns:
  220. dict: Password reset result
  221. """
  222. data = {"reset_token": reset_token, "new_password": new_password}
  223. return await self.client._make_request(
  224. "POST",
  225. "users/reset-password",
  226. json=data,
  227. version="v3",
  228. )
  229. async def list(
  230. self,
  231. ids: Optional[list[str | UUID]] = None,
  232. offset: Optional[int] = 0,
  233. limit: Optional[int] = 100,
  234. ) -> WrappedUsersResponse:
  235. """
  236. List users with pagination and filtering options.
  237. Args:
  238. offset (int, optional): Specifies the number of objects to skip. Defaults to 0.
  239. limit (int, optional): Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.
  240. Returns:
  241. dict: List of users and pagination information
  242. """
  243. params = {
  244. "offset": offset,
  245. "limit": limit,
  246. }
  247. if ids:
  248. params["ids"] = [str(user_id) for user_id in ids] # type: ignore
  249. return await self.client._make_request(
  250. "GET",
  251. "users",
  252. params=params,
  253. version="v3",
  254. )
  255. async def retrieve(
  256. self,
  257. id: str | UUID,
  258. ) -> WrappedUserResponse:
  259. """
  260. Get a specific user.
  261. Args:
  262. id (str | UUID): User ID to retrieve
  263. Returns:
  264. dict: Detailed user information
  265. """
  266. return await self.client._make_request(
  267. "GET",
  268. f"users/{str(id)}",
  269. version="v3",
  270. )
  271. async def me(
  272. self,
  273. ) -> WrappedUserResponse:
  274. """
  275. Get detailed information about the currently authenticated user.
  276. Returns:
  277. dict: Detailed user information
  278. """
  279. return await self.client._make_request(
  280. "GET",
  281. "users/me",
  282. version="v3",
  283. )
  284. async def update(
  285. self,
  286. id: str | UUID,
  287. email: Optional[str] = None,
  288. is_superuser: Optional[bool] = None,
  289. name: Optional[str] = None,
  290. bio: Optional[str] = None,
  291. profile_picture: Optional[str] = None,
  292. ) -> WrappedUserResponse:
  293. """
  294. Update user information.
  295. Args:
  296. id (str | UUID): User ID to update
  297. username (Optional[str]): New username
  298. is_superuser (Optional[bool]): Update superuser status
  299. name (Optional[str]): New name
  300. bio (Optional[str]): New bio
  301. profile_picture (Optional[str]): New profile picture
  302. Returns:
  303. dict: Updated user information
  304. """
  305. data: dict = {}
  306. if email is not None:
  307. data["email"] = email
  308. if is_superuser is not None:
  309. data["is_superuser"] = is_superuser
  310. if name is not None:
  311. data["name"] = name
  312. if bio is not None:
  313. data["bio"] = bio
  314. if profile_picture is not None:
  315. data["profile_picture"] = profile_picture
  316. return await self.client._make_request(
  317. "POST",
  318. f"users/{str(id)}",
  319. json=data, # if len(data.keys()) != 1 else list(data.values())[0]
  320. version="v3",
  321. )
  322. async def list_collections(
  323. self,
  324. id: str | UUID,
  325. offset: Optional[int] = 0,
  326. limit: Optional[int] = 100,
  327. ) -> WrappedCollectionsResponse:
  328. """
  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. return await self.client._make_request(
  342. "GET",
  343. f"users/{str(id)}/collections",
  344. params=params,
  345. version="v3",
  346. )
  347. async def add_to_collection(
  348. self,
  349. id: str | UUID,
  350. collection_id: str | UUID,
  351. ) -> WrappedBooleanResponse:
  352. """
  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. return await self.client._make_request(
  359. "POST",
  360. f"users/{str(id)}/collections/{str(collection_id)}",
  361. version="v3",
  362. )
  363. async def remove_from_collection(
  364. self,
  365. id: str | UUID,
  366. collection_id: str | UUID,
  367. ) -> WrappedBooleanResponse:
  368. """
  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. return await self.client._make_request(
  377. "DELETE",
  378. f"users/{str(id)}/collections/{str(collection_id)}",
  379. version="v3",
  380. )