Skip to content

Commit

Permalink
Merge pull request #29 from dayyass/add_cookies
Browse files Browse the repository at this point in the history
add cookies
  • Loading branch information
dayyass authored Jul 22, 2021
2 parents 7e031ef + c8d8054 commit 450d9f4
Show file tree
Hide file tree
Showing 18 changed files with 267 additions and 280 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ exclude_lines =
omit =
muse_as_service/utils.py
muse_as_service/database/add_user.py
muse_as_service/database/remove_user.py

show_missing = True
ignore_errors = False
9 changes: 0 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,6 @@ MAINTAINER Dani El-Ayyass <[email protected]>
WORKDIR /app
COPY . .

# environment variables
ARG SECRET_KEY
RUN test -n "$SECRET_KEY" # mandating the variable to be passed as the build time argument
ENV SECRET_KEY=$SECRET_KEY

ARG JWT_SECRET_KEY
RUN test -n "$SECRET_KEY" # mandating the variable to be passed as the build time argument
ENV JWT_SECRET_KEY=$JWT_SECRET_KEY

# instal dependencies
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt

Expand Down
49 changes: 23 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,15 @@ pip install --upgrade pip && pip install -r requirements.txt
```

Before using the service you need to:
- download MUSE model with following command:<br>
- download MUSE model executing the following command:<br>
`
python models/download_muse.py
`
- set up two environment variables `SECRET_KEY` and `JWT_SECRET_KEY` (for security):<br>
`
export SECRET_KEY={SECRET_KEY} JWT_SECRET_KEY={JWT_SECRET_KEY}
`

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`.

For testing purposes you can use:<br>
`
export SECRET_KEY=test JWT_SECRET_KEY=test
`

