nci-cidc-api-modules 1.0.0__py3-none-any.whl → 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cidc_api/config/db.py +1 -1
- cidc_api/config/secrets.py +2 -2
- cidc_api/config/settings.py +1 -2
- cidc_api/csms/auth.py +14 -7
- cidc_api/models/csms_api.py +101 -83
- cidc_api/models/files/details.py +18 -38
- cidc_api/models/files/facets.py +6 -6
- cidc_api/models/migrations.py +16 -9
- cidc_api/models/models.py +186 -158
- cidc_api/shared/auth.py +18 -13
- cidc_api/shared/gcloud_client.py +75 -73
- cidc_api/shared/rest_utils.py +6 -5
- {nci_cidc_api_modules-1.0.0.dist-info → nci_cidc_api_modules-1.0.1.dist-info}/METADATA +1 -1
- nci_cidc_api_modules-1.0.1.dist-info/RECORD +25 -0
- {nci_cidc_api_modules-1.0.0.dist-info → nci_cidc_api_modules-1.0.1.dist-info}/WHEEL +1 -1
- nci_cidc_api_modules-1.0.0.dist-info/RECORD +0 -25
- {nci_cidc_api_modules-1.0.0.dist-info → nci_cidc_api_modules-1.0.1.dist-info}/LICENSE +0 -0
- {nci_cidc_api_modules-1.0.0.dist-info → nci_cidc_api_modules-1.0.1.dist-info}/top_level.txt +0 -0
cidc_api/shared/auth.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
from functools import wraps
|
2
|
-
from packaging import version
|
3
2
|
from typing import List
|
4
3
|
|
5
4
|
import requests
|
5
|
+
from packaging import version
|
6
6
|
from jose import jwt
|
7
7
|
from flask import g, request, current_app as app, Flask
|
8
8
|
from werkzeug.exceptions import Unauthorized, BadRequest, PreconditionFailed
|
@@ -13,6 +13,8 @@ from ..config.logging import get_logger
|
|
13
13
|
|
14
14
|
logger = get_logger(__name__)
|
15
15
|
|
16
|
+
TIMEOUT_IN_SECONDS = 20
|
17
|
+
|
16
18
|
|
17
19
|
### Main auth utility functions ###
|
18
20
|
def validate_api_auth(app: Flask):
|
@@ -32,7 +34,7 @@ def validate_api_auth(app: Flask):
|
|
32
34
|
)
|
33
35
|
|
34
36
|
|
35
|
-
def requires_auth(resource: str, allowed_roles: list =
|
37
|
+
def requires_auth(resource: str, allowed_roles: list = None):
|
36
38
|
"""
|
37
39
|
A decorator that adds authentication and basic access to an endpoint.
|
38
40
|
|
@@ -43,6 +45,9 @@ def requires_auth(resource: str, allowed_roles: list = []):
|
|
43
45
|
Unauthorized if unauthorized
|
44
46
|
"""
|
45
47
|
|
48
|
+
if allowed_roles is None:
|
49
|
+
allowed_roles = []
|
50
|
+
|
46
51
|
def decorator(endpoint):
|
47
52
|
# Store metadata on this function stating that it is protected by authentication
|
48
53
|
endpoint.is_protected = True
|
@@ -159,11 +164,11 @@ def _extract_token() -> str:
|
|
159
164
|
assert bearer.lower() == "bearer"
|
160
165
|
else:
|
161
166
|
id_token = request.json["id_token"]
|
162
|
-
except (AssertionError, AttributeError, KeyError, TypeError, ValueError):
|
167
|
+
except (AssertionError, AttributeError, KeyError, TypeError, ValueError) as exc:
|
163
168
|
raise Unauthorized(
|
164
169
|
"Either the 'Authorization' header must be set with structure 'Authorization: Bearer <id token>' "
|
165
170
|
'or "id_token" must be present in the JSON body of the request.'
|
166
|
-
)
|
171
|
+
) from exc
|
167
172
|
|
168
173
|
return id_token
|
169
174
|
|
@@ -184,11 +189,11 @@ def _get_issuer_public_key(token: str) -> dict:
|
|
184
189
|
try:
|
185
190
|
header = jwt.get_unverified_header(token)
|
186
191
|
except jwt.JWTError as e:
|
187
|
-
raise Unauthorized(str(e))
|
192
|
+
raise Unauthorized(str(e)) from e
|
188
193
|
|
189
194
|
# Get public keys from our Auth0 domain
|
190
195
|
jwks_url = f"https://{AUTH0_DOMAIN}/.well-known/jwks.json"
|
191
|
-
jwks = requests.get(jwks_url).json()
|
196
|
+
jwks = requests.get(jwks_url, timeout=TIMEOUT_IN_SECONDS).json()
|
192
197
|
|
193
198
|
# Obtain the public key used to sign this token
|
194
199
|
public_key = None
|
@@ -198,7 +203,7 @@ def _get_issuer_public_key(token: str) -> dict:
|
|
198
203
|
|
199
204
|
# If no matching public key was found, we can't validate the token
|
200
205
|
if not public_key:
|
201
|
-
raise Unauthorized("Found no public key with id
|
206
|
+
raise Unauthorized(f"Found no public key with id {header['kid']}")
|
202
207
|
|
203
208
|
return public_key
|
204
209
|
|
@@ -233,11 +238,11 @@ def _decode_id_token(token: str, public_key: dict) -> dict:
|
|
233
238
|
except jwt.ExpiredSignatureError as e:
|
234
239
|
raise Unauthorized(
|
235
240
|
f"{e} Token expired. Obtain a new login token from the CIDC Portal, then try logging in again."
|
236
|
-
)
|
241
|
+
) from e
|
237
242
|
except jwt.JWTClaimsError as e:
|
238
|
-
raise Unauthorized(str(e))
|
243
|
+
raise Unauthorized(str(e)) from e
|
239
244
|
except jwt.JWTError as e:
|
240
|
-
raise Unauthorized(str(e))
|
245
|
+
raise Unauthorized(str(e)) from e
|
241
246
|
|
242
247
|
# Currently, only id_tokens are accepted for authentication.
|
243
248
|
# Going forward, we could also accept access tokens that we
|
@@ -340,9 +345,9 @@ def _enforce_cli_version():
|
|
340
345
|
|
341
346
|
try:
|
342
347
|
client, client_version = user_agent.split("/", 1)
|
343
|
-
except ValueError:
|
344
|
-
logger.error(
|
345
|
-
raise BadRequest("could not parse User-Agent string")
|
348
|
+
except ValueError as exc:
|
349
|
+
logger.error("Unrecognized user-agent string format: %s", user_agent)
|
350
|
+
raise BadRequest("could not parse User-Agent string") from exc
|
346
351
|
|
347
352
|
# The CLI sets the User-Agent header to `cidc-cli/{version}`,
|
348
353
|
# so we can assess whether the requester needs to update their CLI.
|
cidc_api/shared/gcloud_client.py
CHANGED
@@ -1,23 +1,21 @@
|
|
1
1
|
"""Utilities for interacting with the Google Cloud Platform APIs."""
|
2
|
+
|
3
|
+
# pylint: disable=logging-fstring-interpolation
|
4
|
+
|
2
5
|
import json
|
3
6
|
import os
|
4
7
|
from os import environ
|
5
|
-
|
6
|
-
from cidc_api.config.secrets import get_secrets_manager
|
7
|
-
|
8
|
-
os.environ["TZ"] = "UTC"
|
8
|
+
import base64
|
9
9
|
import datetime
|
10
10
|
import warnings
|
11
11
|
import hashlib
|
12
12
|
from collections import namedtuple
|
13
13
|
from concurrent.futures import Future
|
14
|
-
from sqlalchemy.orm.session import Session
|
15
14
|
from typing import (
|
16
15
|
Any,
|
17
16
|
BinaryIO,
|
18
17
|
Callable,
|
19
18
|
Dict,
|
20
|
-
Iterable,
|
21
19
|
List,
|
22
20
|
Optional,
|
23
21
|
Set,
|
@@ -25,15 +23,18 @@ from typing import (
|
|
25
23
|
Union,
|
26
24
|
)
|
27
25
|
|
28
|
-
import
|
26
|
+
from werkzeug.datastructures import FileStorage
|
27
|
+
from sqlalchemy.orm.session import Session
|
29
28
|
from google.cloud import storage, pubsub, bigquery
|
30
29
|
from google.cloud.bigquery.enums import EntityTypes
|
31
30
|
from google.oauth2.service_account import Credentials
|
32
|
-
from google.auth.credentials import AnonymousCredentials
|
33
|
-
from werkzeug.datastructures import FileStorage
|
34
|
-
import googleapiclient.discovery
|
35
31
|
from google.api_core.iam import Policy
|
36
32
|
from google.api_core.client_options import ClientOptions
|
33
|
+
import googleapiclient.discovery
|
34
|
+
import requests
|
35
|
+
|
36
|
+
from cidc_schemas.prism.constants import ASSAY_TO_FILEPATH
|
37
|
+
from cidc_api.config.secrets import get_secrets_manager
|
37
38
|
|
38
39
|
from ..config.settings import (
|
39
40
|
DEV_USE_GCS,
|
@@ -57,42 +58,43 @@ from ..config.settings import (
|
|
57
58
|
)
|
58
59
|
from ..config.logging import get_logger
|
59
60
|
|
60
|
-
|
61
|
-
|
61
|
+
os.environ["TZ"] = "UTC"
|
62
62
|
logger = get_logger(__name__)
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
TIMEOUT_IN_SECONDS = 20
|
65
|
+
|
66
|
+
# these should be initialized here or used as cached values
|
67
|
+
STORAGE_CLIENT = None
|
68
|
+
BIGQUERY_CLIENT = None
|
69
|
+
CRM_SERVICE = None
|
67
70
|
|
68
71
|
# The Secret Manager object should only be initiated once and reused.
|
69
72
|
# This is due to the fact that every time this object is initiated, a
|
70
73
|
# Google Cloud client is also initiated, which is an expensive handshake
|
71
74
|
# that significantly adds to the latency of the script.
|
72
|
-
|
73
|
-
secret_manager = get_secrets_manager()
|
75
|
+
SECRET_MANAGER = get_secrets_manager(TESTING)
|
74
76
|
|
75
77
|
|
76
78
|
def _get_storage_client() -> storage.Client:
|
77
|
-
global
|
78
|
-
if
|
79
|
+
global STORAGE_CLIENT
|
80
|
+
if STORAGE_CLIENT is None:
|
79
81
|
logger.debug("Getting local client")
|
80
82
|
if os.environ.get("DEV_GOOGLE_STORAGE", None):
|
81
|
-
secret_manager = get_secrets_manager()
|
82
83
|
client_options = ClientOptions(
|
83
84
|
api_endpoint=os.environ.get("DEV_GOOGLE_STORAGE")
|
84
85
|
)
|
85
86
|
credentials = Credentials.from_service_account_info(
|
86
|
-
json.loads(
|
87
|
+
json.loads(SECRET_MANAGER.get("APP_ENGINE_CREDENTIALS"))
|
87
88
|
)
|
88
|
-
|
89
|
+
STORAGE_CLIENT = storage.Client(
|
89
90
|
client_options=client_options, credentials=credentials
|
90
91
|
)
|
91
|
-
logger.debug(f"Local client set to {
|
92
|
-
return
|
93
|
-
|
94
|
-
|
95
|
-
|
92
|
+
logger.debug(f"Local client set to {STORAGE_CLIENT}")
|
93
|
+
return STORAGE_CLIENT
|
94
|
+
|
95
|
+
return _get_storage_client2()
|
96
|
+
|
97
|
+
return STORAGE_CLIENT
|
96
98
|
|
97
99
|
|
98
100
|
def _get_storage_client2() -> storage.Client:
|
@@ -102,36 +104,36 @@ def _get_storage_client2() -> storage.Client:
|
|
102
104
|
|
103
105
|
directly providing service account credentials for signing in get_signed_url() below
|
104
106
|
"""
|
105
|
-
global
|
106
|
-
if
|
107
|
+
global STORAGE_CLIENT
|
108
|
+
if STORAGE_CLIENT is None:
|
107
109
|
credentials = Credentials.from_service_account_info(
|
108
|
-
json.loads(
|
110
|
+
json.loads(SECRET_MANAGER.get(environ.get("APP_ENGINE_CREDENTIALS_ID")))
|
109
111
|
)
|
110
112
|
# client_options = ClientOptions(api_endpoint=os.environ.get("DEV_GOOGLE_STORAGE"))
|
111
|
-
#
|
112
|
-
|
113
|
-
return
|
113
|
+
# STORAGE_CLIENT = storage.Client(client_options=client_options, credentials=credentials)
|
114
|
+
STORAGE_CLIENT = storage.Client(credentials=credentials)
|
115
|
+
return STORAGE_CLIENT
|
114
116
|
|
115
117
|
|
116
118
|
def _get_crm_service() -> googleapiclient.discovery.Resource:
|
117
119
|
"""
|
118
120
|
Initializes a Cloud Resource Manager service.
|
119
121
|
"""
|
120
|
-
global
|
121
|
-
if
|
122
|
+
global CRM_SERVICE
|
123
|
+
if CRM_SERVICE is None:
|
122
124
|
credentials = Credentials.from_service_account_info(
|
123
|
-
json.loads(
|
125
|
+
json.loads(SECRET_MANAGER.get(environ.get("APP_ENGINE_CREDENTIALS_ID")))
|
124
126
|
)
|
125
|
-
|
127
|
+
CRM_SERVICE = googleapiclient.discovery.build(
|
126
128
|
"cloudresourcemanager", "v1", credentials=credentials
|
127
129
|
)
|
128
|
-
return
|
130
|
+
return CRM_SERVICE
|
129
131
|
|
130
132
|
|
131
133
|
def _get_bucket(bucket_name: str) -> storage.Bucket:
|
132
134
|
"""
|
133
135
|
Get the bucket with name `bucket_name` from GCS.
|
134
|
-
This does not make an HTTP request; it simply instantiates a bucket object owned by
|
136
|
+
This does not make an HTTP request; it simply instantiates a bucket object owned by STORAGE_CLIENT.
|
135
137
|
see: https://googleapis.dev/python/storage/latest/client.html#google.cloud.storage.client.Client.bucket
|
136
138
|
"""
|
137
139
|
storage_client = _get_storage_client()
|
@@ -160,26 +162,26 @@ def _get_bigquery_dataset(dataset_id: str) -> bigquery.Dataset:
|
|
160
162
|
Get the bigquery dataset with the id 'dataset_id'.
|
161
163
|
makes an API request to pull this with the bigquery client
|
162
164
|
"""
|
163
|
-
global
|
164
|
-
if
|
165
|
+
global BIGQUERY_CLIENT
|
166
|
+
if BIGQUERY_CLIENT is None:
|
165
167
|
credentials = Credentials.from_service_account_info(
|
166
|
-
json.loads(
|
168
|
+
json.loads(SECRET_MANAGER.get(environ.get("APP_ENGINE_CREDENTIALS_ID")))
|
167
169
|
)
|
168
170
|
# client_options = ClientOptions(api_endpoint=os.environ.get("DEV_GOOGLE_BIGQUERY"))
|
169
|
-
#
|
170
|
-
|
171
|
+
# BIGQUERY_CLIENT = bigquery.Client(client_options=client_options, credentials=credentials)
|
172
|
+
BIGQUERY_CLIENT = bigquery.Client(credentials=credentials)
|
171
173
|
|
172
|
-
dataset =
|
174
|
+
dataset = BIGQUERY_CLIENT.get_dataset(dataset_id) # Make an API request.
|
173
175
|
|
174
176
|
return dataset
|
175
177
|
|
176
178
|
|
177
|
-
|
179
|
+
XLSX_GCS_URI_FORMAT = (
|
178
180
|
"{trial_id}/xlsx/{template_category}/{template_type}/{upload_moment}.xlsx"
|
179
181
|
)
|
180
182
|
|
181
183
|
|
182
|
-
|
184
|
+
PseudoBblob = namedtuple(
|
183
185
|
"_pseudo_blob", ["name", "size", "md5_hash", "crc32c", "time_created"]
|
184
186
|
)
|
185
187
|
|
@@ -200,7 +202,7 @@ def upload_xlsx_to_gcs(
|
|
200
202
|
Returns:
|
201
203
|
arg1: GCS blob object
|
202
204
|
"""
|
203
|
-
blob_name =
|
205
|
+
blob_name = XLSX_GCS_URI_FORMAT.format(
|
204
206
|
trial_id=trial_id,
|
205
207
|
template_category=template_category,
|
206
208
|
template_type=template_type,
|
@@ -211,7 +213,7 @@ def upload_xlsx_to_gcs(
|
|
211
213
|
logger.info(
|
212
214
|
f"Would've saved {blob_name} to {GOOGLE_UPLOAD_BUCKET} and {GOOGLE_ACL_DATA_BUCKET}"
|
213
215
|
)
|
214
|
-
return
|
216
|
+
return PseudoBblob(
|
215
217
|
blob_name, 0, "_pseudo_md5_hash", "_pseudo_crc32c", upload_moment
|
216
218
|
)
|
217
219
|
|
@@ -410,7 +412,7 @@ def get_blob_names(
|
|
410
412
|
blob_list.extend(
|
411
413
|
storage_client.list_blobs(GOOGLE_ACL_DATA_BUCKET, prefix=prefix)
|
412
414
|
)
|
413
|
-
return
|
415
|
+
return {blob.name for blob in blob_list}
|
414
416
|
|
415
417
|
|
416
418
|
def grant_download_access_to_blob_names(
|
@@ -552,14 +554,11 @@ def _build_trial_upload_prefixes(
|
|
552
554
|
if not trial_id:
|
553
555
|
from ..models.models import TrialMetadata
|
554
556
|
|
555
|
-
trial_set =
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
)
|
561
|
-
]
|
562
|
-
)
|
557
|
+
trial_set = {
|
558
|
+
str(t.trial_id)
|
559
|
+
for t in session.query(TrialMetadata).add_columns(TrialMetadata.trial_id)
|
560
|
+
}
|
561
|
+
|
563
562
|
else:
|
564
563
|
trial_set = set([trial_id])
|
565
564
|
|
@@ -654,7 +653,7 @@ def grant_bigquery_iam_access(policy: Policy, user_emails: List[str]) -> None:
|
|
654
653
|
|
655
654
|
# try to set the new policy with edits
|
656
655
|
try:
|
657
|
-
|
656
|
+
CRM_SERVICE.projects().setIamPolicy(
|
658
657
|
resource=GOOGLE_CLOUD_PROJECT,
|
659
658
|
body={
|
660
659
|
"policy": policy,
|
@@ -677,7 +676,7 @@ def grant_bigquery_iam_access(policy: Policy, user_emails: List[str]) -> None:
|
|
677
676
|
)
|
678
677
|
)
|
679
678
|
dataset.access_entries = entries
|
680
|
-
|
679
|
+
BIGQUERY_CLIENT.update_dataset(dataset, ["access_entries"]) # Make an API request.
|
681
680
|
|
682
681
|
|
683
682
|
# Arbitrary upper bound on the number of GCS IAM bindings we expect a user to have for uploads
|
@@ -731,7 +730,7 @@ def revoke_bigquery_iam_access(policy: Policy, user_email: str) -> None:
|
|
731
730
|
# try update of the policy
|
732
731
|
try:
|
733
732
|
policy = (
|
734
|
-
|
733
|
+
CRM_SERVICE.projects()
|
735
734
|
.setIamPolicy(
|
736
735
|
resource=GOOGLE_CLOUD_PROJECT,
|
737
736
|
body={
|
@@ -753,14 +752,15 @@ def revoke_bigquery_iam_access(policy: Policy, user_email: str) -> None:
|
|
753
752
|
entry for entry in entries if entry.entity_id != user_email
|
754
753
|
]
|
755
754
|
|
756
|
-
dataset =
|
755
|
+
dataset = BIGQUERY_CLIENT.update_dataset(
|
757
756
|
dataset,
|
758
757
|
# Update just the `access_entries` property of the dataset.
|
759
758
|
["access_entries"],
|
760
759
|
) # Make an API request.
|
761
760
|
|
762
761
|
|
763
|
-
user_member
|
762
|
+
def user_member(email):
|
763
|
+
return f"user:{email}"
|
764
764
|
|
765
765
|
|
766
766
|
def _build_storage_iam_binding(
|
@@ -897,26 +897,28 @@ def _encode_and_publish(content: str, topic: str) -> Future:
|
|
897
897
|
logger.info(
|
898
898
|
f"Publishing message {content!r} to topic {DEV_CFUNCTIONS_SERVER}/{topic}"
|
899
899
|
)
|
900
|
-
import base64
|
901
900
|
|
902
901
|
bdata = base64.b64encode(content.encode("utf-8"))
|
903
902
|
try:
|
904
903
|
res = requests.post(
|
905
|
-
f"{DEV_CFUNCTIONS_SERVER}/{topic}",
|
904
|
+
f"{DEV_CFUNCTIONS_SERVER}/{topic}",
|
905
|
+
data={"data": bdata},
|
906
|
+
timeout=TIMEOUT_IN_SECONDS,
|
906
907
|
)
|
907
908
|
except Exception as e:
|
908
909
|
raise Exception(
|
909
910
|
f"Couldn't publish message {content!r} to topic {DEV_CFUNCTIONS_SERVER}/{topic}"
|
910
911
|
) from e
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
912
|
+
|
913
|
+
logger.info(f"Got {res}")
|
914
|
+
if res.status_code != 200:
|
915
|
+
raise Exception(
|
916
|
+
f"Couldn't publish message {content!r} to {DEV_CFUNCTIONS_SERVER}/{topic}: {res!r}"
|
917
|
+
)
|
918
|
+
|
917
919
|
else:
|
918
920
|
logger.info(f"Would've published message {content} to topic {topic}")
|
919
|
-
return
|
921
|
+
return None
|
920
922
|
|
921
923
|
# The Pub/Sub publisher client returns a concurrent.futures.Future
|
922
924
|
# containing info about whether the publishing was successful.
|
@@ -965,7 +967,7 @@ def send_email(to_emails: List[str], subject: str, html_content: str, **kw) -> N
|
|
965
967
|
|
966
968
|
logger.info(f"({ENV}) Sending email to {to_emails} with subject {subject}")
|
967
969
|
email_json = json.dumps(
|
968
|
-
|
970
|
+
{"to_emails": to_emails, "subject": subject, "html_content": html_content, **kw}
|
969
971
|
)
|
970
972
|
|
971
973
|
report = _encode_and_publish(email_json, GOOGLE_EMAILS_TOPIC)
|
cidc_api/shared/rest_utils.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Shared utility functions for building CIDC API resource endpoints."""
|
2
|
+
|
2
3
|
from functools import wraps
|
3
4
|
from typing import Optional, Callable, Union
|
4
5
|
|
@@ -6,6 +7,7 @@ from flask import request, jsonify
|
|
6
7
|
from webargs import fields
|
7
8
|
from webargs.flaskparser import use_args
|
8
9
|
from marshmallow import validate
|
10
|
+
from marshmallow.exceptions import ValidationError
|
9
11
|
from werkzeug.exceptions import (
|
10
12
|
PreconditionRequired,
|
11
13
|
PreconditionFailed,
|
@@ -13,7 +15,6 @@ from werkzeug.exceptions import (
|
|
13
15
|
BadRequest,
|
14
16
|
UnprocessableEntity,
|
15
17
|
)
|
16
|
-
from marshmallow.exceptions import ValidationError
|
17
18
|
|
18
19
|
from ..models import BaseModel, BaseSchema, ValidationMultiError
|
19
20
|
|
@@ -48,11 +49,11 @@ def unmarshal_request(schema: BaseSchema, kwarg_name: str, load_sqla: bool = Tru
|
|
48
49
|
loaded_instance.validate()
|
49
50
|
# The many ways that validation errors might get raised...
|
50
51
|
except ValueError as e:
|
51
|
-
raise UnprocessableEntity(str(e))
|
52
|
+
raise UnprocessableEntity(str(e)) from e
|
52
53
|
except ValidationError as e:
|
53
|
-
raise UnprocessableEntity(e.messages)
|
54
|
+
raise UnprocessableEntity(e.messages) from e
|
54
55
|
except ValidationMultiError as e:
|
55
|
-
raise UnprocessableEntity({"errors": e.args[0]})
|
56
|
+
raise UnprocessableEntity({"errors": e.args[0]}) from e
|
56
57
|
|
57
58
|
kwargs[kwarg_name] = body
|
58
59
|
|
@@ -186,7 +187,7 @@ def use_args_with_pagination(argmap: dict, model_schema: BaseSchema):
|
|
186
187
|
return {k: v for k, v in args.items() if k in argmap.keys()}
|
187
188
|
|
188
189
|
def get_pagination_args(args: dict):
|
189
|
-
return {k: v for k, v in args.items() if k in pagination_argmap
|
190
|
+
return {k: v for k, v in args.items() if k in pagination_argmap}
|
190
191
|
|
191
192
|
def decorator(endpoint):
|
192
193
|
@wraps(endpoint)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
cidc_api/config/__init__.py,sha256=5kcMGor07TrBm6e_UW6CCVnJu5wJ-8QX8X9ZRmeuPKA,147
|
2
|
+
cidc_api/config/db.py,sha256=ayeeNV-sV20hGoFyMMTMncI2V-FI9lVN3JV-Lmpr3xI,1981
|
3
|
+
cidc_api/config/logging.py,sha256=gJ2TGgQVREng4Hv0phlCCkQai7HhumKYjJxubpxS6Q0,1090
|
4
|
+
cidc_api/config/secrets.py,sha256=2DXeew1Pm0lnf2SLuo8wW5c5kOJp2WrhjflxZGsY_Ng,1505
|
5
|
+
cidc_api/config/settings.py,sha256=Ua6UpiQu9l1ZD-YlmpaWuQOv9tPyYLxWFLC2DEJdAyQ,4044
|
6
|
+
cidc_api/csms/__init__.py,sha256=eJkY6rWNOAUBmSd4G1_U6h7i472druKEtBdVmgFZVPg,20
|
7
|
+
cidc_api/csms/auth.py,sha256=25Yma2Kz3KLENAPSeBYacFuSZXng-EDgmgInKBsRyP0,3191
|
8
|
+
cidc_api/models/__init__.py,sha256=bl445G8Zic9YbhZ8ZBni07wtBMhLJRMBA-JqjLxx2bw,66
|
9
|
+
cidc_api/models/csms_api.py,sha256=Wp4b53vwOqSlOIaoAYGlI1p8ZfXRXmVJ6MLcsvzq0LA,31664
|
10
|
+
cidc_api/models/migrations.py,sha256=gp9vtkYbA9FFy2s-7woelAmsvQbJ41LO2_DY-YkFIrQ,11464
|
11
|
+
cidc_api/models/models.py,sha256=AYt0rIzaeQ0HHlTSeerbTpYwMJwqt93aadOuFLLEqBA,120820
|
12
|
+
cidc_api/models/schemas.py,sha256=7tDYtmULuzTt2kg7RorWhte06ffalgpQKrFiDRGcPEQ,2711
|
13
|
+
cidc_api/models/files/__init__.py,sha256=8BMTnUSHzUbz0lBeEQY6NvApxDD3GMWMduoVMos2g4Y,213
|
14
|
+
cidc_api/models/files/details.py,sha256=eg1u8uZwtxb0m9mFobcTL_mnPBMq1MPZv3NN3KWMGOI,62309
|
15
|
+
cidc_api/models/files/facets.py,sha256=5xI5eM1J0Uc97W0-MvAPSRRN-2Hs2xb_AIupliJRMJU,29172
|
16
|
+
cidc_api/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
+
cidc_api/shared/auth.py,sha256=VMd_3QJE2iG16QxuGzHBV9MzJJItOZNn9gcw0_iUBLI,11647
|
18
|
+
cidc_api/shared/emails.py,sha256=AhSp2hxWyuMpe21pERuQwrAEy0yCfy3rvSygDNKtgdc,4820
|
19
|
+
cidc_api/shared/gcloud_client.py,sha256=7dDs0crLMJKdIp4IDSfrZBMB3h-zvWNieB81azoeLO4,33746
|
20
|
+
cidc_api/shared/rest_utils.py,sha256=LMfBpvJRjkfQjCzVXuhTTe4Foz4wlvaKg6QntyR-Hkc,6648
|
21
|
+
nci_cidc_api_modules-1.0.1.dist-info/LICENSE,sha256=pNYWVTHaYonnmJyplmeAp7tQAjosmDpAWjb34jjv7Xs,1102
|
22
|
+
nci_cidc_api_modules-1.0.1.dist-info/METADATA,sha256=rhlYaotiiwG6djDCZaSq5GT9CcE_2YVlHs_gnSd9tgY,40275
|
23
|
+
nci_cidc_api_modules-1.0.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
24
|
+
nci_cidc_api_modules-1.0.1.dist-info/top_level.txt,sha256=rNiRzL0lJGi5Q9tY9uSoMdTbJ-7u5c_D2E86KA94yRA,9
|
25
|
+
nci_cidc_api_modules-1.0.1.dist-info/RECORD,,
|
@@ -1,25 +0,0 @@
|
|
1
|
-
cidc_api/config/__init__.py,sha256=5kcMGor07TrBm6e_UW6CCVnJu5wJ-8QX8X9ZRmeuPKA,147
|
2
|
-
cidc_api/config/db.py,sha256=cw0wTfcak_FyAKZE8oKVYLVkph644Lv-_MAbYutGV44,1978
|
3
|
-
cidc_api/config/logging.py,sha256=gJ2TGgQVREng4Hv0phlCCkQai7HhumKYjJxubpxS6Q0,1090
|
4
|
-
cidc_api/config/secrets.py,sha256=DlloiKiy420WJO250SfKs_-T0wKYPbgdwyPncP5MGLY,1518
|
5
|
-
cidc_api/config/settings.py,sha256=GyeGDCj1tt61u4fbhomPIDq_fiamRMIT6dQPY1ufl-w,4076
|
6
|
-
cidc_api/csms/__init__.py,sha256=eJkY6rWNOAUBmSd4G1_U6h7i472druKEtBdVmgFZVPg,20
|
7
|
-
cidc_api/csms/auth.py,sha256=upJ52PX-u1KD4I0-Hy4JptCp_cp6qgs_ZMXPxqfeVI0,3071
|
8
|
-
cidc_api/models/__init__.py,sha256=bl445G8Zic9YbhZ8ZBni07wtBMhLJRMBA-JqjLxx2bw,66
|
9
|
-
cidc_api/models/csms_api.py,sha256=ubT-1Z5ajh5slAZ1jUvY8_0xKKl-7AVMwpQy0QT08eU,31379
|
10
|
-
cidc_api/models/migrations.py,sha256=Z2ycc-0XWdOnfDmCPFNXdg0lHLL8sDLIlhJHiEeYrv8,11292
|
11
|
-
cidc_api/models/models.py,sha256=7VQHkvDDCFoAiZR7Rz6J5roaLoG16LtlgFlkC5ocDGc,119987
|
12
|
-
cidc_api/models/schemas.py,sha256=7tDYtmULuzTt2kg7RorWhte06ffalgpQKrFiDRGcPEQ,2711
|
13
|
-
cidc_api/models/files/__init__.py,sha256=8BMTnUSHzUbz0lBeEQY6NvApxDD3GMWMduoVMos2g4Y,213
|
14
|
-
cidc_api/models/files/details.py,sha256=YP0vvc3QsDzcZlBbYeNjzkprVaDrYHHnspdsSAm_a2g,63531
|
15
|
-
cidc_api/models/files/facets.py,sha256=pigQhlQwuFyJ6Im8QByfaeeX36px2kKA77coQsJ8wM8,29169
|
16
|
-
cidc_api/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
-
cidc_api/shared/auth.py,sha256=KOmakviByttH4b3yFw0zmcjciA-245AWabz8qatIcRQ,11475
|
18
|
-
cidc_api/shared/emails.py,sha256=AhSp2hxWyuMpe21pERuQwrAEy0yCfy3rvSygDNKtgdc,4820
|
19
|
-
cidc_api/shared/gcloud_client.py,sha256=jd1R7PNhuo1ULBFhqI6E1wRGLC7DzfPxLzbZ5yE38cU,33839
|
20
|
-
cidc_api/shared/rest_utils.py,sha256=MCUypPmOOHKQR-vThkSMj8Fe8m_lZmv85jViCxZgGPE,6633
|
21
|
-
nci_cidc_api_modules-1.0.0.dist-info/LICENSE,sha256=pNYWVTHaYonnmJyplmeAp7tQAjosmDpAWjb34jjv7Xs,1102
|
22
|
-
nci_cidc_api_modules-1.0.0.dist-info/METADATA,sha256=c5Vszw16CSoCI4NvE1pTXXV6k-bRJEHwr-NSAF3OCCY,40275
|
23
|
-
nci_cidc_api_modules-1.0.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
24
|
-
nci_cidc_api_modules-1.0.0.dist-info/top_level.txt,sha256=rNiRzL0lJGi5Q9tY9uSoMdTbJ-7u5c_D2E86KA94yRA,9
|
25
|
-
nci_cidc_api_modules-1.0.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|