Skip to content

Commit

Permalink
Merge branch 'main' into 696-update-dagster-op-for-materialize_alldocs
Browse files Browse the repository at this point in the history
  • Loading branch information
dwinston committed Dec 13, 2024
2 parents bdda212 + fa94fd5 commit 6fdf4f9
Show file tree
Hide file tree
Showing 17 changed files with 630 additions and 223 deletions.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion nmdc_runtime/api/core/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def __init__(
bearer_creds: Optional[HTTPAuthorizationCredentials] = Depends(
bearer_credentials
),
grant_type: str = Form(None, regex="^password$|^client_credentials$"),
grant_type: str = Form(None, pattern="^password$|^client_credentials$"),
username: Optional[str] = Form(None),
password: Optional[str] = Form(None),
scope: str = Form(""),
Expand Down
6 changes: 3 additions & 3 deletions nmdc_runtime/api/endpoints/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from nmdc_runtime.api.endpoints.util import (
_claim_job,
_request_dagster_run,
permitted,
check_action_permitted,
persist_content_and_get_drs_object,
)
from nmdc_runtime.api.models.job import Job
Expand Down Expand Up @@ -93,7 +93,7 @@ async def submit_changesheet(
# `/metadata/changesheets:submit` action), themselves, so that they don't have to contact an admin
# or submit an example changesheet in order to find that out.

if not permitted(user.username, "/metadata/changesheets:submit"):
if not check_action_permitted(user.username, "/metadata/changesheets:submit"):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=(
Expand Down Expand Up @@ -188,7 +188,7 @@ async def submit_json_nmdcdb(
Submit a NMDC JSON Schema "nmdc:Database" object.
"""
if not permitted(user.username, "/metadata/json:submit"):
if not check_action_permitted(user.username, "/metadata/json:submit"):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Only specific users are allowed to submit json at this time.",
Expand Down
9 changes: 7 additions & 2 deletions nmdc_runtime/api/endpoints/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
get_mongo_db,
get_nonempty_nmdc_schema_collection_names,
)
from nmdc_runtime.api.endpoints.util import permitted, users_allowed, strip_oid
from nmdc_runtime.api.endpoints.util import (
check_action_permitted,
strip_oid,
)
from nmdc_runtime.api.models.query import (
Query,
QueryResponseOptions,
Expand All @@ -35,7 +38,9 @@ def unmongo(d: dict) -> dict:

def check_can_update_and_delete(user: User):
# update and delete queries require same level of permissions
if not permitted(user.username, "/queries:run(query_cmd:DeleteCommand)"):
if not check_action_permitted(
user.username, "/queries:run(query_cmd:DeleteCommand)"
):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Only specific users are allowed to issue update and delete commands.",
Expand Down
47 changes: 45 additions & 2 deletions nmdc_runtime/api/endpoints/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,13 @@ async def login_for_access_token(
}


@router.get("/users/me", response_model=User, response_model_exclude_unset=True)
@router.get(
"/users/me",
response_model=User,
response_model_exclude_unset=True,
name="Get User Information",
description="Get information about the logged-in user",
)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user

Expand All @@ -156,7 +162,13 @@ def check_can_create_user(requester: User):
)


@router.post("/users", status_code=status.HTTP_201_CREATED, response_model=User)
@router.post(
"/users",
status_code=status.HTTP_201_CREATED,
response_model=User,
name="Create User",
description="Create new user (Admin Only)",
)
def create_user(
user_in: UserIn,
requester: User = Depends(get_current_active_user),
Expand All @@ -170,3 +182,34 @@ def create_user(
).model_dump(exclude_unset=True)
)
return mdb.users.find_one({"username": user_in.username})


@router.put(
"/users",
status_code=status.HTTP_200_OK,
response_model=User,
name="Update User",
description="Update information about the user having the specified username (Admin Only)",
)
def update_user(
user_in: UserIn,
requester: User = Depends(get_current_active_user),
mdb: pymongo.database.Database = Depends(get_mongo_db),
):
check_can_create_user(requester)
username = user_in.username

if user_in.password:
user_dict = UserInDB(
**user_in.model_dump(),
hashed_password=get_password_hash(
user_in.password
), # Store the password hash
).model_dump(exclude_unset=True)
else:
user_dict = UserIn(
**user_in.model_dump(),
).model_dump(exclude_unset=True)

mdb.users.update_one({"username": username}, {"$set": user_dict})
return mdb.users.find_one({"username": user_in.username})
84 changes: 29 additions & 55 deletions nmdc_runtime/api/endpoints/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,9 @@ def timeit(cursor):


def find_resources(req: FindRequest, mdb: MongoDatabase, collection_name: str):
r"""
TODO: Document this function.
"""Find nmdc schema collection entities that match the FindRequest.
"resources" is used generically here, as in "Web resources", e.g. Uniform Resource Identifiers (URIs).
"""
if req.group_by:
raise HTTPException(
Expand Down Expand Up @@ -377,8 +378,11 @@ def find_resources(req: FindRequest, mdb: MongoDatabase, collection_name: str):
def find_resources_spanning(
req: FindRequest, mdb: MongoDatabase, collection_names: Set[str]
):
r"""
TODO: Document this function.
"""Find nmdc schema collection entities -- here, across multiple collections -- that match the FindRequest.
This is useful for collections that house documents that are subclasses of a common ancestor class.
"resources" is used generically here, as in "Web resources", e.g. Uniform Resource Identifiers (URIs).
"""
if req.cursor or not req.page:
raise HTTPException(
Expand Down Expand Up @@ -420,33 +424,11 @@ def find_resources_spanning(

def exists(collection: MongoCollection, filter_: dict):
r"""
TODO: Document this function.
Returns True if there are any documents in the collection that meet the filter requirements.
"""
return collection.count_documents(filter_) > 0


def find_for(resource: str, req: FindRequest, mdb: MongoDatabase):
r"""
TODO: Document this function.
"""
if resource == "biosamples":
return find_resources(req, mdb, "biosample_set")
elif resource == "studies":
return find_resources(req, mdb, "study_set")
elif resource == "data_objects":
return find_resources(req, mdb, "data_object_set")
elif resource == "activities":
return find_resources_spanning(req, mdb, activity_collection_names(mdb))
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=(
f"Unknown API resource '{resource}'. "
f"Known resources: {{activities, biosamples, data_objects, studies}}."
),
)


def persist_content_and_get_drs_object(
content: str,
description: str,
Expand All @@ -456,8 +438,13 @@ def persist_content_and_get_drs_object(
id_ns="json-metadata-in",
exists_ok=False,
):
r"""
TODO: Document this function.
"""Persist a Data Repository Service (DRS) object.
An object may be a blob, analogous to a file, or a bundle, analogous to a folder. Sites register objects,
and sites must ensure that these objects are accessible to the NMDC data broker.
An object may be associated with one or more object types, useful for triggering workflows.
Reference: https://ga4gh.github.io/data-repository-service-schemas/preview/release/drs-1.1.0/docs/#_drs_datatypes
"""
mdb = get_mongo_db()
drs_id = local_part(generate_one_id(mdb, ns=id_ns, shoulder="gfs0"))
Expand Down Expand Up @@ -513,9 +500,7 @@ def _create_object(
self_uri,
exists_ok=False,
):
r"""
TODO: Document this function.
"""
"""Helper function for creating a Data Repository Service (DRS) object."""
drs_obj = DrsObject(
**object_in.model_dump(exclude_unset=True),
id=drs_id,
Expand Down Expand Up @@ -610,10 +595,8 @@ def _claim_job(job_id: str, mdb: MongoDatabase, site: Site):


@lru_cache
def nmdc_workflow_id_to_dagster_job_name_map():
r"""
TODO: Document this function and change its name to a verb.
"""
def map_nmdc_workflow_id_to_dagster_job_name():
"""Returns a dictionary mapping nmdc_workflow_id to dagster_job_name."""
return {
"metadata-in-1.0.0": "apply_metadata_in",
"export-study-biosamples-as-csv-1.0.0": "export_study_biosamples_metadata",
Expand All @@ -629,7 +612,8 @@ def ensure_run_config_data(
user: User,
):
r"""
TODO: Document this function and say what it "ensures" about the "run config data".
Ensures that run_config_data has entries for certain nmdc workflow ids.
Returns return_config_data.
"""
if nmdc_workflow_id == "export-study-biosamples-as-csv-1.0.0":
run_config_data = assoc_in(
Expand Down Expand Up @@ -660,9 +644,7 @@ def ensure_run_config_data(


def inputs_for(nmdc_workflow_id, run_config_data):
r"""
TODO: Document this function.
"""
"""Returns a URI path for given nmdc_workflow_id, constructed from run_config_data."""
if nmdc_workflow_id == "metadata-in-1.0.0":
return [
"/objects/"
Expand Down Expand Up @@ -696,9 +678,11 @@ def _request_dagster_run(
repository_name=None,
):
r"""
TODO: Document this function.
Requests a Dagster run using the specified parameters.
Returns a json dictionary indicating the job's success or failure.
This is a generic wrapper.
"""
dagster_job_name = nmdc_workflow_id_to_dagster_job_name_map()[nmdc_workflow_id]
dagster_job_name = map_nmdc_workflow_id_to_dagster_job_name()[nmdc_workflow_id]

extra_run_config_data = ensure_run_config_data(
nmdc_workflow_id, nmdc_workflow_inputs, extra_run_config_data, mdb, user
Expand Down Expand Up @@ -745,7 +729,7 @@ def _request_dagster_run(

def _get_dagster_run_status(run_id: str):
r"""
TODO: Document this function.
Returns the status (either "success" or "error") of a requested Dagster run.
"""
dagster_client = get_dagster_graphql_client()
try:
Expand All @@ -755,20 +739,10 @@ def _get_dagster_run_status(run_id: str):
return {"type": "error", "detail": str(exc)}


def permitted(username: str, action: str):
r"""
TODO: Document this function and change its name to a verb.
"""
def check_action_permitted(username: str, action: str):
"""Returns True if a Mongo database action is "allowed" and "not denied"."""
db: MongoDatabase = get_mongo_db()
filter_ = {"username": username, "action": action}
denied = db["_runtime.api.deny"].find_one(filter_) is not None
allowed = db["_runtime.api.allow"].find_one(filter_) is not None
return (not denied) and allowed


def users_allowed(action: str):
r"""
TODO: Document this function and change its name to a verb.
"""
db: MongoDatabase = get_mongo_db()
return db["_runtime.api.allow"].distinct("username", {"action": action})
Loading

0 comments on commit 6fdf4f9

Please sign in to comment.