diff options
-rw-r--r-- | fatcat/__init__.py | 3 | ||||
-rw-r--r-- | fatcat/api.py | 14 | ||||
-rw-r--r-- | fatcat/models.py | 20 | ||||
-rw-r--r-- | fatcat/sql.py | 112 | ||||
-rw-r--r-- | tests/test_backend.py | 40 |
5 files changed, 129 insertions, 60 deletions
diff --git a/fatcat/__init__.py b/fatcat/__init__.py index e8c7c21a..d8494a19 100644 --- a/fatcat/__init__.py +++ b/fatcat/__init__.py @@ -11,14 +11,11 @@ examples = { "work": { "id": "rzga5b9cd7efgh04iljk", "rev": "12345", - "previous": None, - "state": None, "redirect_id": None, "edit_id": None, "extra_json": None, "title": "Structure and Interpretation", "work_type": "journal-article", - "date": None, "contributors": [ {"name": "Alyssa P. Hacker"}, ], diff --git a/fatcat/api.py b/fatcat/api.py index 1c68822a..3c407cfe 100644 --- a/fatcat/api.py +++ b/fatcat/api.py @@ -3,15 +3,19 @@ from flask import Flask, render_template, send_from_directory, request, \ url_for, abort, g, redirect, jsonify from fatcat import app, db, examples from fatcat.models import * +from fatcat.sql import * ### Views ################################################################### @app.route('/v0/work/<work_id>', methods=['GET']) -def work_get(work_id): - if work_id == "random": - work = examples['work'] - else: - work = WorkId.query.filter_by(id=work_id).first_or_404() +def api_work_get(work_id): + if not work_id.isdigit(): + return abort(404) + work = hydrate_work(work_id) return jsonify(work) +@app.route('/v0/work/random', methods=['GET']) +def api_work_random(): + work = WorkId.query.order_by(db.func.random()).first() + return redirect('/v0/work/{}'.format(work.id)) diff --git a/fatcat/models.py b/fatcat/models.py index 9abb286e..abac8de9 100644 --- a/fatcat/models.py +++ b/fatcat/models.py @@ -38,8 +38,8 @@ class ReleaseContrib(db.Model): class ReleaseRef(db.Model): __tablename__ = "release_ref" id = db.Column(db.Integer, primary_key=True, nullable=False) - release_rev= db.Column(db.ForeignKey('release_revision.id'), nullable=False) - target_release_id= db.Column(db.ForeignKey('release_id.id'), nullable=True) + release_rev = db.Column(db.ForeignKey('release_revision.id'), nullable=False) + target_release_id = db.Column(db.ForeignKey('release_id.id'), nullable=True) index = db.Column(db.Integer, nullable=True) stub = db.Column(db.String, nullable=True) doi = db.Column(db.String, nullable=True) @@ -69,6 +69,7 @@ class WorkId(db.Model): live = db.Column(db.Boolean, nullable=False, default=False) revision_id = db.Column(db.ForeignKey('work_revision.id'), nullable=True) redirect_id = db.Column(db.ForeignKey('work_id.id'), nullable=True) + revision = db.relationship("WorkRevision") class WorkLog(db.Model): __tablename__ = 'work_log' @@ -85,13 +86,12 @@ class WorkLog(db.Model): 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) edit_id = db.Column(db.ForeignKey('edit.id')) extra_json = db.Column(db.ForeignKey('extra_json.sha1'), nullable=True) title = db.Column(db.String) work_type = db.Column(db.String) - date = db.Column(db.String) + primary_release_id = db.Column(db.ForeignKey('release_id.id'), nullable=True) creators = db.relationship('WorkContrib', lazy='subquery', backref=db.backref('works', lazy=True)) @@ -102,11 +102,11 @@ class ReleaseId(db.Model): live = db.Column(db.Boolean, nullable=False, default=False) revision_id = db.Column(db.ForeignKey('release_revision.id')) redirect_id = db.Column(db.ForeignKey('release_id.id'), nullable=True) + revision = db.relationship("ReleaseRevision") 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) edit_id = db.Column(db.ForeignKey('edit.id')) extra_json = db.Column(db.ForeignKey('extra_json.sha1'), nullable=True) @@ -121,6 +121,8 @@ class ReleaseRevision(db.Model): pages = db.Column(db.String, nullable=True) issue = db.Column(db.String, nullable=True) + #work = db.relationship("WorkId", lazy='subquery') + container = db.relationship("ContainerId", lazy='subquery') creators = db.relationship('ReleaseContrib', lazy='subquery') refs = db.relationship('ReleaseRef', lazy='subquery') @@ -130,11 +132,11 @@ class CreatorId(db.Model): live = db.Column(db.Boolean, nullable=False, default=False) revision_id = db.Column(db.ForeignKey('creator_revision.id')) redirect_id = db.Column(db.ForeignKey('creator_id.id'), nullable=True) + revision = db.relationship("CreatorRevision") 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) 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) @@ -149,16 +151,16 @@ class ContainerId(db.Model): live = db.Column(db.Boolean, nullable=False, default=False) revision_id = db.Column(db.ForeignKey('container_revision.id')) redirect_id = db.Column(db.ForeignKey('container_id.id'), nullable=True) + revision = db.relationship("ContainerRevision") 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) 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_id = db.Column(db.ForeignKey('container_id.id')) + #XXX: container_id = 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 @@ -169,11 +171,11 @@ class FileId(db.Model): live = db.Column(db.Boolean, nullable=False, default=False) revision_id = db.Column('revision', db.ForeignKey('file_revision.id')) redirect_id = db.Column(db.ForeignKey('file_id.id'), nullable=True) + revision = db.relationship("FileRevision") 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) edit_id = db.Column(db.ForeignKey('edit.id')) extra_json = db.Column(db.ForeignKey('extra_json.sha1'), nullable=True) diff --git a/fatcat/sql.py b/fatcat/sql.py index ad0a045f..b73ec464 100644 --- a/fatcat/sql.py +++ b/fatcat/sql.py @@ -14,23 +14,26 @@ def populate_db(): name="Noam D. Elkies", sortname="Elkies, N", orcid=None) - n_elkies_id = CreatorId(revision_id=n_elkies.id) + n_elkies_id = CreatorId(revision=n_elkies) pi_work = WorkRevision( - title="Why is π 2so close to 10?") - pi_work_id = WorkId(revision_id=pi_work.id) + title="Why is π^2 so close to 10?", + work_type="journal-article") + pi_work_id = WorkId(revision=pi_work) pi_release = ReleaseRevision( title=pi_work.title, - work_id=pi_work.id) + work_id=pi_work.id, + release_type="journal-article") pi_contrib = ReleaseContrib(creator=n_elkies_id) pi_release.creators.append(pi_contrib) - pi_release_id = ReleaseId(revision_id=pi_release.id) - pi_work.primary_release = pi_release.id + pi_release_id = ReleaseId(revision=pi_release) + pi_work.primary_release = pi_release # TODO: #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]) + db.session.add_all([n_elkies, n_elkies_id, pi_work, pi_work_id, pi_release, + pi_release_id]) # TODO: #ligo_collab = CreatorRevision(name="LIGO Scientific Collaboration") @@ -58,19 +61,19 @@ def populate_complex_db(count=100): sortname="{}, {}".format(last, first[0]), orcid=None) author_revs.append(ar) - author_ids.append(CreatorId(revision_id=ar.id)) + author_ids.append(CreatorId(revision=ar)) container_revs = [] container_ids = [] for _ in range(5): cr = ContainerRevision( name="The Fake Journal of Stuff", - container_id=None, + #container_id=None, publisher="Big Paper", sortname="Fake Journal of Stuff", issn="1234-5678") container_revs.append(cr) - container_ids.append(ContainerId(revision_id=cr.id)) + container_ids.append(ContainerId(revision=cr)) title_start = ("All about ", "When I grow up I want to be", "The final word on", "Infinity: ", "The end of") @@ -84,22 +87,22 @@ def populate_complex_db(count=100): for _ in range(count): title = "{} {}".format(random.choice(title_start), random.choice(title_ends)) work = WorkRevision(title=title) - work_id = WorkId(revision_id=work.id) + work_id = WorkId(revision=work) authors = set(random.sample(author_ids, 5)) release = ReleaseRevision( title=work.title, creators=[ReleaseContrib(creator=a) for a in list(authors)], - work_id=work.id, - container_id=random.choice(container_ids).id) - release_id = ReleaseId(revision_id=release.id) - work.primary_release = release.id + #work=work, + container=random.choice(container_ids)) + release_id = ReleaseId(revision=release) + work.primary_release = release authors.add(random.choice(author_ids)) release2 = ReleaseRevision( title=work.title + " (again)", creators=[ReleaseContrib(creator=a) for a in list(authors)], - work_id=work.id, - container_id=random.choice(container_ids).id) - release_id2 = ReleaseId(revision_id=release2.id) + #work=work, + container=random.choice(container_ids)) + release_id2 = ReleaseId(revision=release2) work_revs.append(work) work_ids.append(work_id) release_revs.append(release) @@ -115,7 +118,7 @@ def populate_complex_db(count=100): url="http://archive.invalid/{}".format(file_sha), releases=[FileRelease(release=release_id), FileRelease(release=release_id2)], ) - file_id = FileId(revision_id=file_rev.id) + file_id = FileId(revision=file_rev) file_revs.append(file_rev) file_ids.append(file_id) @@ -145,25 +148,25 @@ def add_crossref(meta): sortname="{}, {}".format(am['family'], am['given']), orcid=None) author_revs.append(ar) - author_ids.append(CreatorId(revision_id=ar.id)) + author_ids.append(CreatorId(revision=ar)) # container container = ContainerRevision( issn=meta['ISSN'][0], name=meta['container-title'][0], - container_id=None, + #container_id=None, publisher=meta['publisher'], sortname=meta['short-container-title'][0]) - container_id = ContainerId(revision_id=container.id) + container_id = ContainerId(revision=container) # work and release work = WorkRevision(title=title) - work_id = WorkId(revision_id=work.id) + work_id = WorkId(revision=work) release = ReleaseRevision( title=title, creators=[ReleaseContrib(creator=a) for a in author_ids], - work_id=work.id, - container_id=container_id.id, + #work=work, + container=container_id, release_type=meta['type'], doi=meta['DOI'], date=meta['created']['date-time'], @@ -171,8 +174,8 @@ def add_crossref(meta): issue=meta.get('issue', None), volume=meta.get('volume', None), pages=meta.get('page', None)) - release_id = ReleaseId(revision_id=release.id) - work.primary_release = release.id + release_id = ReleaseId(revision=release) + work.primary_release = release extra = json.dumps({ 'crossref': { 'links': meta.get('link', []), @@ -187,7 +190,7 @@ def add_crossref(meta): # references for i, rm in enumerate(meta.get('reference', [])): ref = ReleaseRef( - release_rev=release.id, + release_rev=release, doi=rm.get("DOI", None), index=i+1, # TODO: how to generate a proper stub here from k/v metadata? @@ -199,3 +202,56 @@ def add_crossref(meta): db.session.add_all(author_revs) db.session.add_all(author_ids) db.session.commit() + +def hydrate_work(wid): + + wid = int(wid) + work = WorkId.query.filter(WorkId.id==wid).first_or_404() + hydro = { + "_type": "work", + "id": wid, + "rev": work.revision_id, + "is_live": work.live, + "redirect_id": work.redirect_id, + } + if not work.revision: + # TODO: look up edit id here from changelog? + hydro["edit_id"] = None + return hydro + + primary = None + if work.revision.primary_release_id: + primary = hydrate_release(work.revision.primary_release_id) + creators = [c.creator_id for c in WorkContrib.query.filter(WorkContrib.work == work).all()] + #releases = [r.id for r in ReleaseId.query.filter(ReleaseId.revision.work_id==work.id).all()] + releases = [] + hydro.update({ + "work_type": work.revision.work_type, + "title": work.revision.title, + "edit_id": work.revision.edit_id, + "primary": primary, + "creators": creators, + "releases": releases, + }) + return hydro + +def hydrate_release(rid): + + wid = int(rid) + release = ReleaseId.query.filter(ReleaseId.id==rid).first_or_404() + + return { + "_type": "release", + "id": rid, + "revision": release.revision_id, + "edit_id": release.revision.edit_id, + "is_live": release.live, + + "work_id": release.revision.work_id, + "release_type": release.revision.release_type, + "title": release.revision.title, + "creators": [], + "releases": [], + "files": [], + "references": [], + } diff --git a/tests/test_backend.py b/tests/test_backend.py index fdb29c47..bc610d25 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -12,10 +12,9 @@ import tempfile ## Helpers ################################################################## def check_entity_fields(e): - for key in ('id', 'rev', 'previous', 'state', 'redirect_id', 'edit_id', - 'extra_json'): + for key in ('id', 'rev', 'redirect_id', 'edit_id'): assert key in e - for key in ('id', 'rev'): + for key in ('id',): assert e[key] is not None ## API Tests ################################################################ @@ -36,24 +35,28 @@ class FatcatTestCase(unittest.TestCase): assert obj['ok'] def test_works(self): + fatcat.sql.populate_db() # 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 == 404 - - # Valid Id (TODO) - #rv = self.app.get('/v0/work/r3zga5b9cd7ef8gh084714iljk') - #assert rv.status_code == 200 - + # Random rv = self.app.get('/v0/work/random') - obj = json.loads(rv.data.decode('utf-8')) - check_entity_fields(obj) - assert obj['title'] - assert obj['work_type'] == "journal-article" + rv = self.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 = self.app.get('/v0/work/{}'.format(work['id'])) + assert rv.status_code == 200 + + # Missing Id + rv = self.app.get('/v0/work/r3zga5b9cd7ef8gh084714iljk') + assert rv.status_code == 404 def test_populate(self): fatcat.sql.populate_db() @@ -67,3 +70,10 @@ class FatcatTestCase(unittest.TestCase): for obj in raw: fatcat.sql.add_crossref(obj) + def test_hydrate_work(self): + fatcat.sql.populate_complex_db() + fatcat.sql.hydrate_work(1) + + def test_hydrate_release(self): + fatcat.sql.populate_complex_db() + fatcat.sql.hydrate_release(1) |