Skip to content

Commit

Permalink
Merge pull request #255 from seattleflu/api/sample-get
Browse files Browse the repository at this point in the history
Adding sample GET endpoint to API
  • Loading branch information
davereinhart authored Nov 24, 2021
2 parents 9046ea2 + a123b7e commit bed2a21
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 4 deletions.
54 changes: 50 additions & 4 deletions lib/id3c/api/datastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
import logging
import psycopg2
from functools import wraps
from psycopg2 import DataError, DatabaseError, IntegrityError, ProgrammingError
from psycopg2 import DataError, DatabaseError, IntegrityError, ProgrammingError, sql
from psycopg2.errors import InsufficientPrivilege
from typing import Any
from uuid import UUID
from werkzeug.exceptions import Forbidden, NotFound
from werkzeug.exceptions import Forbidden, NotFound, Conflict
from .. import db
from ..db import find_identifier, upsert_sample
from ..db.session import DatabaseSession
Expand Down Expand Up @@ -239,7 +239,7 @@ def fetch_identifier(session: DatabaseSession, id: str) -> Any:
""", (id,))

if not identifier:
LOG.error(f"Identifier {id_field} «{id}» not found")
LOG.info(f"Identifier {id_field} «{id}» not found")
raise NotFound(f"Identifier {id_field} «{id}» not found")

return identifier
Expand Down Expand Up @@ -281,7 +281,7 @@ def fetch_identifier_set(session: DatabaseSession, name: str) -> Any:
""", (name,))

if not set:
LOG.error(f"Identifier set «{name}» not found")
LOG.info(f"Identifier set «{name}» not found")
raise NotFound(f"Identifier set «{name}» not found")

return set
Expand Down Expand Up @@ -383,6 +383,52 @@ def fetch_identifier_set_uses(session: DatabaseSession) -> Any:

return list(cursor)

@export
@catch_permission_denied
def get_sample(session: DatabaseSession, barcode: str, barcode_type: str) -> Any:
"""
Fetch the sample with identifier or collection identifier matching *barcode* from the backing
database using *session*.
Returns a named tuple with ``identifier``, ``collection_identifier``, ``encounter_id``,
``details``, ``created``, ``modified``, and ``collected`` attributes.
If the identifier barcode or sample doesn't exist, raises a :class:`~werkzeug.exceptions.NotFound`
exception. If the identifier barcode exists but is not from a sample or collection identifier set,
raises a :class:`~werkzeug.exceptions.Conflict` exception.
"""

with session:
identifier = find_identifier(session, barcode) or None

if not identifier:
LOG.info(f"Identifier barcode «{barcode}» not found")
raise NotFound(f"Identifier barcode «{barcode}» not found")
elif identifier.set_use!=barcode_type and identifier.set_use in ('sample', 'collection'):
raise NotFound(f"Identifier use «{barcode_type}» does not match identifier use «{identifier.set_use}» of barcode")
elif identifier.set_use=='sample':
identifier_field = sql.Identifier('identifier')
elif identifier.set_use=='collection':
identifier_field = sql.Identifier('collection_identifier')
else:
error_msg = f"Identifier barcode «{barcode}» has use type «{identifier.set_use}» instead of expected use type «sample» or «collection»"
LOG.info(error_msg)
raise Conflict(error_msg)

query = sql.SQL("""
select identifier, collection_identifier, encounter_id,
details, created::text, modified::text, collected::text
from warehouse.sample
where {field} = %s
""").format(field=identifier_field)

sample = session.fetch_row(query, (identifier.uuid,))

if not sample:
raise NotFound(f"Sample record with {identifier.set_use} identifier barcode «{barcode}» not found")
else:
return sample

@export
@catch_permission_denied
def store_sample(session: DatabaseSession, sample: dict) -> Any:
Expand Down
32 changes: 32 additions & 0 deletions lib/id3c/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
API route definitions.
"""
import json
import re
from jsonschema import Draft7Validator, ValidationError, draft7_format_checker
import logging
import pkg_resources
Expand All @@ -11,6 +12,7 @@
from . import datastore
from .utils.routes import authenticated_datastore_session_required, content_types_accepted, check_content_length
from . import schemas
from .exceptions import BadRequest

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -266,6 +268,36 @@ def get_identifier_set_uses(*, session):

return jsonify([ use._asdict() for use in uses ])

@api_v1.route("/warehouse/sample", methods = ['GET'])
@api_v1.route("/warehouse/sample/<barcode>", methods = ['GET'])
@authenticated_datastore_session_required
def get_sample(barcode=None, *, session):
"""
Retrieve a sample's metadata.
GET /warehouse/sample/*barcode* to receive a JSON object containing the
sample record with the provided sample barcode.
GET /warehouse/sample?collection_barcode=*barcode* to receive a JSON object
containing the sample record with the provided collection barcode.
"""
if not barcode:
barcode = request.args.get('collection_barcode')
if barcode:
barcode_type = 'collection'
else:
raise BadRequest(f"Missing required argument")
else:
barcode_type = 'sample'

if not re.match("[a-fA-F0-9]{8}", barcode):
raise BadRequest(f"Invalid barcode format")

LOG.debug(f"Fetching sample with barcode «{barcode}»")
sample = datastore.get_sample(session, barcode, barcode_type)

return jsonify(sample._asdict())

@api_v1.route("/warehouse/sample", methods = ['POST'])
@content_types_accepted(["application/json"])
@check_content_length
Expand Down
6 changes: 6 additions & 0 deletions lib/id3c/api/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ <h3 class="code">PUT /v1/warehouse/identifier-sets/<em>&lt;name&gt;</em></h3>
parameter is required for new sets and is optional if only updating
<em>description</em> on an existing set.

<h3 class="code">GET /v1/warehouse/sample/<em>&lt;barcode&gt;</em></h3>
<p>Retrieve metadata about a sample record by sample barcode.

<h3 class="code">GET /v1/warehouse/sample?collection_barcode=<em>&lt;barcode&gt;</em></h3>
<p>Retrieve metadata about a sample record by collection barcode.

<h3 class="code">POST /v1/warehouse/sample</h3>
<p>Insert or update a sample record.
<p>The body of the request should be a JSON object with the following format:
Expand Down

0 comments on commit bed2a21

Please sign in to comment.