From daf21f0b80e1783ed1eb777a7b6a9c5618c069d7 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Thu, 22 Mar 2018 21:30:06 -0700 Subject: restructure --- Pipfile | 5 +- Pipfile.lock | 9 +- backend/config.py | 2 - fatcat/__init__.py | 10 ++ fatcat/api.py | 13 +++ fatcat/backend.py | 301 ------------------------------------------------- fatcat/models.py | 158 ++++++++++++++++++++++++++ fatcat/routes.py | 57 ++++++++++ fatcat/sql.py | 32 ++++++ fatcat/test_backend.py | 53 --------- fatcat/webface.py | 87 -------------- run.py | 27 +++++ tests/test_backend.py | 59 ++++++++++ 13 files changed, 367 insertions(+), 446 deletions(-) delete mode 100644 backend/config.py create mode 100644 fatcat/__init__.py create mode 100644 fatcat/api.py delete mode 100755 fatcat/backend.py create mode 100644 fatcat/models.py create mode 100644 fatcat/routes.py create mode 100644 fatcat/sql.py delete mode 100644 fatcat/test_backend.py delete mode 100755 fatcat/webface.py create mode 100644 run.py create mode 100644 tests/test_backend.py diff --git a/Pipfile b/Pipfile index 091e295a..adb2bbc2 100644 --- a/Pipfile +++ b/Pipfile @@ -11,9 +11,10 @@ name = "pypi" [packages] -flask = "*" -sqlalchemy = "*" +Flask = "*" +SQLAlchemy = "*" requests = "*" +Flask-SQLAlchemy = "*" [requires] diff --git a/Pipfile.lock b/Pipfile.lock index bef64e85..1f82a0dc 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "98d21978b677ea4ad82a3dc1b0aee77ca0c6af87bc4928f0341f2a0a04f47084" + "sha256": "a011142ad79cd80853e0212f35f6de603c33a616965f635d91fc521bf110be13" }, "host-environment-markers": { "implementation_name": "cpython", @@ -57,6 +57,13 @@ ], "version": "==0.12.2" }, + "flask-sqlalchemy": { + "hashes": [ + "sha256:3bc0fac969dd8c0ace01b32060f0c729565293302f0c4269beed154b46bec50b", + "sha256:5971b9852b5888655f11db634e87725a9031e170f37c0ce7851cf83497f56e53" + ], + "version": "==2.3.2" + }, "idna": { "hashes": [ "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", diff --git a/backend/config.py b/backend/config.py deleted file mode 100644 index d4196230..00000000 --- a/backend/config.py +++ /dev/null @@ -1,2 +0,0 @@ - -DATABASE_URI = "sqlite:///test.sqlite" diff --git a/fatcat/__init__.py b/fatcat/__init__.py new file mode 100644 index 00000000..230fd608 --- /dev/null +++ b/fatcat/__init__.py @@ -0,0 +1,10 @@ + +from flask import Flask +from config import Config +from flask_sqlalchemy import SQLAlchemy + +app = Flask(__name__) +app.config.from_object(Config) +db = SQLAlchemy(app) + +from fatcat import routes, models, api, sql diff --git a/fatcat/api.py b/fatcat/api.py new file mode 100644 index 00000000..d09eedd5 --- /dev/null +++ b/fatcat/api.py @@ -0,0 +1,13 @@ + +from flask import Flask, render_template, send_from_directory, request, \ + url_for, abort, g, redirect, jsonify +from fatcat import app, db +from fatcat.models import * + + +### Views ################################################################### + +@app.route('/work/', methods=['GET']) +def work_get(): + work = WorkId.query.filter_by(id=work_id).first_or_404() + return jsonify(work) diff --git a/fatcat/backend.py b/fatcat/backend.py deleted file mode 100755 index a39ae790..00000000 --- a/fatcat/backend.py +++ /dev/null @@ -1,301 +0,0 @@ - -import argparse -from flask import Flask, render_template, send_from_directory, request, \ - url_for, abort, g, redirect, jsonify -from sqlalchemy import create_engine, MetaData, Table - -app = Flask(__name__) -app.config.from_object(__name__) - -# Load default config and override config from an environment variable -app.config.update(dict( - DATABASE_URI='sqlite://:memory:', - SECRET_KEY='development-key', - USERNAME='admin', - PASSWORD='admin' -)) -app.config.from_envvar('FATCAT_BACKEND_CONFIG', silent=True) - -metadata = MetaData() - - -## SQL Schema ############################################################### - -import enum -from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey, \ - Enum - -# TODO: http://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/mixins.html - -class IdState(enum.Enum): - normal = 1 - redirect = 2 - removed = 3 - -work_id = Table('work_id', metadata, - Column('id', Integer, primary_key=True, autoincrement=False), - Column('revision', ForeignKey('work_revision.id')), - ) - -work_revision = Table('work_revision', metadata, - Column('id', Integer, primary_key=True, autoincrement=True), - Column('previous', ForeignKey('work_revision.id'), nullable=True), - Column('state', Enum(IdState)), - Column('redirect_id', ForeignKey('work_id.id'), nullable=True), - Column('edit_id', ForeignKey('edit.id')), - Column('extra_json', ForeignKey('extra_json.sha1'), nullable=True), - - Column('title', String), - Column('work_type', String), - Column('date', String), - ) - -release_id = Table('release_id', metadata, - Column('id', Integer, primary_key=True, autoincrement=False), - Column('revision', ForeignKey('release_revision.id')), - ) - -release_revision = Table('release_revision', metadata, - Column('id', Integer, primary_key=True, autoincrement=True), - Column('previous', ForeignKey('release_revision.id'), nullable=True), - Column('state', Enum(IdState)), - Column('redirect_id', ForeignKey('release_id.id'), nullable=True), - Column('edit_id', ForeignKey('edit.id')), - Column('extra_json', ForeignKey('extra_json.sha1'), nullable=True), - - #Column('work', ForeignKey('work_id.id')), - Column('container', ForeignKey('container_id.id')), - Column('title', String), - Column('license', String), # TODO: oa status foreign key - Column('release_type', String), # TODO: foreign key - Column('date', String), # TODO: datetime - Column('doi', String), # TODO: identifier table - ) - -creator_id = Table('creator_id', metadata, - Column('id', Integer, primary_key=True, autoincrement=False), - Column('revision', ForeignKey('creator_revision.id')), - ) - -creator_revision = Table('creator_revision', metadata, - Column('id', Integer, primary_key=True, autoincrement=True), - Column('previous', ForeignKey('creator_revision.id'), nullable=True), - Column('state', Enum(IdState)), - Column('redirect_id', ForeignKey('creator_id.id'), nullable=True), - Column('edit_id', ForeignKey('edit.id')), - Column('extra_json', ForeignKey('extra_json.sha1'), nullable=True), - - Column('name', String), - Column('sortname', String), - Column('orcid', String), # TODO: identifier table - ) - -work_contrib = Table('work_contrib', metadata, - Column('id', Integer, primary_key=True, autoincrement=True), - Column('work_rev', ForeignKey('work_revision.id'), nullable=False), - Column('creator_id', ForeignKey('creator_id.id'), nullable=False), - Column('stub', String, nullable=False), - ) - -release_contrib = Table('release_contrib', metadata, - Column('id', Integer, primary_key=True, autoincrement=True), - Column('release_rev', ForeignKey('release_revision.id'), nullable=False), - Column('creator_id', ForeignKey('creator_id.id'), nullable=False), - ) - -container_id = Table('container_id', metadata, - Column('id', Integer, primary_key=True, autoincrement=False), - Column('revision', ForeignKey('container_revision.id')), - ) - -container_revision = Table('container_revision', metadata, - Column('id', Integer, primary_key=True, autoincrement=True), - Column('previous', ForeignKey('container_revision.id'), nullable=True), - Column('state', Enum(IdState)), - Column('redirect_id', ForeignKey('container_id.id'), nullable=True), - Column('edit_id', ForeignKey('edit.id')), - Column('extra_json', ForeignKey('extra_json.sha1'), nullable=True), - - Column('name', String), - Column('container', ForeignKey('container_id.id')), - Column('publisher', String), # TODO: foreign key - Column('sortname', String), - Column('issn', String), # TODO: identifier table - ) - -file_id = Table('file_id', metadata, - Column('id', Integer, primary_key=True, autoincrement=False), - Column('revision', ForeignKey('container_revision.id')), - ) - -file_revision = Table('file_revision', metadata, - Column('id', Integer, primary_key=True, autoincrement=True), - Column('previous', ForeignKey('file_revision.id'), nullable=True), - Column('state', Enum(IdState)), - Column('redirect_id', ForeignKey('file_id.id'), nullable=True), - Column('edit_id', ForeignKey('edit.id')), - Column('extra_json', ForeignKey('extra_json.sha1'), nullable=True), - - Column('size', Integer), - Column('sha1', Integer), # TODO: hash table... only or in addition? - Column('url', Integer), # TODO: URL table - ) - -release_file= Table('release_file', metadata, - Column('id', Integer, primary_key=True, autoincrement=True), - Column('release_rev', ForeignKey('release_revision.id'), nullable=False), - Column('file_id', ForeignKey('file_id.id'), nullable=False), - ) - -edit = Table('edit', metadata, - Column('id', Integer, primary_key=True, autoincrement=True), - Column('edit_group', ForeignKey('edit_group.id')), - Column('editor', ForeignKey('editor.id')), - Column('description', String), - ) - -edit_group = Table('edit_group', metadata, - Column('id', Integer, primary_key=True, autoincrement=True), - Column('editor', ForeignKey('editor.id')), - Column('description', String), - ) - -editor = Table('editor', metadata, - Column('id', Integer, primary_key=True, autoincrement=True), - Column('username', String), - ) - -changelog = Table('changelog', metadata, - Column('id', Integer, primary_key=True, autoincrement=True), - Column('edit_id', ForeignKey('edit.id')), - Column('timestamp', Integer), - ) - -extra_json = Table('extra_json', metadata, - Column('sha1', String, primary_key=True, autoincrement=True), - Column('json', String), - ) - -## Helpers ################################################################## - -def is_fcid(s): - return len(s) == 26 and s.isalnum() - -# XXX: why isn't this running? -def test_is_fcid(): - - for s in ("rzga5b9cd7efgh04iljk", "RZGA5B9CD7Efgh04iljk"): - assert is_fcid() is True - - for s in ("rzga59cd7efgh04iljk", "rzga.b9cd7efgh04iljk", "", - "rzga5b9cd7efgh04iljkz"): - assert is_fcid() is False - -def release_list(id_list): - # XXX: MOCK - l = [] - for i in id_list: - l.append({ - "id": i, - "rev": "8fkj28fjhqkjdhkjkj9s", - "previous": "0021jdfjhqkjdhkjkj9s", - "state": "normal", - "redirect_id": None, - "edit_id": "932582iuhckjvssk", - "extra_json": None, - - "container_id": "0021jdfjhqkjdhkjkj9s", - "title": "Mocks are great", - "license": "CC-0", - "release_type": "publication", - "date": "2017-11-22", - "doi": "10.1000/953kj.sdfkj", - }) - return l - -def release_hydrate(release_id): - e = release_list([release_id])[0] - e['container'] = container_hydrate(d['container_id']) - e.pop('container_id') - e['creators'] = [creator_hydrate(c['id']) for c in e['creator_ids']] - return e - -def work_list(id_list): - """This is the fast/light version: populates entity-specific lists (eg, - identifiers), and any primaries, but doesn't transclude all other - entities""" - if len(id_list) == 0: - return [] - - l = [] - for i in id_list: - l.append({ - "id": "rzga5b9cd7efgh04iljk", - "rev": "8fkj28fjhqkjdhkjkj9s", - "previous": "0021jdfjhqkjdhkjkj9s", - "state": "normal", - "redirect_id": None, - "edit_id": "932582iuhckjvssk", - "extra_json": None, - - "title": "Mocks are great", - "contributors": [], - "work_type": "journal-article", - "date": None, - - "primary_release": release_list(["8fkj28fjhqkjdhkjkj9s"])[0], - }) - return l - -def work_hydrate(work_id): - """This is the heavy/slowversion: everything from get_works(), but also - recursively transcludes single-linked entities""" - # XXX: - return work_list([work_id])[0] - -## API Methods ############################################################## - -@app.route('/health', methods=['GET']) -def health(): - return jsonify({'ok': True}) - - -@app.route('/v0/work/', methods=['GET']) -def work_get(work_id): - if not is_fcid(work_id): - print("not fcid: {}".format(work_id)) - return abort(404) - work = work_hydrate(work_id) - return jsonify(work) - -## Entry Point ############################################################## - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--debug', - action='store_true', - help="enable debugging interface") - parser.add_argument('--host', - default="127.0.0.1", - help="listen on this host/IP") - parser.add_argument('--port', - type=int, - default=8040, - help="listen on this port") - parser.add_argument('--database-uri', - default=app.config['DATABASE_URI'], - help="sqlalchemy database string") - args = parser.parse_args() - - app.config['DATABASE_URI'] = args.database_uri - app.conn = create_engine(app.config['DATABASE_URI'], convert_unicode=True) - metadata.create_all(bind=engine) - - # XXX: - db_test_data() - - app.run(debug=args.debug, host=args.host, port=args.port) - - -if __name__ == '__main__': - main() diff --git a/fatcat/models.py b/fatcat/models.py new file mode 100644 index 00000000..dd84070d --- /dev/null +++ b/fatcat/models.py @@ -0,0 +1,158 @@ + +import enum +from fatcat import db + + +# TODO: EntityMixin, EntityIdMixin + +work_contrib = db.Table("work_contrib", + db.Column("work_rev", db.ForeignKey('work_revision.id'), nullable=False, primary_key=True), + db.Column("creator_id", db.ForeignKey('creator_id.id'), nullable=False, primary_key=True), + db.Column("stub", db.String, nullable=True)) + +release_contrib = db.Table("release_contrib", + db.Column("release_rev", db.ForeignKey('release_revision.id'), nullable=False, primary_key=True), + db.Column("creator_id", db.ForeignKey('creator_id.id'), nullable=False, primary_key=True), + db.Column("stub", db.String, nullable=True)) + +class WorkId(db.Model): + __tablename__ = 'work_id' + id = db.Column(db.Integer, primary_key=True) + revision_id = db.Column(db.ForeignKey('work_revision.id')) + +class WorkRevision(db.Model): + __tablename__ = 'work_revision' + id = db.Column(db.Integer, primary_key=True) + previous = db.Column(db.ForeignKey('work_revision.id'), nullable=True) + state = db.Column(db.String) + redirect_id = db.Column(db.ForeignKey('work_id.id'), nullable=True) + edit_id = db.Column(db.ForeignKey('edit.id')) + extra_json = db.Column(db.ForeignKey('extra_json.sha1'), nullable=True) + #work_ids = db.relationship("WorkId", backref="revision", lazy=True) + + title = db.Column(db.String) + work_type = db.Column(db.String) + date = db.Column(db.String) + + creators = db.relationship('CreatorId', secondary=work_contrib, + lazy='subquery', backref=db.backref('works', lazy=True)) + +class ReleaseId(db.Model): + __tablename__ = 'release_id' + id = db.Column(db.Integer, primary_key=True) #autoincrement=False + revision_id = db.Column(db.ForeignKey('release_revision.id')) + +class ReleaseRevision(db.Model): + __tablename__ = 'release_revision' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + previous = db.Column(db.ForeignKey('release_revision.id'), nullable=True) + state = db.Column(db.String) # TODO: enum + redirect_id = db.Column(db.ForeignKey('release_id.id'), nullable=True) + edit_id = db.Column(db.ForeignKey('edit.id')) + extra_json = db.Column(db.ForeignKey('extra_json.sha1'), nullable=True) + #release_ids = db.relationship("ReleaseId", backref="revision", lazy=False) + + work_id = db.ForeignKey('work_id.id') + container = db.Column(db.ForeignKey('container_id.id')) + title = db.Column(db.String) + license = db.Column(db.String) # TODO: oa status foreign key + release_type = db.Column(db.String) # TODO: foreign key + date = db.Column(db.String) # TODO: datetime + doi = db.Column(db.String) # TODO: identifier table + + creators = db.relationship('CreatorId', secondary=release_contrib, + lazy='subquery') + #backref=db.backref('releases', lazy=True)) + +class CreatorId(db.Model): + __tablename__ = 'creator_id' + id = db.Column(db.Integer, primary_key=True) #autoincrement=False) + revision_id = db.Column(db.ForeignKey('creator_revision.id')) + +class CreatorRevision(db.Model): + __tablename__ = 'creator_revision' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + previous = db.Column(db.ForeignKey('creator_revision.id'), nullable=True) + state = db.Column(db.String) + redirect_id = db.Column(db.ForeignKey('creator_id.id'), nullable=True) + edit_id = db.Column(db.ForeignKey('edit.id')) + extra_json = db.Column(db.ForeignKey('extra_json.sha1'), nullable=True) + #creator_ids = db.relationship("CreatorId", backref="revision", lazy=False) + + name = db.Column(db.String) + sortname = db.Column(db.String) + orcid = db.Column(db.String) # TODO: identifier table + +class ContainerId(db.Model): + __tablename__ = 'container_id' + id = db.Column(db.Integer, primary_key=True) #autoincrement=False) + revision_id = db.Column(db.ForeignKey('container_revision.id')) + +class ContainerRevision(db.Model): + __tablename__ = 'container_revision' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + previous = db.Column(db.ForeignKey('container_revision.id'), nullable=True) + state = db.Column(db.String) + redirect_id = db.Column(db.ForeignKey('container_id.id'), nullable=True) + edit_id = db.Column(db.ForeignKey('edit.id')) + extra_json = db.Column(db.ForeignKey('extra_json.sha1'), nullable=True) + + name = db.Column(db.String) + container = db.Column(db.ForeignKey('container_id.id')) + publisher = db.Column(db.String) # TODO: foreign key + sortname = db.Column(db.String) + issn = db.Column(db.String) # TODO: identifier table + +class FileId(db.Model): + __tablename__ = 'file_id' + id = db.Column(db.Integer, primary_key=True, autoincrement=False) + revision_id = db.Column('revision', db.ForeignKey('container_revision.id')) + +class FileRevision(db.Model): + __tablename__ = 'file_revision' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + previous = db.Column(db.ForeignKey('file_revision.id'), nullable=True) + state = db.Column(db.String) + redirect_id = db.Column(db.ForeignKey('file_id.id'), nullable=True) + edit_id = db.Column(db.ForeignKey('edit.id')) + extra_json = db.Column(db.ForeignKey('extra_json.sha1'), nullable=True) + + size = db.Column(db.Integer) + sha1 = db.Column(db.Integer) # TODO: hash table... only or in addition? + url = db.Column(db.Integer) # TODO: URL table + +class ReleaseFil(db.Model): + __tablename__ = 'release_file' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + release_rev = db.Column(db.ForeignKey('release_revision.id'), nullable=False) + file_id = db.Column(db.ForeignKey('file_id.id'), nullable=False) + +class Edit(db.Model): + __tablename__ = 'edit' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + edit_group = db.Column(db.ForeignKey('edit_group.id')) + editor = db.Column(db.ForeignKey('editor.id')) + description = db.Column(db.String) + +class EditGroup(db.Model): + __tablename__ = 'edit_group' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + editor = db.Column(db.ForeignKey('editor.id')) + description = db.Column(db.String) + +class Editor(db.Model): + __tablename__ = 'editor' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + username = db.Column(db.String) + +class ChangelogEntry(db.Model): + __tablename__= 'changelog' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + edit_id = db.Column(db.ForeignKey('edit.id')) + timestamp = db.Column(db.Integer) + +class ExtraJson(db.Model): + __tablename__ = 'extra_json' + sha1 = db.Column(db.String, primary_key=True) + json = db.Column(db.String) + diff --git a/fatcat/routes.py b/fatcat/routes.py new file mode 100644 index 00000000..61228638 --- /dev/null +++ b/fatcat/routes.py @@ -0,0 +1,57 @@ + +import os +from flask import Flask, render_template, send_from_directory, request, \ + url_for, abort, g, redirect, jsonify +from fatcat import app, db + + +### Views ################################################################### + +@app.route('/work/create', methods=['GET']) +def work_create(): + return render_template('work_add.html') + +@app.route('/work/random', methods=['GET']) +def work_random(): + work = { + "title": "Structure and Interpretation", + "work_type": "book", + "date": None, + "contributors": [ + {"name": "Alyssa P. Hacker"}, + ], + "primary": { + "title": "Structure and Interpretation", + "release_type": "online", + "date": "2000-01-01", + "doi": "10.491/599.sdo14", + }, + "releases": [ + ] + } + return render_template('work_view.html', work=work, primary=work['primary']) + +@app.route('/work//random', methods=['GET']) +def work_view(work_id): + return render_template('work_view.html') + + +### Static Routes ########################################################### + +@app.route('/', methods=['GET']) +def homepage(): + return render_template('home.html') + +@app.route('/about', methods=['GET']) +def aboutpage(): + return render_template('about.html') + +@app.route('/robots.txt', methods=['GET']) +def robots(): + return send_from_directory(os.path.join(app.root_path, 'static'), + 'robots.txt', + mimetype='text/plain') + +@app.route('/health', methods=['GET']) +def health(): + return jsonify({'ok': True}) diff --git a/fatcat/sql.py b/fatcat/sql.py new file mode 100644 index 00000000..01da5873 --- /dev/null +++ b/fatcat/sql.py @@ -0,0 +1,32 @@ + +from fatcat import app, db +from fatcat.models import * + +def populate_db(): + + # XXX: doesn't create an edit trail (yet) + n_elkies = CreatorRevision( + name="Noam D. Elkies", + sortname="Elkies, N", + orcid=None) + n_elkies_id = CreatorId(revision_id=n_elkies.id) + pi_work = WorkRevision( + title="Why is π 2so close to 10?") + pi_work_id = WorkId(revision_id=pi_work.id) + pi_release = ReleaseRevision( + title=pi_work.title, + creators=[n_elkies_id], + work_id=pi_work.id) + pi_release_id = ReleaseId(revision_id=pi_release.id) + pi_work.primary_release = pi_release.id + + #pi_file = File( + # sha1="efee52e46c86691e2b892dbeb212f3b92e92e9d3", + # url="http://www.math.harvard.edu/~elkies/Misc/pi10.pdf") + db.session.add_all([n_elkies, pi_work, pi_work_id, pi_release, pi_release_id]) + + #ligo_collab = CreatorRevision(name="LIGO Scientific Collaboration") + #ligo_paper = ReleaseRevision( + # title="Full Band All-sky Search for Periodic Gravitational Waves in the O1 LIGO Data") + db.session.commit() + diff --git a/fatcat/test_backend.py b/fatcat/test_backend.py deleted file mode 100644 index 429b5ae7..00000000 --- a/fatcat/test_backend.py +++ /dev/null @@ -1,53 +0,0 @@ - -import os -import json -import backend -import unittest -import tempfile -from nose.tools import * - -# TODO: replace all these "assert" with unit test version (which displays left -# and right on failure) - -# TODO: http://alextechrants.blogspot.com/2013/08/unit-testing-sqlalchemy-apps.html - -## Helpers ################################################################## - -def check_entity_fields(e): - for key in ('id', 'rev', 'previous', 'state', 'redirect_id', 'edit_id', - 'extra_json'): - assert_in(key, e) - for key in ('id', 'rev'): - assert_is_not_none(e[key]) - -## API Tests ################################################################ - -class BackendTestCase(unittest.TestCase): - - def setUp(self): - backend.app.config['DATABASE_URI'] = 'sqlite://:memory:' - backend.app.testing = True - self.app = backend.app.test_client() - - def test_health(self): - rv = self.app.get('/health') - obj = json.loads(rv.data.decode('utf-8')) - assert obj['ok'] - - def test_works(self): - - # Invalid Id - rv = self.app.get('/v0/work/_') - assert rv.status_code == 404 - - # Missing Id (TODO) - #rv = self.app.get('/v0/work/rzga5b9cd7efgh04iljk') - #assert rv.status is 404 - - # Valid Id - rv = self.app.get('/v0/work/r3zga5b9cd7ef8gh084714iljk') - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - check_entity_fields(obj) - assert obj['title'] - assert_equal(obj['work_type'], "journal-article") diff --git a/fatcat/webface.py b/fatcat/webface.py deleted file mode 100755 index 33833e25..00000000 --- a/fatcat/webface.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python3 - -import os -import argparse -import requests -from flask import Flask, render_template, send_from_directory, request, \ - url_for, abort, g, redirect, jsonify - -app = Flask(__name__) -app.config.from_object(__name__) - - -### Views ################################################################### - -@app.route('/health', methods=['GET']) -def health(): - return jsonify({'ok': True}) - -@app.route('/work/create', methods=['GET']) -def work_create(): - return render_template('work_add.html') - -@app.route('/work/random', methods=['GET']) -def work_random(): - work = { - "title": "Structure and Interpretation", - "work_type": "book", - "date": None, - "contributors": [ - {"name": "Alyssa P. Hacker"}, - ], - "primary": { - "title": "Structure and Interpretation", - "release_type": "online", - "date": "2000-01-01", - "doi": "10.491/599.sdo14", - }, - "releases": [ - ] - } - return render_template('work_view.html', work=work, primary=work['primary']) - -@app.route('/work//random', methods=['GET']) -def work_view(work_id): - return render_template('work_view.html') - - -### Static Routes ########################################################### - -@app.route('/', methods=['GET']) -def homepage(): - return render_template('home.html') - -@app.route('/about', methods=['GET']) -def aboutpage(): - return render_template('about.html') - -@app.route('/robots.txt', methods=['GET']) -def robots(): - return send_from_directory(os.path.join(app.root_path, 'static'), - 'robots.txt', - mimetype='text/plain') - - -### Entry Point ############################################################# - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--debug', - action='store_true', - help="enable debugging interface") - parser.add_argument('--host', - default="127.0.0.1", - help="listen on this host/IP") - parser.add_argument('--port', - type=int, - default=5050, - help="listen on this port") - parser.add_argument('--backend-api', - default="localhost:6060", - help="backend API to connect to") - args = parser.parse_args() - - app.run(debug=args.debug, host=args.host, port=args.port) - -if __name__ == '__main__': - main() diff --git a/run.py b/run.py new file mode 100644 index 00000000..c8f492ea --- /dev/null +++ b/run.py @@ -0,0 +1,27 @@ + +import argparse +from fatcat import app, db + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--debug', + action='store_true', + help="enable debugging interface") + parser.add_argument('--host', + default="127.0.0.1", + help="listen on this host/IP") + parser.add_argument('--port', + type=int, + default=8040, + help="listen on this port") + parser.add_argument('--database-uri', + default=app.config['DATABASE_URI'], + help="sqlalchemy database string") + args = parser.parse_args() + + app.config['DATABASE_URI'] = args.database_uri + db.create_all() + app.run(debug=args.debug, host=args.host, port=args.port) + +if __name__ == '__main__': + main() diff --git a/tests/test_backend.py b/tests/test_backend.py new file mode 100644 index 00000000..afc6215d --- /dev/null +++ b/tests/test_backend.py @@ -0,0 +1,59 @@ + +import os +import json +import fatcat +import fatcat.sql +import unittest +import tempfile +from nose.tools import * + +# TODO: replace all these "assert" with unit test version (which displays left +# and right on failure) + +# TODO: http://alextechrants.blogspot.com/2013/08/unit-testing-sqlalchemy-apps.html + +## Helpers ################################################################## + +def check_entity_fields(e): + for key in ('id', 'rev', 'previous', 'state', 'redirect_id', 'edit_id', + 'extra_json'): + assert_in(key, e) + for key in ('id', 'rev'): + assert_is_not_none(e[key]) + +## API Tests ################################################################ + +class FatcatTestCase(unittest.TestCase): + + def setUp(self): + fatcat.app.config['DATABASE_URI'] = 'sqlite://:memory:' + fatcat.app.testing = True + self.app = fatcat.app.test_client() + fatcat.db.create_all() + + def test_health(self): + rv = self.app.get('/health') + obj = json.loads(rv.data.decode('utf-8')) + assert obj['ok'] + + def test_works(self): + + # Invalid Id + rv = self.app.get('/v0/work/_') + assert rv.status_code == 404 + + # Missing Id (TODO) + #rv = self.app.get('/v0/work/rzga5b9cd7efgh04iljk') + #assert rv.status is 404 + + # Valid Id + rv = self.app.get('/v0/work/r3zga5b9cd7ef8gh084714iljk') + assert rv.status_code == 200 + obj = json.loads(rv.data.decode('utf-8')) + check_entity_fields(obj) + assert obj['title'] + assert_equal(obj['work_type'], "journal-article") + + def test_something(self): + + fatcat.sql.populate_db() -- cgit v1.2.3