diff --git a/api/models/gaia.py b/api/models/gaia.py index b680e99..ec27635 100644 --- a/api/models/gaia.py +++ b/api/models/gaia.py @@ -22,3 +22,41 @@ class Aliases(db.Model): id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) genes_id: db.Mapped[int] = db.mapped_column(ForeignKey("genes.id", ondelete="CASCADE"), nullable=False) alias: db.Mapped[str] = db.mapped_column(db.String(256), nullable=False) + + +class PublicationFigures(db.Model): + __bind_key__ = "gaia" + __tablename__ = "publication_figures" + + id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) + title: db.Mapped[str] = db.mapped_column(db.String(512), nullable=True) + abstract: db.Mapped[str] = db.mapped_column(db.Text, nullable=True) + children: db.Mapped[List["PubIds"]] = relationship() + children: db.Mapped[List["Figures"]] = relationship() + + +class PubIds(db.Model): + __bind_key__ = "gaia" + __tablename__ = "pub_ids" + + id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) + publication_figures_id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False) + publication_figures_id: db.Mapped[int] = db.mapped_column( + ForeignKey("publication_figures.id", ondelete="CASCADE"), nullable=False + ) + pubmed: db.Mapped[str] = db.mapped_column(db.String(16), nullable=True) + pmc: db.Mapped[str] = db.mapped_column(db.String(16), nullable=True) + + +class Figures(db.Model): + __bind_key__ = "gaia" + __tablename__ = "figures" + + id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) + publication_figures_id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False) + publication_figures_id: db.Mapped[int] = db.mapped_column( + ForeignKey("publication_figures.id", ondelete="CASCADE"), nullable=False + ) + img_name: db.Mapped[str] = db.mapped_column(db.String(64), nullable=False) + caption: db.Mapped[str] = db.mapped_column(db.Text, nullable=True) + img_url: db.Mapped[str] = db.mapped_column(db.String(256), nullable=True) diff --git a/api/resources/gaia.py b/api/resources/gaia.py index 51ba417..8c7818e 100644 --- a/api/resources/gaia.py +++ b/api/resources/gaia.py @@ -1,13 +1,41 @@ -from flask_restx import Namespace, Resource +from flask import request +from flask_restx import Namespace, Resource, fields from markupsafe import escape from api import db from api.utils.bar_utils import BARUtils -from api.models.gaia import Genes, Aliases +from api.models.gaia import Genes, Aliases, PubIds, Figures from sqlalchemy import func, or_ +from marshmallow import Schema, ValidationError, fields as marshmallow_fields import json gaia = Namespace("Gaia", description="Gaia", path="/gaia") +parser = gaia.parser() +parser.add_argument( + "terms", + type=list, + action="append", + required=True, + help="Publication IDs", + default=["32492426", "32550561"], +) + +publication_request_fields = gaia.model( + "Publications", + { + "pubmeds": fields.List( + required=True, + example=["32492426", "32550561"], + cls_or_instance=fields.String, + ), + }, +) + + +# Validation is done in a different way to keep things simple +class PublicationSchema(Schema): + pubmeds = marshmallow_fields.List(cls_or_instance=marshmallow_fields.String) + @gaia.route("/aliases/") class GaiaAliases(Resource): @@ -78,3 +106,71 @@ def get(self, identifier=""): else: return BARUtils.error_exit("Invalid identifier"), 400 + + +@gaia.route("/publication_figures") +class GaiaPublicationFigures(Resource): + @gaia.expect(publication_request_fields) + def post(self): + json_data = request.get_json() + + # Validate json + try: + json_data = PublicationSchema().load(json_data) + except ValidationError as err: + return BARUtils.error_exit(err.messages), 400 + + pubmeds = json_data["pubmeds"] + + # Check if pubmed ids are valid + for pubmed in pubmeds: + if not BARUtils.is_integer(pubmed): + return BARUtils.error_exit("Invalid Pubmed ID"), 400 + + # It is valid. Continue + data = [] + + # Left join is important in case aliases do not exist for the given locus / geneid + query = ( + db.select(Figures.img_name, Figures.caption, Figures.img_url, PubIds.pubmed, PubIds.pmc) + .select_from(Figures) + .join(PubIds, PubIds.publication_figures_id == Figures.publication_figures_id) + .filter(PubIds.pubmed.in_(pubmeds)) + .order_by(PubIds.pubmed.desc()) + ) + + rows = db.session.execute(query).fetchall() + + record = {} + + if rows and len(rows) > 0: + for row in rows: + + # Check if record has an id. If it doesn't, this is first row. + if "id" in record: + # Check if this is a new pubmed id + if record["id"]["pubmed"] != row.pubmed: + # new record. Add old now to data and create a new record + data.append(record) + record = {} + + # Check if figures exists, if not add it. + if record.get("figures") is None: + # Create a new figures record + record["figures"] = [] + + # Now append figure to the record + figure = {"img_name": row.img_name, "caption": row.caption, "img_url": row.img_url} + record["figures"].append(figure) + + # Now add the id. If it exists don't add + if record.get("id") is None: + record["id"] = {} + record["id"]["pubmed"] = row.pubmed + record["id"]["pmc"] = row.pmc + + # The last record + data.append(record) + + # Return final data + return BARUtils.success_exit(data) diff --git a/requirements.txt b/requirements.txt index 7794b99..7e27dcb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,23 +1,23 @@ aniso8601==10.0.1 async-timeout==5.0.1 attrs==25.4.0 -black==25.9.0 +black==25.12.0 blinker==1.9.0 cachelib==0.13.0 -certifi==2025.10.5 +certifi==2025.11.12 charset-normalizer==3.4.4 -click==8.3.0 -coverage==7.11.0 -Deprecated==1.2.18 +click==8.3.1 +coverage==7.13.0 +Deprecated==1.3.1 flake8==7.3.0 Flask==3.1.2 Flask-Caching==2.3.1 flask-cors==6.0.1 -Flask-Limiter==4.0.0 +Flask-Limiter==4.1.1 flask-marshmallow==1.3.0 flask-restx==1.3.2 Flask-SQLAlchemy==3.1.1 -greenlet==3.2.4 +greenlet==3.3.0 idna==3.11 importlib_resources==6.5.2 iniconfig==2.3.0 @@ -28,7 +28,7 @@ jsonschema-specifications==2025.9.1 limits==5.6.0 markdown-it-py==4.0.0 MarkupSafe==3.0.3 -marshmallow==4.0.1 +marshmallow==4.1.1 mccabe==0.7.0 mdurl==0.1.2 mypy_extensions==1.1.0 @@ -36,26 +36,26 @@ mysqlclient==2.2.7 ordered-set==4.1.0 packaging==25.0 pathspec==0.12.1 -platformdirs==4.5.0 +platformdirs==4.5.1 pluggy==1.6.0 pycodestyle==2.14.0 pyflakes==3.4.0 Pygments==2.19.2 pyrsistent==0.20.0 -pytest==8.4.2 +pytest==9.0.2 python-dateutil==2.9.0.post0 -pytokens==0.2.0 +pytokens==0.3.0 pytz==2025.2 -redis==7.0.1 +redis==7.1.0 referencing==0.37.0 requests==2.32.5 rich==14.2.0 -rpds-py==0.28.0 +rpds-py==0.30.0 setuptools==80.9.0 six==1.17.0 SQLAlchemy==2.0.44 typing_extensions==4.15.0 -urllib3==2.5.0 -Werkzeug==3.1.3 +urllib3==2.6.1 +Werkzeug==3.1.4 wheel==0.45.1 -wrapt==1.17.3 +wrapt==2.0.1