diff options
-rwxr-xr-x | python/client.py | 2 | ||||
-rwxr-xr-x | python/codegen_python_client.sh | 2 | ||||
-rw-r--r-- | python/fatcat/__init__.py | 6 | ||||
-rw-r--r-- | python/fatcat/api.py | 280 | ||||
-rw-r--r-- | python/fatcat/dummy.py | 135 | ||||
-rw-r--r-- | python/fatcat/models.py | 429 | ||||
-rw-r--r-- | python/fatcat/routes.py | 90 | ||||
-rw-r--r-- | python/fatcat/sql.py | 150 | ||||
-rwxr-xr-x | python/run.py | 5 | ||||
-rw-r--r-- | python/tests/api.py | 308 | ||||
-rw-r--r-- | python/tests/codegen_tests/__init__.py (renamed from python/tests/fatcat_client/__init__.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_changelogentries.py (renamed from python/tests/fatcat_client/test_changelogentries.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_changelogentries_inner.py (renamed from python/tests/fatcat_client/test_changelogentries_inner.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_container_entity.py (renamed from python/tests/fatcat_client/test_container_entity.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_creator_entity.py (renamed from python/tests/fatcat_client/test_creator_entity.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_default_api.py (renamed from python/tests/fatcat_client/test_default_api.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_editgroup.py (renamed from python/tests/fatcat_client/test_editgroup.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_editgroup_edits.py (renamed from python/tests/fatcat_client/test_editgroup_edits.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_editor.py (renamed from python/tests/fatcat_client/test_editor.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_entity_edit.py (renamed from python/tests/fatcat_client/test_entity_edit.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_error_response.py (renamed from python/tests/fatcat_client/test_error_response.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_file_entity.py (renamed from python/tests/fatcat_client/test_file_entity.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_release_contrib.py (renamed from python/tests/fatcat_client/test_release_contrib.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_release_entity.py (renamed from python/tests/fatcat_client/test_release_entity.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_release_ref.py (renamed from python/tests/fatcat_client/test_release_ref.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_success.py (renamed from python/tests/fatcat_client/test_success.py) | 0 | ||||
-rw-r--r-- | python/tests/codegen_tests/test_work_entity.py (renamed from python/tests/fatcat_client/test_work_entity.py) | 0 | ||||
-rw-r--r-- | python/tests/entity_lifecycle.py | 80 | ||||
-rw-r--r-- | python/tests/fixtures.py | 108 | ||||
-rw-r--r-- | python/tests/models.py | 87 | ||||
-rw-r--r-- | python/tests/routes.py | 10 | ||||
-rw-r--r-- | python/tests/test_fixtures.py | 29 |
32 files changed, 53 insertions, 1668 deletions
diff --git a/python/client.py b/python/client.py index d1580be5..070d046a 100755 --- a/python/client.py +++ b/python/client.py @@ -18,7 +18,7 @@ def main(): action='store_true', help="enable debugging interface") parser.add_argument('--host-url', - default="http://localhost:8040", + default="http://localhost:9411", help="connect to this host/port") subparsers = parser.add_subparsers() diff --git a/python/codegen_python_client.sh b/python/codegen_python_client.sh index 1e56f4dd..7824253d 100755 --- a/python/codegen_python_client.sh +++ b/python/codegen_python_client.sh @@ -17,4 +17,4 @@ docker run \ sudo chown -R `whoami`:`whoami` $OUTPUT cp -r $OUTPUT/fatcat_client fatcat_client -cp -r $OUTPUT/test tests/fatcat_client +cp -r $OUTPUT/test tests/codegen_tests diff --git a/python/fatcat/__init__.py b/python/fatcat/__init__.py index a824d220..0240e3e9 100644 --- a/python/fatcat/__init__.py +++ b/python/fatcat/__init__.py @@ -1,15 +1,11 @@ from flask import Flask -from flask_sqlalchemy import SQLAlchemy -from flask_marshmallow import Marshmallow from flask_debugtoolbar import DebugToolbarExtension from config import Config toolbar = DebugToolbarExtension() app = Flask(__name__) app.config.from_object(Config) -db = SQLAlchemy(app) -ma = Marshmallow(app) toolbar = DebugToolbarExtension(app) -from fatcat import routes, models, api, sql, dummy +from fatcat import routes diff --git a/python/fatcat/api.py b/python/fatcat/api.py deleted file mode 100644 index 2c91533b..00000000 --- a/python/fatcat/api.py +++ /dev/null @@ -1,280 +0,0 @@ - -from flask import Flask, render_template, send_from_directory, request, \ - url_for, abort, g, redirect, jsonify, session -from fatcat import app, db -from fatcat.models import * -from fatcat.sql import * - - -### Helpers ################################################################# - -def get_or_create_editgroup(param=None): - if param != None: - editgroup = EditGroup.query.get_or_404(int(param)) - return editgroup - editor = Editor.query.get_or_404(1) - if editor.active_editgroup: - return editor.active_editgroup - - editgroup = EditGroup(editor=editor) - db.session.add(editgroup) - db.session.commit() - editor.active_editgroup = editgroup - db.session.add(editor) - db.session.commit() - return editgroup - -### Views ################################################################### - -@app.route('/v0/work/<int:ident>', methods=['GET']) -def api_work_get(ident): - entity = WorkIdent.query.get_or_404(ident) - return work_schema.jsonify(entity) - -@app.route('/v0/work', methods=['POST']) -def api_work_create(params=None): - # TODO: Special-case to pull out primary and create that? - if params == None: - params = request.get_json() - editgroup = get_or_create_editgroup(params.get('editgroup')) - rev = WorkRev( - title=params.get('title', None), - work_type=params.get('work_type', None), - ) - ident = WorkIdent(is_live=False, rev=rev) - edit = WorkEdit(editgroup=editgroup, ident=ident, rev=rev) - if params.get('extra', None): - rev.extra_json = json.dumps(params['extra'], indent=False).encode('utf-8') - db.session.add_all([edit, ident, rev]) - db.session.commit() - return work_schema.jsonify(ident) - -@app.route('/v0/work/random', methods=['GET']) -def api_work_random(): - entity = WorkIdent.query.order_by(db.func.random()).first() - return redirect('/v0/work/{}'.format(entity.id)) - - -@app.route('/v0/release/<int:ident>', methods=['GET']) -def api_release_get(ident): - entity = ReleaseIdent.query.get_or_404(ident) - return release_schema.jsonify(entity) - -@app.route('/v0/release', methods=['POST']) -def api_release_create(params=None): - if params == None: - params = request.get_json() - editgroup = get_or_create_editgroup(params.get('editgroup')) - creators = params.get('creators', []) - creators = [CreatorIdent.query.get_or_404(c) for c in creators] - targets = [ref['target'] for ref in params.get('refs', []) if ref.get('target') != None] - targets = [ReleaseIdent.query.get_or_404(t) for t in targets] - work = params.get('work') - if work: - work = WorkIdent.query.get_or_404(work) - container = params.get('container') - if container: - container = ContainerIdent.query.get_or_404(container) - rev = ReleaseRev( - title=params.get('title', None), - release_type=params.get('release_type', None), - work=work, - container=container, - doi=params.get('doi', None), - ) - contribs = [ReleaseContrib(release=rev, creator=c) for c in creators] - rev.creators = contribs - db.session.add_all(contribs) - refs = [ReleaseRef(release=rev, target=t) for t in targets] - rev.refs = refs - db.session.add_all(refs) - ident = ReleaseIdent(is_live=False, rev=rev) - edit = ReleaseEdit(editgroup=editgroup, ident=ident, rev=rev) - if params.get('extra', None): - rev.extra_json = json.dumps(params['extra'], indent=False).encode('utf-8') - db.session.add_all([edit, ident, rev]) - db.session.commit() - return release_schema.jsonify(ident) - -@app.route('/v0/release/<int:ident>/changelog', methods=['GET']) -def api_release_changelog(ident): - entries = ChangelogEntry.query\ - .join(ReleaseEdit.editgroup)\ - .filter(ReleaseEdit.ident_id==ident)\ - .all() - return changelogentry_schema.jsonify(entries, many=True) - -@app.route('/v0/release/random', methods=['GET']) -def api_release_random(): - entity = ReleaseIdent.query.order_by(db.func.random()).first() - return redirect('/v0/release/{}'.format(entity.id)) - -@app.route('/v0/release/lookup', methods=['GET']) -def api_release_lookup(): - params = request.get_json() - doi = params['doi'].strip().lower() - # TODO: proper regex - if not (doi.startswith("10.") and len(doi.split('/')) == 2): - abort(400) - entity = ReleaseIdent.query\ - .join(ReleaseIdent.rev)\ - .filter(ReleaseRev.doi==doi)\ - .first_or_404() - return release_schema.jsonify(entity) - - -@app.route('/v0/creator/<int:ident>', methods=['GET']) -def api_creator_get(ident): - entity = CreatorIdent.query.get_or_404(ident) - return creator_schema.jsonify(entity) - -@app.route('/v0/creator', methods=['POST']) -def api_creator_create(params=None): - if params == None: - params = request.get_json() - editgroup = get_or_create_editgroup(params.get('editgroup')) - rev = CreatorRev( - name=params.get('name', None), - orcid=params.get('orcid', None), - ) - ident = CreatorIdent(is_live=False, rev=rev) - edit = CreatorEdit(editgroup=editgroup, ident=ident, rev=rev) - if params.get('extra', None): - rev.extra_json = json.dumps(params['extra'], indent=False).encode('utf-8') - db.session.add_all([edit, ident, rev]) - db.session.commit() - return creator_schema.jsonify(ident) - -@app.route('/v0/creator/lookup', methods=['GET']) -def api_creator_lookup(): - params = request.get_json() - orcid = params['orcid'].strip() - # TODO: proper regex - if not (len(orcid) == len("0000-0002-1825-0097") and len(orcid.split('-')) == 4): - abort(400) - entity = CreatorIdent.query\ - .join(CreatorIdent.rev)\ - .filter(CreatorRev.orcid==orcid)\ - .first_or_404() - return creator_schema.jsonify(entity) - - -@app.route('/v0/container/<int:ident>', methods=['GET']) -def api_container_get(ident): - entity = ContainerIdent.query.get_or_404(ident) - return container_schema.jsonify(entity) - -@app.route('/v0/container', methods=['POST']) -def api_container_create(params=None): - if params == None: - params = request.get_json() - editgroup = get_or_create_editgroup(params.get('editgroup')) - rev = ContainerRev( - name=params.get('name', None), - publisher=params.get('publisher', None), - issn=params.get('issn', None), - ) - ident = ContainerIdent(is_live=False, rev=rev) - edit = ContainerEdit(editgroup=editgroup, ident=ident, rev=rev) - if params.get('extra', None): - rev.extra_json = json.dumps(params['extra'], indent=False).encode('utf-8') - db.session.add_all([edit, ident, rev]) - db.session.commit() - return container_schema.jsonify(ident) - -@app.route('/v0/container/lookup', methods=['GET']) -def api_container_lookup(): - params = request.get_json() - issn = params['issn'].strip() - # TODO: proper regex - if not (len(issn) == 9 and issn[0:4].isdigit() and issn[5:7].isdigit()): - abort(400) - entity = ContainerIdent.query\ - .join(ContainerIdent.rev)\ - .filter(ContainerRev.issn==issn)\ - .first_or_404() - return container_schema.jsonify(entity) - - -@app.route('/v0/file/<int:ident>', methods=['GET']) -def api_file_get(ident): - entity = FileIdent.query.get_or_404(ident) - return file_schema.jsonify(entity) - -@app.route('/v0/file', methods=['POST']) -def api_file_create(params=None): - if params == None: - params = request.get_json() - editgroup = get_or_create_editgroup(params.get('editgroup')) - releases = params.get('releases', []) - releases = [ReleaseIdent.query.get_or_404(r) for r in releases] - rev = FileRev( - sha1=params.get('sha1', None), - size=params.get('size', None), - url=params.get('url', None), - ) - file_releases = [FileRelease(file=rev, release=r) for r in releases] - rev.releases = file_releases - db.session.add_all(file_releases) - ident = FileIdent(is_live=False, rev=rev) - edit = FileEdit(editgroup=editgroup, ident=ident, rev=rev) - if params.get('extra', None): - rev.extra_json = json.dumps(params['extra'], indent=False).encode('utf-8') - db.session.add_all([edit, ident, rev]) - db.session.commit() - return file_schema.jsonify(ident) - - -@app.route('/v0/editgroup/<int:ident>', methods=['GET']) -def api_editgroup_get(ident): - entity = EditGroup.query\ - .join(EditGroup.editor)\ - .filter(EditGroup.id==ident)\ - .first_or_404() - rv = editgroup_schema.dump(entity).data - rv['work_edits'] = work_edit_schema.dump( - WorkEdit.query.filter(EditGroup.id==ident).all(), many=True).data - rv['release_edits'] = release_edit_schema.dump( - ReleaseEdit.query.filter(EditGroup.id==ident).all(), many=True).data - rv['creator_edits'] = creator_edit_schema.dump( - CreatorEdit.query.filter(EditGroup.id==ident).all(), many=True).data - rv['container_edits'] = container_edit_schema.dump( - ContainerEdit.query.filter(EditGroup.id==ident).all(), many=True).data - rv['file_edits'] = file_edit_schema.dump( - FileEdit.query.filter(EditGroup.id==ident).all(), many=True).data - return jsonify(rv) - -@app.route('/v0/editgroup', methods=['POST']) -def api_editgroup_create(params=None): - if params == None: - params = request.get_json() - eg = EditGroup( - editor_id=1, - description=params.get('description', None), - ) - if params.get('extra', None): - eg.extra_json = json.dumps(params['extra'], indent=False).encode('utf-8') - db.session.add(eg) - db.session.commit() - return editgroup_schema.jsonify(eg) - -@app.route('/v0/editgroup/<int:ident>/accept', methods=['POST']) -def api_editgroup_accept(ident): - entity = EditGroup.query.get_or_404(ident) - accept_editgroup(entity) - return jsonify({'success': True}) - - -@app.route('/v0/editor/<username>', methods=['GET']) -def api_editor_get(username): - entity = Editor.query.filter(Editor.username==username).first_or_404() - return editor_schema.jsonify(entity) - -@app.route('/v0/editor/<username>/changelog', methods=['GET']) -def api_editor_changelog(username): - entries = ChangelogEntry.query\ - .join(ChangelogEntry.editgroup)\ - .join(EditGroup.editor)\ - .filter(Editor.username==username)\ - .all() - return changelogentry_schema.jsonify(entries, many=True) diff --git a/python/fatcat/dummy.py b/python/fatcat/dummy.py deleted file mode 100644 index f22c4dcb..00000000 --- a/python/fatcat/dummy.py +++ /dev/null @@ -1,135 +0,0 @@ - -import random -import hashlib -from fatcat import db -from fatcat.models import * - -def insert_example_works(): - """ - TODO: doesn't create an edit trail (yet) - """ - - n_elkies = CreatorRev( - name="Noam D. Elkies", - sortname="Elkies, N", - orcid=None) - n_elkies_id = CreatorIdent(rev=n_elkies) - pi_work = WorkRev( - title="Why is π^2 so close to 10?", - work_type="journal-article") - pi_work_id = WorkIdent(rev=pi_work) - pi_release = ReleaseRev( - title=pi_work.title, - work_ident_id=pi_work.id, - release_type="journal-article") - pi_contrib = ReleaseContrib(creator=n_elkies_id) - pi_release.creators.append(pi_contrib) - pi_release_id = ReleaseIdent(rev=pi_release) - pi_work.primary_release = pi_release_id - - # TODO: - #pi_file = File( - # sha1="efee52e46c86691e2b892dbeb212f3b92e92e9d3", - # url="http://www.math.harvard.edu/~elkies/Misc/pi10.pdf") - db.session.add_all([n_elkies, n_elkies_id, pi_work, pi_work_id, pi_release, - pi_release_id]) - - # TODO: - #ligo_collab = CreatorRev(name="LIGO Scientific Collaboration") - #ligo_paper = ReleaseRev( - # title="Full Band All-sky Search for Periodic Gravitational Waves in the O1 LIGO Data") - db.session.commit() - - -def insert_random_works(count=100): - """ - TODO: doesn't create an edit trail (yet) - """ - - first_names = ("Sarah", "Robin", "Halko", "Jefferson", "Max", "桃井", - "Koizumi", "Rex", "Billie", "Tenzin") - last_names = ("Headroom", "はるこ", "Jun'ichirō", "Wong", "Smith") - - author_revs = [] - author_ids = [] - for _ in range(count): - first = random.choice(first_names) - last = random.choice(last_names) - ar = CreatorRev( - name="{} {}".format(first, last), - sortname="{}, {}".format(last, first[0]), - orcid=None) - author_revs.append(ar) - author_ids.append(CreatorIdent(rev=ar)) - - container_revs = [] - container_ids = [] - for _ in range(5): - cr = ContainerRev( - name="The Fake Journal of Stuff", - #container_id=None, - publisher="Big Paper", - sortname="Fake Journal of Stuff", - issn="1234-5678") - container_revs.append(cr) - container_ids.append(ContainerIdent(rev=cr)) - - title_start = ("All about ", "When I grow up I want to be", - "The final word on", "Infinity: ", "The end of") - title_ends = ("Humankind", "Bees", "Democracy", "Avocados", "«küßî»", "“ЌύБЇ”") - work_revs = [] - work_ids = [] - release_revs = [] - release_ids = [] - file_revs = [] - file_ids = [] - for _ in range(count): - title = "{} {}".format(random.choice(title_start), random.choice(title_ends)) - work = WorkRev(title=title) - work_id = WorkIdent(rev=work) - authors = set(random.sample(author_ids, 5)) - release = ReleaseRev( - title=work.title, - creators=[ReleaseContrib(creator=a) for a in list(authors)], - #work=work, - container=random.choice(container_ids)) - release_id = ReleaseIdent(rev=release) - work.primary_release = release_id - authors.add(random.choice(author_ids)) - release2 = ReleaseRev( - title=work.title + " (again)", - creators=[ReleaseContrib(creator=a) for a in list(authors)], - #work=work, - container=random.choice(container_ids)) - release_id2 = ReleaseIdent(rev=release2) - work_revs.append(work) - work_ids.append(work_id) - release_revs.append(release) - release_revs.append(release2) - release_ids.append(release_id) - release_ids.append(release_id2) - - file_content = str(random.random()) * random.randint(3,100) - file_sha = hashlib.sha1(file_content.encode('utf-8')).hexdigest() - file_rev = FileRev( - sha1=file_sha, - size=len(file_content), - url="http://archive.invalid/{}".format(file_sha), - releases=[FileRelease(release=release_id), FileRelease(release=release_id2)], - ) - file_id = FileIdent(rev=file_rev) - file_revs.append(file_rev) - file_ids.append(file_id) - - db.session.add_all(author_revs) - db.session.add_all(author_ids) - db.session.add_all(work_revs) - db.session.add_all(work_ids) - db.session.add_all(release_revs) - db.session.add_all(release_ids) - db.session.add_all(container_revs) - db.session.add_all(container_ids) - db.session.add_all(file_revs) - db.session.add_all(file_ids) - - db.session.commit() diff --git a/python/fatcat/models.py b/python/fatcat/models.py deleted file mode 100644 index c35e541f..00000000 --- a/python/fatcat/models.py +++ /dev/null @@ -1,429 +0,0 @@ - -""" -states for identifiers: -- pre-live: points to a rev (during edit/accept period) -- live: points to a rev -- redirect: live, points to upstream rev, also points to redirect id - => if live and redirect non-null, all other fields copied from redirect target -- deleted: live, but doesn't point to a rev - -possible refactors: -- '_rev' instead of '_rev' -- use mixins for entities -""" - -import json -import hashlib -from marshmallow import post_dump, pre_load -from fatcat import db, ma - - -### Inter-Entity Relationships ############################################### - -class ReleaseContrib(db.Model): - __tablename__ = "release_contrib" - release_rev = db.Column(db.ForeignKey('release_rev.id'), nullable=False, primary_key=True) - creator_ident_id = db.Column(db.ForeignKey('creator_ident.id'), nullable=False, primary_key=True) - stub = db.Column(db.String, nullable=True) - type = db.Column(db.String, nullable=True) - # TODO: index (int)? - - creator = db.relationship("CreatorIdent") - release = db.relationship("ReleaseRev") - -class ReleaseRef(db.Model): - __tablename__ = "release_ref" - id = db.Column(db.Integer, primary_key=True, nullable=False) - release_rev = db.Column(db.ForeignKey('release_rev.id'), nullable=False) - target_release_ident_id = db.Column(db.ForeignKey('release_ident.id'), nullable=True) - index = db.Column(db.Integer, nullable=True) - stub = db.Column(db.String, nullable=True) - doi = db.Column(db.String, nullable=True) - - release = db.relationship("ReleaseRev") - target = db.relationship("ReleaseIdent") - -class FileRelease(db.Model): - __tablename__ = "file_release" - id = db.Column(db.Integer, primary_key=True, nullable=False) - file_rev= db.Column(db.ForeignKey('file_rev.id'), nullable=False) - release_ident_id = db.Column(db.ForeignKey('release_ident.id'), nullable=False) - - release = db.relationship("ReleaseIdent") - file = db.relationship("FileRev") - - -### Entities ################################################################# - -class WorkRev(db.Model): - __tablename__ = 'work_rev' - id = db.Column(db.Integer, primary_key=True) - extra_json = db.Column(db.String, nullable=True) - - title = db.Column(db.String) - work_type = db.Column(db.String) - primary_release_id = db.Column(db.ForeignKey('release_ident.id'), nullable=True) - primary_release = db.relationship('ReleaseIdent') - -class WorkIdent(db.Model): - """ - If rev_id is null, this was deleted. - If redirect_id is not null, this has been merged with the given id. In this - case rev_id is a "cached" copy of the redirect's rev_id, as - an optimization. If the merged work is "deleted", rev_id can be - null and redirect_id not-null. - """ - __tablename__ = 'work_ident' - id = db.Column(db.Integer, primary_key=True, nullable=False) - is_live = db.Column(db.Boolean, nullable=False, default=False) - rev_id = db.Column(db.ForeignKey('work_rev.id'), nullable=True) - redirect_id = db.Column(db.ForeignKey('work_ident.id'), nullable=True) - rev = db.relationship("WorkRev") - -class WorkEdit(db.Model): - __tablename__ = 'work_edit' - id = db.Column(db.Integer, primary_key=True) - ident_id = db.Column(db.ForeignKey('work_ident.id'), nullable=True) - rev_id = db.Column(db.ForeignKey('work_rev.id'), nullable=True) - redirect_id = db.Column(db.ForeignKey('work_ident.id'), nullable=True) - editgroup_id = db.Column(db.ForeignKey('editgroup.id'), nullable=True) - extra_json = db.Column(db.String, nullable=True) - ident = db.relationship("WorkIdent", foreign_keys="WorkEdit.ident_id") - rev = db.relationship("WorkRev") - editgroup = db.relationship("EditGroup") - - -class ReleaseRev(db.Model): - __tablename__ = 'release_rev' - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - extra_json = db.Column(db.String, nullable=True) - - work_ident_id = db.Column(db.ForeignKey('work_ident.id', use_alter=True), nullable=True) # XXX: nullable=False - container_ident_id = db.Column(db.ForeignKey('container_ident.id'), nullable=True) - title = db.Column(db.String, nullable=False) - license = db.Column(db.String, nullable=True) # TODO: oa status foreign key - release_type = db.Column(db.String) # TODO: foreign key - date = db.Column(db.String, nullable=True) # TODO: datetime - doi = db.Column(db.String, nullable=True) # TODO: identifier table - volume = db.Column(db.String, nullable=True) - pages = db.Column(db.String, nullable=True) - issue = db.Column(db.String, nullable=True) - - work = db.relationship("WorkIdent", lazy='subquery', foreign_keys="ReleaseRev.work_ident_id") - container = db.relationship("ContainerIdent", lazy='subquery') - creators = db.relationship('ReleaseContrib', lazy='subquery') - refs = db.relationship('ReleaseRef', lazy='subquery') - -class ReleaseIdent(db.Model): - __tablename__ = 'release_ident' - id = db.Column(db.Integer, primary_key=True) - is_live = db.Column(db.Boolean, nullable=False, default=False) - rev_id = db.Column(db.ForeignKey('release_rev.id')) - redirect_id = db.Column(db.ForeignKey('release_ident.id'), nullable=True) - rev = db.relationship("ReleaseRev") - -class ReleaseEdit(db.Model): - __tablename__ = 'release_edit' - id = db.Column(db.Integer, primary_key=True) - ident_id = db.Column(db.ForeignKey('release_ident.id'), nullable=True) - rev_id = db.Column(db.ForeignKey('release_rev.id'), nullable=True) - redirect_id = db.Column(db.ForeignKey('release_ident.id'), nullable=True) - editgroup_id = db.Column(db.ForeignKey('editgroup.id'), nullable=True) - extra_json = db.Column(db.String, nullable=True) - ident = db.relationship("ReleaseIdent", foreign_keys="ReleaseEdit.ident_id") - rev = db.relationship("ReleaseRev") - editgroup = db.relationship("EditGroup") - - -class CreatorRev(db.Model): - __tablename__ = 'creator_rev' - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - extra_json = db.Column(db.String, nullable=True) - - name = db.Column(db.String) - sortname = db.Column(db.String) - orcid = db.Column(db.String) # TODO: identifier table - -class CreatorIdent(db.Model): - __tablename__ = 'creator_ident' - id = db.Column(db.Integer, primary_key=True) - is_live = db.Column(db.Boolean, nullable=False, default=False) - rev_id = db.Column(db.ForeignKey('creator_rev.id')) - redirect_id = db.Column(db.ForeignKey('creator_ident.id'), nullable=True) - rev = db.relationship("CreatorRev") - -class CreatorEdit(db.Model): - __tablename__ = 'creator_edit' - id = db.Column(db.Integer, primary_key=True) - ident_id = db.Column(db.ForeignKey('creator_ident.id'), nullable=True) - rev_id = db.Column(db.ForeignKey('creator_rev.id'), nullable=True) - redirect_id = db.Column(db.ForeignKey('creator_ident.id'), nullable=True) - editgroup_id = db.Column(db.ForeignKey('editgroup.id'), nullable=True) - extra_json = db.Column(db.String, nullable=True) - ident = db.relationship("CreatorIdent", foreign_keys="CreatorEdit.ident_id") - rev = db.relationship("CreatorRev") - editgroup = db.relationship("EditGroup") - - -class ContainerRev(db.Model): - __tablename__ = 'container_rev' - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - extra_json = db.Column(db.String, nullable=True) - - name = db.Column(db.String) - parent_id = db.Column(db.ForeignKey('container_ident.id', use_alter=True)) - publisher = db.Column(db.String) # TODO: foreign key - sortname = db.Column(db.String) - issn = db.Column(db.String) # TODO: identifier table - parent = db.relationship("ContainerIdent", foreign_keys="ContainerRev.parent_id") - -class ContainerIdent(db.Model): - __tablename__ = 'container_ident' - id = db.Column(db.Integer, primary_key=True) - is_live = db.Column(db.Boolean, nullable=False, default=False) - rev_id = db.Column(db.ForeignKey('container_rev.id')) - redirect_id = db.Column(db.ForeignKey('container_ident.id'), nullable=True) - rev = db.relationship("ContainerRev", foreign_keys="ContainerIdent.rev_id") - -class ContainerEdit(db.Model): - __tablename__ = 'container_edit' - id = db.Column(db.Integer, primary_key=True) - ident_id = db.Column(db.ForeignKey('container_ident.id'), nullable=True) - rev_id = db.Column(db.ForeignKey('container_rev.id'), nullable=True) - redirect_id = db.Column(db.ForeignKey('container_ident.id'), nullable=True) - editgroup_id = db.Column(db.ForeignKey('editgroup.id'), nullable=True) - extra_json = db.Column(db.String, nullable=True) - ident = db.relationship("ContainerIdent", foreign_keys="ContainerEdit.ident_id") - rev = db.relationship("ContainerRev") - editgroup = db.relationship("EditGroup") - - -class FileRev(db.Model): - __tablename__ = 'file_rev' - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - extra_json = db.Column(db.String, nullable=True) - - size = db.Column(db.Integer) - sha1 = db.Column(db.String) # TODO: hash table... only or in addition? - url = db.Column(db.Integer) # TODO: URL table - releases = db.relationship('FileRelease', lazy='subquery') - -class FileIdent(db.Model): - __tablename__ = 'file_ident' - id = db.Column(db.Integer, primary_key=True) - is_live = db.Column(db.Boolean, nullable=False, default=False) - rev_id = db.Column(db.ForeignKey('file_rev.id')) - redirect_id = db.Column(db.ForeignKey('file_ident.id'), nullable=True) - rev = db.relationship("FileRev") - -class FileEdit(db.Model): - __tablename__ = 'file_edit' - id = db.Column(db.Integer, primary_key=True) - ident_id = db.Column(db.ForeignKey('file_ident.id'), nullable=True) - rev_id = db.Column(db.ForeignKey('file_rev.id'), nullable=True) - redirect_id = db.Column(db.ForeignKey('file_ident.id'), nullable=True) - editgroup_id = db.Column(db.ForeignKey('editgroup.id'), nullable=True) - extra_json = db.Column(db.String, nullable=True) - ident = db.relationship("FileIdent", foreign_keys="FileEdit.ident_id") - rev = db.relationship("FileRev") - editgroup = db.relationship("EditGroup") - - -### Editing ################################################################# - -class EditGroup(db.Model): - __tablename__ = 'editgroup' - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - editor_id = db.Column(db.ForeignKey('editor.id'), nullable=False) - description = db.Column(db.String) - extra_json = db.Column(db.String, nullable=True) - - editor = db.relationship("Editor", foreign_keys="EditGroup.editor_id") - -class Editor(db.Model): - __tablename__ = 'editor' - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - username = db.Column(db.String, nullable=False, unique=True) - is_admin = db.Column(db.Boolean, nullable=False, default=False) - active_editgroup_id = db.Column(db.ForeignKey('editgroup.id', use_alter=True)) - active_editgroup = db.relationship('EditGroup', foreign_keys='Editor.active_editgroup_id') - -class ChangelogEntry(db.Model): - __tablename__= 'changelog' - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - editgroup_id = db.Column(db.ForeignKey('editgroup.id')) - timestamp = db.Column(db.Integer) - editgroup = db.relationship("EditGroup") - - -### Marshmallow Wrappers #################################################### - -class ExtraJsonSchema(ma.ModelSchema): - - @post_dump(pass_many=False) - def json_unflatten(self, data): - extra = data.pop('extra_json', None) - if extra != None: - extra = json.loads(extra) - data['extra'] = extra - - @pre_load(pass_many=False) - def json_flatten(self, data): - extra = data.pop('extra', None) - if extra != None: - extra = json.dumps(extra) - data['extra_json'] = extra - -class EntitySchema(ExtraJsonSchema): - - @post_dump(pass_many=False) - def merge_rev(self, data): - if data.get('rev', None) != None: - rev_id = data['rev'].pop('id') - data.update(data['rev']) - data['rev'] = rev_id - else: - data['rev'] = None - -class ReleaseContribSchema(ma.ModelSchema): - class Meta: - model = ReleaseContrib - creator = db.relationship("CreatorIdent") - release = db.relationship("ReleaseRev") - -class ReleaseRefSchema(ma.ModelSchema): - class Meta: - model = ReleaseRef - release = db.relationship("ReleaseRev") - target = db.relationship("ReleaseIdent") - -class FileReleaseSchema(ma.ModelSchema): - class Meta: - model = FileRelease - release = db.relationship("ReleaseIdent") - file = db.relationship("FileRev") - -class WorkRevSchema(ma.ModelSchema): - class Meta: - model = WorkRev - include_fk = True - -class WorkSchema(EntitySchema): - class Meta: - model = WorkIdent - include_fk = True - rev = ma.Nested(WorkRevSchema) - -class WorkEditSchema(ma.ModelSchema): - class Meta: - model = WorkEdit - -work_rev_schema = WorkRevSchema() -work_schema = WorkSchema() -work_edit_schema = WorkEditSchema() - - -class ReleaseRevSchema(ma.ModelSchema): - class Meta: - model = ReleaseRev - include_fk = True - work = ma.Nested('WorkSchema') - container = ma.Nested('ContainerSchema') - creators = ma.Nested(ReleaseContribSchema, many=True) - refs = ma.Nested(ReleaseRefSchema, many=True) - -class ReleaseSchema(EntitySchema): - class Meta: - model = ReleaseIdent - include_fk = True - rev = ma.Nested(ReleaseRevSchema) - # XXX: files = ma.Nested('FileSchema', many=True) - -class ReleaseEditSchema(ma.ModelSchema): - class Meta: - model = ReleaseEdit - -release_rev_schema = ReleaseRevSchema() -release_schema = ReleaseSchema() -release_edit_schema = ReleaseEditSchema() - - -class CreatorRevSchema(ma.ModelSchema): - class Meta: - model = CreatorRev - include_fk = True - -class CreatorSchema(EntitySchema): - class Meta: - model = CreatorIdent - include_fk = True - rev = ma.Nested(CreatorRevSchema) - -class CreatorEditSchema(ma.ModelSchema): - class Meta: - model = CreatorEdit - -creator_rev_schema = CreatorRevSchema() -creator_schema = CreatorSchema() -creator_edit_schema = CreatorEditSchema() - - -class ContainerRevSchema(ma.ModelSchema): - class Meta: - model = ContainerRev - include_fk = True - -class ContainerSchema(EntitySchema): - class Meta: - model = ContainerIdent - include_fk = True - rev = ma.Nested(ContainerRevSchema) - -class ContainerEditSchema(ma.ModelSchema): - class Meta: - model = ContainerEdit - -container_rev_schema = ContainerRevSchema() -container_schema = ContainerSchema() -container_edit_schema = ContainerEditSchema() - - -class FileRevSchema(ma.ModelSchema): - class Meta: - model = FileRev - include_fk = True - - releases = ma.Nested(FileReleaseSchema, many=True) - -class FileSchema(EntitySchema): - class Meta: - model = FileIdent - include_fk = True - rev = ma.Nested(FileRevSchema) - -class FileEditSchema(ma.ModelSchema): - class Meta: - model = FileEdit - -file_rev_schema = FileRevSchema() -file_schema = FileSchema() -file_edit_schema = FileEditSchema() - - -class EditorSchema(ma.ModelSchema): - class Meta: - model = Editor - -class EditGroupSchema(ma.ModelSchema): - class Meta: - model = EditGroup - editor = ma.Nested(EditorSchema) - -editor_schema = EditorSchema() -editgroup_schema = EditGroupSchema() - -class ChangelogEntrySchema(ma.ModelSchema): - class Meta: - model = ChangelogEntry - -changelogentry_schema = ChangelogEntrySchema() diff --git a/python/fatcat/routes.py b/python/fatcat/routes.py index 0c86bd78..7db0ff6d 100644 --- a/python/fatcat/routes.py +++ b/python/fatcat/routes.py @@ -3,48 +3,11 @@ import os import json from flask import Flask, render_template, send_from_directory, request, \ url_for, abort, g, redirect, jsonify, session -from fatcat import app, db, api +from fatcat import app ### 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(): - rv = api.api_work_random() - ident = rv.location.split('/')[-1] - return redirect("/work/{}".format(ident)) - -@app.route('/work/<int:ident>', methods=['GET']) -def work_view(ident): - rv = api.api_work_get(ident) - entity = json.loads(rv.data.decode('utf-8')) - return render_template('work_view.html', work=entity) - -@app.route('/release/<int:ident>', methods=['GET']) -def release_view(ident): - rv = api.api_release_get(ident) - entity = json.loads(rv.data.decode('utf-8')) - return render_template('release_view.html', release=entity) - -@app.route('/release/<int:ident>/changelog', methods=['GET']) -def release_changelog(ident): - rv = api.api_release_get(ident) - release = json.loads(rv.data.decode('utf-8')) - rv = api.api_release_changelog(ident) - changelog_entries = json.loads(rv.data.decode('utf-8')) - return render_template('release_changelog.html', release=release, - changelog_entries=changelog_entries) - -@app.route('/release/random', methods=['GET']) -def release_random(): - rv = api.api_release_random() - ident = rv.location.split('/')[-1] - return redirect("/release/{}".format(ident)) - @app.route('/container/create', methods=['GET']) def container_create_view(): return render_template('container_add.html') @@ -59,23 +22,51 @@ def container_create(): container = json.loads(rv.data.decode('utf-8')) return redirect("/container/{}".format(container['id'])) -@app.route('/creator/<int:ident>', methods=['GET']) -def creator_view(ident): - rv = api.api_creator_get(ident) - entity = json.loads(rv.data.decode('utf-8')) - return render_template('creator_view.html', creator=entity) - @app.route('/container/<int:ident>', methods=['GET']) def container_view(ident): rv = api.api_container_get(ident) entity = json.loads(rv.data.decode('utf-8')) return render_template('container_view.html', container=entity) +@app.route('/creator/random', methods=['GET']) +def creator_random(): + """Not actually random, just a dummy example""" + return redirect("/creator/f1f046a3-45c9-4b99-adce-000000000002") + +@app.route('/creator/<int:ident>', methods=['GET']) +def creator_view(ident): + rv = api.api_creator_get(ident) + entity = json.loads(rv.data.decode('utf-8')) + return render_template('creator_view.html', creator=entity) + @app.route('/file/<int:ident>', methods=['GET']) def file_view(ident): rv = api.api_file_get(ident) entity = json.loads(rv.data.decode('utf-8')) return render_template('file_view.html', file=entity) +@app.route('/work/create', methods=['GET']) +def work_create(): + return render_template('work_add.html') + +@app.route('/release/<int:ident>', methods=['GET']) +def release_view(ident): + rv = api.api_release_get(ident) + entity = json.loads(rv.data.decode('utf-8')) + return render_template('release_view.html', release=entity) + +@app.route('/release/<int:ident>/changelog', methods=['GET']) +def release_changelog(ident): + rv = api.api_release_get(ident) + release = json.loads(rv.data.decode('utf-8')) + rv = api.api_release_changelog(ident) + changelog_entries = json.loads(rv.data.decode('utf-8')) + return render_template('release_changelog.html', release=release, + changelog_entries=changelog_entries) + +@app.route('/release/random', methods=['GET']) +def release_random(): + """Not actually random, just a dummy example""" + return redirect("/release/f1f046a3-45c9-4b99-3333-000000000002") @app.route('/editgroup/<int:ident>', methods=['GET']) def editgroup_view(ident): @@ -83,6 +74,17 @@ def editgroup_view(ident): entity = json.loads(rv.data.decode('utf-8')) return render_template('editgroup_view.html', editgroup=entity) +@app.route('/work/random', methods=['GET']) +def work_random(): + """Not actually random, just a dummy example""" + return redirect("/work/f1f046a3-45c9-4b99-3333-000000000002") + +@app.route('/work/<int:ident>', methods=['GET']) +def work_view(ident): + rv = api.api_work_get(ident) + entity = json.loads(rv.data.decode('utf-8')) + return render_template('work_view.html', work=entity) + @app.route('/editgroup/current', methods=['GET']) def editgroup_current(): eg = api.get_or_create_editgroup() diff --git a/python/fatcat/sql.py b/python/fatcat/sql.py deleted file mode 100644 index 9b1922ba..00000000 --- a/python/fatcat/sql.py +++ /dev/null @@ -1,150 +0,0 @@ - -import json -import time -import random -import hashlib -from sqlalchemy.orm.session import make_transient -from fatcat import db -import fatcat.api -from fatcat.models import * - -def populate_db(): - admin_editor = Editor(id=1, username="admin", is_admin=True) - db.session.add(admin_editor) - db.session.commit() - -def add_crossref_via_model(meta): - - title = meta['title'][0] - - # authors - author_revs = [] - author_ids = [] - for am in meta['author']: - ar = CreatorRev( - name="{} {}".format(am['given'], am['family']), - sortname="{}, {}".format(am['family'], am['given']), - orcid=None) - author_revs.append(ar) - author_ids.append(CreatorIdent(rev=ar)) - - # container - container = ContainerRev( - issn=meta['ISSN'][0], - name=meta['container-title'][0], - #container_id=None, - publisher=meta['publisher'], - sortname=meta['short-container-title'][0]) - container_id = ContainerIdent(rev=container) - - # work and release - work = WorkRev(title=title) - work_id = WorkIdent(rev=work) - release = ReleaseRev( - title=title, - creators=[ReleaseContrib(creator=a) for a in author_ids], - # XXX: work=work, - container=container_id, - release_type=meta['type'], - doi=meta['DOI'], - date=meta['created']['date-time'], - license=meta.get('license', [dict(URL=None)])[0]['URL'] or None, - issue=meta.get('issue', None), - volume=meta.get('volume', None), - pages=meta.get('page', None)) - release_id = ReleaseIdent(rev=release) - work.primary_release = release_id - release.extra_json = json.dumps({ - 'crossref': { - 'links': meta.get('link', []), - 'subject': meta['subject'], - 'type': meta['type'], - 'alternative-id': meta.get('alternative-id', []), - } - }, indent=None).encode('utf-8') - - # references - for i, rm in enumerate(meta.get('reference', [])): - ref = ReleaseRef( - release_rev=release, - doi=rm.get("DOI", None), - index=i+1, - # TODO: how to generate a proper stub here from k/v metadata? - stub="| ".join(rm.values())) - release.refs.append(ref) - - db.session.add_all([work, work_id, release, release_id, container, - container_id]) - db.session.add_all(author_revs) - db.session.add_all(author_ids) - db.session.commit() - -def accept_editgroup(eg): - - # check if already accepted - # XXX: add a test for this - assert ChangelogEntry.query.filter(ChangelogEntry.editgroup_id==eg.id).count() == 0 - - # start transaction (TODO: explicitly?) - - # for each entity type: - for cls in (WorkEdit, ReleaseEdit, CreatorEdit, ContainerEdit, FileEdit): - edits = cls.query.filter(cls.editgroup_id==eg.id).all() - # for each entity edit->ident: - for edit in edits: - # update entity ident state (activate, redirect, delete) - edit.ident.is_live = True - edit.ident.rev_id = edit.rev_id - edit.ident.redirect_id = edit.redirect_id - db.session.add(edit.ident) - - # append log/changelog row - cle = ChangelogEntry( - editgroup_id=eg.id, - # TODO: is this UTC? - timestamp=int(time.time())) - db.session.add(cle) - - # update edit group state - db.session.add(eg) - - # no longer "active" - eg.editor.active_editgroup = None - db.session.add(eg.editor) - - db.session.commit() - -def merge_works(left_id, right_id, editgroup=None): - """Helper to merge two works together.""" - left = WorkIdent.query.get_or_404(left_id) - right = WorkIdent.query.get_or_404(right_id) - assert left.is_live and right.is_live - assert left.rev and right.rev - assert (left.redirect_id is None) and (right.redirect_id is None) - - if editgroup is None: - editgroup = fatcat.api.get_or_create_editgroup() - - releases = ReleaseIdent.query\ - .join(ReleaseIdent.rev)\ - .filter(ReleaseRev.work_ident_id==right_id)\ - .filter(ReleaseIdent.is_live==True)\ - .all() - - # update all right releases to point to left - for release_ident in releases: - rev = release_ident.rev - old_id = rev.id - db.session.expunge(rev) - make_transient(rev) - rev.id = None - rev.parent = old_id - rev.work_ident_id = left.id - re = ReleaseEdit(editgroup=editgroup, ident=release_ident, rev=rev) - db.session.add_all([rev, re]) - - # redirect right id to left (via editgroup) - neww = WorkEdit(editgroup=editgroup, ident=right, - rev=left.rev, redirect_id=left.id) - - db.session.add_all([neww]) diff --git a/python/run.py b/python/run.py index 0fbd6194..cfddad48 100755 --- a/python/run.py +++ b/python/run.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 import argparse -import fatcat.sql -from fatcat import app, db +from fatcat import app def main(): parser = argparse.ArgumentParser() @@ -14,7 +13,7 @@ def main(): help="listen on this host/IP") parser.add_argument('--port', type=int, - default=8040, + default=9810, help="listen on this port") parser.add_argument('--database-uri', default=app.config['SQLALCHEMY_DATABASE_URI'], diff --git a/python/tests/api.py b/python/tests/api.py deleted file mode 100644 index 02875f64..00000000 --- a/python/tests/api.py +++ /dev/null @@ -1,308 +0,0 @@ - -import json -import unittest -import tempfile -import pytest -import fatcat -import fatcat.sql -from fatcat.models import * -from fixtures import * - - -def test_health(app): - rv = app.get('/health') - obj = json.loads(rv.data.decode('utf-8')) - assert obj['ok'] - -def test_api_work(app): - fatcat.dummy.insert_example_works() - - # Invalid Id - rv = app.get('/v0/work/_') - assert rv.status_code == 404 - - # Random - rv = app.get('/v0/work/random') - rv = app.get(rv.location) - work = json.loads(rv.data.decode('utf-8')) - check_entity_fields(work) - print(work) - assert work['title'] - assert work['work_type'] - - # Valid Id (from random above) - rv = app.get('/v0/work/{}'.format(work['id'])) - assert rv.status_code == 200 - - # Missing Id - rv = app.get('/v0/work/r3zga5b9cd7ef8gh084714iljk') - assert rv.status_code == 404 - -def test_api_work_create(app): - assert WorkIdent.query.count() == 0 - assert WorkRev.query.count() == 0 - assert WorkEdit.query.count() == 0 - rv = app.post('/v0/work', - data=json.dumps(dict(title="dummy", work_type="thing", extra=dict(a=1, b="zing"))), - headers={"content-type": "application/json"}) - print(rv) - assert rv.status_code == 200 - assert WorkIdent.query.count() == 1 - assert WorkRev.query.count() == 1 - assert WorkEdit.query.count() == 1 - # not alive yet - assert WorkIdent.query.filter(WorkIdent.is_live==True).count() == 0 - -def test_api_rich_create(app): - - # TODO: create user? - - rv = app.post('/v0/editgroup', - data=json.dumps(dict( - extra=dict(q=1, u="zing"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - editgroup_id = obj['id'] - - for cls in (WorkIdent, WorkRev, WorkEdit, - ContainerIdent, ContainerRev, ContainerEdit, - CreatorIdent, CreatorRev, CreatorEdit, - ReleaseIdent, ReleaseRev, ReleaseEdit, - FileIdent, FileRev, FileEdit, - ChangelogEntry): - assert cls.query.count() == 0 - - rv = app.post('/v0/container', - data=json.dumps(dict( - name="schmournal", - publisher="society of authors", - issn="2222-3333", - editgroup=editgroup_id, - extra=dict(a=2, i="zing"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - container_id = obj['id'] - - rv = app.post('/v0/creator', - data=json.dumps(dict( - name="anon y. mouse", - orcid="0000-0002-1825-0097", - editgroup=editgroup_id, - extra=dict(w=1, q="zing"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - creator_id = obj['id'] - - rv = app.post('/v0/work', - data=json.dumps(dict( - title="dummy work", - work_type="book", - editgroup=editgroup_id, - extra=dict(a=3, b="zing"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - work_id = obj['id'] - - # this stub work will be referenced - rv = app.post('/v0/release', - data=json.dumps(dict( - title="derivative work", - work_type="journal-article", - work=work_id, - creators=[creator_id], - doi="10.1234/58", - editgroup=editgroup_id, - refs=[ - dict(stub="some other journal article"), - ], - extra=dict(f=7, b="zing"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - stub_release_id = obj['id'] - - rv = app.post('/v0/release', - data=json.dumps(dict( - title="dummy work", - work_type="book", - work=work_id, - container=container_id, - creators=[creator_id], - doi="10.1234/5678", - editgroup=editgroup_id, - refs=[ - dict(stub="some book", target=stub_release_id), - ], - extra=dict(f=7, b="loopy"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - release_id = obj['id'] - - rv = app.post('/v0/file', - data=json.dumps(dict( - sha1="deadbeefdeadbeef", - size=1234, - releases=[release_id], - editgroup=editgroup_id, - extra=dict(f=4, b="zing"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - file_id = obj['id'] - - for cls in (WorkIdent, WorkRev, WorkEdit, - ContainerIdent, ContainerRev, ContainerEdit, - CreatorIdent, CreatorRev, CreatorEdit, - FileIdent, FileRev, FileEdit): - assert cls.query.count() == 1 - for cls in (ReleaseIdent, ReleaseRev, ReleaseEdit): - assert cls.query.count() == 2 - - for cls in (WorkIdent, - ContainerIdent, - CreatorIdent, - ReleaseIdent, - FileIdent): - assert cls.query.filter(cls.is_live==True).count() == 0 - - assert ChangelogEntry.query.count() == 0 - rv = app.post('/v0/editgroup/{}/accept'.format(editgroup_id), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - assert ChangelogEntry.query.count() == 1 - - for cls in (WorkIdent, WorkRev, WorkEdit, - ContainerIdent, ContainerRev, ContainerEdit, - CreatorIdent, CreatorRev, CreatorEdit, - FileIdent, FileRev, FileEdit): - assert cls.query.count() == 1 - for cls in (ReleaseIdent, ReleaseRev, ReleaseEdit): - assert cls.query.count() == 2 - - for cls in (WorkIdent, - ContainerIdent, - CreatorIdent, - FileIdent): - assert cls.query.filter(cls.is_live==True).count() == 1 - assert ReleaseIdent.query.filter(ReleaseIdent.is_live==True).count() == 2 - - # Test that foreign key relations worked - release_rv = json.loads(app.get('/v0/release/{}'.format(release_id)).data.decode('utf-8')) - print(release_rv) - assert release_rv['creators'][0]['creator'] == creator_id - assert release_rv['container']['id'] == container_id - assert release_rv['work']['id'] == work_id - assert release_rv['refs'][0]['target'] == stub_release_id - - file_rv = json.loads(app.get('/v0/file/{}'.format(file_id)).data.decode('utf-8')) - print(file_rv) - assert file_rv['releases'][0]['release'] == release_id - - # test that editor's active edit group is now invalid - editor = Editor.query.first() - assert editor.active_editgroup is None - -def test_api_release_lookup(rich_app): - app = rich_app - - rv = app.get('/v0/release/1', - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - - rv = app.get('/v0/release/lookup', - data=json.dumps(dict(doi="10.1234/5678")), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - assert obj['doi'] == "10.1234/5678" - assert obj.get('id') != None - - rv = app.get('/v0/release/lookup', - data=json.dumps(dict(doi="10.1234/5678_noexit")), - headers={"content-type": "application/json"}) - assert rv.status_code == 404 - - rv = app.get('/v0/release/lookup', - data=json.dumps(dict(doi="not_even_valid_doi")), - headers={"content-type": "application/json"}) - assert rv.status_code == 400 - -def test_api_creator_lookup(rich_app): - app = rich_app - - rv = app.get('/v0/creator/1', - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - - rv = app.get('/v0/creator/lookup', - data=json.dumps(dict(orcid="0000-0002-1825-0097")), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - assert obj['orcid'] == "0000-0002-1825-0097" - assert obj.get('id') != None - - rv = app.get('/v0/creator/lookup', - data=json.dumps(dict(orcid="0000-0002-1825-0098")), - headers={"content-type": "application/json"}) - assert rv.status_code == 404 - - rv = app.get('/v0/creator/lookup', - data=json.dumps(dict(orcid="not_even_valid_orcid")), - headers={"content-type": "application/json"}) - assert rv.status_code == 400 - - -def test_api_container_lookup(rich_app): - app = rich_app - - rv = app.get('/v0/container/1', - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - - rv = app.get('/v0/container/lookup', - data=json.dumps(dict(issn="2222-3333")), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - assert obj['issn'] == "2222-3333" - assert obj.get('id') != None - - rv = app.get('/v0/container/lookup', - data=json.dumps(dict(issn="2222-3334")), - headers={"content-type": "application/json"}) - assert rv.status_code == 404 - - rv = app.get('/v0/container/lookup', - data=json.dumps(dict(issn="not_even_valid_issn")), - headers={"content-type": "application/json"}) - assert rv.status_code == 400 - -def test_api_editor_get(rich_app): - app = rich_app - - rv = app.get('/v0/editor/admin', - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - print(obj) - assert obj['username'] == "admin" - assert obj['id'] == 1 - -def test_api_editor_changelog(rich_app): - app = rich_app - - rv = app.get('/v0/editor/admin/changelog', - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - print(obj) - assert len(obj) == 1 diff --git a/python/tests/fatcat_client/__init__.py b/python/tests/codegen_tests/__init__.py index e69de29b..e69de29b 100644 --- a/python/tests/fatcat_client/__init__.py +++ b/python/tests/codegen_tests/__init__.py diff --git a/python/tests/fatcat_client/test_changelogentries.py b/python/tests/codegen_tests/test_changelogentries.py index 3b2bc885..3b2bc885 100644 --- a/python/tests/fatcat_client/test_changelogentries.py +++ b/python/tests/codegen_tests/test_changelogentries.py diff --git a/python/tests/fatcat_client/test_changelogentries_inner.py b/python/tests/codegen_tests/test_changelogentries_inner.py index ac836714..ac836714 100644 --- a/python/tests/fatcat_client/test_changelogentries_inner.py +++ b/python/tests/codegen_tests/test_changelogentries_inner.py diff --git a/python/tests/fatcat_client/test_container_entity.py b/python/tests/codegen_tests/test_container_entity.py index 0bafb7dd..0bafb7dd 100644 --- a/python/tests/fatcat_client/test_container_entity.py +++ b/python/tests/codegen_tests/test_container_entity.py diff --git a/python/tests/fatcat_client/test_creator_entity.py b/python/tests/codegen_tests/test_creator_entity.py index b235b640..b235b640 100644 --- a/python/tests/fatcat_client/test_creator_entity.py +++ b/python/tests/codegen_tests/test_creator_entity.py diff --git a/python/tests/fatcat_client/test_default_api.py b/python/tests/codegen_tests/test_default_api.py index cda38f38..cda38f38 100644 --- a/python/tests/fatcat_client/test_default_api.py +++ b/python/tests/codegen_tests/test_default_api.py diff --git a/python/tests/fatcat_client/test_editgroup.py b/python/tests/codegen_tests/test_editgroup.py index e770fd54..e770fd54 100644 --- a/python/tests/fatcat_client/test_editgroup.py +++ b/python/tests/codegen_tests/test_editgroup.py diff --git a/python/tests/fatcat_client/test_editgroup_edits.py b/python/tests/codegen_tests/test_editgroup_edits.py index 38c3c814..38c3c814 100644 --- a/python/tests/fatcat_client/test_editgroup_edits.py +++ b/python/tests/codegen_tests/test_editgroup_edits.py diff --git a/python/tests/fatcat_client/test_editor.py b/python/tests/codegen_tests/test_editor.py index 00ca625e..00ca625e 100644 --- a/python/tests/fatcat_client/test_editor.py +++ b/python/tests/codegen_tests/test_editor.py diff --git a/python/tests/fatcat_client/test_entity_edit.py b/python/tests/codegen_tests/test_entity_edit.py index 2f0f7ac3..2f0f7ac3 100644 --- a/python/tests/fatcat_client/test_entity_edit.py +++ b/python/tests/codegen_tests/test_entity_edit.py diff --git a/python/tests/fatcat_client/test_error_response.py b/python/tests/codegen_tests/test_error_response.py index f0d09ee8..f0d09ee8 100644 --- a/python/tests/fatcat_client/test_error_response.py +++ b/python/tests/codegen_tests/test_error_response.py diff --git a/python/tests/fatcat_client/test_file_entity.py b/python/tests/codegen_tests/test_file_entity.py index f5806f9b..f5806f9b 100644 --- a/python/tests/fatcat_client/test_file_entity.py +++ b/python/tests/codegen_tests/test_file_entity.py diff --git a/python/tests/fatcat_client/test_release_contrib.py b/python/tests/codegen_tests/test_release_contrib.py index 9a61ef89..9a61ef89 100644 --- a/python/tests/fatcat_client/test_release_contrib.py +++ b/python/tests/codegen_tests/test_release_contrib.py diff --git a/python/tests/fatcat_client/test_release_entity.py b/python/tests/codegen_tests/test_release_entity.py index 9a1f0e97..9a1f0e97 100644 --- a/python/tests/fatcat_client/test_release_entity.py +++ b/python/tests/codegen_tests/test_release_entity.py diff --git a/python/tests/fatcat_client/test_release_ref.py b/python/tests/codegen_tests/test_release_ref.py index cafb6700..cafb6700 100644 --- a/python/tests/fatcat_client/test_release_ref.py +++ b/python/tests/codegen_tests/test_release_ref.py diff --git a/python/tests/fatcat_client/test_success.py b/python/tests/codegen_tests/test_success.py index 28d855fb..28d855fb 100644 --- a/python/tests/fatcat_client/test_success.py +++ b/python/tests/codegen_tests/test_success.py diff --git a/python/tests/fatcat_client/test_work_entity.py b/python/tests/codegen_tests/test_work_entity.py index 57b0cdad..57b0cdad 100644 --- a/python/tests/fatcat_client/test_work_entity.py +++ b/python/tests/codegen_tests/test_work_entity.py diff --git a/python/tests/entity_lifecycle.py b/python/tests/entity_lifecycle.py deleted file mode 100644 index 4ac7ee68..00000000 --- a/python/tests/entity_lifecycle.py +++ /dev/null @@ -1,80 +0,0 @@ - -import json -import unittest -import tempfile -import pytest -import fatcat -import fatcat.sql -from fatcat.models import * -from fixtures import * - - -def test_merge_works(app): - - # two works, each with releases - rv = app.post('/v0/work', - data=json.dumps(dict()), - headers={"content-type": "application/json"}) - workA_id = json.loads(rv.data.decode('utf-8'))['id'] - - rv = app.post('/v0/work', - data=json.dumps(dict()), - headers={"content-type": "application/json"}) - workB_id = json.loads(rv.data.decode('utf-8'))['id'] - - rv = app.post('/v0/release', - data=json.dumps(dict( - title="some release", - work_type="journal-article", - work=workA_id, - doi="10.1234/A1")), - headers={"content-type": "application/json"}) - releaseA1 = json.loads(rv.data.decode('utf-8'))['id'] - - rv = app.post('/v0/release', - data=json.dumps(dict( - title="some release", - work_type="journal-article", - work=workB_id, - doi="10.1234/B1")), - headers={"content-type": "application/json"}) - releaseB1 = json.loads(rv.data.decode('utf-8'))['id'] - - rv = app.post('/v0/release', - data=json.dumps(dict( - title="some release", - work_type="journal-article", - work=workB_id, - doi="10.1234/A1")), - headers={"content-type": "application/json"}) - releaseB2 = json.loads(rv.data.decode('utf-8'))['id'] - - # XXX: what if workB primary was set? - - editgroup_id = 1 - rv = app.post('/v0/editgroup/{}/accept'.format(editgroup_id), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - assert ChangelogEntry.query.count() == 1 - assert WorkIdent.query.filter(WorkIdent.is_live==True).count() == 2 - assert ReleaseIdent.query.filter(ReleaseIdent.is_live==True).count() == 3 - - # merge works - fatcat.sql.merge_works(workA_id, workB_id) - editgroup_id = 2 - rv = app.post('/v0/editgroup/{}/accept'.format(editgroup_id), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - - # check results - assert ChangelogEntry.query.count() == 2 - assert WorkIdent.query.filter(WorkIdent.is_live==True).count() == 2 - assert ReleaseIdent.query.filter(ReleaseIdent.is_live==True).count() == 3 - - workA_json = json.loads(app.get('/v0/work/{}'.format(workA_id)).data.decode('utf-8')) - workB_json = json.loads(app.get('/v0/work/{}'.format(workB_id)).data.decode('utf-8')) - assert workA_json['rev'] == workB_json['rev'] - print(workA_json) - print(workB_json) - assert workA_json['redirect_id'] == None - assert workB_json['redirect_id'] == workA_json['id'] diff --git a/python/tests/fixtures.py b/python/tests/fixtures.py index d3d8c24b..9092421f 100644 --- a/python/tests/fixtures.py +++ b/python/tests/fixtures.py @@ -5,30 +5,18 @@ import json import signal import pytest import fatcat -import fatcat.sql -from fatcat.models import * @pytest.fixture def full_app(): - fatcat.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' fatcat.app.testing = True fatcat.app.debug = False - fatcat.db.session.remove() - fatcat.db.drop_all() - fatcat.db.create_all() - fatcat.sql.populate_db() return fatcat.app @pytest.fixture def app(full_app): return full_app.test_client() -@pytest.fixture -def rich_app(app): - enrichen_test_app(app) - return app - @pytest.fixture(scope="function") def api_client(full_app): @@ -46,102 +34,6 @@ def api_client(full_app): ## Helpers ################################################################## -def enrichen_test_app(app): - - rv = app.post('/v0/editgroup', - data=json.dumps(dict( - extra=dict(q=1, u="zing"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - editgroup_id = obj['id'] - - rv = app.post('/v0/container', - data=json.dumps(dict( - name="schmournal", - publisher="society of authors", - issn="2222-3333", - editgroup=editgroup_id, - extra=dict(a=2, i="zing"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - container_id = obj['id'] - - rv = app.post('/v0/creator', - data=json.dumps(dict( - name="anon y. mouse", - orcid="0000-0002-1825-0097", - editgroup=editgroup_id, - extra=dict(w=1, q="zing"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - creator_id = obj['id'] - - rv = app.post('/v0/work', - data=json.dumps(dict( - title="dummy work", - work_type="book", - editgroup=editgroup_id, - extra=dict(a=3, b="zing"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - work_id = obj['id'] - - # this stub work will be referenced - rv = app.post('/v0/release', - data=json.dumps(dict( - title="derivative work", - work_type="journal-article", - work=work_id, - creators=[creator_id], - doi="10.1234/58", - editgroup=editgroup_id, - refs=[ - dict(stub="some other journal article"), - ], - extra=dict(f=7, b="zing"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - stub_release_id = obj['id'] - - rv = app.post('/v0/release', - data=json.dumps(dict( - title="dummy work", - work_type="book", - work=work_id, - container=container_id, - creators=[creator_id], - doi="10.1234/5678", - editgroup=editgroup_id, - refs=[ - dict(stub="some book", target=stub_release_id), - ], - extra=dict(f=7, b="loopy"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - release_id = obj['id'] - - rv = app.post('/v0/file', - data=json.dumps(dict( - sha1="deadbeefdeadbeef", - size=1234, - releases=[release_id], - editgroup=editgroup_id, - extra=dict(f=4, b="zing"))), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - obj = json.loads(rv.data.decode('utf-8')) - file_id = obj['id'] - - rv = app.post('/v0/editgroup/{}/accept'.format(editgroup_id), - headers={"content-type": "application/json"}) - assert rv.status_code == 200 - def check_entity_fields(e): for key in ('rev', 'is_live', 'redirect_id'): assert key in e diff --git a/python/tests/models.py b/python/tests/models.py deleted file mode 100644 index 98bb6bc7..00000000 --- a/python/tests/models.py +++ /dev/null @@ -1,87 +0,0 @@ - -import json -import unittest -import tempfile -import pytest -import fatcat -import fatcat.sql -from fatcat.models import * -from fixtures import * - - -def test_example_works(app): - fatcat.dummy.insert_example_works() - -def test_random_works(app): - fatcat.dummy.insert_random_works() - -def test_load_crossref(app): - with open('./tests/files/crossref-works.2018-01-21.badsample.json', 'r') as f: - raw = [json.loads(l) for l in f.readlines() if len(l) > 3] - for obj in raw: - fatcat.sql.add_crossref_via_model(obj) - -def test_schema_release_rev(app): - assert ReleaseRev.query.count() == 0 - e = { - "title": "Bogus title", - "release_type": "book", - "creators": [], - "refs": [], - } - model = release_rev_schema.load(e) - fatcat.db.session.add(model.data) - fatcat.db.session.commit() - assert ReleaseRev.query.count() == 1 - model_after = ReleaseRev.query.first() - serial = release_rev_schema.dump(model_after).data - #check_release(serial) - for k in e: - assert e[k] == serial[k] - -def test_schema_creator_rev(app): - assert ReleaseRev.query.count() == 0 - e = { - "name": "Robin (Batman)", - } - model = creator_rev_schema.load(e) - fatcat.db.session.add(model.data) - fatcat.db.session.commit() - assert CreatorRev.query.count() == 1 - model_after = CreatorRev.query.first() - serial = creator_rev_schema.dump(model_after).data - check_creator(serial) - for k in e.keys(): - assert e[k] == serial[k] - -def test_schema_container_rev(app): - assert ReleaseRev.query.count() == 0 - e = { - "name": "Papers Monthly", - } - model = container_rev_schema.load(e) - fatcat.db.session.add(model.data) - fatcat.db.session.commit() - assert ContainerRev.query.count() == 1 - model_after = ContainerRev.query.first() - serial = container_rev_schema.dump(model_after).data - check_container(serial) - for k in e.keys(): - assert e[k] == serial[k] - -def test_schema_file_rev(app): - assert ReleaseRev.query.count() == 0 - e = { - "sha1": "asdf", - "size": 6, - } - model = file_rev_schema.load(e) - print(model) - fatcat.db.session.add(model.data) - fatcat.db.session.commit() - assert FileRev.query.count() == 1 - model_after = FileRev.query.first() - serial = file_rev_schema.dump(model_after).data - check_file(serial) - for k in e.keys(): - assert e[k] == serial[k] diff --git a/python/tests/routes.py b/python/tests/routes.py index 79d97fe4..80eb15fe 100644 --- a/python/tests/routes.py +++ b/python/tests/routes.py @@ -3,14 +3,10 @@ import json import tempfile import pytest import fatcat -import fatcat.sql -from fatcat.models import * from fixtures import * -def test_static_routes(rich_app): - app = rich_app - +def test_static_routes(app): for route in ('/health', '/robots.txt', '/', '/about'): rv = app.get(route) assert rv.status_code == 200 @@ -18,9 +14,7 @@ def test_static_routes(rich_app): assert app.get("/static/bogus/route").status_code == 404 -def test_all_views(rich_app): - app = rich_app - +def test_all_views(app): for route in ('work', 'release', 'creator', 'container', 'file'): print(route) rv = app.get('/{}/1'.format(route)) diff --git a/python/tests/test_fixtures.py b/python/tests/test_fixtures.py deleted file mode 100644 index 0a0d3176..00000000 --- a/python/tests/test_fixtures.py +++ /dev/null @@ -1,29 +0,0 @@ - -import pytest -import fatcat.api_client -from fixtures import * - - -def test_rich_app_fixture(rich_app): - app = rich_app - - assert ChangelogEntry.query.count() == 1 - - for cls in (WorkIdent, WorkRev, WorkEdit, - ContainerIdent, ContainerRev, ContainerEdit, - CreatorIdent, CreatorRev, CreatorEdit, - FileIdent, FileRev, FileEdit): - assert cls.query.count() == 1 - for cls in (ReleaseIdent, ReleaseRev, ReleaseEdit): - assert cls.query.count() == 2 - - for cls in (WorkIdent, - ContainerIdent, - CreatorIdent, - FileIdent): - assert cls.query.filter(cls.is_live==True).count() == 1 - assert ReleaseIdent.query.filter(ReleaseIdent.is_live==True).count() == 2 - - # test that editor's active edit group is now invalid - editor = Editor.query.first() - assert editor.active_editgroup == None |