Skip to content

Commit b956b05

Browse files
authored
Merge pull request #291 from asherpasha/dev
GAIA publications collection initial commit.
2 parents 13da704 + fd37e03 commit b956b05

File tree

3 files changed

+152
-18
lines changed

3 files changed

+152
-18
lines changed

api/models/gaia.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,41 @@ class Aliases(db.Model):
2222
id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True)
2323
genes_id: db.Mapped[int] = db.mapped_column(ForeignKey("genes.id", ondelete="CASCADE"), nullable=False)
2424
alias: db.Mapped[str] = db.mapped_column(db.String(256), nullable=False)
25+
26+
27+
class PublicationFigures(db.Model):
28+
__bind_key__ = "gaia"
29+
__tablename__ = "publication_figures"
30+
31+
id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True)
32+
title: db.Mapped[str] = db.mapped_column(db.String(512), nullable=True)
33+
abstract: db.Mapped[str] = db.mapped_column(db.Text, nullable=True)
34+
children: db.Mapped[List["PubIds"]] = relationship()
35+
children: db.Mapped[List["Figures"]] = relationship()
36+
37+
38+
class PubIds(db.Model):
39+
__bind_key__ = "gaia"
40+
__tablename__ = "pub_ids"
41+
42+
id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True)
43+
publication_figures_id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False)
44+
publication_figures_id: db.Mapped[int] = db.mapped_column(
45+
ForeignKey("publication_figures.id", ondelete="CASCADE"), nullable=False
46+
)
47+
pubmed: db.Mapped[str] = db.mapped_column(db.String(16), nullable=True)
48+
pmc: db.Mapped[str] = db.mapped_column(db.String(16), nullable=True)
49+
50+
51+
class Figures(db.Model):
52+
__bind_key__ = "gaia"
53+
__tablename__ = "figures"
54+
55+
id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True)
56+
publication_figures_id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False)
57+
publication_figures_id: db.Mapped[int] = db.mapped_column(
58+
ForeignKey("publication_figures.id", ondelete="CASCADE"), nullable=False
59+
)
60+
img_name: db.Mapped[str] = db.mapped_column(db.String(64), nullable=False)
61+
caption: db.Mapped[str] = db.mapped_column(db.Text, nullable=True)
62+
img_url: db.Mapped[str] = db.mapped_column(db.String(256), nullable=True)

api/resources/gaia.py

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,41 @@
1-
from flask_restx import Namespace, Resource
1+
from flask import request
2+
from flask_restx import Namespace, Resource, fields
23
from markupsafe import escape
34
from api import db
45
from api.utils.bar_utils import BARUtils
5-
from api.models.gaia import Genes, Aliases
6+
from api.models.gaia import Genes, Aliases, PubIds, Figures
67
from sqlalchemy import func, or_
8+
from marshmallow import Schema, ValidationError, fields as marshmallow_fields
79
import json
810

911
gaia = Namespace("Gaia", description="Gaia", path="/gaia")
1012

13+
parser = gaia.parser()
14+
parser.add_argument(
15+
"terms",
16+
type=list,
17+
action="append",
18+
required=True,
19+
help="Publication IDs",
20+
default=["32492426", "32550561"],
21+
)
22+
23+
publication_request_fields = gaia.model(
24+
"Publications",
25+
{
26+
"pubmeds": fields.List(
27+
required=True,
28+
example=["32492426", "32550561"],
29+
cls_or_instance=fields.String,
30+
),
31+
},
32+
)
33+
34+
35+
# Validation is done in a different way to keep things simple
36+
class PublicationSchema(Schema):
37+
pubmeds = marshmallow_fields.List(cls_or_instance=marshmallow_fields.String)
38+
1139

1240
@gaia.route("/aliases/<string:identifier>")
1341
class GaiaAliases(Resource):
@@ -78,3 +106,71 @@ def get(self, identifier=""):
78106

