Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add "POST ../access" & "PUT ../{access_id}" #32

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 91 additions & 1 deletion drs_filer/api/additional.data_repository_service.swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,50 @@ paths:
- DRS-Filer
x-swagger-router-controller: ga4gh.drs.server
'/objects/{object_id}/access/{access_id}':
put:
summary: Create or update an access method.
description: |-
Create a access method with a predefined ID. Overwrites any
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create an access method

existing access method with the same ID.
operationId: PutAccessMethod
responses:
'200':
description: The `AccessMethod` was successfully created/updated.
schema:
type: string
'400':
description: The request is malformed.
schema:
$ref: '#/definitions/Error'
'401':
description: The request is unauthorized.
schema:
$ref: '#/definitions/Error'
'403':
description: The requester is not authorized to perform this action.
schema:
$ref: '#/definitions/Error'
'500':
description: An unexpected error occurred.
schema:
$ref: '#/definitions/Error'
parameters:
- name: object_id
in: path
required: true
type: string
- name: access_id
in: path
required: true
type: string
- in: body
name: AccessMethodRegister
description: Access method metadata.
schema:
$ref: '#/definitions/AccessMethodRegister'
tags:
- DRS-Filer
x-swagger-router-controller: ga4gh.drs.server
delete:
summary: Delete object's access method.
description: >-
Expand Down Expand Up @@ -156,7 +200,53 @@ paths:
type: string
tags:
- DRS-Filer
x-swagger-router-controller: ga4gh.drs.server
x-swagger-router-controller: ga4gh.drs.server

'/objects/{object_id}/access':
post:
summary: Add an object's access method.
description: >-
Add `AccessMethod` of an existing `DrsObject`.
operationId: PostAccessMethod
responses:
'200':
description: The `AccessMethod` was successfully added.
schema:
type: string
'400':
description: The request is malformed.
schema:
$ref: '#/definitions/Error'
'401':
description: The request is unauthorized.
schema:
$ref: '#/definitions/Error'
'403':
description: The requester is not authorized to perform this action.
schema:
$ref: '#/definitions/Error'
'404':
description: The requested resource was not found.
schema:
$ref: '#/definitions/Error'
'500':
description: An unexpected error occurred.
schema:
$ref: '#/definitions/Error'
parameters:
- name: object_id
in: path
required: true
type: string
- in: body
name: AccessMethodRegister
description: Access method metadata.
schema:
$ref: '#/definitions/AccessMethodRegister'
tags:
- DRS-Filer
x-swagger-router-controller: ga4gh.drs.server

definitions:
DrsObjectRegister:
type: object
Expand Down
133 changes: 133 additions & 0 deletions drs_filer/ga4gh/drs/endpoints/register_access_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import logging
from random import choice
import string
from typing import Dict, Optional

from flask import current_app

from drs_filer.errors.exceptions import (
ObjectNotFound,
InternalServerError
)

logger = logging.getLogger(__name__)


def register_access_method(
data: Dict,
object_id: str,
access_id: Optional[str] = None,
retries: int = 9
) -> str:
"""Register access method.

Args:
data: Request object of type `AccessMethodRegister`.
object_id: DRS object identifier.
access_id: Access method identifier. Auto-generated if not provided.
retries: If `access_id` is not supplied, how many times should the
generation of a random identifier and insertion into the database
be retried in case of duplicate access_ids.

Returns:
A unique identifier for the access method.
"""
# Set parameters
db_collection = (
current_app.config['FOCA'].db.dbs['drsStore'].
collections['objects'].client
)
obj = db_collection.find_one({"id": object_id})
if not obj:
logger.error(f"DRS object with id: {object_id} not found.")
raise ObjectNotFound

# Set flags and parameters for POST/PUT routes
replace = True
if access_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)))

# Try to generate unique ID and insert object into database
for i in range(retries + 1):
logger.debug(f"Trying to insert/update access method: try {i}")
# Set or generate object identifier
if access_id is not None:
data['access_id'] = access_id
else:
data['access_id'] = generate_id(
charset=id_charset, # type: ignore
length=id_length, # type: ignore
)
# Replace access method, then return (PUT)
if replace:
result_replace = db_collection.update_one(
filter={'id': object_id},
update={
'$set': {
'access_methods.$[element]': data
}
},
array_filters=[{'element.access_id': data['access_id']}],
)

if(result_replace.modified_count):
logger.info(
f"Replaced access method with access_id: "
f"{data['access_id']} of DRS object with id: {object_id}"
)
break

# Try inserting the access method incase of POST or incase
# no element matches with the filter incase of PUT.
result_insert = db_collection.update_one(
filter={
'id': object_id,
'access_methods.access_id': {'$ne': data['access_id']}
},
update={
'$push': {
'access_methods': data
}
}
)
if(result_insert.modified_count):
logger.info(
f"Added access method with access_id: {data['access_id']}"
f" to DRS object with id: {object_id}"
)
break
# Access method neither added nor updated.
else:
logger.error(
f"Could not generate unique identifier. Tried {retries + 1} times."
)
raise InternalServerError
return data['access_id']


def generate_id(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can import that function from FOCA now, it should be available in 0.6.0

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))
40 changes: 40 additions & 0 deletions drs_filer/ga4gh/drs/server.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""Controllers for DRS endpoints."""

from drs_filer.ga4gh.drs.endpoints.register_access_methods import (
register_access_method
)
import logging
from typing import Dict

Expand Down Expand Up @@ -165,3 +168,40 @@ def PutObject(object_id: str):
data=request.json,
object_id=object_id,
)


@log_traffic
def PostAccessMethod(object_id: str):
"""Add a new `AccessMethod` in an existing DRS object

Args:
object_id: Identifier of the DRS object, in which the
`AccessMethod` is to be added.

Returns:
Identifier of the added `AccessMethod`.
"""
return register_access_method(
data=request.json,
object_id=object_id
)


@log_traffic
def PutAccessMethod(object_id: str, access_id: str):
"""Add/replace an `AccessMethod` in an existing DRS object

Args:
object_id: Identifier of the DRS object, to which the
`AccessMethod` is to be added.
access_id: Identifier of the `AccessMethod` which is to
be added/updated.

Returns:
Identifier of the added/updated `AccessMethod`.
"""
return register_access_method(
data=request.json,
object_id=object_id,
access_id=access_id
)