diff --git a/app/crud/author.py b/app/crud/author.py index 087335c..a074da4 100644 --- a/app/crud/author.py +++ b/app/crud/author.py @@ -57,8 +57,8 @@ def get( OPTIONAL MATCH (a)-[:member_of]->(u:Workstream) RETURN a.uuid as uuid, a.orcid as orcid, a.first_name as first_name, a.last_name as last_name, - collect(p.id, p.name) as affiliations, - collect(u.id, u.name) as workstreams;""" + collect(p) as affiliations, + collect(u) as workstreams;""" author, _, _ = db.execute_query(author_query, uuid=id) results = author[0].data() @@ -70,7 +70,7 @@ def get( LIMIT 5""" colabs, summary, keys = db.execute_query(collab_query, uuid=id) - results["collaborators"] = colabs + results["collaborators"] = [x.data() for x in colabs] if result_type and result_type in [ "publication", @@ -88,10 +88,10 @@ def get( ORDER BY r.rank } OPTIONAL MATCH (p)-[:REFERS_TO]->(c:Country) - RETURN p as outputs, + RETURN p as results, collect(DISTINCT c) as countries, collect(DISTINCT b) as authors - ORDER BY outputs.publication_year DESCENDING;""" + ORDER BY results.publication_year DESCENDING;""" result, _, _ = db.execute_query( publications_query, uuid=id, result_type=result_type @@ -108,19 +108,22 @@ def get( ORDER BY r.rank } OPTIONAL MATCH (p)-[:REFERS_TO]->(c:Country) - RETURN p as outputs, + RETURN p as results, collect(DISTINCT c) as countries, collect(DISTINCT b) as authors - ORDER BY outputs.publication_year DESCENDING;""" - - result, summary, keys = db.execute_query(publications_query, uuid=id) - - results["outputs"] = [x.data() for x in result] - for result in results["outputs"]: - neo4j_datetime = result["outputs"]["cited_by_count_date"] - result["outputs"]["cited_by_count_date"] = datetime.fromtimestamp( - neo4j_datetime.to_native().timestamp() - ) + ORDER BY results.publication_year DESCENDING;""" + + records, _, _ = db.execute_query(publications_query, uuid=id) + outputs = [] + for x in records: + data = x.data() + package = data['results'] + package['authors'] = data['authors'] + package['countries'] = data['countries'] + outputs.append(package) + + results['outputs'] = {} + results['outputs']['results'] = outputs return results @connect_to_db @@ -145,18 +148,28 @@ def count(self, id: str, db: Driver) -> Dict[str, int]: RETURN o.result_type as result_type, count(o) as count """ records, summary, keys = db.execute_query(query, uuid=id) - return {x.data()["result_type"]: x.data()["count"] for x in records} - + if len(records) > 0: + counts = {x.data()["result_type"]: x.data()["count"] for x in records} + counts['total'] = sum(counts.values()) + return counts + else: + return {'total': 0, + 'publications': 0, + 'datasets': 0, + 'other': 0, + 'software': 0} + @connect_to_db - def get_all(self, db: Driver) -> List[Dict[str, Any]]: + def get_all(self, db: Driver, skip: int, limit: int) -> List[Dict[str, Any]]: """Retrieve list of authors from the database.""" query = """MATCH (a:Author) OPTIONAL MATCH (a)-[:member_of]->(p:Partner) OPTIONAL MATCH (a)-[:member_of]->(u:Workstream) - optional MATCH (a)-[:author_of]->(o:PUBLICATION) - RETURN a.first_name as first_name, a.last_name as last_name, a.uuid as uuid, a.orcid as orcid, collect(p.id, p.name) as affiliations, collect(u.id, u.name) as workstreams - ORDER BY last_name; + RETURN a.first_name as first_name, a.last_name as last_name, a.uuid as uuid, a.orcid as orcid, collect(p) as affiliations, collect(u) as workstreams + ORDER BY last_name + SKIP $skip + LIMIT $limit; """ - records, summary, keys = db.execute_query(query) + records, _, _ = db.execute_query(query, skip=skip, limit=limit) return [record.data() for record in records] diff --git a/app/crud/output.py b/app/crud/output.py index 005f8e2..cfd30cd 100644 --- a/app/crud/output.py +++ b/app/crud/output.py @@ -35,47 +35,10 @@ def get(self, id: str, db: Driver) -> Dict[str, Any]: - orcid : str Author's ORCID identifier """ - query = """MATCH (p:Article) - WHERE p.uuid = $uuid - OPTIONAL MATCH (p)-[:REFERS_TO]->(c:Country) - RETURN DISTINCT p as output, collect(DISTINCT c) as countries;""" - records, summary, keys = db.execute_query(query, uuid=id) - print(records[0].data()) - results = {} - results = records[0].data()["output"] - results["countries"] = records[0].data()["countries"] - authors_query = """MATCH (a:Author)-[r:author_of]->(p:Article) - WHERE p.uuid = $uuid - RETURN a.uuid as uuid, a.first_name as first_name, a.last_name as last_name, a.orcid as orcid;""" - - records, summary, keys = db.execute_query(authors_query, uuid=id) - - results["authors"] = [x.data() for x in records] - - return results - @connect_to_db - def get_all(self, db: Driver) -> List[Dict[str, Any]]: - """Retrieve all article outputs with their associated countries and authors. - - Parameters - ---------- - db : Driver - Neo4j database driver - - Returns - ------- - List[Dict[str, Any]] - List of dictionaries containing: - - outputs : Dict - Article properties - - countries : List[Dict] - List of referenced countries - - authors : List[Dict] - List of authors ordered by rank - """ query = """ MATCH (o:Article) + WHERE o.uuid = $uuid OPTIONAL MATCH (o)-[:REFERS_TO]->(c:Country) CALL { @@ -84,10 +47,16 @@ def get_all(self, db: Driver) -> List[Dict[str, Any]]: RETURN a ORDER BY b.rank } - RETURN o as outputs, collect(DISTINCT c) as countries, collect(DISTINCT a) as authors; - """ - records, summary, keys = db.execute_query(query) - return [x.data() for x in records] + RETURN o as outputs, collect(DISTINCT c) as countries, collect(DISTINCT a) as authors + """ + records, summary, keys = db.execute_query(query, + uuid=id) + data = [x.data() for x in records][0] + package = data['outputs'] + package['authors'] = data['authors'] + package['countries'] = data['countries'] + + return package @connect_to_db def count(self, db: Driver) -> Dict[str, int]: @@ -109,10 +78,19 @@ def count(self, db: Driver) -> Dict[str, int]: RETURN o.result_type as result_type, count(o) as count """ records, summary, keys = db.execute_query(query) - return {x.data()["result_type"]: x.data()["count"] for x in records} + if len(records) > 0: + counts = {x.data()["result_type"]: x.data()["count"] for x in records} + counts['total'] = sum(counts.values()) + return counts + else: + return {'total': 0, + 'publications': 0, + 'datasets': 0, + 'other': 0, + 'software': 0} @connect_to_db - def filter_type(self, db: Driver, result_type: str) -> List[Dict[str, Any]]: + def filter_type(self, db: Driver, result_type: str, skip: int, limit: int) -> List[Dict[str, Any]]: """Filter articles by result type and return with ordered authors. Parameters @@ -149,9 +127,93 @@ def filter_type(self, db: Driver, result_type: str) -> List[Dict[str, Any]]: RETURN a ORDER BY b.rank } + + RETURN o as outputs, + collect(DISTINCT c) as countries, + collect(DISTINCT a) as authors + SKIP $skip + LIMIT $limit; + """ + records, _, _ = db.execute_query(query, + result_type=result_type, + skip=skip, + limit=limit) + outputs = [] + for x in records: + data = x.data() + package = data['outputs'] + package['authors'] = data['authors'] + package['countries'] = data['countries'] + outputs.append(package) + + return outputs + + @connect_to_db + def filter_country(self, + db: Driver, + result_type: str, + skip: int, + limit: int, + country: str) -> List[Dict[str, Any]]: + """Filter articles by country and result type and return with ordered authors. + + Parameters + ---------- + db : Driver + Neo4j database driver + result_type : str + Type of result to filter by (e.g. 'journal_article') + skip: int + Number of rows in the output to skip + limit: int + Number of rows to return + country: str + Three letter ISO country code + + Returns + ------- + List[Dict[str, Any]] + Filtered list of articles containing: + - outputs : Dict + Article properties + - countries : List[Dict] + List of referenced countries + - authors : List[Dict] + List of authors ordered by rank + + Raises + ------ + ValueError + If result_type is invalid + """ + query = """ + MATCH (o:Article)-[:REFERS_TO]->(c:Country) + WHERE o.result_type = $result_type + AND c.id = $country_id + CALL + { + WITH o + MATCH (a:Author)-[b:author_of]->(o) + RETURN a + ORDER BY b.rank + } RETURN o as outputs, collect(DISTINCT c) as countries, - collect(DISTINCT a) as authors; + collect(DISTINCT a) as authors + SKIP $skip + LIMIT $limit; """ - records, summary, keys = db.execute_query(query, result_type=result_type) - return [x.data() for x in records] + records, _, _ = db.execute_query(query, + result_type=result_type, + country_id=country, + skip=skip, + limit=limit) + outputs = [] + for x in records: + data = x.data() + package = data['outputs'] + package['authors'] = data['authors'] + package['countries'] = data['countries'] + outputs.append(package) + + return outputs diff --git a/app/crud/workstream.py b/app/crud/workstream.py index d5ca985..a0a56a8 100644 --- a/app/crud/workstream.py +++ b/app/crud/workstream.py @@ -13,7 +13,7 @@ def get_all(self, db: Driver) -> Dict[str, Any]: RETURN p.id as id, p.name as name, collect(a) as members""" records, summary, keys = db.execute_query(query) return [x.data() for x in records] - + @connect_to_db def get(self, id: str, db: Driver) -> Dict[str, Any]: query = """MATCH (p:Workstream) diff --git a/app/db/session.py b/app/db/session.py index 5144cf8..f981d4e 100644 --- a/app/db/session.py +++ b/app/db/session.py @@ -1,11 +1,13 @@ from functools import wraps from app.core.config import settings + from neo4j import GraphDatabase MG_HOST = settings.MG_HOST MG_PORT = settings.MG_PORT + def connect_to_db(f): @wraps(f) def with_connection_(*args, **kwargs): @@ -15,11 +17,10 @@ def with_connection_(*args, **kwargs): AUTH = ("", "") with GraphDatabase.driver(URI, auth=AUTH) as db: db.verify_connectivity() - result = f(*args, db, **kwargs) + return f(*args, db, **kwargs) except Exception as e: raise ValueError(e) finally: db.close() - return result return with_connection_ diff --git a/app/ingest.py b/app/ingest.py deleted file mode 100644 index ebc76fc..0000000 --- a/app/ingest.py +++ /dev/null @@ -1,71 +0,0 @@ -from csv import DictReader -from os import environ -from os.path import join -from typing import Optional - -from gqlalchemy import Field, Memgraph, Node, Relationship - -MG_HOST = environ.get("MG_HOST", "127.0.0.1") -MG_PORT = int(environ.get("MG_PORT", 7687)) - -db = Memgraph(host=MG_HOST, port=MG_PORT) -db.drop_database() - - -class Author(Node): - uuid: str = Field(unique=True, index=True, db=db) - orcid: Optional[str] - last_name: Optional[str] - first_name: Optional[str] - - -class Output(Node): - uuid: str = Field(unique=True, index=True, db=db) - - -class Article(Output): - doi: Optional[str] - title: Optional[str] - abstract: Optional[str] - - -class author_of(Relationship): - pass - - -author_objects = {} -with open(join("data", "authors.csv")) as authors_csv: - reader = DictReader(authors_csv) - for author in reader: - print(author) - author_objects[author["uuid"]] = Author( - uuid=author["uuid"], - first_name=author["First Name"], - last_name=author["Last Name"], - orcid=author["Orcid"], - ).save(db) - -output_objects = {} -with open(join("data", "papers.csv")) as papers_csv: - reader = DictReader(papers_csv) - for output in reader: - print(output) - output_objects[output["paper_uuid"]] = Article( - uuid=output["paper_uuid"], - doi=output["DOI"], - title=output["title"], - abstract=output["Abstract"], - ).save(db) - -with open(join("data", "relations.csv")) as relations_csv: - reader = DictReader(relations_csv) - for rel in reader: - author_uuid = rel["uuid"] - paper_uuid = rel["paper_uuid"] - - loaded_author = Author(uuid=author_uuid).load(db=db) - loaded_output = Article(uuid=paper_uuid).load(db=db) - - author_of( - _start_node_id=loaded_author._id, _end_node_id=loaded_output._id - ).save(db) diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py index e69de29..6c5ad77 100644 --- a/app/schemas/__init__.py +++ b/app/schemas/__init__.py @@ -0,0 +1,17 @@ +from typing import Dict, List, Optional + +from pydantic import BaseModel, Field, HttpUrl +from uuid import UUID + +class AuthorBase(BaseModel): + """ + Base data model representing an academic author or contributor + with their associated metadata. + """ + + uuid: UUID = Field(..., + description="Unique identifier for the author") + first_name: str = Field(..., min_length=1) + last_name: str = Field(..., min_length=1) + orcid: Optional[HttpUrl] = \ + Field(None, description="Author's ORCID identifier") \ No newline at end of file diff --git a/app/schemas/affiliation.py b/app/schemas/affiliation.py new file mode 100644 index 0000000..7cdc90a --- /dev/null +++ b/app/schemas/affiliation.py @@ -0,0 +1,9 @@ +from pydantic import BaseModel +from typing import List, Optional +from .author import AuthorBase + +class AffiliationModel(BaseModel): + id: Optional[str] = None + name: Optional[str] = None + ror: Optional[str] = None + ccg_partner: bool diff --git a/app/schemas/author.py b/app/schemas/author.py index 15dc01e..58b3e76 100644 --- a/app/schemas/author.py +++ b/app/schemas/author.py @@ -1,26 +1,24 @@ from typing import Dict, List, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, HttpUrl +from uuid import UUID +from . import AuthorBase +from .workstream import WorkstreamBase +from .affiliation import AffiliationModel +from .output import OutputListModel +from .meta import Meta -class AuthorBase(BaseModel): - """ - Base data model representing an academic author or contributor - with their associated metadata. - """ - - uuid: str = Field(..., description="Unique identifier for the author") - first_name: str = Field(..., min_length=1) - last_name: str = Field(..., min_length=1) - orcid: Optional[str] = Field(None, description="Author's ORCID identifier") - -class AuthorModel(AuthorBase): +class AuthorListModel(AuthorBase): """ Data model representing an academic author or contributor with their associated metadata and relationships. """ - affiliations: Optional[Dict] = None - workstreams: Optional[Dict[str, str]] = None - collaborators: Optional[List] = None - outputs: Optional[List] = None + affiliations: Optional[List[AffiliationModel]] = None + workstreams: Optional[List[WorkstreamBase]] = None + + +class AuthorModel(AuthorListModel): + collaborators: List[AuthorBase] = None + outputs: OutputListModel diff --git a/app/schemas/country.py b/app/schemas/country.py index a9c7114..fc886c5 100644 --- a/app/schemas/country.py +++ b/app/schemas/country.py @@ -3,16 +3,18 @@ class CountryModel(BaseModel): """Data model representing country-level research metrics and relationships""" - + outputs: Optional[List] = None authors: Optional[Dict] = None metrics: Optional[Dict] = None - - -class CountryNodeModel(BaseModel): + + +class CountryBaseModel(BaseModel): + id: str + name: str + +class CountryNodeModel(CountryBaseModel): dbpedia: Optional[str] = None - id: Optional[str] = None latitude: Optional[float] = None longitude: Optional[float] = None - name: Optional[str] = None official_name: Optional[str] = None \ No newline at end of file diff --git a/app/schemas/meta.py b/app/schemas/meta.py new file mode 100644 index 0000000..a9adc44 --- /dev/null +++ b/app/schemas/meta.py @@ -0,0 +1,39 @@ +from typing import Dict, List, Optional + +from pydantic import BaseModel, Field + +class Count(BaseModel): + """Represents count of the outputs + ```json + {'total': 245684392, + 'publication': 1234, + 'software': 5678, + 'dataset': 1234, + 'other': 39494}, + ``` + """ + + total: int = 0 + publication: int = 0 + software: int = 0 + dataset: int = 0 + other: int = 0 + + +class Meta(BaseModel): + """ + Base data model representing an academic author or contributor + with their associated metadata. + + ```json + "count": {} + "db_response_time_ms": 929, + "page": 1, + "per_page": 25 + ``` + + """ + count: Count | None + db_response_time_ms: int + page: int + per_page: int diff --git a/app/schemas/output.py b/app/schemas/output.py index 917dbb1..015bc3e 100644 --- a/app/schemas/output.py +++ b/app/schemas/output.py @@ -1,13 +1,11 @@ from pydantic import BaseModel, Field, HttpUrl -from datetime import datetime from typing import List, Optional from uuid import UUID +from .author import AuthorBase +from .country import CountryBaseModel +from .meta import Meta +from .topic import TopicBaseModel -class Author(BaseModel): - uuid: UUID - first_name: Optional[str] = None - last_name: Optional[str] = None - orcid: Optional[HttpUrl] = None class DateTimeComponents(BaseModel): _Date__ordinal: Optional[int] = None @@ -15,6 +13,7 @@ class DateTimeComponents(BaseModel): _Date__month: Optional[int] = None _Date__day: Optional[int] = None + class TimeComponents(BaseModel): _Time__ticks: Optional[int] = None _Time__hour: Optional[int] = None @@ -23,20 +22,64 @@ class TimeComponents(BaseModel): _Time__nanosecond: Optional[int] = None _Time__tzinfo: Optional[str] = None + class CitedByDateTime(BaseModel): _DateTime__date: Optional[DateTimeComponents] = None _DateTime__time: Optional[TimeComponents] = None + class OutputModel(BaseModel): + """Schema for an Output + + An output should look like this: + + ```json + { + "uuid": "f05b1fc5-f831-4755-966f-06de074ab51c", # required + "doi": "http://doi.org/10.5281/zenodo.7015450", # required + "title": 'An example title', # required + "abstract": 'A long abstract', # optional + "journal": "Applied JSON", # optional + "cited_by_count_date": '2024-03-01', # optional + "cited_by_count": 34, # optional + "openalex": "https://openalex.org/W4393416420", # optional + "publication_day": 03, # optional + "publication_month": 12, # optional + "publication_year": 2023, # optional + "publisher": "Elsevier", # optional + "result_type": 'publication', # required + "countries": ['KEN', 'BEN'], # optional + "author": [ + { + "uuid": "c613b25b-967c-4586-9101-ece3a901fd9c", + "first_name": "Will", + "last_name": "Usher", + "orcid": "https://orcid.org/0000-0001-9367-1791", + } + ] # required + } + ``` + """ uuid: UUID + doi: str = Field(pattern=r"^10\.\d{4,9}/[-._;()/:a-zA-Z0-9]+$") + title: str + result_type: str + authors: List[AuthorBase] abstract: Optional[str] = None + journal: Optional[str] = None # Only academic publications have a journal cited_by_count_date: Optional[CitedByDateTime] = None - doi: Optional[str] = None + cited_by_count: Optional[int] = None publication_day: Optional[int] = None publication_month: Optional[int] = None publication_year: Optional[int] = None publisher: Optional[str] = None - result_type: Optional[str] = None - title: Optional[str] = None - countries: List = Field(default_factory=list) - authors: Optional[List[Author]] = Field(default_factory=list) \ No newline at end of file + countries: Optional[List[CountryBaseModel]] = Field(default_factory=list) + topics: Optional[List[TopicBaseModel]] = Field(default_factory=list) + + +class OutputListModel(BaseModel): + """Represents a list of outputs including metadata + + """ + meta: Meta + results: List[OutputModel] diff --git a/app/schemas/topic.py b/app/schemas/topic.py new file mode 100644 index 0000000..4e24a3c --- /dev/null +++ b/app/schemas/topic.py @@ -0,0 +1,36 @@ +"""The topics schema mimics the OpenAlex implementation of topics + +See the OpenAlex documentation: https://docs.openalex.org/api-entities/topics/topic-object + +""" +from typing import Dict, List +from pydantic import BaseModel, HttpUrl +from uuid import UUID + + +class DomainModel(BaseModel): + id: int + display_name: str + + +class FieldModel(BaseModel): + id: int + display_name: str + + +class TopicBaseModel(BaseModel): + """The topics schema mimics the OpenAlex implementation of topics + + See the OpenAlex documentation: + https://docs.openalex.org/api-entities/topics/topic-object + + """ + id: UUID + openalex_id: HttpUrl + description: str + display_name: str + domain: DomainModel + field: FieldModel + subfield: FieldModel + ids: Dict[str, HttpUrl] + keywords: List[str] diff --git a/app/schemas/workstream.py b/app/schemas/workstream.py index 70afabf..948a3e6 100644 --- a/app/schemas/workstream.py +++ b/app/schemas/workstream.py @@ -1,8 +1,12 @@ from pydantic import BaseModel from typing import List, Optional -from .author import AuthorBase +from . import AuthorBase +from .output import OutputListModel -class WorkstreamModel(BaseModel): +class WorkstreamBase(BaseModel): id: Optional[str] = None name: Optional[str] = None - members: Optional[List[AuthorBase]] = None + +class WorkstreamModel(WorkstreamBase): + members: List[AuthorBase] + outputs: OutputListModel diff --git a/main.py b/main.py index 3dc45ea..f2039ad 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -from typing import List, Dict, Any +from typing import List from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse @@ -11,10 +11,11 @@ from app.crud.output import Output from app.crud.workstream import Workstream -from app.schemas.author import AuthorModel -from app.schemas.country import CountryModel, CountryNodeModel -from app.schemas.output import OutputModel -from app.schemas.workstream import WorkstreamModel +from app.schemas.author import AuthorModel, AuthorListModel +from app.schemas.country import CountryNodeModel +from app.schemas.output import OutputModel, OutputListModel +from app.schemas.workstream import WorkstreamBase, WorkstreamModel +from app.schemas.topic import TopicBaseModel app = FastAPI() @@ -24,7 +25,7 @@ @app.get("/", response_class=HTMLResponse) @app.get("/index", response_class=HTMLResponse) -async def index(request: Request): +def index(request: Request): nodes = Nodes().get() edges = Edges().get() countries = Country().get_all() @@ -41,7 +42,7 @@ async def index(request: Request): @app.get("/countries/{id}", response_class=HTMLResponse) -async def country(request: Request, id: str, type: str = None): +def country(request: Request, id: str, type: str = None): country_model = Country() outputs, country = country_model.get(id, result_type=type) count = country_model.count(id) @@ -58,7 +59,7 @@ async def country(request: Request, id: str, type: str = None): @app.get("/countries", response_class=HTMLResponse) -async def country_list(request: Request): +def country_list(request: Request): country_model = Country() entity = country_model.get_all() return templates.TemplateResponse( @@ -68,27 +69,31 @@ async def country_list(request: Request): @app.get("/authors/{id}", response_class=HTMLResponse) -async def author(request: Request, id: str, type: str = None): +def author(request: Request, id: str, type: str = None): author_model = Author() entity = author_model.get(id, result_type=type) count = author_model.count(id) return templates.TemplateResponse( "author.html", - {"request": request, "title": "Author", "author": entity, "count": count}, + {"request": request, "title": "Author", + "author": entity, + "count": count}, ) @app.get("/authors", response_class=HTMLResponse) -async def author_list(request: Request): +def author_list(request: Request): model = Author() entity = model.get_all() return templates.TemplateResponse( - "authors.html", {"request": request, "title": "Author List", "authors": entity} + "authors.html", {"request": request, + "title": "Author List", + "authors": entity} ) @app.get("/outputs", response_class=HTMLResponse) -async def output_list(request: Request, type: str = None): +def output_list(request: Request, type: str = None): model = Output() entity = model.filter_type(result_type=type) if type else model.get_all() count = model.count() @@ -99,7 +104,7 @@ async def output_list(request: Request, type: str = None): @app.get("/outputs/{id}", response_class=HTMLResponse) -async def output(request: Request, id: str): +def output(request: Request, id: str): output_model = Output() entity = output_model.get(id) return templates.TemplateResponse( @@ -107,28 +112,41 @@ async def output(request: Request, id: str): ) -@app.get("/outputs/{id}/popup", response_class=HTMLResponse) -async def output_popup(request: Request, id: str): - output_model = Output() - entity = output_model.get(id) - return templates.TemplateResponse( - "output_popup.html", {"request": request, "title": "Output", "output": entity} - ) - - @app.get("/api/authors/{id}") -async def author(id: str, type: str = None) -> AuthorModel: +def api_author(id: str, type: str = None) -> AuthorModel: author_model = Author() - return author_model.get(id, result_type=type) + results = author_model.get(id, result_type=type) + count = author_model.count(id) + results['outputs']['meta'] = {"count": count, + "db_response_time_ms": 0, + "page": 0, + "per_page": 0} + + return results @app.get("/api/authors") -async def author_list()-> List[AuthorModel]: +def api_author_list(skip: int = 0, limit: int = 20) -> List[AuthorListModel]: model = Author() - return model.get_all() + return model.get_all(skip=skip, limit=limit) + @app.get("/api/countries/{id}") -async def country(id: str, type: str = None)-> CountryModel: +def api_country(id: str, type: str = None)-> OutputListModel: + """Return a list of outputs filtered by the country id provided + + Arguments + --------- + id: str + The 3-letter ISO country code + type: str + One of "dataset", "publication", "tools", "other" + + Returns + ------- + OutputListModel schema + + """ country_model = Country() outputs, country = country_model.get(id, result_type=type) count = country_model.count(id) @@ -136,30 +154,73 @@ async def country(id: str, type: str = None)-> CountryModel: @app.get("/api/countries") -async def country_list()-> List[CountryNodeModel]: +def api_country_list()-> List[CountryNodeModel]: country_model = Country() results = country_model.get_all() return [result['c'] for result in results] # The queries should return a list of dictionaries, each containing a 'c' key with the country information # This is a temporary workaround but the queries should be updated to return the correct data structure - + + @app.get("/api/outputs") -async def output_list(type: str = None) -> List[OutputModel]: +def api_output_list(skip: int = 0, + limit: int = 20, + type: str = 'publication', + country: str = None) -> OutputListModel: + """Return a list of outputs + + Arguments + --------- + skip: int, default = 0 + limit: int, default = 20 + type: enum, default = None + country: str, default = None + """ model = Output() - results = model.filter_type(result_type=type) if type else model.get_all() - return [result['outputs'] for result in results] - + if country: + results = model.filter_country(result_type=type, + skip=skip, + limit=limit, + country=country) + else: + results = model.filter_type(result_type=type, + skip=skip, + limit=limit) + + count = model.count() + + return { + "meta": {"count": count, + "db_response_time_ms": 0, + "page": 0, + "per_page": 0}, + "results": results + } + @app.get("/api/outputs/{id}") -async def output(id: str) -> OutputModel: +def api_output(id: str) -> OutputModel: output_model = Output() - return output_model.get(id) + results = output_model.get(id) + return results + @app.get("/api/workstreams") -async def workstream_list() -> List[WorkstreamModel]: +def api_workstream_list() -> List[WorkstreamBase]: model = Workstream() return model.get_all() + @app.get("/api/workstreams/{id}") -async def workstream(id: str) -> WorkstreamModel: +def api_workstream(id: str) -> WorkstreamModel: model = Workstream() - return model.get(id) \ No newline at end of file + return model.get(id) + + +@app.get("api/topics") +def api_topics_list() -> List[TopicBaseModel]: + raise NotImplementedError("Have not yet implemented topics in the database") + + +@app.get("api/topics/{id}") +def api_topics_list(id: str) -> TopicBaseModel: + raise NotImplementedError("Have not yet implemented topics in the database") \ No newline at end of file