### Launch the Service
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:
```shell script
docker build --build-arg SECRET_KEY="${SECRET_KEY}" --build-arg JWT_SECRET_KEY="${JWT_SECRET_KEY}" -t muse_as_service .
docker build -t muse_as_service .
```
**NOTE**: instead of building a docker image, you can pull it from [Docker Hub](https://hub.docker.com/r/dayyass/muse_as_service).

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

To remove the user with `username` run:
```shell script
python muse_as_service/database/remove_user.py --username {username}
```

MUSE as Service has the following endpoints:
<pre>
- /login - POST request with `username` and `password` to get tokens (access and refresh)
- /logout/access - POST request to remove access token (access token required)
- /logout/refresh - POST request to remove refresh token (refresh token required)
- /logout - POST request to remove tokens (access and refresh)
- /token/refresh - POST request to refresh access token (refresh token required)
- /tokenize - GET request for `sentence` tokenization (access token required)
- /embed - GET request for `sentence` embedding (access token required)
Expand All @@ -130,29 +123,37 @@ port = 5000

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

# start session
session = requests.Session()

# login
response = requests.post(
response = session.post(
url=f"http://{ip}:{port}/login",
json={"username": "admin", "password": "admin"},
)
token = response.json()["access_token"]

# tokenizer
response = requests.get(
response = session.get(
url=f"http://{ip}:{port}/tokenize",
params={"sentence": sentences},
headers={"Authorization": f"Bearer {token}"},
)
tokenized_sentence = response.json()["tokens"]

# embedder
response = requests.get(
response = session.get(
url=f"http://{ip}:{port}/embed",
params={"sentence": sentences},
headers={"Authorization": f"Bearer {token}"},
)
embedding = np.array(response.json()["embedding"])

# logout
response = session.post(
url=f"http://{ip}:{port}/logout",
)

# close session
session.close()

# results
print(tokenized_sentence) # [
# ["▁This", "▁is", "▁sentence", "▁example", "."],
Expand Down Expand Up @@ -211,10 +212,6 @@ pre-commit install
`

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

**NOTE**: since we launched Flask application in background, we need to stop it after running tests and code coverage with following command:
**NOTE**: since we launched Flask application in background, we need to stop it after running tests and code coverage with the following command:
```shell script
kill $(ps aux | grep '[a]pp.py' | awk '{print $2}')
```
Expand Down
1 change: 1 addition & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ codecov:
ignore:
- "muse_as_service/utils.py"
- "muse_as_service/database/add_user.py"
- "muse_as_service/database/remove_user.py"

coverage:
status:
Expand Down
20 changes: 14 additions & 6 deletions examples/usage_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,37 @@

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

# start session
session = requests.Session()

# login
response = requests.post(
response = session.post(
url=f"http://{ip}:{port}/login",
json={"username": "admin", "password": "admin"},
)
token = response.json()["access_token"]

# tokenizer
response = requests.get(
response = session.get(
url=f"http://{ip}:{port}/tokenize",
params={"sentence": sentences},
headers={"Authorization": f"Bearer {token}"},
)
tokenized_sentence = response.json()["tokens"]

# embedder
response = requests.get(
response = session.get(
url=f"http://{ip}:{port}/embed",
params={"sentence": sentences},
headers={"Authorization": f"Bearer {token}"},
)
embedding = np.array(response.json()["embedding"])

# logout
response = session.post(
url=f"http://{ip}:{port}/logout",
)

# close session
session.close()

# results
print(tokenized_sentence) # [
# ["▁This", "▁is", "▁sentence", "▁example", "."],
Expand Down
2 changes: 1 addition & 1 deletion gunicorn.conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def max_workers_and_threads() -> int:
# GUNICORN OPTIONS

bind = f"{HOST}:{PORT}" # The socket to bind
workers = 4 # The number of worker processes for handling requests
workers = 1 # The number of worker processes for handling requests
threads = min( # The number of worker threads for handling requests
8, max_workers_and_threads()
)
Expand Down
23 changes: 3 additions & 20 deletions muse_as_service/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import os

from flask import Flask
from flask_jwt_extended import JWTManager
from flask_restful import Api
Expand All @@ -9,34 +7,19 @@
api = Api(app)


app.config.from_json("config.json")
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
app.config["JWT_SECRET_KEY"] = os.environ["JWT_SECRET_KEY"]
app.config.from_pyfile("config.py")


db: SQLAlchemy = SQLAlchemy(app)
jwt: JWTManager = JWTManager(app)


@jwt.token_in_blocklist_loader
def check_if_token_in_blocklist(jwt_header, jwt_payload):
jti = jwt_payload["jti"]
return RevokedTokenModel.is_jti_blocklisted(jti)


from muse_as_service.auth import ( # noqa: E402
TokenRefresh,
UserLogin,
UserLogoutAccess,
UserLogoutRefresh,
)
from muse_as_service.database.database import RevokedTokenModel # noqa: E402
from muse_as_service.auth import TokenRefresh, UserLogin, UserLogout # noqa: E402
from muse_as_service.endpoints import Embedder, Tokenizer # noqa: E402

# auth
api.add_resource(UserLogin, "/login")
api.add_resource(UserLogoutAccess, "/logout/access")
api.add_resource(UserLogoutRefresh, "/logout/refresh")
api.add_resource(UserLogout, "/logout")
api.add_resource(TokenRefresh, "/token/refresh")


Expand Down
49 changes: 18 additions & 31 deletions muse_as_service/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
from flask_jwt_extended import (
create_access_token,
create_refresh_token,
get_jwt,
get_jwt_identity,
jwt_required,
set_access_cookies,
set_refresh_cookies,
unset_jwt_cookies,
)
from flask_restful import Resource, reqparse

from muse_as_service.database.database import RevokedTokenModel, UserModel
from muse_as_service.database.database import UserModel


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

return jsonify(
message=f"Logged in as {current_user.username}",
access_token=access_token,
refresh_token=refresh_token,
)
response = jsonify(message="Logged in")

# set cookies
set_access_cookies(response, access_token)
set_refresh_cookies(response, refresh_token)

class UserLogoutAccess(Resource):
"""
User logout API resource.
"""

@jwt_required()
def post(self) -> Response:

jti = get_jwt()["jti"]
return response

revoked_token = RevokedTokenModel(jti=jti)
revoked_token.add()

return jsonify(message="Access token has been revoked")


class UserLogoutRefresh(Resource):
class UserLogout(Resource):
"""
User logout API resource.
"""

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

jti = get_jwt()["jti"]
response = jsonify(message="Logged out")
unset_jwt_cookies(response)

revoked_token = RevokedTokenModel(jti=jti)
revoked_token.add()

return jsonify(message="Refresh token has been revoked")
return response


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

return jsonify(
message="Access token has been refreshed", access_token=access_token
)
response = jsonify(message="Access token has been refreshed")

set_access_cookies(response, access_token)

return response
35 changes: 21 additions & 14 deletions muse_as_service/client/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
### Client
MUSE as Service has next endpoints:
MUSE as Service has the following endpoints:
<pre>
- /login - POST request with `username` and `password` to get JWT tokens (access and refresh)
- /logout/access - POST request to remove JWT access token (JWT access token required)
- /logout/refresh - POST request to remove JWT refresh token (JWT refresh token required)
- /token/refresh - POST request to refresh JWT access token (JWT refresh token required)
- /tokenize - GET request for `sentence` tokenization (JWT access token required)
- /embed - GET request for `sentence` embedding (JWT access token required)
- /login - POST request with `username` and `password` to get tokens (access and refresh)
- /logout - POST request to remove tokens (access and refresh)
- /token/refresh - POST request to refresh access token (refresh token required)
- /tokenize - GET request for `sentence` tokenization (access token required)
- /embed - GET request for `sentence` embedding (access token required)
</pre>

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

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

# start session
session = requests.Session()

# login
response = requests.post(
response = session.post(
url=f"http://{ip}:{port}/login",
json={"username": "admin", "password": "admin"},
)
token = response.json()["access_token"]

# tokenizer
response = requests.get(
response = session.get(
url=f"http://{ip}:{port}/tokenize",
params={"sentence": sentences},
headers={"Authorization": f"Bearer {token}"},
)
tokenized_sentence = response.json()["tokens"]

# embedder
response = requests.get(
response = session.get(
url=f"http://{ip}:{port}/embed",
params={"sentence": sentences},
headers={"Authorization": f"Bearer {token}"},
)
embedding = np.array(response.json()["embedding"])

# logout
response = session.post(
url=f"http://{ip}:{port}/logout",
)

# close session
session.close()

# results
print(tokenized_sentence) # [
# ["▁This", "▁is", "▁sentence", "▁example", "."],
Expand All @@ -51,7 +58,7 @@ print(tokenized_sentence) # [
print(embedding.shape) # (2, 512)
```

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.
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.

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

0 comments on commit 450d9f4

Please sign in to comment.