Skip to content
This repository was archived by the owner on Oct 23, 2023. It is now read-only.

Commit 6dcf156

Browse files
authored
Merge pull request #135 from CSCfi/dev
Release 1.5.0
2 parents c5f0727 + fa04762 commit 6dcf156

31 files changed

Lines changed: 880 additions & 375 deletions

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ omit =
1414
[report]
1515
# Regexes for lines to exclude from consideration
1616
exclude_lines =
17+
pragma: no cover
1718
# Don't complain about missing debug-only code:
1819
def __repr__
1920
if self\.debug

Pipfile

Lines changed: 0 additions & 18 deletions
This file was deleted.

beacon_api/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
__license__ = CONFIG_INFO.license
1414
__copyright__ = CONFIG_INFO.copyright
1515
__docs_url__ = CONFIG_INFO.docs_url
16-
__handover_drs__ = CONFIG_INFO.handover_drs
16+
__handover_drs__ = CONFIG_INFO.handover_drs.rstrip("/")
1717
__handover_datasets__ = CONFIG_INFO.handover_datasets
1818
__handover_beacon__ = CONFIG_INFO.handover_beacon
1919
__handover_base__ = CONFIG_INFO.handover_base
@@ -26,7 +26,6 @@
2626
__createtime__ = CONFIG_INFO.createtime
2727
__updatetime__ = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') # Every restart of the application means an update to it
2828

29-
3029
__org_id__ = CONFIG_INFO.org_id
3130
__org_name__ = CONFIG_INFO.org_name
3231
__org_description__ = CONFIG_INFO.org_description
@@ -37,3 +36,7 @@
3736
__org_info__ = {'orgInfo': CONFIG_INFO.org_info}
3837

3938
__sample_queries__ = SAMPLE_QUERIES
39+
40+
# GA4GH Discovery
41+
__service_type__ = f'{CONFIG_INFO.service_type}:{__apiVersion__}'
42+
__service_env__ = CONFIG_INFO.environment

beacon_api/api/exceptions.py

Lines changed: 39 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,35 @@
1010
from ..conf import CONFIG_INFO
1111

1212

13-
class BeaconError(Exception):
14-
"""BeaconError Exception specific class.
13+
def process_exception_data(request, host, error_code, error):
14+
"""Return request data as dictionary.
1515
1616
Generates custom exception messages based on request parameters.
1717
"""
18-
19-
def __init__(self, request, host, error_code, error):
20-
"""Return request data as dictionary."""
21-
self.data = {'beaconId': '.'.join(reversed(host.split('.'))),
22-
"apiVersion": __apiVersion__,
23-
'exists': None,
24-
'error': {'errorCode': error_code,
25-
'errorMessage': error},
26-
'alleleRequest': {'referenceName': request.get("referenceName", None),
27-
'referenceBases': request.get("referenceBases", None),
28-
'includeDatasetResponses': request.get("includeDatasetResponses", "NONE"),
29-
'assemblyId': request.get("assemblyId", None)},
30-
# showing empty datasetsAlleRsponse as no datasets found
31-
# A null/None would represent no data while empty array represents
32-
# none found or error and corresponds with exists null/None
33-
'datasetAlleleResponses': []}
34-
# include datasetIds only if they are specified
35-
# as per specification if they don't exist all datatsets will be queried
36-
# Only one of `alternateBases` or `variantType` is required, validated by schema
37-
oneof_fields = ["alternateBases", "variantType", "start", "end", "startMin", "startMax",
38-
"endMin", "endMax", "datasetIds"]
39-
self.data['alleleRequest'].update({k: request.get(k) for k in oneof_fields if k in request})
40-
return self.data
41-
42-
43-
class BeaconBadRequest(BeaconError):
18+
data = {'beaconId': '.'.join(reversed(host.split('.'))),
19+
"apiVersion": __apiVersion__,
20+
'exists': None,
21+
'error': {'errorCode': error_code,
22+
'errorMessage': error},
23+
'alleleRequest': {'referenceName': request.get("referenceName", None),
24+
'referenceBases': request.get("referenceBases", None),
25+
'includeDatasetResponses': request.get("includeDatasetResponses", "NONE"),
26+
'assemblyId': request.get("assemblyId", None)},
27+
# showing empty datasetsAlleRsponse as no datasets found
28+
# A null/None would represent no data while empty array represents
29+
# none found or error and corresponds with exists null/None
30+
'datasetAlleleResponses': []}
31+
# include datasetIds only if they are specified
32+
# as per specification if they don't exist all datatsets will be queried
33+
# Only one of `alternateBases` or `variantType` is required, validated by schema
34+
oneof_fields = ["alternateBases", "variantType", "start", "end", "startMin", "startMax",
35+
"endMin", "endMax", "datasetIds"]
36+
data['alleleRequest'].update({k: request.get(k) for k in oneof_fields if k in request})
37+
38+
return data
39+
40+
41+
class BeaconBadRequest(web.HTTPBadRequest):
4442
"""Exception returns with 400 code and a custom error message.
4543
4644
The method is called if one of the required parameters are missing or invalid.
@@ -49,13 +47,12 @@ class BeaconBadRequest(BeaconError):
4947

