Skip to content

Commit

Permalink
Merge pull request #10 from kids-first/person-entity
Browse files Browse the repository at this point in the history
 ✨ Create person entity
  • Loading branch information
dankolbman authored Jan 17, 2018
2 parents 619edfd + 6f6bc39 commit 43dfbab
Show file tree
Hide file tree
Showing 22 changed files with 790 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
*.sqlite
*.swp
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down Expand Up @@ -99,3 +101,6 @@ ENV/

# mypy
.mypy_cache/

# PyCharm
.idea/
69 changes: 69 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import os
basedir = os.path.abspath(os.path.dirname(__file__))


class Config:
HOST = "0.0.0.0"
SSL_DISABLE = os.environ.get("SSL_DISABLE", False)

RESTPLUS_MASK_SWAGGER = False

@staticmethod
def init_app(app):
pass


class DevelopmentConfig(Config):
DEBUG = True
SSL_DISABLE = True
SQLALCHEMY_DATABASE_URI = os.environ.get("DEV_DATABASE_URL") or \
"sqlite:///" + os.path.join(basedir, "data-dev.sqlite")
SQLALCHEMY_TRACK_MODIFICATIONS = True


class TestingConfig(Config):
SERVER_NAME = "localhost"
TESTING = True
WTF_CSRF_ENABLED = False
SQLALCHEMY_DATABASE_URI = os.environ.get("TEST_DATABASE_URL") or \
"sqlite:///" + os.path.join(basedir, "data-test.sqlite")
SQLALCHEMY_TRACK_MODIFICATIONS = True


class ProductionConfig(Config):
# Should use postgres
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") or \
"sqlite:///" + os.path.join(basedir, "data.sqlite")

@classmethod
def init_app(cls, app):
Config.init_app(app)

# email errors to the administrators
import logging
from logging.handlers import SMTPHandler
credentials = None
secure = None


class UnixConfig(ProductionConfig):
@classmethod
def init_app(cls, app):
ProductionConfig.init_app(app)

# log to syslog
import logging
from logging.handlers import SysLogHandler
syslog_handler = SysLogHandler()
syslog_handler.setLevel(logging.WARNING)
app.logger.addHandler(syslog_handler)


config = {
"development": DevelopmentConfig,
"testing": TestingConfig,
"production": ProductionConfig,
"unix": UnixConfig,

"default": DevelopmentConfig
}
69 changes: 69 additions & 0 deletions dataservice/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
"""The app module, containing the app factory function."""
from flask import Flask

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])

# 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)
49 changes: 49 additions & 0 deletions dataservice/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Primary API for interacting with the Kid's First Data Model.

## Service-Wide Standards and Functionality

### Field Masking

Partial data fetching is supported via the `X-Fields` header.
To specifiy that only some fields are to be returned, include a bracketed,
coma-delimited list under the `X-Fields` header:

`X-Fields: {kf_id, name}`

Brackets may be nested to filter on nested fields:

`X-Fields: {kf_id, name, type{format, extension}}`

An asterisk may be used to specify all fields:

`X-Fields: *`

Or all sub-fields:

`X-Fields: {kf_id, type{*}}`

Or all root fields, but with only some sub-fields:

`X-Fields: {*, type{format}}`



### Pagination

```
{
pages: [
{ "doc_id": 30, "value": "Lorem" },
{ "doc_id": 31, "value": "ipsum" },
{ "doc_id": 32, "value": "dolor" },
...
{ "doc_id": 40, "value": "amet" }
],
from: 30,
to: 40,
results: 10,
total: 1204,
message: "Success",
status: 200
}
```
34 changes: 34 additions & 0 deletions dataservice/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from flask import Blueprint
from flask_restplus import Api
from dataservice.api.person import person_api

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='',
default_label='')

api.add_namespace(person_api)


@api.documentation
def redoc_ui():
""" Uses ReDoc for swagger documentation """
docs_page = """<!DOCTYPE html>
<html>
<head>
<title>API Docs</title>
<!-- needed for mobile devices -->
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<redoc spec-url="{}"></redoc>
<script src="https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js">
</script>
</body>
</html>
""".format(api.specs_url)
return docs_page
Empty file.
5 changes: 5 additions & 0 deletions dataservice/api/common/id_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import uuid


def assign_id():
return str(uuid.uuid4())
49 changes: 49 additions & 0 deletions dataservice/api/common/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from datetime import datetime
from sqlalchemy.ext.declarative import declared_attr

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


class IDMixin:
"""
Defines base ID columns common on all Kids First tables
"""
_id = db.Column(db.Integer(), primary_key=True)
kf_id = db.Column(db.String(32), unique=True, default=assign_id())
uuid = db.Column(db.String(32), unique=True, default=assign_id())


class TimestampMixin:
"""
Defines the common timestammp columns on all Kids First tables
"""
created_at = db.Column(db.DateTime(), default=datetime.now())
modified_at = db.Column(db.DateTime(), default=datetime.now())


class HasFileMixin:
@declared_attr
def file_id(cls):
return db.Column('file_id', db.ForeignKey('file._id'))

@declared_attr
def files(cls):
return db.relationship("File")


class Base(db.Model, IDMixin, TimestampMixin):
"""
Defines base SQlAlchemy model class
"""
pass


class File(Base):
"""
Defines a file
"""
__tablename__ = "file"
name = db.Column(db.String(32))
data_type = db.Column(db.String(32))
size = db.Column(db.Integer(), default=0)
21 changes: 21 additions & 0 deletions dataservice/api/person/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
A person is one of the central entities of the Kid's First DCC.

### Fields

`kf_id` - the unique identifier assigned by Kid's First used as the primary
reference to this Person

`source_name` - the identifier used by the original contributor of the data

`date_created` - the date the person`s record was created in the DCC

`date_modified` - the last date that the record's fields were modified.
Restricted to fields of the entity itself, not any of it's related entities.

### Identifiers

The Kid's First DCC assigns a unique, internal identifier of the form:
`KF-P000000` on creation of a new Person. This identifier is used accross the
Kids First Data Service and Data Resource Portal It is expected that the Person
also have an identifier unique to the study it came from. This field is to be
captured in the `source_name` property of the Person upon creation.
1 change: 1 addition & 0 deletions dataservice/api/person/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
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))
Loading

0 comments on commit 43dfbab

Please sign in to comment.