79107
else:
80108
return BARUtils.error_exit("Invalid identifier"), 400
109+
110+
111+
@gaia.route("/publication_figures")
112+
class GaiaPublicationFigures(Resource):
113+
@gaia.expect(publication_request_fields)
114+
def post(self):
115+
json_data = request.get_json()
116+
117+
# Validate json
118+
try:
119+
json_data = PublicationSchema().load(json_data)
120+
except ValidationError as err:
121+
return BARUtils.error_exit(err.messages), 400
122+
123+
pubmeds = json_data["pubmeds"]
124+
125+
# Check if pubmed ids are valid
126+
for pubmed in pubmeds:
127+
if not BARUtils.is_integer(pubmed):
128+
return BARUtils.error_exit("Invalid Pubmed ID"), 400
129+
130+
# It is valid. Continue
131+
data = []
132+
133+
# Left join is important in case aliases do not exist for the given locus / geneid
134+
query = (
135+
db.select(Figures.img_name, Figures.caption, Figures.img_url, PubIds.pubmed, PubIds.pmc)
136+
.select_from(Figures)
137+
.join(PubIds, PubIds.publication_figures_id == Figures.publication_figures_id)
138+
.filter(PubIds.pubmed.in_(pubmeds))
139+
.order_by(PubIds.pubmed.desc())
140+
)
141+
142+
rows = db.session.execute(query).fetchall()
143+
144+
record = {}
145+
146+
if rows and len(rows) > 0:
147+
for row in rows:
148+
149+
# Check if record has an id. If it doesn't, this is first row.
150+
if "id" in record:
151+
# Check if this is a new pubmed id
152+
if record["id"]["pubmed"] != row.pubmed:
153+
# new record. Add old now to data and create a new record
154+
data.append(record)
155+
record = {}
156+
157+
# Check if figures exists, if not add it.
158+
if record.get("figures") is None:
159+
# Create a new figures record
160+
record["figures"] = []
161+
162+
# Now append figure to the record
163+
figure = {"img_name": row.img_name, "caption": row.caption, "img_url": row.img_url}
164+
record["figures"].append(figure)
165+
166+
# Now add the id. If it exists don't add
167+
if record.get("id") is None:
168+
record["id"] = {}
169+
record["id"]["pubmed"] = row.pubmed
170+
record["id"]["pmc"] = row.pmc
171+
172+
# The last record
173+
data.append(record)
174+
175+
# Return final data
176+
return BARUtils.success_exit(data)

requirements.txt

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
aniso8601==10.0.1
22
async-timeout==5.0.1
33
attrs==25.4.0
4-
black==25.9.0
4+
black==25.12.0
55
blinker==1.9.0
66
cachelib==0.13.0
7-
certifi==2025.10.5
7+
certifi==2025.11.12
88
charset-normalizer==3.4.4
9-
click==8.3.0
10-
coverage==7.11.0
11-
Deprecated==1.2.18
9+
click==8.3.1
10+
coverage==7.13.0
11+
Deprecated==1.3.1
1212
flake8==7.3.0
1313
Flask==3.1.2
1414
Flask-Caching==2.3.1
1515
flask-cors==6.0.1
16-
Flask-Limiter==4.0.0
16+
Flask-Limiter==4.1.1
1717
flask-marshmallow==1.3.0
1818
flask-restx==1.3.2
1919
Flask-SQLAlchemy==3.1.1
20-
greenlet==3.2.4
20+
greenlet==3.3.0
2121
idna==3.11
2222
importlib_resources==6.5.2
2323
iniconfig==2.3.0
@@ -28,34 +28,34 @@ jsonschema-specifications==2025.9.1
2828
limits==5.6.0
2929
markdown-it-py==4.0.0
3030
MarkupSafe==3.0.3
31-
marshmallow==4.0.1
31+
marshmallow==4.1.1
3232
mccabe==0.7.0
3333
mdurl==0.1.2
3434
mypy_extensions==1.1.0
3535
mysqlclient==2.2.7
3636
ordered-set==4.1.0
3737
packaging==25.0
3838
pathspec==0.12.1
39-
platformdirs==4.5.0
39+
platformdirs==4.5.1
4040
pluggy==1.6.0
4141
pycodestyle==2.14.0
4242
pyflakes==3.4.0
4343
Pygments==2.19.2
4444
pyrsistent==0.20.0
45-
pytest==8.4.2
45+
pytest==9.0.2
4646
python-dateutil==2.9.0.post0
47-
pytokens==0.2.0
47+
pytokens==0.3.0
4848
pytz==2025.2
49-
redis==7.0.1
49+
redis==7.1.0
5050
referencing==0.37.0
5151
requests==2.32.5
5252
rich==14.2.0
53-
rpds-py==0.28.0
53+
rpds-py==0.30.0
5454
setuptools==80.9.0
5555
six==1.17.0
5656
SQLAlchemy==2.0.44
5757
typing_extensions==4.15.0
58-
urllib3==2.5.0
59-
Werkzeug==3.1.3
58+
urllib3==2.6.1
59+
Werkzeug==3.1.4
6060
wheel==0.45.1
61-
wrapt==1.17.3
61+
wrapt==2.0.1

0 commit comments

Comments
 (0)