Skip to content

Commit 42768bb

Browse files
authored
Merge pull request #122 from david-engelmann/master
Functions and Classes for UserDetails to Pair with create_user
2 parents d8f2733 + a68fbfc commit 42768bb

File tree

12 files changed

+713
-1
lines changed

12 files changed

+713
-1
lines changed

doccano_client/client.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from doccano_client.models.role import Role
2424
from doccano_client.models.task_status import TaskStatus
2525
from doccano_client.models.user import User
26+
from doccano_client.models.user_details import PasswordUpdated, UserDetails
2627
from doccano_client.repositories.base import BaseRepository
2728
from doccano_client.repositories.comment import CommentRepository
2829
from doccano_client.repositories.data_download import DataDownloadRepository
@@ -47,6 +48,7 @@
4748
from doccano_client.repositories.role import RoleRepository
4849
from doccano_client.repositories.task_status import TaskStatusRepository
4950
from doccano_client.repositories.user import UserRepository
51+
from doccano_client.repositories.user_details import UserDetailsRepository
5052
from doccano_client.services.label_type import LabelTypeService
5153
from doccano_client.usecase.comment import CommentUseCase
5254
from doccano_client.usecase.data_download import DataDownloadUseCase
@@ -63,6 +65,7 @@
6365
from doccano_client.usecase.label_type import LabelTypeUseCase
6466
from doccano_client.usecase.member import MemberUseCase
6567
from doccano_client.usecase.project import ProjectType, ProjectUseCase
68+
from doccano_client.usecase.user_details import UserDetailsUseCase
6669

6770

6871
class DoccanoClient:
@@ -82,6 +85,7 @@ def __init__(self, base_url: str, verify: Optional[str | bool] = None):
8285
"""
8386
self._base_repository = BaseRepository(base_url, verify=verify)
8487
self._user_repository = UserRepository(self._base_repository)
88+
self._user_details_repository = UserDetailsRepository(self._base_repository)
8589
self._role_repository = RoleRepository(self._base_repository)
8690
self._project_repository = ProjectRepository(self._base_repository)
8791
self._metrics_repository = MetricsRepository(self._base_repository)
@@ -182,6 +186,10 @@ def bounding_box(self) -> BoundingBoxUseCase:
182186
def text(self) -> TextUseCase:
183187
return TextUseCase(self._text_repository)
184188

189+
@property
190+
def user_details(self) -> UserDetailsUseCase:
191+
return UserDetailsUseCase(self._user_details_repository)
192+
185193
def _get_label_type_usecase(self, type: Literal["category", "span", "relation"]) -> LabelTypeUseCase:
186194
if type == "category":
187195
return self.category_type
@@ -208,6 +216,36 @@ def get_profile(self) -> User:
208216
"""
209217
return self._user_repository.get_profile()
210218

