Skip to content

Commit

Permalink
Refactor dataservice to be more modular
Browse files Browse the repository at this point in the history
Create common module for common code among resources
Create new folder for person tests, move all person tests into it
Separate out model code, resource code, serialization code for Person
resources
Refactor and replace Flask-Script with built in CLI
Add support for api versioning
Clean up Flask CLI commands
Change api prefix from /api/v1 to /v1
Remove Flask-Script dependenct
Replace relative imports with absolute imports
  • Loading branch information
znatty22 committed Jan 16, 2018
1 parent be2bd14 commit 7af27b0
Show file tree
Hide file tree
Showing 16 changed files with 159 additions and 110 deletions.
70 changes: 60 additions & 10 deletions dataservice/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,69 @@
import os
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_restplus import Api
from config import config, DevelopmentConfig
# -*- coding: utf-8 -*-
"""The app module, containing the app factory function."""
from flask import Flask

db = SQLAlchemy()
from dataservice import commands
from dataservice.extensions import db, migrate
from dataservice.api.person.models import Person
from config import config


def create_app(config_name):
"""
An application factory
"""
app = Flask(__name__)
app.url_map.strict_slashes = False
app.config.from_object(config[config_name])
config[config_name].init_app(app)

db.init_app(app)
from dataservice.api import api
api.init_app(app)
# Register Flask extensions
register_extensions(app)
register_shellcontext(app)
register_commands(app)
register_blueprints(app)

return app


def register_shellcontext(app):
"""
Register shell context objects
"""

def shell_context():
"""Shell context objects."""
return {'db': db,
'Person': Person}

app.shell_context_processor(shell_context)


def register_commands(app):
"""
Register Click commands
"""
app.cli.add_command(commands.test)


def register_extensions(app):
"""
Register Flask extensions
"""

# SQLAlchemy
db.init_app(app)

# Migrate
migrate.init_app(app, db)


def register_error_handlers(app):
"""
Register error handlers
"""
pass


def register_blueprints(app):
from dataservice.api import api_v1
app.register_blueprint(api_v1)
8 changes: 6 additions & 2 deletions dataservice/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from flask import Blueprint
from flask_restplus import Api
from .person import person_api
from dataservice.api.person import person_api

