async def blast(req): """ BLAST a contig sequence that is part of a NuVs result record. The resulting BLAST data will be attached to that sequence. """ db = req.app["db"] settings = req.app["settings"] analysis_id = req.match_info["analysis_id"] sequence_index = int(req.match_info["sequence_index"]) document = await db.analyses.find_one( {"_id": analysis_id}, ["ready", "workflow", "results", "sample"]) if not document: return not_found("Analysis not found") if document["workflow"] != "nuvs": return conflict("Not a NuVs analysis") if not document["ready"]: return conflict("Analysis is still running") sequence = virtool.analyses.utils.find_nuvs_sequence_by_index( document, sequence_index) if sequence is None: return not_found("Sequence not found") sample = await db.samples.find_one({"_id": document["sample"]["id"]}, virtool.samples.db.PROJECTION) if not sample: return bad_request("Parent sample does not exist") _, write = virtool.samples.utils.get_sample_rights(sample, req["client"]) if not write: return insufficient_rights() # Start a BLAST at NCBI with the specified sequence. Return a RID that identifies the BLAST run. rid, _ = await virtool.bio.initialize_ncbi_blast(req.app["settings"], sequence) blast_data, document = await virtool.analyses.db.update_nuvs_blast( db, settings, analysis_id, sequence_index, rid) # Wait on BLAST request as a Task until the it completes on NCBI. At that point the sequence in the DB will be # updated with the BLAST result. await aiojobs.aiohttp.spawn( req, virtool.bio.wait_for_blast_result(req.app, analysis_id, sequence_index, rid)) headers = { "Location": f"/api/analyses/{analysis_id}/{sequence_index}/blast" } return json_response(blast_data, headers=headers, status=201)
async def install(req): """ Install the latest official HMM database from GitHub. """ db = req.app["db"] user_id = req["client"].user_id if await db.status.count_documents({"_id": "hmm", "updates.ready": False}): return conflict("Install already in progress") process = await virtool.processes.db.register(db, "install_hmms") document = await db.status.find_one_and_update( {"_id": "hmm"}, {"$set": { "process": { "id": process["id"] } }}) release = document.get("release") if release is None: return bad_request("Target release does not exist") update = virtool.github.create_update_subdocument(release, False, user_id) await db.status.update_one({"_id": "hmm"}, {"$push": {"updates": update}}) await aiojobs.aiohttp.spawn( req, virtool.hmm.db.install(req.app, process["id"], release, user_id)) return json_response(update)
async def remove(req): """ Remove a job. """ db = req.app["db"] job_id = req.match_info["job_id"] document = await db.jobs.find_one(job_id) if not document: return not_found() if virtool.jobs.is_running_or_waiting(document): return conflict("Job is running or waiting and cannot be removed") # Removed the documents associated with the job ids from the database. await db.jobs.delete_one({"_id": job_id}) try: # Calculate the log path and remove the log file. If it exists, return True. path = os.path.join(req.app["settings"]["data_path"], "logs", "jobs", job_id + ".log") await req.app["run_in_thread"](virtool.utils.rm, path) except OSError: pass return no_content()
async def create_first(req): """ Add a first user to the user database. """ db = req.app["db"] data = await req.json() if await db.users.count_documents({}): return conflict("Virtool already has at least one user") if data["user_id"] == "virtool": return bad_request("Reserved user name: virtool") error = await virtool.users.checks.check_password_length(req) if error: return bad_request(error) user_id = data["user_id"] await virtool.users.db.create(db, user_id, data["password"], force_reset=False) document = await virtool.users.db.edit(db, user_id, administrator=True) headers = {"Location": f"/api/users/{user_id}"} session, token = await virtool.users.sessions.create_session( db, virtool.http.auth.get_ip(req), user_id) req["client"].authorize(session, is_api=False) resp = json_response(virtool.utils.base_processor( {key: document[key] for key in virtool.users.db.PROJECTION}), headers=headers, status=201) virtool.http.utils.set_session_id_cookie(resp, session["_id"]) virtool.http.utils.set_session_token_cookie(resp, token) return resp
async def edit(req): db = req.app["db"] data = await req.json() if "password" in data: error = await virtool.users.checks.check_password_length(req) if error: return bad_request(error) groups = await db.groups.distinct("_id") if "groups" in data: missing = [g for g in data["groups"] if g not in groups] if missing: return bad_request("Groups do not exist: " + ", ".join(missing)) primary_group = data.get("primary_group") if primary_group and primary_group not in groups: return bad_request("Primary group does not exist") user_id = req.match_info["user_id"] if "administrator" in data and user_id == req["client"].user_id: return bad_request( "Users cannot modify their own administrative status") try: document = await virtool.users.db.edit(db, user_id, **data) except virtool.errors.DatabaseError as err: if "User does not exist" in str(err): return not_found("User does not exist") if "User is not member of group" in str(err): return conflict("User is not member of group") raise projected = virtool.db.utils.apply_projection(document, virtool.users.db.PROJECTION) return json_response(virtool.utils.base_processor(projected))
async def remove(req): """ Remove an analysis document by its id. """ db = req.app["db"] analysis_id = req.match_info["analysis_id"] document = await db.analyses.find_one({"_id": analysis_id}, ["job", "ready", "sample"]) if not document: return not_found() sample_id = document["sample"]["id"] sample = await db.samples.find_one({"_id": sample_id}, virtool.samples.db.PROJECTION) if not sample: return bad_request("Parent sample does not exist") read, write = virtool.samples.utils.get_sample_rights( sample, req["client"]) if not read or not write: return insufficient_rights() if not document["ready"]: return conflict("Analysis is still running") await db.analyses.delete_one({"_id": analysis_id}) path = os.path.join(req.app["settings"]["data_path"], "samples", sample_id, "analysis", analysis_id) await req.app["run_in_thread"](virtool.utils.rm, path, True) await virtool.samples.db.recalculate_workflow_tags(db, sample_id) return no_content()
async def cancel(req): """ Cancel a job. """ db = req.app["db"] job_id = req.match_info["job_id"] document = await db.jobs.find_one(job_id, ["status"]) if not document: return not_found() if not virtool.jobs.is_running_or_waiting(document): return conflict("Not cancellable") await req.app["jobs"].cancel(job_id) document = await db.jobs.find_one(job_id) return json_response(virtool.utils.base_processor(document))
async def revert(req): """ Remove the change document with the given ``change_id`` and any subsequent changes. """ db = req.app["db"] change_id = req.match_info["change_id"] document = await db.history.find_one(change_id, ["reference"]) if not document: return not_found() if not await virtool.references.db.check_right( req, document["reference"]["id"], "modify_otu"): return insufficient_rights() try: await virtool.history.db.revert(req.app, change_id) except virtool.errors.DatabaseError: return conflict("Change is already built") return no_content()
async def create(req): """ Starts a job to rebuild the otus Bowtie2 index on disk. Does a check to make sure there are no unverified otus in the collection and updates otu history to show the version and id of the new index. """ db = req.app["db"] ref_id = req.match_info["ref_id"] reference = await db.references.find_one(ref_id, ["groups", "users"]) if reference is None: return not_found() if not await virtool.references.db.check_right(req, reference, "build"): return insufficient_rights() if await db.indexes.count_documents({"reference.id": ref_id, "ready": False}): return conflict("Index build already in progress") if await db.otus.count_documents({"reference.id": ref_id, "verified": False}): return bad_request("There are unverified OTUs") if not await db.history.count_documents({"reference.id": ref_id, "index.id": "unbuilt"}): return bad_request("There are no unbuilt changes") index_id = await virtool.db.utils.get_new_id(db.indexes) index_version = await virtool.indexes.db.get_next_version(db, ref_id) job_id = await virtool.db.utils.get_new_id(db.jobs) manifest = await virtool.references.db.get_manifest(db, ref_id) user_id = req["client"].user_id document = { "_id": index_id, "version": index_version, "created_at": virtool.utils.timestamp(), "manifest": manifest, "ready": False, "has_files": True, "job": { "id": job_id }, "reference": { "id": ref_id }, "user": { "id": user_id } } await db.indexes.insert_one(document) await db.history.update_many({"index.id": "unbuilt", "reference.id": ref_id}, { "$set": { "index": { "id": index_id, "version": index_version } } }) # A dict of task_args for the rebuild job. task_args = { "ref_id": ref_id, "user_id": user_id, "index_id": index_id, "index_version": index_version, "manifest": manifest } # Create job document. job = await virtool.jobs.db.create( db, req.app["settings"], "build_index", task_args, user_id, job_id=job_id ) await req.app["jobs"].enqueue(job["_id"]) headers = { "Location": "/api/indexes/" + index_id } return json_response(virtool.utils.base_processor(document), status=201, headers=headers)