Add basic routes to update relationships
This commit is contained in:
parent
dd1af6a8f0
commit
a8f8590c5a
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
docs/
|
docs/
|
||||||
|
dev.db
|
||||||
|
30
database.py
30
database.py
@ -1,9 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
This file contains the database schema in SQLAlchemy format.
|
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.engine import Engine
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm import relationship as sqlalchemy_relationship
|
||||||
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
@ -18,11 +20,27 @@ def set_sqlite_pragma(dbapi_connection, connection_record):
|
|||||||
cursor.close()
|
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):
|
class Paper(Base):
|
||||||
__tablename__ = 'papers'
|
__tablename__ = "papers"
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
doi = Column(String(), nullable=True, unique=True)
|
doi = Column(String(), nullable=True, unique=True)
|
||||||
arxiv_id = Column(String(25), 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):
|
def __repr__(self):
|
||||||
return "<Paper(id='%d', doi='%s', arxiv_id='%s')>" % (
|
return "<Paper(id='%d', doi='%s', arxiv_id='%s')>" % (
|
||||||
@ -49,3 +67,11 @@ class Paper(Base):
|
|||||||
# TODO
|
# 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
12
main.py
@ -8,7 +8,8 @@ import routes
|
|||||||
import tools
|
import tools
|
||||||
|
|
||||||
# Initialize db and include the SQLAlchemy plugin in bottle
|
# 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()
|
app = bottle.Bottle()
|
||||||
plugin = sqlalchemy.Plugin(
|
plugin = sqlalchemy.Plugin(
|
||||||
@ -33,6 +34,7 @@ app.install(plugin)
|
|||||||
|
|
||||||
|
|
||||||
# Routes
|
# Routes
|
||||||
|
# TODO: Routes for deletion
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def index():
|
def index():
|
||||||
return tools.APIResponse(tools.pretty_json({
|
return tools.APIResponse(tools.pretty_json({
|
||||||
@ -41,13 +43,19 @@ def index():
|
|||||||
|
|
||||||
app.get("/papers", callback=routes.get.fetch_papers)
|
app.get("/papers", callback=routes.get.fetch_papers)
|
||||||
app.get("/papers/<id:int>", callback=routes.get.fetch_by_id)
|
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
|
# TODO: Fetch relationships
|
||||||
|
|
||||||
|
|
||||||
app.post("/papers", callback=routes.post.create_paper)
|
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__":
|
if __name__ == "__main__":
|
||||||
|
@ -97,3 +97,10 @@ def fetch_by_id(id, db):
|
|||||||
"data": resource.json_api_repr()
|
"data": resource.json_api_repr()
|
||||||
}))
|
}))
|
||||||
return bottle.HTTPError(404, "Not found")
|
return bottle.HTTPError(404, "Not found")
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_relationship(id, name, db):
|
||||||
|
"""
|
||||||
|
TODO
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
@ -138,3 +138,67 @@ def create_by_arxiv(arxiv, db):
|
|||||||
|
|
||||||
# Return the paper
|
# Return the paper
|
||||||
return 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
|
||||||
|
Loading…
Reference in New Issue
Block a user