219+
def change_current_user_password(self, password: str, confirm_password: str) -> PasswordUpdated:
220+
"""Change the current user's password
221+
222+
Args:
223+
password (str): the new password to set for the current user
224+
confirm_password(str): confirm the new password to set for the current user
225+
226+
Returns:
227+
PasswordUpdated: Message confirming password change.
228+
"""
229+
return self.user_details.change_current_user_password(password=password, confirm_password=confirm_password)
230+
231+
def update_current_user_details(
232+
self, username: str = None, first_name: str = None, last_name: str = None
233+
) -> UserDetails:
234+
"""Update either username, first name or last name of the current user.
235+
If any args are left as None the current info will be kept
236+
237+
Args:
238+
username (str): The username to change the current user to.
239+
first_name (str): The first name to change the current user to.
240+
last_name (str): The last name to change the current user to
241+
242+
Returns:
243+
UserDetails: the updated user login info
244+
"""
245+
return self.user_details.update_current_user_details(
246+
username=username, first_name=first_name, last_name=last_name
247+
)
248+
211249
def search_users(self, name: str = "") -> List[User]:
212250
"""Search users by name.
213251
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from pydantic import BaseModel, root_validator
2+
from pydantic.types import ConstrainedStr
3+
4+
5+
class UserDetails(BaseModel):
6+
"""Contains the data relevant to a user on a Doccano project"""
7+
8+
pk: int
9+
username: str
10+
email: str
11+
first_name: str
12+
last_name: str
13+
14+
15+
class PasswordUpdated(BaseModel):
16+
"""Contains the data relevant to a password adjustment"""
17+
18+
detail: str
19+
20+
21+
class Password(ConstrainedStr):
22+
min_length = 2
23+
max_length = 128
24+
strip_whitespace = True
25+
26+
27+
class PasswordChange(BaseModel):
28+
new_password: Password
29+
confirm_password: Password
30+
31+
@root_validator
32+
def new_password_matches_confirm_password(cls, values):
33+
new_password = values.get("new_password")
34+
confirm_password = values.get("confirm_password")
35+
if new_password != confirm_password:
36+
raise ValueError("The new password does not match the confirm one.")
37+
return values
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from __future__ import annotations
2+
3+
from doccano_client.models.user_details import (
4+
PasswordChange,
5+
PasswordUpdated,
6+
UserDetails,
7+
)
8+
from doccano_client.repositories.base import BaseRepository
9+
10+
11+
class UserDetailsRepository:
12+
"""Repository for interacting with the Doccano UserDetails API"""
13+
14+
def __init__(self, client: BaseRepository):
15+
self._client = client
16+
17+
def get_current_user_details(self) -> UserDetails:
18+
"""Get the Current User Details
19+
20+
Returns:
21+
UserDetails: The user login info.
22+
"""
23+
response = self._client.get("auth/user/")
24+
return UserDetails.parse_obj(response.json())
25+
26+
def update_current_user_details(self, user_details: UserDetails) -> UserDetails:
27+
"""Update either username, first name or last name of the current user.
28+
If any args are left as None the current info will be kept
29+
30+
Args:
31+
user_details (UserDetails): The user details.
32+
33+
Returns:
34+
UserDetails: the updated user login info
35+
"""
36+
response = self._client.put("auth/user/", json=user_details.dict())
37+
return UserDetails.parse_obj(response.json())
38+
39+
def change_current_user_password(self, password_change: PasswordChange) -> PasswordUpdated:
40+
"""Change the password of the Current User
41+
42+
Args:
43+
password_change (PasswordChange): the new password to set for the current user
44+
45+
Returns:
46+
PasswordUpdated: Message confirming password change.
47+
"""
48+
response = self._client.post(
49+
"auth/password/change/",
50+
json={"new_password1": password_change.new_password, "new_password2": password_change.confirm_password},
51+
)
52+
return PasswordUpdated.parse_obj(response.json())
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from doccano_client.models.user_details import (
2+
PasswordChange,
3+
PasswordUpdated,
4+
UserDetails,
5+
)
6+
from doccano_client.repositories.user_details import UserDetailsRepository
7+
8+
9+
class UserDetailsUseCase:
10+
def __init__(self, user_details_repository: UserDetailsRepository):
11+
self._user_details_repository = user_details_repository
12+
13+
def get_current_user_details(self) -> UserDetails:
14+
"""Get the Current User Details
15+
16+
Returns:
17+
UserDetails: The user login info.
18+
"""
19+
return self._user_details_repository.get_current_user_details()
20+
21+
def update_current_user_details(
22+
self, username: str = None, first_name: str = None, last_name: str = None
23+
) -> UserDetails:
24+
"""Update either username, first name or last name of the current user.
25+
If any args are left as None the current info will be kept
26+
27+
Args:
28+
username (str): The username to change the current user to.
29+
first_name (str): The first name to change the current user to.
30+
last_name (str): The last name to change the current user to
31+
32+
Returns:
33+
UserDetails: the updated user login info
34+
"""
35+
user_details = self.get_current_user_details()
36+
user_details = UserDetails(
37+
pk=user_details.pk,
38+
username=username or user_details.username,
39+
first_name=first_name or user_details.first_name,
40+
last_name=last_name or user_details.last_name,
41+
email=user_details.email,
42+
)
43+
return self._user_details_repository.update_current_user_details(user_details)
44+
45+
def change_current_user_password(self, password: str, confirm_password: str) -> PasswordUpdated:
46+
"""Change the password of the current user
47+
48+
Args:
49+
password (str): the new password to set for the current user
50+
confirm_password(str): confirm the new password to set for the current user
51+
52+
Returns:
53+
PasswordUpdated: Message confirming password change.
54+
"""
55+
password_change = PasswordChange(new_password=password, confirm_password=confirm_password)
56+
return self._user_details_repository.change_current_user_password(password_change)

docs/usage.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@
1111

1212
## User
1313

14-
::: doccano_client.DoccanoClient.get_profile
1514
::: doccano_client.DoccanoClient.search_users
1615
::: doccano_client.DoccanoClient.find_user_by_name
1716

17+
## User Details
18+
19+
::: doccano_client.DoccanoClient.get_profile
20+
::: doccano_client.DoccanoClient.change_current_user_password
21+
::: doccano_client.DoccanoClient.update_current_user_details
22+
1823
## Role
1924

2025
::: doccano_client.DoccanoClient.list_roles
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
interactions:
2+
- request:
3+
body: '{"new_password1": "foobarbaz1", "new_password2": "foobarbaz1"}'
4+
headers:
5+
Accept-Encoding:
6+
- gzip, deflate
7+
Connection:
8+
- keep-alive
9+
Content-Length:
10+
- '62'
11+
Cookie:
12+
- csrftoken=1maJEoZrCA1kd0Nfr0oWeZYuVVPFo1sziTKwuMSkSi5Cn3WdgrBLyMkS0teizEun;
13+
sessionid=prauleg5y1gzxdagjyq4zgt7yccz7puj
14+
User-Agent:
15+
- python-requests/2.28.1
16+
X-CSRFToken:
17+
- 1maJEoZrCA1kd0Nfr0oWeZYuVVPFo1sziTKwuMSkSi5Cn3WdgrBLyMkS0teizEun
18+
accept:
19+
- application/json
20+
content-type:
21+
- application/json
22+
referer:
23+
- http://localhost:8000
24+
method: POST
25+
uri: http://localhost:8000/v1/auth/password/change/
26+
response:
27+
body:
28+
string: '{"detail":"New password has been saved."}'
29+
headers:
30+
Allow:
31+
- POST, OPTIONS
32+
Content-Length:
33+
- '41'
34+
Content-Type:
35+
- application/json
36+
Cross-Origin-Opener-Policy:
37+
- same-origin
38+
Date:
39+
- Thu, 27 Oct 2022 05:26:19 GMT
40+
Referrer-Policy:
41+
- same-origin
42+
Server:
43+
- WSGIServer/0.2 CPython/3.8.15
44+
Set-Cookie:
45+
- sessionid=yzvlcvrsvei7fx43svdc4xyrh2pwmhm1; expires=Thu, 10 Nov 2022 05:26:19
46+
GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax
47+
Vary:
48+
- Accept, Origin, Cookie
49+
X-Content-Type-Options:
50+
- nosniff
51+
X-Frame-Options:
52+
- DENY
53+
status:
54+
code: 200
55+
message: OK
56+
- request:
57+
body: '{"new_password1": "foobarbaz", "new_password2": "foobarbaz"}'
58+
headers:
59+
Accept-Encoding:
60+
- gzip, deflate
61+
Connection:
62+
- keep-alive
63+
Content-Length:
64+
- '60'
65+
Cookie:
66+
- csrftoken=1maJEoZrCA1kd0Nfr0oWeZYuVVPFo1sziTKwuMSkSi5Cn3WdgrBLyMkS0teizEun;
67+
sessionid=yzvlcvrsvei7fx43svdc4xyrh2pwmhm1
68+
User-Agent:
69+
- python-requests/2.28.1
70+
X-CSRFToken:
71+
- 1maJEoZrCA1kd0Nfr0oWeZYuVVPFo1sziTKwuMSkSi5Cn3WdgrBLyMkS0teizEun
72+
accept:
73+
- application/json
74+
content-type:
75+
- application/json
76+
referer:
77+
- http://localhost:8000
78+
method: POST
79+
uri: http://localhost:8000/v1/auth/password/change/
80+
response:
81+
body:
82+
string: '{"detail":"New password has been saved."}'
83+
headers:
84+
Allow:
85+
- POST, OPTIONS
86+
Content-Length:
87+
- '41'
88+
Content-Type:
89+
- application/json
90+
Cross-Origin-Opener-Policy:
91+
- same-origin
92+
Date:
93+
- Thu, 27 Oct 2022 05:26:19 GMT
94+
Referrer-Policy:
95+
- same-origin
96+
Server:
97+
- WSGIServer/0.2 CPython/3.8.15
98+
Set-Cookie:
99+
- sessionid=srwlwknapbj7op6hu8yuqy0pl2cmor7c; expires=Thu, 10 Nov 2022 05:26:19
100+
GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax
101+
Vary:
102+
- Accept, Origin, Cookie
103+
X-Content-Type-Options:
104+
- nosniff
105+
X-Frame-Options:
106+
- DENY
107+
status:
108+
code: 200
109+
message: OK
110+
version: 1

0 commit comments

Comments
 (0)