async def update(req): """ Update application settings based on request data. """ raw_data = await req.json() data = {key: req["data"][key] for key in raw_data} proc = data.get("proc", None) mem = data.get("mem", None) settings = req.app["settings"] error_message = virtool.settings.check_resource_limits( proc, mem, settings.data) if error_message: return conflict(error_message) proc = proc or settings["proc"] mem = mem or settings["mem"] error_message = virtool.settings.check_task_specific_limits( proc, mem, data) if error_message: return conflict(error_message) app_settings = req.app["settings"] app_settings.update(data) await app_settings.write() return json_response(app_settings.data)
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", "algorithm", "results", "sample"]) if not document: return not_found("Analysis not found") if document["algorithm"] != "nuvs": return conflict("Not a NuVs analysis") if not document["ready"]: return conflict("Analysis is still running") sequence = virtool.analyses.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.db.samples.PROJECTION) if not sample: return bad_request("Parent sample does not exist") _, write = virtool.samples.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.db.analyses.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(db, req.app["settings"], 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({"_id": "hmm", "updates.ready": False}): return conflict("Install already in progress") process = await virtool.db.processes.register(db, "install_hmms") document = await db.status.find_one_and_update( {"_id": "hmm"}, {"$set": { "process": { "id": process["id"] } }}) release = document.get("release", None) 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.db.hmm.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.utils.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"].get("data_path"), "logs", "jobs", job_id + ".log") await virtool.utils.rm(path) except OSError: pass return no_content()
async def remove(req): db = req.app["db"] settings = req.app["settings"] subtraction_id = req.match_info["subtraction_id"] if await db.samples.count({"subtraction.id": subtraction_id}): return conflict("Has linked samples") delete_result = await db.subtraction.delete_one({"_id": subtraction_id}) if delete_result.deleted_count == 0: return not_found() index_path = virtool.subtractions.calculate_index_path(settings, subtraction_id) await req.loop.run_in_executor(None, shutil.rmtree, index_path, True) return no_content()
async def edit(req): db = req.app["db"] data = await req.json() settings = req.app["settings"] if "password" in data and len( data["password"]) < settings["minimum_password_length"]: return bad_request("Password does not meet length requirement") 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", None) 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.db.users.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.db.users.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.db.samples.PROJECTION) if not sample: return bad_request("Parent sample does not exist") read, write = virtool.samples.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.db.samples.recalculate_algorithm_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.utils.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.db.references.check_right(req, document["reference"]["id"], "modify_otu"): return insufficient_rights() try: await virtool.db.history.revert(db, 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.db.references.check_right(req, reference, "build"): return insufficient_rights() if await db.indexes.count({"reference.id": ref_id, "ready": False}): return conflict("Index build already in progress") if await db.otus.count({"reference.id": ref_id, "verified": False}): return bad_request("There are unverified OTUs") if not await db.history.count({"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.db.indexes.get_next_version(db, ref_id) job_id = await virtool.db.utils.get_new_id(db.jobs) manifest = await virtool.db.references.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.db.jobs.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)