Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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()
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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))
Ejemplo n.º 6
0
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()
Ejemplo n.º 7
0
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))
Ejemplo n.º 8
0
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()
Ejemplo n.º 9
0
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)