5048
def __init__(self, request, host, error):
5149
"""Return custom bad request exception."""
52-
data = super().__init__(request, host, 400, error)
53-
54-
LOG.error(f'400 ERROR MESSAGE: {error}')
55-
raise web.HTTPBadRequest(content_type="application/json", text=json.dumps(data))
50+
data = process_exception_data(request, host, 400, error)
51+
super().__init__(text=json.dumps(data), content_type="application/json")
52+
LOG.error(f'401 ERROR MESSAGE: {error}')
5653

5754

58-
class BeaconUnauthorised(BeaconError):
55+
class BeaconUnauthorised(web.HTTPUnauthorized):
5956
"""HTTP Exception returns with 401 code with a custom error message.
6057
6158
The method is called if the user is not registered or if the token from the authentication has expired.
@@ -64,17 +61,17 @@ class BeaconUnauthorised(BeaconError):
6461

6562
def __init__(self, request, host, error, error_message):
6663
"""Return custom unauthorized exception."""
67-
data = super().__init__(request, host, 401, error)
64+
data = process_exception_data(request, host, 401, error)
6865
headers_401 = {"WWW-Authenticate": f"Bearer realm=\"{CONFIG_INFO.url}\"\n\
6966
error=\"{error}\"\n\
7067
error_description=\"{error_message}\""}
68+
super().__init__(content_type="application/json", text=json.dumps(data),
69+
# we use auth scheme Bearer by default
70+
headers=headers_401)
7171
LOG.error(f'401 ERROR MESSAGE: {error}')
72-
raise web.HTTPUnauthorized(content_type="application/json", text=json.dumps(data),
73-
# we use auth scheme Bearer by default
74-
headers=headers_401)
7572

7673

77-
class BeaconForbidden(BeaconError):
74+
class BeaconForbidden(web.HTTPForbidden):
7875
"""HTTP Exception returns with 403 code with the error message.
7976
8077
`'Resource not granted for authenticated user or resource protected for all users.'`.
@@ -84,13 +81,12 @@ class BeaconForbidden(BeaconError):
8481

8582
def __init__(self, request, host, error):
8683
"""Return custom forbidden exception."""
87-
data = super().__init__(request, host, 403, error)
88-
84+
data = process_exception_data(request, host, 403, error)
85+
super().__init__(content_type="application/json", text=json.dumps(data))
8986
LOG.error(f'403 ERROR MESSAGE: {error}')
90-
raise web.HTTPForbidden(content_type="application/json", text=json.dumps(data))
9187

9288

