Add basic routes to update relationships

This commit is contained in:
Lucas Verney 2015-12-25 20:09:56 +01:00
parent dd1af6a8f0
commit a8f8590c5a
5 changed files with 110 additions and 4 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
__pycache__
docs/
dev.db

View File

@ -1,9 +1,11 @@
"""
This file contains the database schema in SQLAlchemy format.
"""
from sqlalchemy import event, Column, Integer, String
from sqlalchemy import event
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.engine import Engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship as sqlalchemy_relationship
Base = declarative_base()
@ -18,11 +20,27 @@ def set_sqlite_pragma(dbapi_connection, connection_record):
cursor.close()
# TODO: Backref
class Association(Base):
# Relationships are to be read "left RELATION right"
__tablename__ = "association"
id = Column(Integer, primary_key=True)
left_id = Column(Integer, ForeignKey("papers.id", ondelete="CASCADE"))
right_id = Column(Integer, ForeignKey("papers.id", ondelete="CASCADE"))
relationship_id = Column(Integer,
ForeignKey("relationships.id",
ondelete="CASCADE"))
right_paper = sqlalchemy_relationship("Paper", foreign_keys=right_id)
relationship = sqlalchemy_relationship("Relationship")
class Paper(Base):
__tablename__ = 'papers'
__tablename__ = "papers"
id = Column(Integer, primary_key=True)
doi = Column(String(), nullable=True, unique=True)
arxiv_id = Column(String(25), nullable=True, unique=True)
related = sqlalchemy_relationship("Association",
foreign_keys="Association.left_id")
def __repr__(self):
return "<Paper(id='%d', doi='%s', arxiv_id='%s')>" % (
@ -49,3 +67,11 @@ class Paper(Base):
# TODO
}
}
class Relationship(Base):
__tablename__ = "relationships"
id = Column(Integer, primary_key=True)
name = Column(String(), unique=True)
associations = sqlalchemy_relationship("Association",
back_populates="relationship")

12
main.py
View File

@ -8,7 +8,8 @@ import routes
import tools
# Initialize db and include the SQLAlchemy plugin in bottle
engine = create_engine('sqlite:///:memory:', echo=True)
# engine = create_engine('sqlite:///:memory:', echo=True)
engine = create_engine('sqlite:///dev.db', echo=True)
app = bottle.Bottle()
plugin = sqlalchemy.Plugin(
@ -33,6 +34,7 @@ app.install(plugin)
# Routes
# TODO: Routes for deletion
@app.get("/")
def index():
return tools.APIResponse(tools.pretty_json({
@ -41,13 +43,19 @@ def index():
app.get("/papers", callback=routes.get.fetch_papers)
app.get("/papers/<id:int>", callback=routes.get.fetch_by_id)
app.get("/papers/<id:int>/relationships/<name>",
callback=routes.get.fetch_relationship)
# TODO: Fetch relationships
app.post("/papers", callback=routes.post.create_paper)
# TODO: Update relationships
app.post("/papers/<id:int>/relationships/<name>",
callback=routes.post.update_relationships)
# Complete replacement of relationships is forbidden
app.route("/papers/<id:int>/relationships/<name>", method="PATCH",
callback=lambda id, name: bottle.HTTPError(403, "Forbidden"))
if __name__ == "__main__":

View File

@ -97,3 +97,10 @@ def fetch_by_id(id, db):
"data": resource.json_api_repr()
}))
return bottle.HTTPError(404, "Not found")
def fetch_relationship(id, name, db):
"""
TODO
"""
pass

View File

@ -138,3 +138,67 @@ def create_by_arxiv(arxiv, db):
# Return the paper
return paper
def update_relationships(id, name, db):
"""
Update the relationships associated to a given paper.
:param id: The id of the paper to update relationships.
:param name: The name of the relationship to update.
:param db: A database session, passed by Bottle plugin.
:returns: No content. 204 on success, 403 on error.
"""
data = json.loads(bottle.request.body.read().decode("utf-8"))
# Validate the request
if "data" not in data:
return bottle.HTTPError(403, "Forbidden")
# Filter data, invalid entries are ignored
data = [i for i in data["data"]
if "type" in i and i["type"] == name and "id" in i]
# Complete replacement (data == []) is forbidden
if len(data) == 0:
return bottle.HTTPError(403, "Forbidden")
# Update all the relationships
for i in data:
updated = update_relationship_backend(id, i["id"], name, db)
if updated is None:
# An error occurred => 403
return bottle.HTTPError(403, "Forbidden")
# Return an empty 204 on success
return tools.APIResponse(status=204, body="")
def update_relationship_backend(left_id, right_id, name, db):
"""
Backend method to update a single relationship between two papers.
:param left_id: ID of the paper on the left of the relationship.
:param right_id: ID of the paper on the right of the relationship.
:param name: Name of the relationship between the two papers.
:param db: A database session.
:returns: The updated left paper on success, ``None`` otherwise.
"""
# Load necessary resources
left_paper = db.query(database.Paper).filter_by(id=left_id).first()
right_paper = db.query(database.Paper).filter_by(id=right_id).first()
if left_paper is None or right_paper is None:
# Abort
return None
relationship = db.query(database.Relationship).filter_by(name=name).first()
if relationship is None:
relationship = database.Relationship(name=name)
db.add(relationship)
db.flush()
# Update the relationship
a = database.Association(relationship_id=relationship.id)
a.right_paper = right_paper
left_paper.related.append(a)
try:
db.add(a)
db.add(left_paper)
except IntegrityError:
# Unique constraint violation, relationship already exists
db.rollback()
return None
return left_paper