api = Api(title='Kids First Data Service',
api_v1 = Blueprint('api', __name__, url_prefix='/v1')

api = Api(api_v1,
title='Kids First Data Service',
description=open('dataservice/api/README.md').read(),
version='0.1',
default='',
Expand Down
Empty file.
File renamed without changes.
21 changes: 3 additions & 18 deletions dataservice/model.py → dataservice/api/common/model.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from sqlalchemy.ext.declarative import declared_attr

from datetime import datetime
from . import db
from sqlalchemy.ext.declarative import declared_attr

from .id_service import assign_id
from dataservice.extensions import db
from dataservice.api.common.id_service import assign_id


class IDMixin:
Expand Down Expand Up @@ -48,17 +47,3 @@ class File(Base):
name = db.Column(db.String(32))
data_type = db.Column(db.String(32))
size = db.Column(db.Integer(), default=0)


class Person(Base):
"""
Person entity.
:param _id: Unique id assigned by RDBMS
:param kf_id: Unique id given by the Kid's First DCC
:param external_id: Name given to person by contributor
:param created_at: Time of object creation
:param modified_at: Last time of object modification
"""
__tablename__ = "person"
external_id = db.Column(db.String(32))
2 changes: 1 addition & 1 deletion dataservice/api/person/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .person import person_api
from .resources import person_api
16 changes: 16 additions & 0 deletions dataservice/api/person/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from dataservice.extensions import db
from dataservice.api.common.model import Base


class Person(Base):
"""
Person entity.
:param _id: Unique id assigned by RDBMS
:param kf_id: Unique id given by the Kid's First DCC
:param external_id: Name given to person by contributor
:param created_at: Time of object creation
:param modified_at: Last time of object modification
"""
__tablename__ = "person"
external_id = db.Column(db.String(32))
Original file line number Diff line number Diff line change
@@ -1,42 +1,15 @@
from datetime import datetime
from flask import request
from flask_restplus import Namespace, Resource, fields, abort

from ... import model
from ... import db
from dataservice.extensions import db
from dataservice.api.person import models

description = open('dataservice/api/person/README.md').read()

person_api = Namespace(name='persons', description=description)

person_model = person_api.model('Person', {
'kf_id': fields.String(
example='KF00001',
description='ID assigned by Kids First'),
'created_at': fields.String(
example=datetime.now().isoformat(),
description='Date Person was registered in with the DCC'),
'modified_at': fields.String(
example=datetime.now().isoformat(),
description='Date of last update to the Persons data'),
'external_id': fields.String(
example='SUBJ-3993',
description='Identifier used in the original study data')
})

person_list = person_api.model("Persons", {
"persons": fields.List(fields.Nested(person_model))
})

response_model = person_api.model('Response', {
'content': fields.Nested(person_list),
'status': fields.Integer(
description='HTTP response status code',
example=200),
'message': fields.String(
description='Additional information about the response',
example='Success')
})
from dataservice.api.person.serializers import (person_model,
response_model)


@person_api.route('/')
Expand All @@ -46,7 +19,7 @@ def get(self):
"""
Get all persons
"""
persons = model.Person.query.all()
persons = models.Person.query.all()
return {'status': 200,
'message': '{} persons'.format(len(persons)),
'content': {'persons': persons}}, 200
Expand All @@ -62,7 +35,7 @@ def post(self):
Creates a new person and assigns a Kids First id
"""
body = request.json
person = model.Person(**body)
person = models.Person(**body)
db.session.add(person)
db.session.commit()
return {'status': 201,
Expand All @@ -80,7 +53,7 @@ def get(self, kf_id):
Get a person by id
Gets a person given a Kids First id
"""
person = model.Person.query.filter_by(kf_id=kf_id).one_or_none()
person = models.Person.query.filter_by(kf_id=kf_id).one_or_none()
if not person:
self._not_found(kf_id)

Expand All @@ -98,7 +71,7 @@ def put(self, kf_id):
Update an existing person
"""
body = request.json
person = model.Person.query.filter_by(kf_id=kf_id).one_or_none()
person = models.Person.query.filter_by(kf_id=kf_id).one_or_none()
if not person:
self._not_found(kf_id)

Expand All @@ -118,7 +91,7 @@ def delete(self, kf_id):
Deletes a person given a Kids First id
"""
person = model.Person.query.filter_by(kf_id=kf_id).one_or_none()
person = models.Person.query.filter_by(kf_id=kf_id).one_or_none()
if not person:
self._not_found(kf_id)

Expand Down
33 changes: 33 additions & 0 deletions dataservice/api/person/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from datetime import datetime
from flask_restplus import fields

from dataservice.api.person.resources import person_api

person_model = person_api.model('Person', {
'kf_id': fields.String(
example='KF00001',
description='ID assigned by Kids First'),
'created_at': fields.String(
example=datetime.now().isoformat(),
description='Date Person was registered in with the DCC'),
'modified_at': fields.String(
example=datetime.now().isoformat(),
description='Date of last update to the Persons data'),
'external_id': fields.String(
example='SUBJ-3993',
description='Identifier used in the original study data')
})

person_list = person_api.model("Persons", {
"persons": fields.List(fields.Nested(person_model))
})

response_model = person_api.model('Response', {
'content': fields.Nested(person_list),
'status': fields.Integer(
description='HTTP response status code',
example=200),
'message': fields.String(
description='Additional information about the response',
example='Success')
})
12 changes: 12 additions & 0 deletions dataservice/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
"""Click commands."""

import click


@click.command()
def test():
""" Run the unit tests and pep8 checks """
from subprocess import call
call(["python", "-m", "pytest", "tests"])
call(["python", "-m", "pytest", "--pep8", "dataservice"])
7 changes: 7 additions & 0 deletions dataservice/extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy, Model


db = SQLAlchemy()

migrate = Migrate()
38 changes: 4 additions & 34 deletions manage.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,9 @@
#!/usr/bin/env python
import os
#! /usr/bin/env python

from dataservice import create_app, db
from flask_script import Manager, Shell
from flask_migrate import Migrate, MigrateCommand
import os
from dataservice import create_app

app = create_app(os.getenv('FLASK_CONFIG') or 'default')
manager = Manager(app)
migrate = Migrate(app, db)


def make_shell_context():
return dict(app=app, db=db)


manager.add_command("shell", Shell(make_context=make_shell_context))
manager.add_command('db', MigrateCommand)


@manager.command
def test(coverage=False):
""" Run the unit tests and pep8 checks """
from subprocess import call
call(["python","-m","pytest","tests"])
call(["python","-m","pytest","--pep8","dataservice"])


@manager.command
def deploy():
""" Run deployment tasks """
from flask.ext.migrate import upgrade

# migrate database to latest revision
upgrade()


if __name__ == '__main__':
manager.run()
app.run()
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ Flask==0.12.2
Flask-Migrate==2.1.1
Flask-Profile==0.2
flask-restplus==0.10.1
Flask-Script==2.0.6
Flask-SQLAlchemy==2.3.2
itsdangerous==0.24
Jinja2==2.8
Expand Down
7 changes: 3 additions & 4 deletions tests/test_person_model.py → tests/person/test_models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from datetime import datetime
import uuid

from dataservice import db
from dataservice.model import Person

from utils import FlaskTestCase
from dataservice.extensions import db
from dataservice.api.person.models import Person
from tests.utils import FlaskTestCase


class ModelTest(FlaskTestCase):
Expand Down
6 changes: 3 additions & 3 deletions tests/test_person_api.py → tests/person/test_resources.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import json
from flask import url_for

from dataservice.model import Person
from utils import FlaskTestCase
from dataservice.api.person.models import Person
from tests.utils import FlaskTestCase

PERSONS_PREFIX = 'persons'
PERSONS_PREFIX = 'api.persons'
PERSON_URL = '{}_{}'.format(PERSONS_PREFIX, 'person')
PERSON_LIST_URL = '{}_{}'.format(PERSONS_PREFIX, 'person_list')

Expand Down
3 changes: 2 additions & 1 deletion tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import unittest
from dataservice import create_app, db
from dataservice import create_app
from dataservice.extensions import db
from flask import url_for


Expand Down

0 comments on commit 7af27b0

Please sign in to comment.