Merge pull request #5 from sartography/feature/create-study-endpoint

Adds endpoints for creating and updating a Study.
This commit is contained in:
Dan Funk 2020-01-03 11:46:01 -05:00 committed by GitHub
commit d0454da644
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 168 additions and 48 deletions

17
Pipfile.lock generated
View File

@ -544,10 +544,10 @@
},
"waitress": {
"hashes": [
"sha256:3776cbb9abebefb51e5b654f8728928aa17b656d9f6943c58ce8f48e87cef4e3",
"sha256:f4118cbce75985fd60aeb4f0d781aba8dc7ae28c18e50753e913d7a7dee76b62"
"sha256:67a60a376f0eb335ed88967c42b73983a58d66a2a72eb9009a42725f7453b142",
"sha256:cbf1c62fc41393a6f27cb78483f8f6e252630a3598984668244b7bf4e35856f1"
],
"version": "==1.4.1"
"version": "==1.4.2"
},
"webob": {
"hashes": [
@ -626,10 +626,10 @@
},
"py": {
"hashes": [
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
"sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa",
"sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"
],
"version": "==1.8.0"
"version": "==1.8.1"
},
"pyparsing": {
"hashes": [
@ -655,10 +655,9 @@
},
"wcwidth": {
"hashes": [
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
"sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"
],
"version": "==0.1.7"
"version": "==0.1.8"
},
"zipp": {
"hashes": [

View File

@ -27,6 +27,29 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
operationId: crc.api.study.add_study
summary: Creates a new study with the given parameters.
tags:
- Studies
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Study'
responses:
'200':
description: Study created successfully.
content:
application/json:
schema:
$ref: "#/components/schemas/Study"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/study/{study_id}:
get:
operationId: crc.api.study.get_study
@ -54,6 +77,37 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
operationId: crc.api.study.update_study
summary: Updates an existing study with the given parameters.
tags:
- Studies
parameters:
- name: study_id
in: path
required: true
description: The id of the study for which workflows should be returned.
schema:
type: integer
format: int32
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Study'
responses:
'200':
description: Study updated successfully.
content:
application/json:
schema:
$ref: "#/components/schemas/Study"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/study-update/{study_id}:
post:
operationId: crc.api.study.post_update_study_from_protocol_builder

View File

@ -7,7 +7,7 @@ from flask import send_file
from crc import db
from crc.api.common import ApiErrorSchema, ApiError
from crc.models.file import FileSchema, FileModel, FileDataModel, FileType
from crc.models.file import FileModelSchema, FileModel, FileDataModel, FileType
def update_file_from_request(file_model):
@ -39,12 +39,12 @@ def update_file_from_request(file_model):
db.session.add(file_data_model)
db.session.add(file_model)
db.session.commit()
db.session.flush() # Assure the id is set on the model before returning it.
db.session.flush() # Assure the id is set on the model before returning it.
def get_files(spec_id):
if spec_id:
schema = FileSchema(many=True)
schema = FileModelSchema(many=True)
return schema.dump(db.session.query(FileModel).filter_by(workflow_spec_id=spec_id).all())
else:
error = ApiError('no_files_found', 'Please provide some parameters so we can find the files you need.')
@ -57,7 +57,7 @@ def add_file():
'Please specify a workflow_spec_id for this file in the form')), 404
file_model = FileModel(version=0, workflow_spec_id=connexion.request.form['workflow_spec_id'])
update_file_from_request(file_model)
return FileSchema().dump(file_model)
return FileModelSchema().dump(file_model)
def update_file(file_id):
@ -65,7 +65,7 @@ def update_file(file_id):
if file_model is None:
return ApiErrorSchema().dump(ApiError('no_such_file', 'The file id you provided does not exist')), 404
update_file_from_request(file_model)
return FileSchema().dump(file_model)
return FileModelSchema().dump(file_model)
def get_file(file_id):
@ -84,7 +84,7 @@ def get_file_info(file_id):
file_model = db.session.query(FileModel).filter_by(id=file_id).with_for_update().first()
if file_model is None:
return ApiErrorSchema().dump(ApiError('no_such_file', 'The file id you provided does not exist')), 404
return FileSchema().dump(file_model)
return FileModelSchema().dump(file_model)
def delete_file(file_id):

View File

@ -2,20 +2,44 @@ from connexion import NoContent
from crc import db
from crc.api.common import ApiError, ApiErrorSchema
from crc.models.study import StudySchema, StudyModel
from crc.models.workflow import WorkflowModel, WorkflowSchema, WorkflowSpecModel
from crc.models.study import StudyModelSchema, StudyModel
from crc.models.workflow import WorkflowModel, WorkflowModelSchema, WorkflowSpecModel
from crc.workflow_processor import WorkflowProcessor
def all_studies():
# todo: Limit returned studies to a user
schema = StudySchema(many=True)
schema = StudyModelSchema(many=True)
return schema.dump(db.session.query(StudyModel).all())
def add_study(body):
study = StudyModelSchema().load(body, session=db.session)
db.session.add(study)
db.session.commit()
return StudyModelSchema().dump(study)
def update_study(study_id, body):
if study_id is None:
error = ApiError('unknown_study', 'Please provide a valid Study ID.')
return ApiErrorSchema.dump(error), 404
study = db.session.query(StudyModel).filter_by(id=study_id).first()
if study is None:
error = ApiError('unknown_study', 'The study "' + study_id + '" is not recognized.')
return ApiErrorSchema.dump(error), 404
study = StudyModelSchema().load(body, session=db.session)
db.session.add(study)
db.session.commit()
return StudyModelSchema().dump(study)
def get_study(study_id):
study = db.session.query(StudyModel).filter_by(id=study_id).first()
schema = StudySchema()
schema = StudyModelSchema()
if study is None:
return NoContent, 404
return schema.dump(study)
@ -28,7 +52,7 @@ def post_update_study_from_protocol_builder(study_id):
def get_study_workflows(study_id):
workflows = db.session.query(WorkflowModel).filter_by(study_id=study_id).all()
schema = WorkflowSchema(many=True)
schema = WorkflowModelSchema(many=True)
return schema.dump(workflows)
@ -45,4 +69,4 @@ def add_workflow_to_study(study_id, body):
workflow_spec_id=workflow_spec_model.id)
db.session.add(workflow)
db.session.commit()
return WorkflowSchema().dump(workflow)
return WorkflowModelSchema().dump(workflow)

View File

@ -1,16 +1,16 @@
from crc import db
from crc.models.workflow import WorkflowModel, WorkflowSchema, WorkflowSpecSchema, WorkflowSpecModel, \
from crc.models.workflow import WorkflowModel, WorkflowModelSchema, WorkflowSpecModelSchema, WorkflowSpecModel, \
Task, TaskSchema
from crc.workflow_processor import WorkflowProcessor
def all_specifications():
schema = WorkflowSpecSchema(many=True)
schema = WorkflowSpecModelSchema(many=True)
return schema.dump(db.session.query(WorkflowSpecModel).all())
def get_workflow(workflow_id):
schema = WorkflowSchema()
schema = WorkflowModelSchema()
workflow = db.session.query(WorkflowModel).filter_by(id=workflow_id).first()
return schema.dump(workflow)

View File

@ -1,7 +1,7 @@
import enum
from flask_marshmallow.sqla import ModelSchema
from marshmallow_enum import EnumField
from marshmallow_sqlalchemy import ModelSchema
from sqlalchemy import func
from crc import db
@ -20,6 +20,7 @@ class FileDataModel(db.Model):
file_model_id = db.Column(db.Integer, db.ForeignKey('file.id'))
file_model = db.relationship("FileModel")
class FileModel(db.Model):
__tablename__ = 'file'
id = db.Column(db.Integer, primary_key=True)
@ -32,7 +33,7 @@ class FileModel(db.Model):
workflow_spec_id = db.Column(db.Integer, db.ForeignKey('workflow_spec.id'))
class FileSchema(ModelSchema):
class FileModelSchema(ModelSchema):
class Meta:
model = FileModel
type = EnumField(FileType)

View File

@ -25,8 +25,8 @@ class StudyModel(db.Model):
ind_number = db.Column(db.String)
class StudySchema(ModelSchema):
class StudyModelSchema(ModelSchema):
class Meta:
model = StudyModel
protocol_builder_status = EnumField(ProtocolBuilderStatus)
protocol_builder_status = EnumField(ProtocolBuilderStatus)

View File

@ -14,10 +14,11 @@ class WorkflowSpecModel(db.Model):
description = db.Column(db.Text)
class WorkflowSpecSchema(ModelSchema):
class WorkflowSpecModelSchema(ModelSchema):
class Meta:
model = WorkflowSpecModel
class WorkflowStatus(enum.Enum):
new = "new"
user_input_required = "user_input_required"
@ -34,7 +35,7 @@ class WorkflowModel(db.Model):
workflow_spec_id = db.Column(db.Integer, db.ForeignKey('workflow_spec.id'))
class WorkflowSchema(ModelSchema):
class WorkflowModelSchema(ModelSchema):
class Meta:
model = WorkflowModel
@ -100,6 +101,7 @@ class FormSchema(ma.Schema):
class TaskSchema(ma.Schema):
class Meta:
fields = ["id", "name", "title", "type", "state", "form", "documentation"]
documentation = marshmallow.fields.String(required=False, allow_none=True)
form = marshmallow.fields.Nested(FormSchema)

View File

@ -1,10 +1,11 @@
from datetime import datetime
import json
import unittest
from crc import db
from crc.models.study import StudyModel, StudySchema
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecSchema, WorkflowModel, WorkflowStatus, \
WorkflowSchema, TaskSchema
from crc.models.study import StudyModel, StudyModelSchema, ProtocolBuilderStatus
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowStatus, \
WorkflowModelSchema, TaskSchema
from tests.base_test import BaseTest
@ -15,6 +16,45 @@ class TestStudy(BaseTest, unittest.TestCase):
study = db.session.query(StudyModel).first()
self.assertIsNotNone(study)
def test_add_study(self):
study = {
"id": 12345,
"title": "Phase III Trial of Genuine People Personalities (GPP) Autonomous Intelligent Emotional Agents for Interstellar Spacecraft",
"last_updated": datetime.now(),
"protocol_builder_status": ProtocolBuilderStatus.in_process,
"primary_investigator_id": "tricia.marie.mcmillan@heartofgold.edu",
"sponsor": "Sirius Cybernetics Corporation",
"ind_number": "567890",
}
rv = self.app.post('/v1.0/study',
content_type="application/json",
data=json.dumps(StudyModelSchema().dump(study)))
self.assert_success(rv)
db_study = db.session.query(StudyModel).first()
self.assertIsNotNone(db_study)
self.assertEqual(study["id"], db_study.id)
self.assertEqual(study["title"], db_study.title)
self.assertEqual(study["last_updated"], db_study.last_updated)
self.assertEqual(study["protocol_builder_status"], db_study.protocol_builder_status)
self.assertEqual(study["primary_investigator_id"], db_study.primary_investigator_id)
self.assertEqual(study["sponsor"], db_study.sponsor)
self.assertEqual(study["ind_number"], db_study.ind_number)
def test_update_study(self):
self.load_example_data()
study: StudyModel = db.session.query(StudyModel).first()
study.title = "Pilot Study of Fjord Placement for Single Fraction Outcomes to Cortisol Susceptibility"
study.protocol_builder_status = ProtocolBuilderStatus.complete
rv = self.app.post('/v1.0/study',
content_type="application/json",
data=json.dumps(StudyModelSchema().dump(study)))
self.assert_success(rv)
db_study = db.session.query(StudyModel).first()
self.assertIsNotNone(db_study)
self.assertEqual(study.title, db_study.title)
self.assertEqual(study.protocol_builder_status, db_study.protocol_builder_status)
def test_study_api_get_single_study(self):
self.load_example_data()
study = db.session.query(StudyModel).first()
@ -23,7 +63,7 @@ class TestStudy(BaseTest, unittest.TestCase):
content_type="application/json")
self.assert_success(rv)
json_data = json.loads(rv.get_data(as_text=True))
study2 = StudySchema().load(json_data, session=db.session)
study2 = StudyModelSchema().load(json_data, session=db.session)
self.assertEqual(study, study2)
self.assertEqual(study.id, study2.id)
self.assertEqual(study.title, study2.title)
@ -41,7 +81,7 @@ class TestStudy(BaseTest, unittest.TestCase):
content_type="application/json")
self.assert_success(rv)
json_data = json.loads(rv.get_data(as_text=True))
specs = WorkflowSpecSchema(many=True).load(json_data, session=db.session)
specs = WorkflowSpecModelSchema(many=True).load(json_data, session=db.session)
spec2 = specs[0]
self.assertEqual(spec.id, spec2.id)
self.assertEqual(spec.display_name, spec2.display_name)
@ -52,8 +92,8 @@ class TestStudy(BaseTest, unittest.TestCase):
study = db.session.query(StudyModel).first()
self.assertEqual(0, db.session.query(WorkflowModel).count())
spec = db.session.query(WorkflowSpecModel).first()
rv = self.app.post('/v1.0/study/%i/workflows' % study.id,content_type="application/json",
data=json.dumps(WorkflowSpecSchema().dump(spec)))
rv = self.app.post('/v1.0/study/%i/workflows' % study.id, content_type="application/json",
data=json.dumps(WorkflowSpecModelSchema().dump(spec)))
self.assert_success(rv)
self.assertEqual(1, db.session.query(WorkflowModel).count())
workflow = db.session.query(WorkflowModel).first()
@ -63,7 +103,7 @@ class TestStudy(BaseTest, unittest.TestCase):
self.assertEqual(spec.id, workflow.workflow_spec_id)
json_data = json.loads(rv.get_data(as_text=True))
workflow = WorkflowSchema().load(json_data, session=db.session)
workflow = WorkflowModelSchema().load(json_data, session=db.session)
self.assertEqual(workflow.id, workflow.id)
def test_delete_workflow(self):
@ -71,10 +111,10 @@ class TestStudy(BaseTest, unittest.TestCase):
study = db.session.query(StudyModel).first()
spec = db.session.query(WorkflowSpecModel).first()
rv = self.app.post('/v1.0/study/%i/workflows' % study.id, content_type="application/json",
data=json.dumps(WorkflowSpecSchema().dump(spec)))
data=json.dumps(WorkflowSpecModelSchema().dump(spec)))
self.assertEqual(1, db.session.query(WorkflowModel).count())
json_data = json.loads(rv.get_data(as_text=True))
workflow = WorkflowSchema().load(json_data, session=db.session)
workflow = WorkflowModelSchema().load(json_data, session=db.session)
rv = self.app.delete('/v1.0/workflow/%i' % workflow.id)
self.assert_success(rv)
self.assertEqual(0, db.session.query(WorkflowModel).count())
@ -84,7 +124,7 @@ class TestStudy(BaseTest, unittest.TestCase):
study = db.session.query(StudyModel).first()
spec = db.session.query(WorkflowSpecModel).filter_by(id='random_fact').first()
self.app.post('/v1.0/study/%i/workflows' % study.id, content_type="application/json",
data=json.dumps(WorkflowSpecSchema().dump(spec)))
data=json.dumps(WorkflowSpecModelSchema().dump(spec)))
rv = self.app.get('/v1.0/workflow/%i/tasks' % study.id, content_type="application/json")
self.assert_success(rv)
json_data = json.loads(rv.get_data(as_text=True))
@ -97,9 +137,9 @@ class TestStudy(BaseTest, unittest.TestCase):
study = db.session.query(StudyModel).first()
spec = db.session.query(WorkflowSpecModel).filter_by(id='two_forms').first()
rv = self.app.post('/v1.0/study/%i/workflows' % study.id, content_type="application/json",
data=json.dumps(WorkflowSpecSchema().dump(spec)))
data=json.dumps(WorkflowSpecModelSchema().dump(spec)))
json_data = json.loads(rv.get_data(as_text=True))
workflow = WorkflowSchema().load(json_data, session=db.session)
workflow = WorkflowModelSchema().load(json_data, session=db.session)
rv = self.app.get('/v1.0/workflow/%i/tasks' % workflow.id, content_type="application/json")
json_data = json.loads(rv.get_data(as_text=True))