93-
class BeaconServerError(BeaconError):
89+
class BeaconServerError(web.HTTPInternalServerError):
9490
"""HTTP Exception returns with 500 code with the error message.
9591
9692
The 500 error is not specified by the Beacon API, thus as simple error would do.
@@ -100,6 +96,5 @@ def __init__(self, error):
10096
"""Return custom forbidden exception."""
10197
data = {'errorCode': 500,
10298
'errorMessage': error}
103-
99+
super().__init__(content_type="application/json", text=json.dumps(data))
104100
LOG.error(f'500 ERROR MESSAGE: {error}')
105-
raise web.HTTPInternalServerError(content_type="application/json", text=json.dumps(data))

beacon_api/api/info.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from .. import __apiVersion__, __title__, __version__, __description__, __url__, __alturl__, __handover_beacon__
1010
from .. import __createtime__, __updatetime__, __org_id__, __org_name__, __org_description__
1111
from .. import __org_address__, __org_logoUrl__, __org_welcomeUrl__, __org_info__, __org_contactUrl__
12-
from .. import __sample_queries__, __handover_drs__, __docs_url__
12+
from .. import __sample_queries__, __handover_drs__, __docs_url__, __service_type__, __service_env__
1313
from ..utils.data_query import fetch_dataset_metadata
1414
from ..extensions.handover import make_handover
1515
from aiocache import cached
@@ -26,13 +26,18 @@ async def ga4gh_info(host):
2626
# TO DO implement some fallback mechanism for ID
2727
'id': '.'.join(reversed(host.split('.'))),
2828
'name': __title__,
29-
"type": "urn:ga4gh:beacon",
29+
"type": __service_type__,
3030
'description': __description__,
31-
'documentationUrl': __docs_url__,
32-
"organization": __org_id__,
31+
"organization": {
32+
"name": __org_name__,
33+
"url": __org_welcomeUrl__,
34+
},
3335
'contactUrl': __org_contactUrl__,
34-
'version': __version__,
35-
"extension": {}
36+
'documentationUrl': __docs_url__,
37+
'createdAt': __createtime__,
38+
'updatedAt': __updatetime__,
39+
'environment': __service_env__,
40+
'version': __version__
3641
}
3742
return beacon_info
3843

beacon_api/app.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ async def initialize(app):
8282

8383
async def destroy(app):
8484
"""Upon server close, close the DB connection pool."""
85-
await app['pool'].close()
85+
# will defer this to asyncpg
86+
await app['pool'].close() # pragma: no cover
8687

8788

8889
def set_cors(server):

beacon_api/conf/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ def parse_config_file(path):
3333
'url': config.get('beacon_api_info', 'url'),
3434
'alturl': config.get('beacon_api_info', 'alturl'),
3535
'createtime': config.get('beacon_api_info', 'createtime'),
36+
'service_type': config.get('beacon_api_info', 'service_type'),
37+
'environment': config.get('beacon_api_info', 'environment'),
3638
'org_id': config.get('organisation_info', 'org_id'),
3739
'org_name': config.get('organisation_info', 'org_name'),
3840
'org_description': config.get('organisation_info', 'org_description'),

beacon_api/conf/config.ini

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
title=GA4GHBeacon at CSC
88

99
# Version of the Beacon implementation
10-
version=1.4.0
10+
version=1.5.0
1111

1212
# Author of this software
1313
author=CSC developers
@@ -41,6 +41,13 @@ alturl=
4141
# Datetime when this Beacon was created
4242
createtime=2018-07-25T00:00:00Z
4343

44+
# GA4GH Discovery type `groupId` and `artifactId`, joined in /service-info with apiVersion
45+
# See https://github.com/ga4gh-discovery/ga4gh-service-registry for more information and possible values
46+
service_type=org.ga4gh:beacon
47+
48+
# GA4GH Discovery server environment, possible values: prod, dev, test
49+
environment=prod
50+
4451

4552
[organisation_info]
4653
# Globally unique identifier for organisation that hosts this Beacon service

beacon_api/extensions/handover.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def make_handover(paths, datasetIds, chr='', start=0, end=0, ref='', alt='', var
2222
for dataset in set(datasetIds):
2323
handovers.append({"handoverType": {"id": "CUSTOM", "label": label},
2424
"description": desc,
25-
"url": __handover_drs__ + path.format(dataset=dataset, chr=chr, start=start,
26-
end=end, ref=ref, alt=alt)})
25+
"url": __handover_drs__ + "/" + path.format(dataset=dataset, chr=chr, start=start,
26+
end=end, ref=ref, alt=alt)})
2727

2828
return handovers

0 commit comments

Comments
 (0)