Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
callidus
Machina Callida
Commits
b147d620
Commit
b147d620
authored
Mar 09, 2021
by
Konstantin Schulz
Browse files
H5P exercises from Zenodo can now be processed in the Machina Callida
parent
e4786a46
Pipeline
#16936
passed with stages
in 6 minutes and 27 seconds
Changes
122
Pipelines
1
Expand all
Hide whitespace changes
Inline
Side-by-side
mc_backend/.gitignore
View file @
b147d620
...
...
@@ -14,3 +14,4 @@ __pycache__/
.pydevproject
.pylintrc
.vscode/
__open_alchemy_*
mc_backend/README.md
View file @
b147d620
...
...
@@ -32,8 +32,6 @@ Or combine both commands in one line: `pip list -o --format=freeze | grep -v '^\
----------------------------------------------------------------
# Database
To use the database for the first time:
If you use Postgres, you need to create the database "callidus" manually:
`CREATE DATABASE callidus;`
To autogenerate a new migration script:
1.
Start the Docker container with the database:
`docker-compose up -p 5432:5432 -d db`
2.
Create a new migration:
`flask db migrate`
.
...
...
@@ -51,4 +49,4 @@ To generate class structures for this project automatically:
2.
Run:
`openapi-generator generate -i ./mcserver/mcserver_api.yaml -g python-flask -o ./openapi/ && python openapi_generator.py`
.
# Testing
To check the coverage of the current tests, run
`coverage run --rcfile=.coveragerc tests.py && coverage combine && coverage report -m`
.
`coverage run --rcfile=.coveragerc tests.py && coverage combine && coverage report -m`
.
\ No newline at end of file
mc_backend/mcserver/app/__init__.py
View file @
b147d620
...
...
@@ -18,6 +18,11 @@ from flask_migrate import Migrate
from
flask_sqlalchemy
import
SQLAlchemy
from
graphannis.cs
import
CorpusStorageManager
from
open_alchemy
import
init_yaml
from
sqlalchemy
import
create_engine
from
sqlalchemy.engine
import
Connection
,
Engine
from
sqlalchemy.orm
import
Session
,
sessionmaker
from
sqlalchemy_utils
import
database_exists
from
mcserver.config
import
Config
...
...
@@ -61,11 +66,27 @@ def create_app(cfg: Type[Config] = Config) -> Flask:
return
app
def
create_database
()
->
SQLAlchemy
:
def
create_database
_handler
()
->
SQLAlchemy
:
"""Creates a new connection to a database, which will handle all future database transactions."""
return
SQLAlchemy
()
# session_options={"autocommit": True}
def
create_postgres_database
(
cfg
:
Type
[
Config
]
=
Config
)
->
None
:
""" Creates a new Postgres database. """
idx
:
int
=
cfg
.
SQLALCHEMY_DATABASE_URI
.
rindex
(
"/"
)
# remove database name from the URI
uri_without_db
:
str
=
cfg
.
SQLALCHEMY_DATABASE_URI
[:
idx
]
engine
:
Engine
=
create_engine
(
uri_without_db
)
session
:
Session
=
sessionmaker
(
bind
=
engine
)()
connection
:
Connection
=
session
.
connection
()
# database creation must be outside of a transaction
connection
.
connection
.
set_isolation_level
(
0
)
db_name
:
str
=
cfg
.
SQLALCHEMY_DATABASE_URI
.
split
(
"/"
)[
-
1
]
session
.
execute
(
f
'CREATE DATABASE
{
db_name
}
'
)
session
.
close
()
engine
.
dispose
()
def
full_init
(
app
:
Flask
,
cfg
:
Type
[
Config
]
=
Config
)
->
None
:
""" Fully initializes the application, including logging."""
from
mcserver.app.services
import
DatabaseService
...
...
@@ -108,6 +129,9 @@ def init_app_common(cfg: Type[Config] = Config) -> Flask:
app
.
config
.
from_object
(
cfg
)
app
.
app_context
().
push
()
init_logging
(
app
,
Config
.
LOG_PATH_MCSERVER
)
# in Postgres, databases are not created automatically, so we have to check manually
if
not
cfg
.
TESTING
and
not
database_exists
(
cfg
.
SQLALCHEMY_DATABASE_URI
):
create_postgres_database
(
cfg
)
db
.
init_app
(
app
)
migrate
.
init_app
(
app
,
db
)
db
.
create_all
()
...
...
@@ -160,7 +184,7 @@ def shutdown_session(exception=None):
db
.
session
.
remove
()
db
:
SQLAlchemy
=
create_database
()
db
:
SQLAlchemy
=
create_database
_handler
()
migrate
:
Migrate
=
Migrate
(
directory
=
Config
.
MIGRATIONS_DIRECTORY
)
if
not
hasattr
(
open_alchemy
.
models
,
Config
.
DATABASE_TABLE_CORPUS
):
# initialize the database and models _BEFORE_ you add any APIs to your application
...
...
mc_backend/mcserver/app/api/h5pAPI.py
View file @
b147d620
from
io
import
BytesIO
import
json
import
os
import
shutil
import
zipfile
from
typing
import
List
,
Union
,
Set
,
Any
from
zipfile
import
ZipFile
import
connexion
import
requests
from
connexion.lifecycle
import
ConnexionResponse
from
flask
import
Response
,
send_from_directory
from
mcserver
import
Config
from
mcserver.app.models
import
Language
,
ExerciseType
,
Solution
,
MimeType
,
FileType
from
mcserver.app.services
import
TextService
,
NetworkService
,
DatabaseService
from
mcserver.models_auto
import
Exercise
from
mocks
import
Mocks
from
openapi.openapi_server.models
import
H5PForm
,
ExerciseAuthor
from
openapi.openapi_server.models.base_model_
import
Model
...
...
@@ -27,9 +29,12 @@ def determine_language(lang: str) -> Language:
def
get
(
eid
:
str
,
lang
:
str
,
solution_indices
:
List
[
int
])
->
Union
[
Response
,
ConnexionResponse
]:
""" The GET method for the H5P REST API. It provides JSON templates for client-side H5P exercise layouts. """
if
eid
.
endswith
(
f
".
{
FileType
.
H5P
}
"
):
return
get_remote_exercise
(
eid
)
language
:
Language
=
determine_language
(
lang
)
exercise
:
Exercise
=
DatabaseService
.
query
(
Exercise
,
filter_by
=
dict
(
eid
=
eid
),
first
=
True
)
if
eid
==
Config
.
EXERCISE_ID_TEST
:
from
mocks
import
Mocks
# use local import to avoid incorrect import order
exercise
=
Mocks
.
exercise
if
not
exercise
:
return
connexion
.
problem
(
404
,
Config
.
ERROR_TITLE_NOT_FOUND
,
Config
.
ERROR_MESSAGE_EXERCISE_NOT_FOUND
)
...
...
@@ -38,8 +43,7 @@ def get(eid: str, lang: str, solution_indices: List[int]) -> Union[Response, Con
return
connexion
.
problem
(
422
,
Config
.
ERROR_TITLE_UNPROCESSABLE_ENTITY
,
Config
.
ERROR_MESSAGE_UNPROCESSABLE_ENTITY
)
response_dict
:
dict
=
TextService
.
json_template_mark_words
response_dict
=
get_response
(
response_dict
,
language
,
TextService
.
json_template_drag_text
,
exercise
,
text_field_content
,
TextService
.
feedback_template
)
response_dict
=
localize_exercise
(
response_dict
,
language
,
exercise
,
text_field_content
)
return
NetworkService
.
make_json_response
(
response_dict
)
...
...
@@ -48,9 +52,31 @@ def get_possible_enum_values(openapi_enum: Union[Model, Any]) -> Set[str]:
return
set
([
x
for
x
in
list
(
dict
(
openapi_enum
.
__dict__
).
values
())[
2
:]
if
type
(
x
)
==
str
])
def
get_response
(
response_dict
:
dict
,
lang
:
Language
,
json_template_drag_text
:
dict
,
exercise
:
Exercise
,
text_field_content
:
str
,
feedback_template
:
str
)
->
dict
:
"""Performs localization for an existing H5P exercise template and insert the relevant exercise materials."""
def
get_remote_exercise
(
uri
:
str
)
->
Union
[
Response
,
ConnexionResponse
]:
""" Retrieves an H5P archive from a remote location and builds a JSON template for it."""
response
:
requests
.
Response
=
requests
.
get
(
uri
)
zipinmemory
=
BytesIO
(
response
.
content
)
zip_file
:
ZipFile
=
ZipFile
(
zipinmemory
,
"r"
)
lib_dict
:
dict
=
json
.
load
(
zip_file
.
open
(
"h5p.json"
))
h5p_dict
:
dict
=
json
.
load
(
zip_file
.
open
(
"content/content.json"
))
h5p_dict
[
"mainLibrary"
]
=
lib_dict
[
"mainLibrary"
]
return
NetworkService
.
make_json_response
(
h5p_dict
)
def
get_text_field_content
(
exercise
:
Exercise
,
solution_indices
:
List
[
int
])
->
str
:
"""Builds the text field content for a H5P exercise, i.e. the task, exercise material and solutions."""
text_field_content
:
str
=
""
if
exercise
.
exercise_type
in
[
ExerciseType
.
cloze
.
value
,
ExerciseType
.
markWords
.
value
]:
text_field_content
=
TextService
.
get_h5p_text_with_solutions
(
exercise
,
solution_indices
)
elif
exercise
.
exercise_type
==
ExerciseType
.
matching
.
value
:
solutions
:
List
[
Solution
]
=
TextService
.
get_solutions_by_index
(
exercise
,
solution_indices
)
for
solution
in
solutions
:
text_field_content
+=
"{0} *{1}*
\n
"
.
format
(
solution
.
target
.
content
,
solution
.
value
.
content
)
return
text_field_content
def
localize_exercise
(
response_dict
:
dict
,
lang
:
Language
,
exercise
:
Exercise
,
text_field_content
:
str
)
->
dict
:
"""Performs localization for an existing H5P exercise template and inserts the relevant exercise materials."""
# default values for buttons and response
button_dict
:
dict
=
{
"check"
:
[
"checkAnswerButton"
,
"Prüfen"
if
lang
==
Language
.
German
else
"Check"
],
"again"
:
[
"tryAgainButton"
,
"Nochmal"
if
lang
==
Language
.
German
else
"Retry"
],
...
...
@@ -59,31 +85,19 @@ def get_response(response_dict: dict, lang: Language, json_template_drag_text: d
button_dict
[
"check"
][
0
]
=
"checkAnswer"
button_dict
[
"again"
][
0
]
=
"tryAgain"
button_dict
[
"solution"
][
0
]
=
"showSolution"
response_dict
=
json_template_drag_text
response_dict
=
TextService
.
json_template_drag_text
for
button
in
button_dict
:
response_dict
[
button_dict
[
button
][
0
]]
=
button_dict
[
button
][
1
]
response_dict
[
"taskDescription"
]
=
"<p>{0}: {1}</p>
\n
"
.
format
(
exercise
.
exercise_type_translation
,
exercise
.
instructions
)
response_dict
[
"textField"
]
=
text_field_content
feedback
:
str
=
feedback_template
.
format
(
"Punkte"
,
"von"
)
feedback
:
str
=
TextService
.
feedback_template
.
format
(
"Punkte"
,
"von"
)
if
lang
!=
Language
.
German
:
feedback
=
feedback_template
.
format
(
"Score"
,
"of"
)
feedback
=
TextService
.
feedback_template
.
format
(
"Score"
,
"of"
)
response_dict
[
"overallFeedback"
][
0
][
"feedback"
]
=
feedback
return
response_dict
def
get_text_field_content
(
exercise
:
Exercise
,
solution_indices
:
List
[
int
])
->
str
:
"""Builds the text field content for a H5P exercise, i.e. the task, exercise material and solutions."""
text_field_content
:
str
=
""
if
exercise
.
exercise_type
in
[
ExerciseType
.
cloze
.
value
,
ExerciseType
.
markWords
.
value
]:
text_field_content
=
TextService
.
get_h5p_text_with_solutions
(
exercise
,
solution_indices
)
elif
exercise
.
exercise_type
==
ExerciseType
.
matching
.
value
:
solutions
:
List
[
Solution
]
=
TextService
.
get_solutions_by_index
(
exercise
,
solution_indices
)
for
solution
in
solutions
:
text_field_content
+=
"{0} *{1}*
\n
"
.
format
(
solution
.
target
.
content
,
solution
.
value
.
content
)
return
text_field_content
def
make_h5p_archive
(
file_name_no_ext
:
str
,
response_dict
:
dict
,
target_dir
:
str
,
file_name
:
str
):
"""Creates a H5P archive (in ZIP format) for a given exercise."""
source_dir
:
str
=
os
.
path
.
join
(
Config
.
H5P_DIRECTORY
,
file_name_no_ext
)
...
...
@@ -95,7 +109,7 @@ def make_h5p_archive(file_name_no_ext: str, response_dict: dict, target_dir: str
white_list
:
Set
[
str
]
=
{
'.svg'
,
'.otf'
,
'.json'
,
'.css'
,
'.diff'
,
'.woff'
,
'.eot'
,
'.png'
,
'.gif'
,
'.woff2'
,
'.js'
,
'.ttf'
}
excluded_folders
:
Set
[
str
]
=
get_possible_enum_values
(
ExerciseAuthor
).
union
({
"content"
})
with
zipfile
.
ZipFile
(
os
.
path
.
join
(
target_dir
,
file_name
),
"w"
)
as
zipObj
:
with
ZipFile
(
os
.
path
.
join
(
target_dir
,
file_name
),
"w"
)
as
zipObj
:
# Iterate over all the files in directory
for
folder_name
,
subfolders
,
file_names
in
os
.
walk
(
source_dir
):
if
any
(
x
for
x
in
excluded_folders
if
folder_name
.
endswith
(
x
)):
...
...
@@ -122,8 +136,7 @@ def post(h5p_data: dict):
return
connexion
.
problem
(
422
,
Config
.
ERROR_TITLE_UNPROCESSABLE_ENTITY
,
Config
.
ERROR_MESSAGE_UNPROCESSABLE_ENTITY
)
response_dict
:
dict
=
TextService
.
json_template_mark_words
response_dict
=
get_response
(
response_dict
,
language
,
TextService
.
json_template_drag_text
,
exercise
,
text_field_content
,
TextService
.
feedback_template
)
response_dict
=
localize_exercise
(
response_dict
,
language
,
exercise
,
text_field_content
)
file_name_no_ext
:
str
=
str
(
h5p_form
.
exercise_type_path
)
file_name
:
str
=
f
"
{
file_name_no_ext
}
.
{
FileType
.
ZIP
}
"
target_dir
:
str
=
Config
.
TMP_DIRECTORY
...
...
mc_backend/mcserver/app/api/zenodoAPI.py
View file @
b147d620
"""The Zenodo API. Add it to your REST API to provide users with exercise materials from the Zenodo repository."""
from
collections
import
Counter
from
requests
import
Response
from
sickle
import
Sickle
from
typing
import
List
,
Set
,
Any
,
Dict
import
inspect
...
...
@@ -6,10 +9,10 @@ from sickle.models import Record
from
mcserver
import
Config
from
mcserver.app.services
import
NetworkService
from
openapi.openapi_server.models
import
ZenodoRecord
from
openapi.openapi_server.models
import
ZenodoRecord
,
ZenodoForm
,
ZenodoMetadataPrefix
def
get
():
def
get
()
->
Response
:
"""The GET method for the Zenodo REST API. It provides exercise materials from the Zenodo repository."""
record_members
:
List
[
tuple
]
=
inspect
.
getmembers
(
ZenodoRecord
,
lambda
a
:
not
(
inspect
.
isroutine
(
a
)))
record_properties
:
Set
[
str
]
=
set
([
x
[
0
]
for
x
in
record_members
if
type
(
x
[
1
])
==
property
])
...
...
@@ -17,11 +20,14 @@ def get():
work_property
:
str
=
"Werk:"
version_property
:
str
=
"Version:"
sickle
:
Sickle
=
Sickle
(
Config
.
ZENODO_API_URL
)
records
:
List
[
Record
]
=
list
(
sickle
.
ListRecords
(
metadataPrefix
=
'oai_dc'
,
set
=
Config
.
ZENODO_SET
))
# use oai_datacite because it is the recommended default metadata scheme for Zenodo
records
:
List
[
Record
]
=
list
(
sickle
.
ListRecords
(
metadataPrefix
=
ZenodoMetadataPrefix
.
OAI_DATACITE
,
set
=
Config
.
ZENODO_SET
))
property_map
:
Dict
[
str
,
str
]
=
{
"creator_name"
:
"creatorName"
}
zenodo_records
:
List
[
ZenodoRecord
]
=
[]
for
record
in
records
:
md
:
dict
=
record
.
metadata
params
:
Dict
[
str
,
Any
]
=
{
rp
:
md
.
get
(
r
p
,
[])
for
rp
in
record_properties
}
params
:
Dict
[
str
,
Any
]
=
{
rp
:
md
.
get
(
p
roperty_map
.
get
(
rp
,
rp
)
,
[])
for
rp
in
record_properties
}
new_record
:
ZenodoRecord
=
ZenodoRecord
(
**
params
)
tags
:
List
[
str
]
=
md
.
get
(
"subject"
,
[])
new_record
.
author
=
[
next
((
x
.
split
(
author_property
)[
-
1
]
for
x
in
tags
if
x
.
startswith
(
author_property
)),
""
)]
...
...
@@ -29,4 +35,28 @@ def get():
new_record
.
version
=
\
[
next
((
x
.
split
(
version_property
)[
-
1
]
for
x
in
tags
if
x
.
startswith
(
version_property
)),
"1.0"
)]
zenodo_records
.
append
(
new_record
)
remove_older_versions
(
zenodo_records
)
return
NetworkService
.
make_json_response
([
x
.
to_dict
()
for
x
in
zenodo_records
])
def
post
(
zenodo_data
:
dict
)
->
Response
:
"""The POST method for the Zenodo REST API. It provides file URIs for exercises from the Zenodo repository."""
zenodo_form
:
ZenodoForm
=
ZenodoForm
.
from_dict
(
zenodo_data
)
sickle
:
Sickle
=
Sickle
(
Config
.
ZENODO_API_URL
)
record
:
Record
=
sickle
.
GetRecord
(
metadataPrefix
=
ZenodoMetadataPrefix
.
MARC21
,
identifier
=
f
"oai:zenodo.org:
{
zenodo_form
.
record_id
}
"
)
uris
:
List
[
str
]
=
[
x
for
x
in
record
.
metadata
[
'subfield'
]
if
x
.
startswith
(
"http"
)]
return
NetworkService
.
make_json_response
(
uris
)
def
remove_older_versions
(
zenodo_records
:
List
[
ZenodoRecord
])
->
None
:
""" Removes older versions of a record from the list, if there are multiple versions of the same record. """
counter
:
Counter
=
Counter
([
x
.
title
[
0
]
for
x
in
zenodo_records
])
titles_to_delete
:
Set
[
str
]
=
set
([
x
for
x
in
counter
.
keys
()
if
counter
[
x
]
>
1
])
for
tdd
in
titles_to_delete
:
relevant_records
:
List
[
ZenodoRecord
]
=
[
x
for
x
in
zenodo_records
if
x
.
title
[
0
]
==
tdd
]
versions
:
List
[
float
]
=
[
float
(
x
.
version
[
0
])
for
x
in
relevant_records
]
max_version
:
float
=
max
(
versions
)
relevant_records
=
[
relevant_records
[
i
]
for
i
in
range
(
len
(
versions
))
if
versions
[
i
]
<
max_version
]
for
rr
in
relevant_records
:
zenodo_records
.
remove
(
rr
)
mc_backend/mcserver/app/assets/vocabulary/vocabulary_vischer.json
deleted
100644 → 0
View file @
e4786a46
This diff is collapsed.
Click to expand it.
mc_backend/mcserver/app/models.py
View file @
b147d620
...
...
@@ -157,7 +157,6 @@ class VocabularyCorpus(Enum):
agldt
=
Config
.
VOCABULARY_AGLDT_FILE_NAME
bws
=
Config
.
VOCABULARY_BWS_FILE_NAME
proiel
=
Config
.
VOCABULARY_PROIEL_FILE_NAME
vischer
=
Config
.
VOCABULARY_VISCHER_FILE_NAME
viva
=
Config
.
VOCABULARY_VIVA_FILE_NAME
...
...
@@ -206,7 +205,7 @@ class ExerciseMC:
exercise_type_translation
:
str
=
""
,
language
:
str
=
"de"
,
solutions
:
str
=
"[]"
,
text_complexity
:
float
=
0
,
text_complexity
:
float
=
0.
0
,
urn
:
str
=
""
,
)
->
TExercise
:
return
Exercise
.
from_dict
(
...
...
@@ -355,13 +354,14 @@ class Score:
self
.
max
:
int
=
json_dict
[
"max"
]
self
.
min
:
int
=
json_dict
[
"min"
]
self
.
raw
:
int
=
json_dict
[
"raw"
]
self
.
scaled
:
float
=
json_dict
[
"scaled"
]
# explicit float conversion for the case when scaled is 0, i.e. integer
self
.
scaled
:
float
=
float
(
json_dict
[
"scaled"
])
class
Result
:
def
__init__
(
self
,
json_dict
:
dict
):
self
.
completion
:
bool
=
json_dict
[
"completion"
]
self
.
duration
:
str
=
json_dict
[
"duration"
]
self
.
duration
:
str
=
json_dict
.
get
(
"duration"
,
"PT0S"
)
self
.
response
:
str
=
json_dict
[
"response"
]
self
.
score
:
Score
=
Score
(
json_dict
[
"score"
])
self
.
success
:
bool
=
json_dict
.
get
(
"success"
,
self
.
score
.
raw
==
self
.
score
.
max
)
...
...
mc_backend/mcserver/app/services/corpusService.py
View file @
b147d620
...
...
@@ -2,7 +2,7 @@ import sys
from
datetime
import
datetime
import
rapidjson
as
json
import
os
from
typing
import
List
,
Union
,
Set
,
Tuple
,
Dict
from
typing
import
List
,
Union
,
Set
,
Dict
import
requests
from
MyCapytain.retrievers.cts5
import
HttpCtsRetriever
from
conllu
import
TokenList
...
...
mc_backend/mcserver/app/services/databaseService.py
View file @
b147d620
...
...
@@ -43,7 +43,7 @@ class DatabaseService:
ui_cts
:
UpdateInfo
=
DatabaseService
.
query
(
UpdateInfo
,
filter_by
=
dict
(
resource_type
=
rt
.
name
),
first
=
True
)
if
ui_cts
is
None
:
ui_cts
=
UpdateInfo
.
from_dict
(
resource_type
=
rt
.
name
,
last_modified_time
=
1
,
ui_cts
=
UpdateInfo
.
from_dict
(
resource_type
=
rt
.
name
,
last_modified_time
=
1
.0
,
created_time
=
datetime
.
utcnow
().
timestamp
())
db
.
session
.
add
(
ui_cts
)
DatabaseService
.
commit
()
...
...
mc_backend/mcserver/config.py
View file @
b147d620
...
...
@@ -67,8 +67,8 @@ class Config(object):
DATABASE_TABLE_CORPUS
=
"Corpus"
DATABASE_TABLE_EXERCISE
=
"Exercise"
DATABASE_TABLE_UPDATEINFO
=
"UpdateInfo"
DATABASE_URL_DOCKER
=
"postgresql://postgres@db:5432/"
DATABASE_URL_LOCAL
=
"postgresql://postgres@0.0.0.0:5432/
postgre
s"
DATABASE_URL_DOCKER
=
"postgresql://postgres@db:5432/
callidus
"
DATABASE_URL_LOCAL
=
"postgresql://postgres@0.0.0.0:5432/
callidu
s"
DATABASE_URL_SQLITE
=
f
"sqlite:///
{
os
.
path
.
join
(
basedir
,
'app.db'
)
}
"
DATABASE_URL_SQLITE_MEMORY
=
"sqlite:///:memory:"
DATABASE_URL_FALLBACK
=
DATABASE_URL_DOCKER
if
IS_DOCKER
else
DATABASE_URL_SQLITE
...
...
@@ -161,7 +161,6 @@ class Config(object):
VOCABULARY_BWS_FILE_NAME
=
"vocabulary_bamberg_core.json"
VOCABULARY_DIRECTORY
=
os
.
path
.
join
(
ASSETS_DIRECTORY
,
"vocabulary"
)
VOCABULARY_PROIEL_FILE_NAME
=
"vocabulary_proiel_treebank.json"
VOCABULARY_VISCHER_FILE_NAME
=
"vocabulary_vischer.json"
VOCABULARY_VIVA_FILE_NAME
=
"vocabulary_viva.json"
ZENODO_API_URL
=
"https://zenodo.org/oai2d"
ZENODO_SET
=
"user-lexiprojekt-latin-unipotsdam"
...
...
mc_backend/mcserver/gunicorn_config.py
View file @
b147d620
"""Configuration for the gunicorn server"""
import
multiprocessing
from
mcserver
import
Config
from
mcserver
import
Config
,
get_cfg
bind
=
"{
0}:{1}"
.
format
(
Config
.
HOST_IP_MCSERVER
,
Config
.
HOST_PORT
)
debug
=
False
bind
=
f
"
{
Config
.
HOST_IP_MCSERVER
}
:
{
Config
.
HOST_PORT
}
"
debug
=
not
get_cfg
().
IS_PRODUCTION
reload
=
True
timeout
=
3600
workers
=
multiprocessing
.
cpu_count
()
*
2
+
1
mc_backend/mcserver/mcserver_api.yaml
View file @
b147d620
...
...
@@ -454,8 +454,29 @@ paths:
type
:
array
items
:
$ref
:
'
../openapi_models.yaml#/components/schemas/ZenodoRecord'
post
:
summary
:
Shows which exercise files are available for download in a specific record on Zenodo.
operationId
:
mcserver.app.api.zenodoAPI.post
responses
:
"
200"
:
description
:
Lists URIs for exercise files that can be downloaded.
content
:
application/json
:
schema
:
type
:
array
items
:
type
:
string
example
:
https://zenodo.org/record/4548959/files/Val_Fl_IbisV_Wortschatzuebung_Meeresbilder.docx
requestBody
:
required
:
true
content
:
application/x-www-form-urlencoded
:
schema
:
$ref
:
'
../openapi_models.yaml#/components/schemas/ZenodoForm'
# include this here so the data model gets generated correctly
components
:
schemas
:
ExerciseAuthorExtension
:
$ref
:
'
../openapi_models.yaml#/components/schemas/ExerciseAuthor'
ZenodoMetadataPrefixExtension
:
$ref
:
'
../openapi_models.yaml#/components/schemas/ZenodoMetadataPrefix'
mc_backend/mcserver/models_auto.py
View file @
b147d620
...
...
@@ -8,6 +8,8 @@ from sqlalchemy import orm
from
open_alchemy
import
models
Base
=
models
.
Base
# type: ignore
class
_CorpusDictBase
(
typing
.
TypedDict
,
total
=
True
):
"""TypedDict for properties that are required."""
...
...
@@ -49,24 +51,15 @@ class TCorpus(typing.Protocol):
query
:
orm
.
Query
# Model properties
author
:
str
cid
:
int
citation_level_1
:
str
citation_level_2
:
str
citation_level_3
:
str
source_urn
:
str
title
:
str
author
:
'sqlalchemy.Column[
str
]'
cid
:
'sqlalchemy.Column[
int
]'
citation_level_1
:
'sqlalchemy.Column[
str
]'
citation_level_2
:
'sqlalchemy.Column[
str
]'
citation_level_3
:
'sqlalchemy.Column[
str
]'
source_urn
:
'sqlalchemy.Column[
str
]'
title
:
'sqlalchemy.Column[
str
]'
def
__init__
(
self
,
source_urn
:
str
,
author
:
str
=
"Anonymus"
,
cid
:
typing
.
Optional
[
int
]
=
None
,
citation_level_1
:
str
=
"default"
,
citation_level_2
:
str
=
"default"
,
citation_level_3
:
str
=
"default"
,
title
:
str
=
"Anonymus"
,
)
->
None
:
def
__init__
(
self
,
source_urn
:
str
,
author
:
str
=
"Anonymus"
,
cid
:
typing
.
Optional
[
int
]
=
None
,
citation_level_1
:
str
=
"default"
,
citation_level_2
:
str
=
"default"
,
citation_level_3
:
str
=
"default"
,
title
:
str
=
"Anonymus"
)
->
None
:
"""
Construct.
...
...
@@ -83,16 +76,7 @@ class TCorpus(typing.Protocol):
...
@
classmethod
def
from_dict
(
cls
,
source_urn
:
str
,
author
:
str
=
"Anonymus"
,
cid
:
typing
.
Optional
[
int
]
=
None
,
citation_level_1
:
str
=
"default"
,
citation_level_2
:
str
=
"default"
,
citation_level_3
:
str
=
"default"
,
title
:
str
=
"Anonymus"
,
)
->
"TCorpus"
:
def
from_dict
(
cls
,
source_urn
:
str
,
author
:
str
=
"Anonymus"
,
cid
:
typing
.
Optional
[
int
]
=
None
,
citation_level_1
:
str
=
"default"
,
citation_level_2
:
str
=
"default"
,
citation_level_3
:
str
=
"default"
,
title
:
str
=
"Anonymus"
)
->
"TCorpus"
:
"""
Construct from a dictionary (eg. a POST payload).
...
...
@@ -143,7 +127,7 @@ class TCorpus(typing.Protocol):
...
Corpus
:
TCorpus
=
models
.
Corpus
# type: ignore
Corpus
:
typing
.
Type
[
TCorpus
]
=
models
.
Corpus
# type: ignore
class
_ExerciseDictBase
(
typing
.
TypedDict
,
total
=
True
):
...
...
@@ -158,6 +142,7 @@ class _ExerciseDictBase(typing.TypedDict, total=True):
class
ExerciseDict
(
_ExerciseDictBase
,
total
=
False
):
"""TypedDict for properties that are not required."""
exercise_type_translation
:
str
correct_feedback
:
str
general_feedback
:
str
incorrect_feedback
:
str
...
...
@@ -170,7 +155,6 @@ class ExerciseDict(_ExerciseDictBase, total=False):
solutions
:
str
text_complexity
:
float
urn
:
str
exercise_type_translation
:
str
class
TExercise
(
typing
.
Protocol
):
...
...
@@ -180,6 +164,7 @@ class TExercise(typing.Protocol):
Data for creating and evaluating interactive exercises.
Attrs:
exercise_type_translation: Localized expression of the exercise type.
correct_feedback: Feedback for successful completion of the exercise.
general_feedback: Feedback for finishing the exercise.
incorrect_feedback: Feedback for failing to complete the exercise
...
...
@@ -203,7 +188,6 @@ class TExercise(typing.Protocol):
text_complexity: Overall text complexity as measured by the software's
internal language analysis.
urn: CTS URN for the text passage from which the exercise was created.
exercise_type_translation: Localized expression of the exercise type.
"""
...
...
@@ -213,48 +197,31 @@ class TExercise(typing.Protocol):
query
:
orm
.
Query
# Model properties
correct_feedback
:
str
general_feedback
:
str
incorrect_feedback
:
str
instructions
:
str
language
:
str
partially_correct_feedback
:
str
search_values
:
str
work_author
:
str
work_title
:
str
conll
:
str
eid
:
str
exercise_type
:
str
last_access_time
:
float
solutions
:
str
text_complexity
:
float
urn
:
str
exercise_type_translation
:
str
def
__init__
(
self
,
instructions
:
str
,
search_values
:
str
,
eid
:
str
,
last_access_time
:
float
,
correct_feedback
:
str
=
""
,
general_feedback
:
str
=
""
,
incorrect_feedback
:
str
=
""
,
language
:
str
=
"de"
,
partially_correct_feedback
:
str
=
""
,
work_author
:
str
=
""
,
work_title
:
str
=
""
,
conll
:
str
=
""
,
exercise_type
:
str
=
""
,
solutions
:
str
=
"[]"
,
text_complexity
:
float
=
0
,
urn
:
str
=
""
,
exercise_type_translation
:
str
=
""
,
)
->
None
:
exercise_type_translation
:
'sqlalchemy.Column[str]'
correct_feedback
:
'sqlalchemy.Column[str]'
general_feedback
:
'sqlalchemy.Column[str]'
incorrect_feedback
:
'sqlalchemy.Column[str]'
instructions
:
'sqlalchemy.Column[str]'
language
:
'sqlalchemy.Column[str]'
partially_correct_feedback
:
'sqlalchemy.Column[str]'
search_values
:
'sqlalchemy.Column[str]'
work_author
:
'sqlalchemy.Column[str]'
work_title
:
'sqlalchemy.Column[str]'
conll
:
'sqlalchemy.Column[str]'
eid
:
'sqlalchemy.Column[str]'
exercise_type
:
'sqlalchemy.Column[str]'
last_access_time
:
'sqlalchemy.Column[float]'
solutions
:
'sqlalchemy.Column[str]'
text_complexity
:
'sqlalchemy.Column[float]'
urn
:
'sqlalchemy.Column[str]'
def
__init__
(
self
,
instructions
:
str
,
search_values
:
str
,
eid
:
str
,
last_access_time
:
float
,
exercise_type_translation
:
str
=
""
,
correct_feedback
:
str
=
""
,
general_feedback
:
str
=
""
,
incorrect_feedback
:
str
=
""
,
language
:
str
=
"de"
,
partially_correct_feedback
:
str
=
""
,
work_author
:
str
=
""
,
work_title
:
str
=
""
,
conll
:
str
=
""
,
exercise_type
:
str
=
""
,
solutions
:
str
=
"[]"
,
text_complexity
:
float
=
0.0
,
urn
:
str
=
""
)
->
None
:
"""
Construct.
Args:
exercise_type_translation: Localized expression of the exercise
type.
correct_feedback: Feedback for successful completion of the
exercise.
general_feedback: Feedback for finishing the exercise.
...
...
@@ -280,37 +247,18 @@ class TExercise(typing.Protocol):
software's internal language analysis.
urn: CTS URN for the text passage from which the exercise was
created.
exercise_type_translation: Localized expression of the exercise
type.
"""
...
@
classmethod
def
from_dict
(
cls
,
instructions
:
str
,
search_values
:
str
,
eid
:
str
,
last_access_time
:
float
,
correct_feedback
:
str
=
""
,
general_feedback
:
str
=
""
,
incorrect_feedback
:
str
=
""
,
language
:
str
=
"de"
,
partially_correct_feedback
:
str
=
""
,
work_author
:
str
=
""
,
work_title
:
str
=
""
,