View File

@ -5,7 +5,7 @@ from datetime import datetime
from crc import db
from crc.models.workflow import WorkflowSpecModel
from crc.models.file import FileModel, FileType, FileSchema, FileDataModel
from crc.models.file import FileModel, FileType, FileModelSchema, FileDataModel
from tests.base_test import BaseTest
@ -20,7 +20,7 @@ class TestApiFiles(BaseTest, unittest.TestCase):
self.assert_success(rv)
json_data = json.loads(rv.get_data(as_text=True))
self.assertEqual(1, len(json_data))
file = FileSchema(many=True).load(json_data, session=db.session)
file = FileModelSchema(many=True).load(json_data, session=db.session)
self.assertEqual("random_fact.bpmn", file[0].name)
def test_list_multiple_files_for_workflow_spec(self):
@ -50,7 +50,7 @@ class TestApiFiles(BaseTest, unittest.TestCase):
self.assert_success(rv)
self.assertIsNotNone(rv.get_data())
json_data = json.loads(rv.get_data(as_text=True))
file = FileSchema().load(json_data, session=db.session)
file = FileModelSchema().load(json_data, session=db.session)
self.assertEqual(1, file.version)
self.assertEqual(FileType.svg, file.type)
self.assertFalse(file.primary)
@ -60,7 +60,7 @@ class TestApiFiles(BaseTest, unittest.TestCase):
rv = self.app.get('/v1.0/file/%i' % file.id)
self.assert_success(rv)
json_data = json.loads(rv.get_data(as_text=True))
file2 = FileSchema().load(json_data, session=db.session)
file2 = FileModelSchema().load(json_data, session=db.session)
self.assertEqual(file, file2)
def test_update_file(self):
@ -77,7 +77,7 @@ class TestApiFiles(BaseTest, unittest.TestCase):
self.assert_success(rv)
self.assertIsNotNone(rv.get_data())
json_data = json.loads(rv.get_data(as_text=True))
file = FileSchema().load(json_data, session=db.session)
file = FileModelSchema().load(json_data, session=db.session)
self.assertEqual(2, file.version)
self.assertEqual(FileType.bpmn, file.type)
self.assertTrue(file.primary)