Skip to content

Commit 450d9f4

Browse files
authored
Merge pull request #29 from dayyass/add_cookies
add cookies
2 parents 7e031ef + c8d8054 commit 450d9f4

18 files changed

Lines changed: 267 additions & 280 deletions

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ exclude_lines =
1313
omit =
1414
muse_as_service/utils.py
1515
muse_as_service/database/add_user.py
16+
muse_as_service/database/remove_user.py
1617

1718
show_missing = True
1819
ignore_errors = False

Dockerfile

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,6 @@ MAINTAINER Dani El-Ayyass <dayyass@yandex.ru>
33
WORKDIR /app
44
COPY . .
55

6-
# environment variables
7-
ARG SECRET_KEY
8-
RUN test -n "$SECRET_KEY" # mandating the variable to be passed as the build time argument
9-
ENV SECRET_KEY=$SECRET_KEY
10-
11-
ARG JWT_SECRET_KEY
12-
RUN test -n "$SECRET_KEY" # mandating the variable to be passed as the build time argument
13-
ENV JWT_SECRET_KEY=$JWT_SECRET_KEY
14-
156
# instal dependencies
167
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
178

README.md

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,26 +49,15 @@ pip install --upgrade pip && pip install -r requirements.txt
4949
```
5050

5151
Before using the service you need to:
52-
- download MUSE model with following command:<br>
52+
- download MUSE model executing the following command:<br>
5353
`
5454
python models/download_muse.py
5555
`
56-
- set up two environment variables `SECRET_KEY` and `JWT_SECRET_KEY` (for security):<br>
57-
`
58-
export SECRET_KEY={SECRET_KEY} JWT_SECRET_KEY={JWT_SECRET_KEY}
59-
`
60-
61-
To generate these keys you can use [this](https://stackoverflow.com/questions/34902378/where-do-i-get-a-secret-key-for-flask/34903502) for `SECRET_KEY` and [this](https://mkjwk.org) for `JWT_SECRET_KEY`.
62-
63-
For testing purposes you can use:<br>
64-
`
65-
export SECRET_KEY=test JWT_SECRET_KEY=test
66-
`
6756

6857
### Launch the Service
6958
To build a **docker image** with a service parametrized with [gunicorn.conf.py](https://github.com/dayyass/muse_as_service/blob/main/gunicorn.conf.py) file run:
7059
```shell script
71-
docker build --build-arg SECRET_KEY="${SECRET_KEY}" --build-arg JWT_SECRET_KEY="${JWT_SECRET_KEY}" -t muse_as_service .
60+
docker build -t muse_as_service .
7261
```
7362
**NOTE**: instead of building a docker image, you can pull it from [Docker Hub](https://hub.docker.com/r/dayyass/muse_as_service).
7463

@@ -109,11 +98,15 @@ python muse_as_service/database/add_user.py --username {username} --password {pa
10998
```
11099
**NOTE**: no passwords are stored in the database, only their hashes.
111100

101+
To remove the user with `username` run:
102+
```shell script
103+
python muse_as_service/database/remove_user.py --username {username}
104+
```
105+
112106
MUSE as Service has the following endpoints:
113107
<pre>
114108
- /login - POST request with `username` and `password` to get tokens (access and refresh)
115-
- /logout/access - POST request to remove access token (access token required)
116-
- /logout/refresh - POST request to remove refresh token (refresh token required)
109+
- /logout - POST request to remove tokens (access and refresh)
117110
- /token/refresh - POST request to refresh access token (refresh token required)
118111
- /tokenize - GET request for `sentence` tokenization (access token required)
119112
- /embed - GET request for `sentence` embedding (access token required)
@@ -130,29 +123,37 @@ port = 5000
130123

131124
sentences = ["This is sentence example.", "This is yet another sentence example."]
132125

