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

Address documentation/docstring TODOs #840

Merged
merged 19 commits into from
Dec 13, 2024
Merged
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
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
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})
14 changes: 9 additions & 5 deletions tests/test_api/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ def test_create_user():
rs = ensure_test_resources(mdb)
base_url = os.getenv("API_HOST")

@retry(wait=wait_random_exponential(multiplier=1, max=60), stop=stop_after_attempt(3))
@retry(
wait=wait_random_exponential(multiplier=1, max=60), stop=stop_after_attempt(3)
)
def get_token():
"""

Expand Down Expand Up @@ -197,7 +199,9 @@ def test_update_user():
base_url = os.getenv("API_HOST")

# Try up to three times, waiting for up to 60 seconds between each attempt.
@retry(wait=wait_random_exponential(multiplier=1, max=60), stop=stop_after_attempt(3))
@retry(
wait=wait_random_exponential(multiplier=1, max=60), stop=stop_after_attempt(3)
)
def get_token():
"""
Fetch an auth token from the Runtime API.
Expand Down Expand Up @@ -228,7 +232,7 @@ def get_token():
headers=headers,
json=user_in1.model_dump(exclude_unset=True),
)

u1 = mdb.users.find_one({"username": user_in1.username})

user_in2 = UserIn(username="foo", password="newpass")
Expand All @@ -240,12 +244,11 @@ def get_token():
json=user_in2.model_dump(exclude_unset=True),
)


u2 = mdb.users.find_one({"username": user_in2.username})
try:
assert rv_create.status_code == status.HTTP_201_CREATED
assert rv_update.status_code == status.HTTP_200_OK
assert u1['hashed_password'] != u2['hashed_password']
assert u1["hashed_password"] != u2["hashed_password"]

finally:
mdb.users.delete_one({"username": user_in1.username})
Expand All @@ -254,6 +257,7 @@ def get_token():
{"$pull": {"site_admin": "nmdc-runtime-useradmin"}},
)


@pytest.fixture
def api_site_client():
mdb = get_mongo_db()
Expand Down
Loading