Skip to content

Commit

Permalink
finalize PR
Browse files Browse the repository at this point in the history
  • Loading branch information
uniqueg committed Nov 6, 2020
1 parent f8c6ac4 commit 78bb29b
Show file tree
Hide file tree
Showing 7 changed files with 312 additions and 270 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ paths:
summary: Create object.
description: |-
Register metadata of a data object.
operationId: RegisterObjects
operationId: PostObject
responses:
'200':
description: The `DrsObject` was successfully created.
Expand Down
125 changes: 0 additions & 125 deletions drs_filer/ga4gh/drs/endpoints/register_new_objects.py

This file was deleted.

153 changes: 153 additions & 0 deletions drs_filer/ga4gh/drs/endpoints/register_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"""Controller for registering new DRS objects."""

import logging
from random import choice
import string
from typing import (Dict, List, Optional)

from flask import current_app
from pymongo.errors import DuplicateKeyError

from drs_filer.errors.exceptions import InternalServerError

logger = logging.getLogger(__name__)


def register_object(
data: Dict,
object_id: Optional[str] = None,
retries: int = 9,
) -> str:
"""Register data object.
Args:
data: Request object of type `DrsObjectRegister`.
object_id: DRS object identifier. Auto-generated if not provided.
retries: If `object_id` is not supplied, how many times should the
generation of a random identifier and insertion into the database
be retried in case of `DuplicateKeyError`s.
Returns:
A unique identifier for the object.
"""
# Set parameters
db_collection = (
current_app.config['FOCA'].db.dbs['drsStore'].
collections['objects'].client
)
url_prefix = current_app.config['FOCA'].endpoints['url_prefix']
external_host = current_app.config['FOCA'].endpoints['external_host']
external_port = current_app.config['FOCA'].endpoints['external_port']
api_path = current_app.config['FOCA'].endpoints['api_path']

# Set flags and parameters for POST/PUT routes
replace = True
was_replaced = False
if object_id is None:
replace = False
id_length = (
current_app.config['FOCA'].endpoints['objects']['id_length']
)
id_charset: str = (
current_app.config['FOCA'].endpoints['objects']['id_charset']
)
# evaluate character set expression or interpret literal string as set
try:
id_charset = eval(id_charset)
except Exception:
id_charset = ''.join(sorted(set(id_charset)))

# Add unique access identifiers for each access method
if 'access_methods' in data:
data['access_methods'] = __add_access_ids(
data=data['access_methods']
)

# Try to generate unique ID and insert object into database
for i in range(retries + 1):
logger.debug(f"Trying to insert/update object: try {i}")

# Set or generate object identifier
data['id'] = object_id if object_id is not None else generate_id(
charset=id_charset, # type: ignore
length=id_length, # type: ignore
)

# Generate DRS URL
data['self_uri'] = (
f"{url_prefix}://{external_host}:{external_port}/"
f"{api_path}/{data['id']}"
)

# Replace or insert object, then return (PUT)
if replace:
result_object = db_collection.replace_one(
filter={'id': data['id']},
replacement=data,
upsert=True,
)
if result_object.modified_count:
was_replaced = True
break

# Try to insert new object (POST); continue with next iteration if
# key exists
try:
db_collection.insert_one(data)
break
except DuplicateKeyError:
continue

else:
logger.error(
f"Could not generate unique identifier. Tried {retries + 1} times."
)
raise InternalServerError

if was_replaced:
logger.info(f"Replaced object with id '{data['id']}'.")
else:
logger.info(f"Added object with id '{data['id']}'.")
return data['id']


def __add_access_ids(data: List) -> List:
"""Add access identifiers to posted access methods metadata.
Args:
data: List of access method metadata objects.
Returns:
Access methods metadata complete with unique access identifiers.
"""
id_charset = eval(
current_app.config['FOCA'].endpoints['access_methods']['id_charset']
)
id_length = (
current_app.config['FOCA'].endpoints['access_methods']['id_length']
)
access_ids = []
for method in data:
access_id = generate_id(id_charset, id_length)
if access_id not in access_ids:
method['access_id'] = access_id
access_ids.append(access_id)

return data


def generate_id(
charset: str = ''.join([string.ascii_letters, string.digits]),
length: int = 6
) -> str:
"""Generate random string based on allowed set of characters.
Args:
charset: String of allowed characters.
length: Length of returned string.
Returns:
Random string of specified length and composed of defined set of
allowed characters.
"""
return ''.join(choice(charset) for __ in range(length))
21 changes: 12 additions & 9 deletions drs_filer/ga4gh/drs/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,13 @@
URLNotFound,
BadRequest,
)
from drs_filer.ga4gh.drs.endpoints.register_new_objects import (
register_new_objects,
from drs_filer.ga4gh.drs.endpoints.register_objects import (
register_object,
)

logger = logging.getLogger(__name__)


@log_traffic
def RegisterObjects() -> str:
"""Register new DRS object."""
return register_new_objects(request)


@log_traffic
def GetObject(object_id: str) -> Dict:
"""Get DRS object.
Expand Down Expand Up @@ -150,6 +144,12 @@ def DeleteAccessMethod(object_id: str, access_id: str) -> str:
raise InternalServerError


@log_traffic
def PostObject() -> str:
"""Register new DRS object."""
return register_object(data=request.json)


@log_traffic
def PutObject(object_id: str):
"""Add/replace DRS object with a user-supplied ID.
Expand All @@ -161,4 +161,7 @@ def PutObject(object_id: str):
Identifier of created/updated DRS object.
"""
return register_new_objects(request, object_id)
return register_object(
data=request.json,
object_id=object_id,
)
Loading

0 comments on commit 78bb29b

Please sign in to comment.