126+
# start session
127+
session = requests.Session()
128+
133129
# login
134-
response = requests.post(
130+
response = session.post(
135131
url=f"http://{ip}:{port}/login",
136132
json={"username": "admin", "password": "admin"},
137133
)
138-
token = response.json()["access_token"]
139134

140135
# tokenizer
141-
response = requests.get(
136+
response = session.get(
142137
url=f"http://{ip}:{port}/tokenize",
143138
params={"sentence": sentences},
144-
headers={"Authorization": f"Bearer {token}"},
145139
)
146140
tokenized_sentence = response.json()["tokens"]
147141

148142
# embedder
149-
response = requests.get(
143+
response = session.get(
150144
url=f"http://{ip}:{port}/embed",
151145
params={"sentence": sentences},
152-
headers={"Authorization": f"Bearer {token}"},
153146
)
154147
embedding = np.array(response.json()["embedding"])
155148

149+
# logout
150+
response = session.post(
151+
url=f"http://{ip}:{port}/logout",
152+
)
153+
154+
# close session
155+
session.close()
156+
156157
# results
157158
print(tokenized_sentence) # [
158159
# ["▁This", "▁is", "▁sentence", "▁example", "."],
@@ -211,10 +212,6 @@ pre-commit install
211212
`
212213

213214
Before running tests and code coverage, you need to:
214-
- set up two environment variables `SECRET_KEY` and `JWT_SECRET_KEY` (for security):<br>
215-
`
216-
export SECRET_KEY=test JWT_SECRET_KEY=test
217-
`
218215
- run [app.py](https://github.com/dayyass/muse_as_service/blob/main/app.py) in background:<br>
219216
`
220217
python app.py &
@@ -230,7 +227,7 @@ To measure [**code coverage**](https://coverage.readthedocs.io) run:<br>
230227
coverage run -m unittest discover && coverage report -m
231228
`
232229

233-
**NOTE**: since we launched Flask application in background, we need to stop it after running tests and code coverage with following command:
230+
**NOTE**: since we launched Flask application in background, we need to stop it after running tests and code coverage with the following command:
234231
```shell script
235232
kill $(ps aux | grep '[a]pp.py' | awk '{print $2}')
236233
```

codecov.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ codecov:
44
ignore:
55
- "muse_as_service/utils.py"
66
- "muse_as_service/database/add_user.py"
7+
- "muse_as_service/database/remove_user.py"
78

89
coverage:
910
status:

examples/usage_requests.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,37 @@
77

88
sentences = ["This is sentence example.", "This is yet another sentence example."]
99

10+
# start session
11+
session = requests.Session()
12+
1013
# login
11-
response = requests.post(
14+
response = session.post(
1215
url=f"http://{ip}:{port}/login",
1316
json={"username": "admin", "password": "admin"},
1417
)
15-
token = response.json()["access_token"]
1618

1719
# tokenizer
18-
response = requests.get(
20+
response = session.get(
1921
url=f"http://{ip}:{port}/tokenize",
2022
params={"sentence": sentences},
21-
headers={"Authorization": f"Bearer {token}"},
2223
)
2324
tokenized_sentence = response.json()["tokens"]
2425

2526
# embedder
26-
response = requests.get(
27+
response = session.get(
2728
url=f"http://{ip}:{port}/embed",
2829
params={"sentence": sentences},
29-
headers={"Authorization": f"Bearer {token}"},
3030
)
3131
embedding = np.array(response.json()["embedding"])
3232

33+
# logout
34+
response = session.post(
35+
url=f"http://{ip}:{port}/logout",
36+
)
37+
38+
# close session
39+
session.close()
40+
3341
# results
3442
print(tokenized_sentence) # [
3543
# ["▁This", "▁is", "▁sentence", "▁example", "."],

gunicorn.conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def max_workers_and_threads() -> int:
2020
# GUNICORN OPTIONS
2121

2222
bind = f"{HOST}:{PORT}" # The socket to bind
23-
workers = 4 # The number of worker processes for handling requests
23+
workers = 1 # The number of worker processes for handling requests
2424
threads = min( # The number of worker threads for handling requests
2525
8, max_workers_and_threads()
2626
)

muse_as_service/app.py

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import os
2-
31
from flask import Flask
42
from flask_jwt_extended import JWTManager
53
from flask_restful import Api
@@ -9,34 +7,19 @@
97
api = Api(app)
108

119

12-
app.config.from_json("config.json")
13-
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
14-
app.config["JWT_SECRET_KEY"] = os.environ["JWT_SECRET_KEY"]
10+
app.config.from_pyfile("config.py")
1511

1612

1713
db: SQLAlchemy = SQLAlchemy(app)
1814
jwt: JWTManager = JWTManager(app)
1915

2016

21-
@jwt.token_in_blocklist_loader
22-
def check_if_token_in_blocklist(jwt_header, jwt_payload):
23-
jti = jwt_payload["jti"]
24-
return RevokedTokenModel.is_jti_blocklisted(jti)
25-
26-
27-
from muse_as_service.auth import ( # noqa: E402
28-
TokenRefresh,
29-
UserLogin,
30-
UserLogoutAccess,
31-
UserLogoutRefresh,
32-
)
33-
from muse_as_service.database.database import RevokedTokenModel # noqa: E402
17+
from muse_as_service.auth import TokenRefresh, UserLogin, UserLogout # noqa: E402
3418
from muse_as_service.endpoints import Embedder, Tokenizer # noqa: E402
3519

3620
# auth
3721
api.add_resource(UserLogin, "/login")
38-
api.add_resource(UserLogoutAccess, "/logout/access")
39-
api.add_resource(UserLogoutRefresh, "/logout/refresh")
22+
api.add_resource(UserLogout, "/logout")
4023
api.add_resource(TokenRefresh, "/token/refresh")
4124

4225

muse_as_service/auth.py

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
from flask_jwt_extended import (
33
create_access_token,
44
create_refresh_token,
5-
get_jwt,
65
get_jwt_identity,
76
jwt_required,
7+
set_access_cookies,
8+
set_refresh_cookies,
9+
unset_jwt_cookies,
810
)
911
from flask_restful import Resource, reqparse
1012

11-
from muse_as_service.database.database import RevokedTokenModel, UserModel
13+
from muse_as_service.database.database import UserModel
1214

1315

1416
def unauthorized() -> Response:
@@ -66,43 +68,26 @@ def post(self) -> Response:
6668
access_token = create_access_token(identity=args["username"])
6769
refresh_token = create_refresh_token(identity=args["username"])
6870

69-
return jsonify(
70-
message=f"Logged in as {current_user.username}",
71-
access_token=access_token,
72-
refresh_token=refresh_token,
73-
)
71+
response = jsonify(message="Logged in")
7472

73+
# set cookies
74+
set_access_cookies(response, access_token)
75+
set_refresh_cookies(response, refresh_token)
7576

76-
class UserLogoutAccess(Resource):
77-
"""
78-
User logout API resource.
79-
"""
80-
81-
@jwt_required()
82-
def post(self) -> Response:
83-
84-
jti = get_jwt()["jti"]
77+
return response
8578

86-
revoked_token = RevokedTokenModel(jti=jti)
87-
revoked_token.add()
8879

89-
return jsonify(message="Access token has been revoked")
90-
91-
92-
class UserLogoutRefresh(Resource):
80+
class UserLogout(Resource):
9381
"""
9482
User logout API resource.
9583
"""
9684

97-
@jwt_required(refresh=True)
9885
def post(self) -> Response:
9986

100-
jti = get_jwt()["jti"]
87+
response = jsonify(message="Logged out")
88+
unset_jwt_cookies(response)
10189

102-
revoked_token = RevokedTokenModel(jti=jti)
103-
revoked_token.add()
104-
105-
return jsonify(message="Refresh token has been revoked")
90+
return response
10691

10792

10893
class TokenRefresh(Resource):
@@ -116,6 +101,8 @@ def post(self) -> Response:
116101
current_user = get_jwt_identity()
117102
access_token = create_access_token(identity=current_user)
118103

119-
return jsonify(
120-
message="Access token has been refreshed", access_token=access_token
121-
)
104+
response = jsonify(message="Access token has been refreshed")
105+
106+
set_access_cookies(response, access_token)
107+
108+
return response

muse_as_service/client/README.md

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
### Client
2-
MUSE as Service has next endpoints:
2+
MUSE as Service has the following endpoints:
33
<pre>
4-
- /login - POST request with `username` and `password` to get JWT tokens (access and refresh)
5-
- /logout/access - POST request to remove JWT access token (JWT access token required)
6-
- /logout/refresh - POST request to remove JWT refresh token (JWT refresh token required)
7-
- /token/refresh - POST request to refresh JWT access token (JWT refresh token required)
8-
- /tokenize - GET request for `sentence` tokenization (JWT access token required)
9-
- /embed - GET request for `sentence` embedding (JWT access token required)
4+
- /login - POST request with `username` and `password` to get tokens (access and refresh)
5+
- /logout - POST request to remove tokens (access and refresh)
6+
- /token/refresh - POST request to refresh access token (refresh token required)
7+
- /tokenize - GET request for `sentence` tokenization (access token required)
8+
- /embed - GET request for `sentence` embedding (access token required)
109
</pre>
1110

1211
You can use python **requests** package to work with HTTP requests:
@@ -20,29 +19,37 @@ port = 5000
2019

2120
sentences = ["This is sentence example.", "This is yet another sentence example."]
2221

22+
# start session
23+
session = requests.Session()
24+
2325
# login
24-
response = requests.post(
26+
response = session.post(
2527
url=f"http://{ip}:{port}/login",
2628
json={"username": "admin", "password": "admin"},
2729
)
28-
token = response.json()["access_token"]
2930

3031
# tokenizer
31-
response = requests.get(
32+
response = session.get(
3233
url=f"http://{ip}:{port}/tokenize",
3334
params={"sentence": sentences},
34-
headers={"Authorization": f"Bearer {token}"},
3535
)
3636
tokenized_sentence = response.json()["tokens"]
3737

3838
# embedder
39-
response = requests.get(
39+
response = session.get(
4040
url=f"http://{ip}:{port}/embed",
4141
params={"sentence": sentences},
42-
headers={"Authorization": f"Bearer {token}"},
4342
)
4443
embedding = np.array(response.json()["embedding"])
4544

45+
# logout
46+
response = session.post(
47+
url=f"http://{ip}:{port}/logout",
48+
)
49+
50+
# close session
51+
session.close()
52+
4653
# results
4754
print(tokenized_sentence) # [
4855
# ["▁This", "▁is", "▁sentence", "▁example", "."],
@@ -51,7 +58,7 @@ print(tokenized_sentence) # [
5158
print(embedding.shape) # (2, 512)
5259
```
5360

54-
But it is better to use the built-in client **MUSEClient** for sentence tokenization and embedding, that wraps the functionality of the python **requests** package and provides a user with a simpler interface.
61+
However it is better to use built-in client **MUSEClient** for sentence tokenization and embedding, that wraps the functionality of the python **requests** package and provides user with a simpler interface.
5562

5663
Instead of using endpoints, listed above, directly, **MUSEClient** provides the following methods to work with:
5764
<pre>

0 commit comments

Comments
 (0)