nmdc-runtime 2.8.0__py3-none-any.whl → 2.10.0__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.
Potentially problematic release.
This version of nmdc-runtime might be problematic. Click here for more details.
- nmdc_runtime/api/__init__.py +0 -0
- nmdc_runtime/api/analytics.py +70 -0
- nmdc_runtime/api/boot/__init__.py +0 -0
- nmdc_runtime/api/boot/capabilities.py +9 -0
- nmdc_runtime/api/boot/object_types.py +126 -0
- nmdc_runtime/api/boot/triggers.py +84 -0
- nmdc_runtime/api/boot/workflows.py +116 -0
- nmdc_runtime/api/core/__init__.py +0 -0
- nmdc_runtime/api/core/auth.py +208 -0
- nmdc_runtime/api/core/idgen.py +170 -0
- nmdc_runtime/api/core/metadata.py +788 -0
- nmdc_runtime/api/core/util.py +109 -0
- nmdc_runtime/api/db/__init__.py +0 -0
- nmdc_runtime/api/db/mongo.py +447 -0
- nmdc_runtime/api/db/s3.py +37 -0
- nmdc_runtime/api/endpoints/__init__.py +0 -0
- nmdc_runtime/api/endpoints/capabilities.py +25 -0
- nmdc_runtime/api/endpoints/find.py +794 -0
- nmdc_runtime/api/endpoints/ids.py +192 -0
- nmdc_runtime/api/endpoints/jobs.py +143 -0
- nmdc_runtime/api/endpoints/lib/__init__.py +0 -0
- nmdc_runtime/api/endpoints/lib/helpers.py +274 -0
- nmdc_runtime/api/endpoints/lib/path_segments.py +165 -0
- nmdc_runtime/api/endpoints/metadata.py +260 -0
- nmdc_runtime/api/endpoints/nmdcschema.py +581 -0
- nmdc_runtime/api/endpoints/object_types.py +38 -0
- nmdc_runtime/api/endpoints/objects.py +277 -0
- nmdc_runtime/api/endpoints/operations.py +105 -0
- nmdc_runtime/api/endpoints/queries.py +679 -0
- nmdc_runtime/api/endpoints/runs.py +98 -0
- nmdc_runtime/api/endpoints/search.py +38 -0
- nmdc_runtime/api/endpoints/sites.py +229 -0
- nmdc_runtime/api/endpoints/triggers.py +25 -0
- nmdc_runtime/api/endpoints/users.py +214 -0
- nmdc_runtime/api/endpoints/util.py +774 -0
- nmdc_runtime/api/endpoints/workflows.py +353 -0
- nmdc_runtime/api/main.py +401 -0
- nmdc_runtime/api/middleware.py +43 -0
- nmdc_runtime/api/models/__init__.py +0 -0
- nmdc_runtime/api/models/capability.py +14 -0
- nmdc_runtime/api/models/id.py +92 -0
- nmdc_runtime/api/models/job.py +37 -0
- nmdc_runtime/api/models/lib/__init__.py +0 -0
- nmdc_runtime/api/models/lib/helpers.py +78 -0
- nmdc_runtime/api/models/metadata.py +11 -0
- nmdc_runtime/api/models/minter.py +0 -0
- nmdc_runtime/api/models/nmdc_schema.py +146 -0
- nmdc_runtime/api/models/object.py +180 -0
- nmdc_runtime/api/models/object_type.py +20 -0
- nmdc_runtime/api/models/operation.py +66 -0
- nmdc_runtime/api/models/query.py +246 -0
- nmdc_runtime/api/models/query_continuation.py +111 -0
- nmdc_runtime/api/models/run.py +161 -0
- nmdc_runtime/api/models/site.py +87 -0
- nmdc_runtime/api/models/trigger.py +13 -0
- nmdc_runtime/api/models/user.py +140 -0
- nmdc_runtime/api/models/util.py +253 -0
- nmdc_runtime/api/models/workflow.py +15 -0
- nmdc_runtime/api/openapi.py +242 -0
- nmdc_runtime/config.py +55 -4
- nmdc_runtime/core/db/Database.py +1 -3
- nmdc_runtime/infrastructure/database/models/user.py +0 -9
- nmdc_runtime/lib/extract_nmdc_data.py +0 -8
- nmdc_runtime/lib/nmdc_dataframes.py +3 -7
- nmdc_runtime/lib/nmdc_etl_class.py +1 -7
- nmdc_runtime/minter/adapters/repository.py +1 -2
- nmdc_runtime/minter/config.py +2 -0
- nmdc_runtime/minter/domain/model.py +35 -1
- nmdc_runtime/minter/entrypoints/fastapi_app.py +1 -1
- nmdc_runtime/mongo_util.py +1 -2
- nmdc_runtime/site/backup/nmdcdb_mongodump.py +1 -1
- nmdc_runtime/site/backup/nmdcdb_mongoexport.py +1 -3
- nmdc_runtime/site/export/ncbi_xml.py +1 -2
- nmdc_runtime/site/export/ncbi_xml_utils.py +1 -1
- nmdc_runtime/site/graphs.py +33 -28
- nmdc_runtime/site/ops.py +97 -237
- nmdc_runtime/site/repair/database_updater.py +8 -0
- nmdc_runtime/site/repository.py +7 -117
- nmdc_runtime/site/resources.py +4 -4
- nmdc_runtime/site/translation/gold_translator.py +22 -21
- nmdc_runtime/site/translation/neon_benthic_translator.py +0 -1
- nmdc_runtime/site/translation/neon_soil_translator.py +4 -5
- nmdc_runtime/site/translation/neon_surface_water_translator.py +0 -2
- nmdc_runtime/site/translation/submission_portal_translator.py +64 -54
- nmdc_runtime/site/translation/translator.py +63 -1
- nmdc_runtime/site/util.py +8 -3
- nmdc_runtime/site/validation/util.py +10 -5
- nmdc_runtime/util.py +9 -321
- {nmdc_runtime-2.8.0.dist-info → nmdc_runtime-2.10.0.dist-info}/METADATA +57 -6
- nmdc_runtime-2.10.0.dist-info/RECORD +138 -0
- nmdc_runtime/site/translation/emsl.py +0 -43
- nmdc_runtime/site/translation/gold.py +0 -53
- nmdc_runtime/site/translation/jgi.py +0 -32
- nmdc_runtime/site/translation/util.py +0 -132
- nmdc_runtime/site/validation/jgi.py +0 -43
- nmdc_runtime-2.8.0.dist-info/RECORD +0 -84
- {nmdc_runtime-2.8.0.dist-info → nmdc_runtime-2.10.0.dist-info}/WHEEL +0 -0
- {nmdc_runtime-2.8.0.dist-info → nmdc_runtime-2.10.0.dist-info}/entry_points.txt +0 -0
- {nmdc_runtime-2.8.0.dist-info → nmdc_runtime-2.10.0.dist-info}/licenses/LICENSE +0 -0
- {nmdc_runtime-2.8.0.dist-info → nmdc_runtime-2.10.0.dist-info}/top_level.txt +0 -0
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Based on <https://github.com/tom-draper/api-analytics/tree/main/analytics/python/fastapi>
|
|
3
|
+
under MIT License <https://github.com/tom-draper/api-analytics/blob/main/analytics/python/fastapi/LICENSE>
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
import threading
|
|
8
|
+
from time import time
|
|
9
|
+
from typing import Dict, List
|
|
10
|
+
|
|
11
|
+
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
|
12
|
+
from starlette.requests import Request
|
|
13
|
+
from starlette.responses import Response
|
|
14
|
+
from starlette.types import ASGIApp
|
|
15
|
+
from toolz import merge
|
|
16
|
+
|
|
17
|
+
from nmdc_runtime.api.db.mongo import get_mongo_db
|
|
18
|
+
|
|
19
|
+
_requests = []
|
|
20
|
+
_last_posted = datetime.now()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _post_requests(collection: str, requests_data: List[Dict], source: str):
|
|
24
|
+
mdb = get_mongo_db()
|
|
25
|
+
mdb[collection].insert_many([merge(d, {"source": source}) for d in requests_data])
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def log_request(collection: str, request_data: Dict, source: str = "FastAPI"):
|
|
29
|
+
global _requests, _last_posted
|
|
30
|
+
_requests.append(request_data)
|
|
31
|
+
now = datetime.now()
|
|
32
|
+
# flush queue every minute at most
|
|
33
|
+
if (now - _last_posted).total_seconds() > 60.0:
|
|
34
|
+
threading.Thread(
|
|
35
|
+
target=_post_requests, args=(collection, _requests, source)
|
|
36
|
+
).start()
|
|
37
|
+
_requests = []
|
|
38
|
+
_last_posted = now
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Analytics(BaseHTTPMiddleware):
|
|
42
|
+
def __init__(self, app: ASGIApp, collection: str = "_runtime.analytics"):
|
|
43
|
+
super().__init__(app)
|
|
44
|
+
self.collection = collection
|
|
45
|
+
|
|
46
|
+
async def dispatch(
|
|
47
|
+
self, request: Request, call_next: RequestResponseEndpoint
|
|
48
|
+
) -> Response:
|
|
49
|
+
start = time()
|
|
50
|
+
response = await call_next(request)
|
|
51
|
+
|
|
52
|
+
# Build a dictionary that describes the incoming request.
|
|
53
|
+
#
|
|
54
|
+
# Note: `request.headers` is an instance of `MultiDict`. References:
|
|
55
|
+
# - https://www.starlette.io/requests/#headers
|
|
56
|
+
# - https://multidict.aio-libs.org/en/stable/multidict/
|
|
57
|
+
#
|
|
58
|
+
request_data = {
|
|
59
|
+
"hostname": request.url.hostname,
|
|
60
|
+
"ip_address": request.client.host,
|
|
61
|
+
"path": request.url.path,
|
|
62
|
+
"user_agent": request.headers.get("user-agent"),
|
|
63
|
+
"method": request.method,
|
|
64
|
+
"status": response.status_code,
|
|
65
|
+
"response_time": int((time() - start) * 1000),
|
|
66
|
+
"created_at": datetime.now().isoformat(),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
log_request(self.collection, request_data, "FastAPI")
|
|
70
|
+
return response
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from nmdc_runtime.api.models.capability import Capability
|
|
2
|
+
import nmdc_runtime.api.boot.workflows as workflows_boot
|
|
3
|
+
|
|
4
|
+
# Include 1-to-1 "I can run this workflow" capabilities.
|
|
5
|
+
_raw = [item for item in workflows_boot._raw]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def construct():
|
|
9
|
+
return [Capability(**kwargs) for kwargs in _raw]
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
|
|
3
|
+
from toolz import get_in
|
|
4
|
+
|
|
5
|
+
from nmdc_runtime.api.models.object_type import ObjectType
|
|
6
|
+
from nmdc_runtime.util import nmdc_jsonschema
|
|
7
|
+
|
|
8
|
+
_raw = [
|
|
9
|
+
{
|
|
10
|
+
"id": "read_qc_analysis_activity_set",
|
|
11
|
+
"created_at": datetime(2021, 9, 14, tzinfo=timezone.utc),
|
|
12
|
+
"name": "metaP analysis activity",
|
|
13
|
+
# "description": "JSON documents satisfying schema for readqc analysis activity",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "metagenome_sequencing_activity_set",
|
|
17
|
+
"created_at": datetime(2021, 9, 14, tzinfo=timezone.utc),
|
|
18
|
+
"name": "metaP analysis activity",
|
|
19
|
+
# "description": "JSON documents satisfying schema for metagenome sequencing activity",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "mags_activity_set",
|
|
23
|
+
"created_at": datetime(2021, 9, 14, tzinfo=timezone.utc),
|
|
24
|
+
"name": "metaP analysis activity",
|
|
25
|
+
# "description": "JSON documents satisfying schema for mags activity",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "metagenome_annotation_activity_set",
|
|
29
|
+
"created_at": datetime(2021, 9, 14, tzinfo=timezone.utc),
|
|
30
|
+
"name": "metaP analysis activity",
|
|
31
|
+
# "description": "JSON documents satisfying schema for metagenome annotation activity",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "metagenome_assembly_set",
|
|
35
|
+
"created_at": datetime(2021, 9, 14, tzinfo=timezone.utc),
|
|
36
|
+
"name": "metaP analysis activity",
|
|
37
|
+
# "description": "JSON documents satisfying schema for metagenome assembly activity",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "read_based_taxonomy_analysis_activity_set",
|
|
41
|
+
"created_at": datetime(2021, 9, 14, tzinfo=timezone.utc),
|
|
42
|
+
"name": "metaP analysis activity",
|
|
43
|
+
# "description": "JSON documents satisfying schema for read based analysis activity",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "metadata-in",
|
|
47
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
48
|
+
"name": "metadata submission",
|
|
49
|
+
"description": "Input to the portal ETL process",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "metaproteomics_analysis_activity_set",
|
|
53
|
+
"created_at": datetime(2021, 8, 23, tzinfo=timezone.utc),
|
|
54
|
+
"name": "metaP analysis activity",
|
|
55
|
+
"description": "JSON documents satisfying schema for metaproteomics analysis activity",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "metagenome_raw_paired_end_reads",
|
|
59
|
+
"created_at": datetime(2021, 8, 24, tzinfo=timezone.utc),
|
|
60
|
+
"name": "Metagenome Raw Paired-End Reads Workflow Input",
|
|
61
|
+
"description": "workflow input",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "metatranscriptome_raw_paired_end_reads",
|
|
65
|
+
"created_at": datetime(2021, 9, 7, tzinfo=timezone.utc),
|
|
66
|
+
"name": "Metatranscriptome Raw Paired-End Reads Workflow Input",
|
|
67
|
+
"description": "workflow input 2",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "gcms-metab-input",
|
|
71
|
+
"created_at": datetime(2021, 9, 7, tzinfo=timezone.utc),
|
|
72
|
+
"name": "Raw GCMS MetaB Input",
|
|
73
|
+
"description": "",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "gcms-metab-calibration",
|
|
77
|
+
"created_at": datetime(2021, 9, 7, tzinfo=timezone.utc),
|
|
78
|
+
"name": "Raw GCMS MetaB Calibration",
|
|
79
|
+
"description": "",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "nom-input",
|
|
83
|
+
"created_at": datetime(2021, 9, 7, tzinfo=timezone.utc),
|
|
84
|
+
"name": "Raw FTMS MetaB Input",
|
|
85
|
+
"description": "",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "test",
|
|
89
|
+
"created_at": datetime(2021, 9, 7, tzinfo=timezone.utc),
|
|
90
|
+
"name": "A test object type",
|
|
91
|
+
"description": "For use in unit and integration tests",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "metadata-changesheet",
|
|
95
|
+
"created_at": datetime(2021, 9, 30, tzinfo=timezone.utc),
|
|
96
|
+
"name": "metadata changesheet",
|
|
97
|
+
"description": "Specification for changes to existing metadata",
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
_raw.extend(
|
|
102
|
+
[
|
|
103
|
+
{
|
|
104
|
+
"id": key,
|
|
105
|
+
"created_at": datetime(2021, 9, 14, tzinfo=timezone.utc),
|
|
106
|
+
"name": key,
|
|
107
|
+
# "description": spec["description"],
|
|
108
|
+
}
|
|
109
|
+
for key, spec in nmdc_jsonschema["properties"].items()
|
|
110
|
+
if key.endswith("_set")
|
|
111
|
+
]
|
|
112
|
+
)
|
|
113
|
+
_raw.append(
|
|
114
|
+
{
|
|
115
|
+
"id": "schema#/definitions/Database",
|
|
116
|
+
"created_at": datetime(2021, 9, 14, tzinfo=timezone.utc),
|
|
117
|
+
"name": "Bundle of one or more metadata `*_set`s.",
|
|
118
|
+
"description": get_in(
|
|
119
|
+
["definitions", "Database", "description"], nmdc_jsonschema
|
|
120
|
+
),
|
|
121
|
+
}
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def construct():
|
|
126
|
+
return [ObjectType(**kwargs) for kwargs in _raw]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
|
|
3
|
+
from nmdc_runtime.api.models.trigger import Trigger
|
|
4
|
+
|
|
5
|
+
_raw = [
|
|
6
|
+
{
|
|
7
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
8
|
+
"object_type_id": "metadata-in",
|
|
9
|
+
"workflow_id": "metadata-in-1.0.0",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"created_at": datetime(2021, 9, 1, tzinfo=timezone.utc),
|
|
13
|
+
"object_type_id": "metaproteomics_analysis_activity_set",
|
|
14
|
+
"workflow_id": "metap-metadata-1.0.0",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"created_at": datetime(2021, 9, 1, tzinfo=timezone.utc),
|
|
18
|
+
"object_type_id": "metagenome_raw_paired_end_reads",
|
|
19
|
+
"workflow_id": "metag-1.0.0",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"created_at": datetime(2021, 9, 7, tzinfo=timezone.utc),
|
|
23
|
+
"object_type_id": "metatranscriptome_raw_paired_end_reads",
|
|
24
|
+
"workflow_id": "metat-1.0.0",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"created_at": datetime(2021, 9, 9, tzinfo=timezone.utc),
|
|
28
|
+
"object_type_id": "test",
|
|
29
|
+
"workflow_id": "test",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"created_at": datetime(2021, 9, 20, tzinfo=timezone.utc),
|
|
33
|
+
"object_type_id": "nom-input",
|
|
34
|
+
"workflow_id": "nom-1.0.0",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"created_at": datetime(2021, 9, 20, tzinfo=timezone.utc),
|
|
38
|
+
"object_type_id": "gcms-metab-input",
|
|
39
|
+
"workflow_id": "gcms-metab-1.0.0",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"created_at": datetime(2021, 9, 30, tzinfo=timezone.utc),
|
|
43
|
+
"object_type_id": "metadata-changesheet",
|
|
44
|
+
"workflow_id": "apply-changesheet-1.0.0",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"created_at": datetime(2022, 1, 20, tzinfo=timezone.utc),
|
|
48
|
+
"object_type_id": "metagenome_sequencing_activity_set",
|
|
49
|
+
"workflow_id": "mgrc-1.0.6",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"created_at": datetime(2022, 1, 20, tzinfo=timezone.utc),
|
|
53
|
+
"object_type_id": "metagenome_sequencing_activity_set",
|
|
54
|
+
"workflow_id": "metag-1.0.0",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"created_at": datetime(2022, 1, 20, tzinfo=timezone.utc),
|
|
58
|
+
"object_type_id": "metagenome_annotation_activity_set",
|
|
59
|
+
"workflow_id": "mags-1.0.4",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"created_at": datetime(2022, 1, 20, tzinfo=timezone.utc),
|
|
63
|
+
"object_type_id": "metagenome_assembly_set",
|
|
64
|
+
"workflow_id": "mgann-1.0.0",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"created_at": datetime(2022, 1, 20, tzinfo=timezone.utc),
|
|
68
|
+
"object_type_id": "read_qc_analysis_activity_set",
|
|
69
|
+
"workflow_id": "mgasm-1.0.3",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"created_at": datetime(2022, 1, 20, tzinfo=timezone.utc),
|
|
73
|
+
"object_type_id": "read_qc_analysis_activity_set",
|
|
74
|
+
"workflow_id": "mgrba-1.0.2",
|
|
75
|
+
},
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def construct():
|
|
80
|
+
models = []
|
|
81
|
+
for kwargs in _raw:
|
|
82
|
+
kwargs["id"] = f'{kwargs["object_type_id"]}--{kwargs["workflow_id"]}'
|
|
83
|
+
models.append(Trigger(**kwargs))
|
|
84
|
+
return models
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
|
|
3
|
+
from nmdc_runtime.api.models.workflow import Workflow
|
|
4
|
+
|
|
5
|
+
_raw = [
|
|
6
|
+
{
|
|
7
|
+
"id": "metag-1.0.0",
|
|
8
|
+
"created_at": datetime(2021, 8, 24, tzinfo=timezone.utc),
|
|
9
|
+
"name": "Metagenome Analysis Workflow (v1.0.0)",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"id": "readqc-1.0.6",
|
|
13
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
14
|
+
"name": "Reads QC Workflow (v1.0.1)",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "mags-1.0.4",
|
|
18
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
19
|
+
"name": "Read-based Analysis (v1.0.1)",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "mgrba-1.0.2",
|
|
23
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
24
|
+
"name": "Read-based Analysis (v1.0.1)",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "mgasm-1.0.3",
|
|
28
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
29
|
+
"name": "Metagenome Assembly (v1.0.1)",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "mgann-1.0.0",
|
|
33
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
34
|
+
"name": "Metagenome Annotation (v1.0.0)",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "mgasmbgen-1.0.1",
|
|
38
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
39
|
+
"name": "Metagenome Assembled Genomes (v1.0.2)",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "metat-0.0.2",
|
|
43
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
44
|
+
"name": "Metatranscriptome (v0.0.2)",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "metap-1.0.0",
|
|
48
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
49
|
+
"name": "Metaproteomic (v1.0.0)",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "metab-2.1.0",
|
|
53
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
54
|
+
"name": "Metabolomics (v2.1.0)",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "gold-translation-1.0.0",
|
|
58
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
59
|
+
"name": "GOLD db dump translation",
|
|
60
|
+
"description": "Transform metadata obtained from the JGI GOLD database.",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "metap-metadata-1.0.0",
|
|
64
|
+
"created_at": datetime(2021, 6, 1, tzinfo=timezone.utc),
|
|
65
|
+
"name": "metaP metadata ETL",
|
|
66
|
+
"description": "Ingest and validate metaP metadata",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "metadata-in-1.0.0",
|
|
70
|
+
"created_at": datetime(2021, 10, 12, tzinfo=timezone.utc),
|
|
71
|
+
"name": "general metadata ETL",
|
|
72
|
+
"description": "Validate and ingest metadata from JSON files",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "test",
|
|
76
|
+
"created_at": datetime(2021, 9, 9, tzinfo=timezone.utc),
|
|
77
|
+
"name": "A test workflow",
|
|
78
|
+
"description": "For use in unit and integration tests",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "gcms-metab-1.0.0",
|
|
82
|
+
"created_at": datetime(2021, 9, 20, tzinfo=timezone.utc),
|
|
83
|
+
"name": "GCMS-based metabolomics",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "nom-1.0.0",
|
|
87
|
+
"created_at": datetime(2021, 9, 20, tzinfo=timezone.utc),
|
|
88
|
+
"name": "Natural Organic Matter characterization",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "apply-changesheet-1.0.0",
|
|
92
|
+
"created_at": datetime(2021, 9, 30, tzinfo=timezone.utc),
|
|
93
|
+
"name": "apply metadata changesheet",
|
|
94
|
+
"description": "Validate and apply metadata changes from TSV/CSV files",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "export-study-biosamples-as-csv-1.0.0",
|
|
98
|
+
"created_at": datetime(2022, 6, 8, tzinfo=timezone.utc),
|
|
99
|
+
"name": "export study biosamples metadata as CSV",
|
|
100
|
+
"description": "Export study biosamples metadata as CSV",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "gold_study_to_database",
|
|
104
|
+
"created_at": datetime(2023, 2, 17, tzinfo=timezone.utc),
|
|
105
|
+
"name": "Get nmdc:Database for GOLD study",
|
|
106
|
+
"description": "For a given GOLD study ID, produce an nmdc:Database representing that study and related entities",
|
|
107
|
+
},
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def construct():
|
|
112
|
+
models = []
|
|
113
|
+
for kwargs in _raw:
|
|
114
|
+
kwargs["capability_ids"] = []
|
|
115
|
+
models.append(Workflow(**kwargs))
|
|
116
|
+
return models
|
|
File without changes
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime, timedelta, timezone
|
|
3
|
+
from typing import Optional, Dict
|
|
4
|
+
|
|
5
|
+
from fastapi import Depends
|
|
6
|
+
from fastapi.exceptions import HTTPException
|
|
7
|
+
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
|
|
8
|
+
from fastapi.param_functions import Form
|
|
9
|
+
from fastapi.security import (
|
|
10
|
+
OAuth2,
|
|
11
|
+
HTTPBasic,
|
|
12
|
+
HTTPBasicCredentials,
|
|
13
|
+
HTTPBearer,
|
|
14
|
+
HTTPAuthorizationCredentials,
|
|
15
|
+
)
|
|
16
|
+
from fastapi.security.utils import get_authorization_scheme_param
|
|
17
|
+
from jose import JWTError, jwt
|
|
18
|
+
from passlib.context import CryptContext
|
|
19
|
+
from pydantic import BaseModel
|
|
20
|
+
from starlette.requests import Request
|
|
21
|
+
from starlette.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED
|
|
22
|
+
|
|
23
|
+
ORCID_PRODUCTION_BASE_URL = "https://orcid.org"
|
|
24
|
+
|
|
25
|
+
SECRET_KEY = os.getenv("JWT_SECRET_KEY")
|
|
26
|
+
ALGORITHM = "HS256"
|
|
27
|
+
ORCID_NMDC_CLIENT_ID = os.getenv("ORCID_NMDC_CLIENT_ID")
|
|
28
|
+
ORCID_NMDC_CLIENT_SECRET = os.getenv("ORCID_NMDC_CLIENT_SECRET")
|
|
29
|
+
ORCID_BASE_URL = os.getenv("ORCID_BASE_URL", default=ORCID_PRODUCTION_BASE_URL)
|
|
30
|
+
|
|
31
|
+
# Define the JSON Web Key Set (JWKS) for ORCID.
|
|
32
|
+
#
|
|
33
|
+
# Note: The URL from which we got this dictionary is: https://orcid.org/oauth/jwks
|
|
34
|
+
# We got _that_ URL from the dictionary at: https://orcid.org/.well-known/openid-configuration
|
|
35
|
+
#
|
|
36
|
+
# TODO: Consider _live-loading_ this dictionary from the Internet.
|
|
37
|
+
#
|
|
38
|
+
ORCID_JWK = {
|
|
39
|
+
"e": "AQAB",
|
|
40
|
+
"kid": "production-orcid-org-7hdmdswarosg3gjujo8agwtazgkp1ojs",
|
|
41
|
+
"kty": "RSA",
|
|
42
|
+
"n": "jxTIntA7YvdfnYkLSN4wk__E2zf_wbb0SV_HLHFvh6a9ENVRD1_rHK0EijlBzikb-1rgDQihJETcgBLsMoZVQqGj8fDUUuxnVHsuGav_bf41PA7E_58HXKPrB2C0cON41f7K3o9TStKpVJOSXBrRWURmNQ64qnSSryn1nCxMzXpaw7VUo409ohybbvN6ngxVy4QR2NCC7Fr0QVdtapxD7zdlwx6lEwGemuqs_oG5oDtrRuRgeOHmRps2R6gG5oc-JqVMrVRv6F9h4ja3UgxCDBQjOVT1BFPWmMHnHCsVYLqbbXkZUfvP2sO1dJiYd_zrQhi-FtNth9qrLLv3gkgtwQ",
|
|
43
|
+
"use": "sig",
|
|
44
|
+
}
|
|
45
|
+
# If the application is using a _non-production_ ORCID environment, overwrite
|
|
46
|
+
# the "kid" and "n" values with those from the sandbox ORCID environment.
|
|
47
|
+
#
|
|
48
|
+
# Source: https://sandbox.orcid.org/oauth/jwks
|
|
49
|
+
#
|
|
50
|
+
if ORCID_BASE_URL != ORCID_PRODUCTION_BASE_URL:
|
|
51
|
+
ORCID_JWK["kid"] = "sandbox-orcid-org-3hpgosl3b6lapenh1ewsgdob3fawepoj"
|
|
52
|
+
ORCID_JWK["n"] = (
|
|
53
|
+
"pl-jp-kTAGf6BZUrWIYUJTvqqMVd4iAnoLS6vve-KNV0q8TxKvMre7oi9IulDcqTuJ1alHrZAIVlgrgFn88MKirZuTqHG6LCtEsr7qGD9XyVcz64oXrb9vx4FO9tLNQxvdnIWCIwyPAYWtPMHMSSD5oEVUtVL_5IaxfCJvU-FchdHiwfxvXMWmA-i3mcEEe9zggag2vUPPIqUwbPVUFNj2hE7UsZbasuIToEMFRZqSB6juc9zv6PEUueQ5hAJCEylTkzMwyBMibrt04TmtZk2w9DfKJR91555s2ZMstX4G_su1_FqQ6p9vgcuLQ6tCtrW77tta-Rw7McF_tyPmvnhQ"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
ORCID_JWS_VERITY_ALGORITHM = "RS256"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ClientCredentials(BaseModel):
|
|
60
|
+
client_id: str
|
|
61
|
+
client_secret: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TokenExpires(BaseModel):
|
|
65
|
+
days: Optional[int] = 1
|
|
66
|
+
hours: Optional[int] = 0
|
|
67
|
+
minutes: Optional[int] = 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
ACCESS_TOKEN_EXPIRES = TokenExpires(days=1, hours=0, minutes=0)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Token(BaseModel):
|
|
74
|
+
access_token: str
|
|
75
|
+
token_type: str
|
|
76
|
+
expires: Optional[TokenExpires] = None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TokenData(BaseModel):
|
|
80
|
+
subject: Optional[str] = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
84
|
+
|
|
85
|
+
credentials_exception = HTTPException(
|
|
86
|
+
status_code=HTTP_401_UNAUTHORIZED,
|
|
87
|
+
detail="Could not validate credentials",
|
|
88
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def verify_password(plain_password, hashed_password):
|
|
93
|
+
return pwd_context.verify(plain_password, hashed_password)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_password_hash(password):
|
|
97
|
+
return pwd_context.hash(password)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
|
101
|
+
to_encode = data.copy()
|
|
102
|
+
if expires_delta:
|
|
103
|
+
expire = datetime.now(timezone.utc) + expires_delta
|
|
104
|
+
else:
|
|
105
|
+
expire = datetime.now(timezone.utc) + timedelta(minutes=15)
|
|
106
|
+
to_encode.update({"exp": expire})
|
|
107
|
+
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
|
108
|
+
return encoded_jwt
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_access_token_expiration(token) -> datetime:
|
|
112
|
+
try:
|
|
113
|
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
114
|
+
return payload.get("exp")
|
|
115
|
+
except JWTError:
|
|
116
|
+
raise credentials_exception
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class OAuth2PasswordOrClientCredentialsBearer(OAuth2):
|
|
120
|
+
def __init__(
|
|
121
|
+
self,
|
|
122
|
+
tokenUrl: str,
|
|
123
|
+
scheme_name: Optional[str] = None,
|
|
124
|
+
scopes: Optional[Dict[str, str]] = None,
|
|
125
|
+
auto_error: bool = True,
|
|
126
|
+
):
|
|
127
|
+
if not scopes:
|
|
128
|
+
scopes = {}
|
|
129
|
+
flows = OAuthFlowsModel(
|
|
130
|
+
password={"tokenUrl": tokenUrl, "scopes": scopes},
|
|
131
|
+
clientCredentials={"tokenUrl": tokenUrl},
|
|
132
|
+
)
|
|
133
|
+
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
|
|
134
|
+
|
|
135
|
+
async def __call__(self, request: Request) -> Optional[str]:
|
|
136
|
+
authorization: str = request.headers.get("Authorization")
|
|
137
|
+
scheme, param = get_authorization_scheme_param(authorization)
|
|
138
|
+
if not authorization or scheme.lower() != "bearer":
|
|
139
|
+
if self.auto_error:
|
|
140
|
+
raise HTTPException(
|
|
141
|
+
status_code=HTTP_401_UNAUTHORIZED,
|
|
142
|
+
detail="Not authenticated",
|
|
143
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
print(request.url)
|
|
147
|
+
return None
|
|
148
|
+
return param
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
oauth2_scheme = OAuth2PasswordOrClientCredentialsBearer(
|
|
152
|
+
tokenUrl="token", auto_error=False
|
|
153
|
+
)
|
|
154
|
+
optional_oauth2_scheme = OAuth2PasswordOrClientCredentialsBearer(
|
|
155
|
+
tokenUrl="token", auto_error=False
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
bearer_scheme = HTTPBearer(scheme_name="bearerAuth", auto_error=False)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def basic_credentials(req: Request):
|
|
162
|
+
return await HTTPBasic(auto_error=False)(req)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async def bearer_credentials(req: Request):
|
|
166
|
+
return await HTTPBearer(scheme_name="bearerAuth", auto_error=False)(req)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class OAuth2PasswordOrClientCredentialsRequestForm:
|
|
170
|
+
def __init__(
|
|
171
|
+
self,
|
|
172
|
+
basic_creds: Optional[HTTPBasicCredentials] = Depends(basic_credentials),
|
|
173
|
+
bearer_creds: Optional[HTTPAuthorizationCredentials] = Depends(
|
|
174
|
+
bearer_credentials
|
|
175
|
+
),
|
|
176
|
+
grant_type: str = Form(None, pattern="^password$|^client_credentials$"),
|
|
177
|
+
username: Optional[str] = Form(None),
|
|
178
|
+
password: Optional[str] = Form(None),
|
|
179
|
+
scope: str = Form(""),
|
|
180
|
+
client_id: Optional[str] = Form(None),
|
|
181
|
+
client_secret: Optional[str] = Form(None),
|
|
182
|
+
):
|
|
183
|
+
if bearer_creds:
|
|
184
|
+
self.grant_type = "client_credentials"
|
|
185
|
+
self.username, self.password = None, None
|
|
186
|
+
self.scopes = scope.split()
|
|
187
|
+
self.client_id = bearer_creds.credentials
|
|
188
|
+
self.client_secret = None
|
|
189
|
+
elif grant_type == "password" and (username is None or password is None):
|
|
190
|
+
raise HTTPException(
|
|
191
|
+
status_code=HTTP_400_BAD_REQUEST,
|
|
192
|
+
detail="grant_type password requires username and password",
|
|
193
|
+
)
|
|
194
|
+
elif grant_type == "client_credentials" and (client_id is None):
|
|
195
|
+
if basic_creds:
|
|
196
|
+
client_id = basic_creds.username
|
|
197
|
+
client_secret = basic_creds.password
|
|
198
|
+
else:
|
|
199
|
+
raise HTTPException(
|
|
200
|
+
status_code=HTTP_400_BAD_REQUEST,
|
|
201
|
+
detail="grant_type client_credentials requires client_id and client_secret",
|
|
202
|
+
)
|
|
203
|
+
self.grant_type = grant_type
|
|
204
|
+
self.username = username
|
|
205
|
+
self.password = password
|
|
206
|
+
self.scopes = scope.split()
|
|
207
|
+
self.client_id = client_id
|
|
208
|
+
self.client_secret = client_secret
|