Commit 90b6557c authored by Konstantin Schulz's avatar Konstantin Schulz

added a script for testing code coverage locally

parent 763e0d22
Pipeline #11492 passed with stages
in 3 minutes and 10 seconds
...@@ -23,7 +23,7 @@ ci_backend: ...@@ -23,7 +23,7 @@ ci_backend:
coverage: coverage:
stage: coverage stage: coverage
script: script:
- ./coverage.sh - ./coverage_ci.sh
- cat coverage.log - cat coverage.log
artifacts: artifacts:
paths: paths:
......
...@@ -27,8 +27,10 @@ Alternatively, you can use `ssh root@localhost -p 8022 -o "UserKnownHostsFile /d ...@@ -27,8 +27,10 @@ Alternatively, you can use `ssh root@localhost -p 8022 -o "UserKnownHostsFile /d
To snapshot a running container, use `docker commit CONTAINER_ID`. It returns a snapshot ID, which you can access via `docker run -it SNAPSHOT_ID`. To snapshot a running container, use `docker commit CONTAINER_ID`. It returns a snapshot ID, which you can access via `docker run -it SNAPSHOT_ID`.
----------------------------------------------------------------
## Documentation ## Documentation
### Changelog ### Changelog
To update the changelog, use: `git log --oneline --decorate > CHANGELOG` To update the changelog, use: `git log --oneline --decorate > CHANGELOG`
## Testing
### Locally
To test your code locally, run `./coverage_local.sh`
docker-compose build
docker-compose run --rm --entrypoint="npm run test-ci" mc_frontend > ci_frontend.log
docker-compose run --rm mcserver bash -c "source ../venv/bin/activate && coverage run --rcfile=.coveragerc tests.py && coverage combine && coverage report -m" > ci_backend.log
./coverage_ci.sh
cat coverage.log
...@@ -35,18 +35,6 @@ services: ...@@ -35,18 +35,6 @@ services:
tty: true tty: true
volumes: volumes:
- $PWD/mc_frontend/www:/home/mc/mc_frontend/www - $PWD/mc_frontend/www:/home/mc/mc_frontend/www
nginx:
command: nginx -g "daemon off;"
image: nginx:alpine
ports:
- "8100:80"
restart: always
volumes:
- $PWD/mc_frontend/www:/usr/share/nginx/html
- ./mc_frontend/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- mc_frontend
- mcserver
mcserver: mcserver:
build: build:
context: ./mc_backend context: ./mc_backend
...@@ -63,5 +51,14 @@ services: ...@@ -63,5 +51,14 @@ services:
- "5000:5000" - "5000:5000"
restart: always restart: always
stdin_open: true stdin_open: true
nginx:
command: nginx -g "daemon off;"
image: nginx:alpine
ports:
- "8100:80"
restart: always
volumes:
- $PWD/mc_frontend/www:/usr/share/nginx/html
- ./mc_frontend/nginx.conf:/etc/nginx/nginx.conf
volumes: volumes:
db-data: db-data:
...@@ -13,5 +13,6 @@ parallel = True ...@@ -13,5 +13,6 @@ parallel = True
exclude_lines = exclude_lines =
# Don't complain if non-runnable code isn't run: # Don't complain if non-runnable code isn't run:
if __name__ == .__main__.: if __name__ == .__main__.:
except ModuleNotFoundError:
ignore_errors = True ignore_errors = True
show_missing = True show_missing = True
...@@ -4,4 +4,4 @@ from mcserver import get_app, get_cfg ...@@ -4,4 +4,4 @@ from mcserver import get_app, get_cfg
app: Flask = get_app() app: Flask = get_app()
if __name__ == "__main__": if __name__ == "__main__":
app.run(host=get_cfg().HOST_IP, port=get_cfg().HOST_PORT, use_reloader=False) app.run(host=get_cfg().HOST_IP_MCSERVER, port=get_cfg().HOST_PORT, use_reloader=False)
...@@ -20,7 +20,7 @@ def get_cfg() -> Type[Config]: ...@@ -20,7 +20,7 @@ def get_cfg() -> Type[Config]:
def run_app() -> None: def run_app() -> None:
cfg: Type[Config] = get_cfg() cfg: Type[Config] = get_cfg()
get_app().run(host=cfg.HOST_IP, port=cfg.CORPUS_STORAGE_MANAGER_PORT, use_reloader=False) get_app().run(host=cfg.HOST_IP_CSM, port=cfg.CORPUS_STORAGE_MANAGER_PORT, use_reloader=False)
if __name__ == "__main__": if __name__ == "__main__":
......
from csm import get_app, get_cfg from csm import get_app, get_cfg
get_app().run(host=get_cfg().HOST_IP, port=get_cfg().HOST_PORT, use_reloader=False) get_app().run(host=get_cfg().HOST_IP_CSM, port=get_cfg().HOST_PORT, use_reloader=False)
import flask import flask
from flask_restful import Resource, reqparse from flask_restful import Resource
from mcserver.app.services import NetworkService, CorpusService, AnnotationService from flask_restful.reqparse import RequestParser
from mcserver.app.services import NetworkService, CorpusService
class AnnisFindAPI(Resource): class AnnisFindAPI(Resource):
def __init__(self): def __init__(self):
self.reqparse = reqparse.RequestParser() self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
self.reqparse.add_argument("aql", type=str, required=True, location="form", help="No AQL provided") self.reqparse.add_argument("aql", type=str, required=True, location="form", help="No AQL provided")
self.reqparse.add_argument("urn", type=str, required=True, default="", location="form", help="No URN provided") self.reqparse.add_argument("urn", type=str, required=True, default="", location="form", help="No URN provided")
super(AnnisFindAPI, self).__init__() super(AnnisFindAPI, self).__init__()
......
...@@ -4,7 +4,8 @@ from typing import Dict, List ...@@ -4,7 +4,8 @@ from typing import Dict, List
import flask import flask
from conllu import TokenList from conllu import TokenList
from flask_restful import Resource, reqparse, abort from flask_restful import Resource, abort
from flask_restful.reqparse import RequestParser
from mcserver.app.models import ExerciseType, Phenomenon, AnnisResponse from mcserver.app.models import ExerciseType, Phenomenon, AnnisResponse
from mcserver.app.services import CorpusService, NetworkService from mcserver.app.services import CorpusService, NetworkService
...@@ -16,7 +17,7 @@ class CorpusStorageManagerAPI(Resource): ...@@ -16,7 +17,7 @@ class CorpusStorageManagerAPI(Resource):
It manages the database and everything corpus-related.""" It manages the database and everything corpus-related."""
def __init__(self): def __init__(self):
self.reqparse = reqparse.RequestParser() self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
self.reqparse.add_argument("title", type=str, required=True, location="data", help="No title provided") self.reqparse.add_argument("title", type=str, required=True, location="data", help="No title provided")
self.reqparse.add_argument("annotations", required=True, location="data", self.reqparse.add_argument("annotations", required=True, location="data",
help="No annotations provided") help="No annotations provided")
......
from typing import List, Dict, Set from typing import List, Dict, Set
import flask import flask
from flask_restful import Resource, reqparse from flask_restful import Resource
from flask_restful.reqparse import RequestParser
from mcserver.app.models import FrequencyAnalysis, Phenomenon from mcserver.app.models import FrequencyAnalysis, Phenomenon
from mcserver.app.services import NetworkService, CorpusService, AnnotationService from mcserver.app.services import NetworkService, CorpusService, AnnotationService
class FrequencyAPI(Resource): class FrequencyAPI(Resource):
def __init__(self): def __init__(self):
self.reqparse = reqparse.RequestParser() self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
self.reqparse.add_argument("urn", type=str, required=True, default="", location="form", help="No URN provided") self.reqparse.add_argument("urn", type=str, required=True, default="", location="form", help="No URN provided")
super(FrequencyAPI, self).__init__() super(FrequencyAPI, self).__init__()
......
...@@ -2,7 +2,9 @@ import json ...@@ -2,7 +2,9 @@ import json
from typing import Dict, List from typing import Dict, List
import flask import flask
from flask_restful import Resource, reqparse from flask_restful import Resource
from flask_restful.reqparse import RequestParser
from mcserver.app.models import ExerciseData, GraphData, Solution, SolutionElement, AnnisResponse from mcserver.app.models import ExerciseData, GraphData, Solution, SolutionElement, AnnisResponse
from mcserver.app.services import CorpusService, AnnotationService, NetworkService from mcserver.app.services import CorpusService, AnnotationService, NetworkService
...@@ -10,7 +12,7 @@ from mcserver.app.services import CorpusService, AnnotationService, NetworkServi ...@@ -10,7 +12,7 @@ from mcserver.app.services import CorpusService, AnnotationService, NetworkServi
class SubgraphAPI(Resource): class SubgraphAPI(Resource):
def __init__(self): def __init__(self):
self.reqparse = reqparse.RequestParser() self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
self.reqparse.add_argument("aqls", required=False, location="data", help="No AQLs provided", action="append") self.reqparse.add_argument("aqls", required=False, location="data", help="No AQLs provided", action="append")
self.reqparse.add_argument("ctx_left", type=str, required=False, default="", location="data", self.reqparse.add_argument("ctx_left", type=str, required=False, default="", location="data",
help="No left context provided") help="No left context provided")
......
import rapidjson as json import rapidjson as json
import flask import flask
from flask_restful import Resource, reqparse from flask_restful import Resource
from flask_restful.reqparse import RequestParser
from mcserver.app.models import AnnisResponse, GraphData, TextComplexity from mcserver.app.models import AnnisResponse, GraphData, TextComplexity
from mcserver.app.services import NetworkService, CorpusService, TextComplexityService from mcserver.app.services import NetworkService, CorpusService, TextComplexityService
...@@ -11,7 +12,7 @@ class TextComplexityAPI(Resource): ...@@ -11,7 +12,7 @@ class TextComplexityAPI(Resource):
"""The Text Complexity API resource. It gives users measures for text complexity for a given text.""" """The Text Complexity API resource. It gives users measures for text complexity for a given text."""
def __init__(self): def __init__(self):
self.reqparse = reqparse.RequestParser() self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
self.reqparse.add_argument('urn', type=str, location="data", required=True, help='No URN provided') self.reqparse.add_argument('urn', type=str, location="data", required=True, help='No URN provided')
self.reqparse.add_argument('measure', type=str, location="data", required=True, help='No MEASURE provided') self.reqparse.add_argument('measure', type=str, location="data", required=True, help='No MEASURE provided')
self.reqparse.add_argument('annis_response', type=dict, location="data", required=False, self.reqparse.add_argument('annis_response', type=dict, location="data", required=False,
......
"""Configuration for the gunicorn server""" """Configuration for the gunicorn server"""
from mcserver import Config from mcserver import Config
bind = "{0}:{1}".format(Config.HOST_IP, Config.CORPUS_STORAGE_MANAGER_PORT) bind = "{0}:{1}".format(Config.HOST_IP_CSM, Config.CORPUS_STORAGE_MANAGER_PORT)
debug = False debug = False
reload = True reload = True
timeout = 3600 timeout = 3600
......
...@@ -20,4 +20,4 @@ def get_cfg() -> Type[Config]: ...@@ -20,4 +20,4 @@ def get_cfg() -> Type[Config]:
if __name__ == "__main__": if __name__ == "__main__":
# reloader has to be disabled because of a bug with Flask and multiprocessing # reloader has to be disabled because of a bug with Flask and multiprocessing
get_app().run(host=get_cfg().HOST_IP, port=get_cfg().HOST_PORT, use_reloader=False) get_app().run(host=get_cfg().HOST_IP_MCSERVER, port=get_cfg().HOST_PORT, use_reloader=False)
from mcserver import get_app, get_cfg from mcserver import get_app, get_cfg
get_app().run(host=get_cfg().HOST_IP, port=get_cfg().HOST_PORT, use_reloader=False) get_app().run(host=get_cfg().HOST_IP_MCSERVER, port=get_cfg().HOST_PORT, use_reloader=False)
...@@ -5,7 +5,9 @@ from logging.handlers import RotatingFileHandler ...@@ -5,7 +5,9 @@ from logging.handlers import RotatingFileHandler
from threading import Thread from threading import Thread
from time import strftime from time import strftime
from typing import Type from typing import Type
from flask import Flask, got_request_exception, request, Response
import flask
from flask import Flask, got_request_exception, request, Response, send_from_directory
from flask_cors import CORS from flask_cors import CORS
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
...@@ -59,6 +61,12 @@ def init_app_common(cfg: Type[Config] = Config, is_csm: bool = False) -> Flask: ...@@ -59,6 +61,12 @@ def init_app_common(cfg: Type[Config] = Config, is_csm: bool = False) -> Flask:
request.full_path, response.status) request.full_path, response.status)
return response return response
@app.route(Config.SERVER_URI_FAVICON)
def get_favicon():
"""Sends the favicon to browsers, which is used, e.g., in the tabs as a symbol for our application."""
mime_type: str = 'image/vnd.microsoft.icon'
return send_from_directory(Config.ASSETS_DIRECTORY, Config.FAVICON_FILE_NAME, mimetype=mime_type)
@app.teardown_appcontext @app.teardown_appcontext
def shutdown_session(exception=None): def shutdown_session(exception=None):
""" Shuts down the session when the application exits. (maybe also after every request ???) """ """ Shuts down the session when the application exits. (maybe also after every request ???) """
...@@ -100,7 +108,7 @@ def log_exception(sender_app: Flask, exception, **extra): ...@@ -100,7 +108,7 @@ def log_exception(sender_app: Flask, exception, **extra):
**extra -- any additional arguments **extra -- any additional arguments
""" """
# TODO: RETURN ERROR IN JSON FORMAT # TODO: RETURN ERROR IN JSON FORMAT
sender_app.logger.exception("ERROR") sender_app.logger.exception(f"ERROR for {flask.request.url}")
def start_updater(app: Flask) -> Thread: def start_updater(app: Flask) -> Thread:
......
"""The corpus API. Add it to your REST API to provide users with metadata about specific texts.""" """The corpus API. Add it to your REST API to provide users with metadata about specific texts."""
from flask_restful import Resource, reqparse, abort, marshal from flask_restful import Resource, abort, marshal
from flask_restful.reqparse import RequestParser
from mcserver.app import db from mcserver.app import db
from mcserver.app.models import Corpus, corpus_fields from mcserver.app.models import Corpus, corpus_fields
from mcserver.app.services import NetworkService
class CorpusAPI(Resource): class CorpusAPI(Resource):
...@@ -10,7 +12,7 @@ class CorpusAPI(Resource): ...@@ -10,7 +12,7 @@ class CorpusAPI(Resource):
def __init__(self): def __init__(self):
"""Initialize possible arguments for calls to the corpus REST API.""" """Initialize possible arguments for calls to the corpus REST API."""
self.reqparse = reqparse.RequestParser() self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
self.reqparse.add_argument("title", type=str, required=False, help="No title provided") self.reqparse.add_argument("title", type=str, required=False, help="No title provided")
self.reqparse.add_argument("author", type=str, required=False, help="No author provided") self.reqparse.add_argument("author", type=str, required=False, help="No author provided")
self.reqparse.add_argument("source_urn", type=str, required=False, help="No source URN provided") self.reqparse.add_argument("source_urn", type=str, required=False, help="No source URN provided")
......
...@@ -2,12 +2,13 @@ ...@@ -2,12 +2,13 @@
from datetime import datetime from datetime import datetime
from flask import jsonify from flask import jsonify
from flask_restful import Resource, reqparse, marshal from flask_restful import Resource, marshal
from flask_restful.reqparse import RequestParser
from sqlalchemy.exc import OperationalError, InvalidRequestError from sqlalchemy.exc import OperationalError, InvalidRequestError
from mcserver.app import db from mcserver.app import db
from mcserver.app.models import UpdateInfo, ResourceType, Corpus, corpus_fields from mcserver.app.models import UpdateInfo, ResourceType, Corpus, corpus_fields
from mcserver.app.services import CorpusService from mcserver.app.services import CorpusService, NetworkService
class CorpusListAPI(Resource): class CorpusListAPI(Resource):
...@@ -15,7 +16,7 @@ class CorpusListAPI(Resource): ...@@ -15,7 +16,7 @@ class CorpusListAPI(Resource):
def __init__(self): def __init__(self):
"""Initialize possible arguments for calls to the corpus list REST API.""" """Initialize possible arguments for calls to the corpus list REST API."""
self.reqparse = reqparse.RequestParser() self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
self.reqparse.add_argument("last_update_time", type=int, required=True, self.reqparse.add_argument("last_update_time", type=int, required=True,
help="No milliseconds time for last update provided") help="No milliseconds time for last update provided")
super(CorpusListAPI, self).__init__() super(CorpusListAPI, self).__init__()
......
...@@ -6,7 +6,8 @@ import rapidjson as json ...@@ -6,7 +6,8 @@ import rapidjson as json
from typing import List, Dict from typing import List, Dict
import requests import requests
from flask_restful import Resource, reqparse, marshal, abort from flask_restful import Resource, marshal, abort
from flask_restful.reqparse import RequestParser
from mcserver.app import db from mcserver.app import db
from mcserver.app.models import ExerciseType, Solution, ExerciseData, Exercise, exercise_fields, AnnisResponse, \ from mcserver.app.models import ExerciseType, Solution, ExerciseData, Exercise, exercise_fields, AnnisResponse, \
...@@ -21,7 +22,7 @@ class ExerciseAPI(Resource): ...@@ -21,7 +22,7 @@ class ExerciseAPI(Resource):
def __init__(self): def __init__(self):
"""Initialize possible arguments for calls to the exercise REST API.""" """Initialize possible arguments for calls to the exercise REST API."""
# TODO: switch to other request parser, e.g. Marshmallow, because the one used by Flask-RESTful does not allow parsing arguments from different locations, e.g. one argument from 'location=args' and another argument from 'location=form' # TODO: switch to other request parser, e.g. Marshmallow, because the one used by Flask-RESTful does not allow parsing arguments from different locations, e.g. one argument from 'location=args' and another argument from 'location=form'
self.reqparse = reqparse.RequestParser() self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
self.reqparse.add_argument("urn", type=str, required=False, location="form", help="No URN provided") self.reqparse.add_argument("urn", type=str, required=False, location="form", help="No URN provided")
self.reqparse.add_argument("type", type=str, required=False, location="form", help="No exercise type provided") self.reqparse.add_argument("type", type=str, required=False, location="form", help="No exercise type provided")
self.reqparse.add_argument("search_values", type=str, required=False, location="form", self.reqparse.add_argument("search_values", type=str, required=False, location="form",
...@@ -49,6 +50,8 @@ class ExerciseAPI(Resource): ...@@ -49,6 +50,8 @@ class ExerciseAPI(Resource):
ar: AnnisResponse = CorpusService.get_corpus(cts_urn=exercise.urn, is_csm=False) ar: AnnisResponse = CorpusService.get_corpus(cts_urn=exercise.urn, is_csm=False)
if not ar.nodes: if not ar.nodes:
abort(404) abort(404)
exercise.last_access_time = datetime.utcnow()
db.session.commit()
exercise_type: ExerciseType = ExerciseType(exercise.exercise_type) exercise_type: ExerciseType = ExerciseType(exercise.exercise_type)
ar.solutions = json.loads(exercise.solutions) ar.solutions = json.loads(exercise.solutions)
ar.uri = exercise.uri ar.uri = exercise.uri
......
...@@ -4,7 +4,9 @@ from typing import List, Set ...@@ -4,7 +4,9 @@ from typing import List, Set
import conllu import conllu
from conllu import TokenList from conllu import TokenList
from flask_restful import Resource, reqparse from flask_restful import Resource
from flask_restful.reqparse import RequestParser
from mcserver.app.models import Exercise, Language, VocabularyCorpus, UpdateInfo, ResourceType from mcserver.app.models import Exercise, Language, VocabularyCorpus, UpdateInfo, ResourceType
from mcserver.app.services import NetworkService, FileService from mcserver.app.services import NetworkService, FileService
...@@ -14,7 +16,7 @@ class ExerciseListAPI(Resource): ...@@ -14,7 +16,7 @@ class ExerciseListAPI(Resource):
def __init__(self): def __init__(self):
"""Initialize possible arguments for calls to the exercise list REST API.""" """Initialize possible arguments for calls to the exercise list REST API."""
self.reqparse = reqparse.RequestParser() self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
self.reqparse.add_argument("lang", type=str, required=True, help="No language specified") self.reqparse.add_argument("lang", type=str, required=True, help="No language specified")
self.reqparse.add_argument("last_update_time", type=int, required=False, default=0, self.reqparse.add_argument("last_update_time", type=int, required=False, default=0,
help="No milliseconds time for last update provided") help="No milliseconds time for last update provided")
......
...@@ -7,7 +7,8 @@ from typing import List, Union ...@@ -7,7 +7,8 @@ from typing import List, Union
import flask import flask
from flask import send_from_directory from flask import send_from_directory
from flask_restful import Resource, reqparse, abort from flask_restful import Resource, abort
from flask_restful.reqparse import RequestParser
from werkzeug.wrappers import ETagResponseMixin from werkzeug.wrappers import ETagResponseMixin
from mcserver.app import db from mcserver.app import db
...@@ -22,7 +23,7 @@ class FileAPI(Resource): ...@@ -22,7 +23,7 @@ class FileAPI(Resource):
def __init__(self): def __init__(self):
"""Initialize possible arguments for calls to the file REST API.""" """Initialize possible arguments for calls to the file REST API."""
self.reqparse = reqparse.RequestParser() self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
self.reqparse.add_argument("id", type=str, required=False, location="args", self.reqparse.add_argument("id", type=str, required=False, location="args",
help="No exercise ID or URN provided") help="No exercise ID or URN provided")
self.reqparse.add_argument("type", type=str, required=False, location="args", help="No file type provided") self.reqparse.add_argument("type", type=str, required=False, location="args", help="No file type provided")
......
import flask import flask
import requests import requests
from flask_restful import Resource, reqparse from flask_restful import Resource
import rapidjson as json import rapidjson as json
from flask_restful.reqparse import RequestParser
from mcserver import Config from mcserver import Config
from mcserver.app.services import NetworkService from mcserver.app.services import NetworkService
...@@ -10,7 +11,7 @@ from mcserver.app.services import NetworkService ...@@ -10,7 +11,7 @@ from mcserver.app.services import NetworkService
class FrequencyAPI(Resource): class FrequencyAPI(Resource):
def __init__(self): def __init__(self):
# TODO: FIX THE REQUEST PARSING FOR ALL APIs # TODO: FIX THE REQUEST PARSING FOR ALL APIs
self.reqparse = reqparse.RequestParser() self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
self.reqparse.add_argument("urn", type=str, required=True, default="", location="form", help="No URN provided") self.reqparse.add_argument("urn", type=str, required=True, default="", location="form", help="No URN provided")
super(FrequencyAPI, self).__init__() super(FrequencyAPI, self).__init__()
......
import json import json
from typing import List from typing import List
from flask_restful import Resource, reqparse, abort from flask_restful import Resource, abort
from flask_restful.reqparse import RequestParser
from mcserver.app.models import Language, Exercise, ExerciseType, Solution from mcserver.app.models import Language, Exercise, ExerciseType, Solution
from mcserver.app.services import TextService, NetworkService from mcserver.app.services import TextService, NetworkService
...@@ -12,7 +13,7 @@ class H5pAPI(Resource): ...@@ -12,7 +13,7 @@ class H5pAPI(Resource):
def __init__(self): def __init__(self):
"""Initialize possible arguments for calls to the H5P REST API.""" """Initialize possible arguments for calls to the H5P REST API."""
self.reqparse = reqparse.RequestParser() self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
self.reqparse.add_argument("eid", type=str, required=True, default="", help="No exercise ID provided") self.reqparse.add_argument("eid", type=str, required=True, default="", help="No exercise ID provided")
self.reqparse.add_argument("lang", type=str, required=True, default="en", help="No language code provided") self.reqparse.add_argument("lang", type=str, required=True, default="en", help="No language code provided")
self.reqparse.add_argument("solution_indices", type=str, required=False, help="No solution IDs provided") self.reqparse.add_argument("solution_indices", type=str, required=False, help="No solution IDs provided")
......
...@@ -10,7 +10,8 @@ from typing import List, Dict ...@@ -10,7 +10,8 @@ from typing import List, Dict
import requests import requests