<commit_before>"""
Objects for dealing with Qiita analyses

This module provides the implementation of the Analysis and Collection classes.

Classes
-------
- `Analysis` -- A Qiita Analysis class
- `Collection` -- A Qiita Collection class for grouping multiple analyses
"""

# -----------------------------------------------------------------------------
# Copyright (c) 2014--, The Qiita Development Team.
#
# Distributed under the terms of the BSD 3-clause License.
#
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------
from __future__ import division
from collections import defaultdict
from itertools import product
from os.path import join

from future.utils import viewitems
from biom import load_table
from biom.util import biom_open
import pandas as pd
from skbio.util import find_duplicates

from qiita_core.exceptions import IncompetentQiitaDeveloperError
from .sql_connection import TRN
from .base import QiitaStatusObject
from .data import ProcessedData
from .study import Study
from .exceptions import QiitaDBStatusError, QiitaDBError, QiitaDBUnknownIDError
from .util import (convert_to_id, get_work_base_dir,
                   get_mountpoint, insert_filepaths)
from qiita_core.qiita_settings import qiita_config


class Analysis(QiitaStatusObject):
    """
    Analysis object to access to the Qiita Analysis information

    Attributes
    ----------
    owner
    name
    description
    samples
    dropped_samples
    data_types
    biom_tables
    step
    shared_with
    jobs
    pmid
    parent
    children

    Methods
    -------
    has_access
    add_samples
    remove_samples
    share
    unshare
    build_files
    summary_data
    exists
    create
    delete
    """

    _table = "analysis"
    _portal_table = "analysis_portal"
    _analysis_id_column = 'analysis_id'

    def _lock_check(self):
        """Raises QiitaDBStatusError if analysis is not in_progress"""
        if self.check_status({"queued", "running", "public", "completed",
                              "error"}):
            raise QiitaDBStatusError("Analysis is locked!")

    def _status_setter_checks(self):
        r"""Perform a check to make sure not setting status away from public
        """
        if self.check_status({"public"}):
            raise QiitaDBStatusError("Can't set status away from public!")

    @classmethod
    def get_by_status(cls, status):
        """Returns analysis ids for all Analyses with given status

        Parameters
        ----------
        status : str
            Status to search analyses for

        Returns
        -------
        set of int
            All analyses in the database with the given status
        """
        with TRN:
            sql = """SELECT analysis_id
                     FROM qiita.{0}
                        JOIN qiita.{0}_status USING (analysis_status_id)
                        JOIN qiita.analysis_portal USING (analysis_id)
                        JOIN qiita.portal_type USING (portal_type_id)
                     WHERE status = %s AND portal = %s""".format(cls._table)
            TRN.add(sql, [status, qiita_config.portal])
            return set(TRN.execute_fetchflatten())

    @classmethod
    def create(cls, owner, name, description, parent=None, from_default=False):
        """Creates a new analysis on the database

        Parameters
        ----------
        owner : User object
            The analysis' owner
        name : str
            Name of the analysis
        description : str
            Description of the analysis
        parent : Analysis object, optional
            The analysis this one was forked from
        from_default : bool, optional
            If True, use the default analysis to populate selected samples.
            Default False.
        """
        with TRN:
            status_id = convert_to_id('in_construction',
                                      'analysis_status', 'status')
            portal_id = convert_to_id(qiita_config.portal, 'portal_type',
                                      'portal')

            if from_default:
                # insert analysis and move samples into that new analysis
                dflt_id = owner.default_analysis

                # Get the analysis id placeholder
                a_id_idx = TRN.index
                a_id_ph = "{%s:0:0}" % a_id_idx
                sql = """INSERT INTO qiita.{0}
                            (email, name, description, analysis_status_id)
                        VALUES (%s, %s, %s, %s)
                        RETURNING analysis_id""".format(cls._table)
                TRN.add(sql, [owner.id, name, description, status_id])
                # MAGIC NUMBER 3: command selection step
                # needed so we skip the sample selection step
                sql = """INSERT INTO qiita.analysis_workflow
                            (analysis_id, step)
                        VALUES (%s, %s)"""
                TRN.add(sql, [a_id_ph, 3])

                sql = """UPDATE qiita.analysis_sample
                         SET analysis_id = %s
                         WHERE analysis_id = %s"""
                TRN.add(sql, [a_id_ph, dflt_id])
            else:
                # Get the analysis id placeholder
                a_id_idx = TRN.index
                a_id_ph = "{%s:0:0}" % a_id_idx
                # insert analysis information into table as "in construction"
                sql = """INSERT INTO qiita.{0}
                            (email, name, description, analysis_status_id)
                         VALUES (%s, %s, %s, %s)
                         RETURNING analysis_id""".format(cls._table)
                TRN.add(sql, [owner.id, name, description, status_id])

            # Add to both QIITA and given portal (if not QIITA)
            sql = """INSERT INTO qiita.analysis_portal
                        (analysis_id, portal_type_id)
                     VALUES (%s, %s)"""
            args = [[a_id_ph, portal_id]]

            if qiita_config.portal != 'QIITA':
                qp_id = convert_to_id('QIITA', 'portal_type', 'portal')
                args.append([a_id_ph, qp_id])
            TRN.add(sql, args, many=True)

            # add parent if necessary
            if parent:
                sql = """INSERT INTO qiita.analysis_chain
                            (parent_id, child_id)
                         VALUES (%s, %s)"""
                TRN.add(sql, [parent.id, a_id_ph])

            # The analysis id is in the `a_id_idx` query, first row, first elem
            return cls(TRN.execute()[a_id_idx][0][0])

    @classmethod
    def delete(cls, _id):
        """Deletes an analysis

        Parameters
        ----------
        _id : int
            The analysis id

        Raises
        ------
        QiitaDBUnknownIDError
            If the analysis id doesn't exist
        """
        with TRN:
            # check if the analysis exist
            if not cls.exists(_id):
                raise QiitaDBUnknownIDError(_id, "analysis")

            sql = "DELETE FROM qiita.analysis_filepath WHERE {0} = %s".format(
                cls._analysis_id_column)
            args = [_id]
            TRN.add(sql, args)

            sql = "DELETE FROM qiita.analysis_workflow WHERE {0} = %s".format(
                cls._analysis_id_column)<commit_msg>Updating analysis to not use the placeholders<commit_after>"""
Objects for dealing with Qiita analyses

This module provides the implementation of the Analysis and Collection classes.

Classes
-------
- `Analysis` -- A Qiita Analysis class
- `Collection` -- A Qiita Collection class for grouping multiple analyses
"""

# -----------------------------------------------------------------------------
# Copyright (c) 2014--, The Qiita Development Team.
#
# Distributed under the terms of the BSD 3-clause License.
#
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------
from __future__ import division
from collections import defaultdict
from itertools import product
from os.path import join

from future.utils import viewitems
from biom import load_table
from biom.util import biom_open
import pandas as pd
from skbio.util import find_duplicates

from qiita_core.exceptions import IncompetentQiitaDeveloperError
from .sql_connection import TRN
from .base import QiitaStatusObject
from .data import ProcessedData
from .study import Study
from .exceptions import QiitaDBStatusError, QiitaDBError, QiitaDBUnknownIDError
from .util import (convert_to_id, get_work_base_dir,
                   get_mountpoint, insert_filepaths)
from qiita_core.qiita_settings import qiita_config


class Analysis(QiitaStatusObject):
    """
    Analysis object to access to the Qiita Analysis information

    Attributes
    ----------
    owner
    name
    description
    samples
    dropped_samples
    data_types
    biom_tables
    step
    shared_with
    jobs
    pmid
    parent
    children

    Methods
    -------
    has_access
    add_samples
    remove_samples
    share
    unshare
    build_files
    summary_data
    exists
    create
    delete
    """

    _table = "analysis"
    _portal_table = "analysis_portal"
    _analysis_id_column = 'analysis_id'

    def _lock_check(self):
        """Raises QiitaDBStatusError if analysis is not in_progress"""
        if self.check_status({"queued", "running", "public", "completed",
                              "error"}):
            raise QiitaDBStatusError("Analysis is locked!")

    def _status_setter_checks(self):
        r"""Perform a check to make sure not setting status away from public
        """
        if self.check_status({"public"}):
            raise QiitaDBStatusError("Can't set status away from public!")

    @classmethod
    def get_by_status(cls, status):
        """Returns analysis ids for all Analyses with given status

        Parameters
        ----------
        status : str
            Status to search analyses for

        Returns
        -------
        set of int
            All analyses in the database with the given status
        """
        with TRN:
            sql = """SELECT analysis_id
                     FROM qiita.{0}
                        JOIN qiita.{0}_status USING (analysis_status_id)
                        JOIN qiita.analysis_portal USING (analysis_id)
                        JOIN qiita.portal_type USING (portal_type_id)
                     WHERE status = %s AND portal = %s""".format(cls._table)
            TRN.add(sql, [status, qiita_config.portal])
            return set(TRN.execute_fetchflatten())

    @classmethod
    def create(cls, owner, name, description, parent=None, from_default=False):
        """Creates a new analysis on the database

        Parameters
        ----------
        owner : User object
            The analysis' owner
        name : str
            Name of the analysis
        description : str
            Description of the analysis
        parent : Analysis object, optional
            The analysis this one was forked from
        from_default : bool, optional
            If True, use the default analysis to populate selected samples.
            Default False.
        """
        with TRN:
            status_id = convert_to_id('in_construction',
                                      'analysis_status', 'status')
            portal_id = convert_to_id(qiita_config.portal, 'portal_type',
                                      'portal')

            if from_default:
                # insert analysis and move samples into that new analysis
                dflt_id = owner.default_analysis

                sql = """INSERT INTO qiita.{0}
                            (email, name, description, analysis_status_id)
                        VALUES (%s, %s, %s, %s)
                        RETURNING analysis_id""".format(cls._table)
                TRN.add(sql, [owner.id, name, description, status_id])
                a_id = TRN.execute_fetchlast()
                # MAGIC NUMBER 3: command selection step
                # needed so we skip the sample selection step
                sql = """INSERT INTO qiita.analysis_workflow
                            (analysis_id, step)
                        VALUES (%s, %s)"""
                TRN.add(sql, [a_id, 3])

                sql = """UPDATE qiita.analysis_sample
                         SET analysis_id = %s
                         WHERE analysis_id = %s"""
                TRN.add(sql, [a_id, dflt_id])
            else:
                # insert analysis information into table as "in construction"
                sql = """INSERT INTO qiita.{0}
                            (email, name, description, analysis_status_id)
                         VALUES (%s, %s, %s, %s)
                         RETURNING analysis_id""".format(cls._table)
                TRN.add(sql, [owner.id, name, description, status_id])
                a_id = TRN.execute_fetchlast()

            # Add to both QIITA and given portal (if not QIITA)
            sql = """INSERT INTO qiita.analysis_portal
                        (analysis_id, portal_type_id)
                     VALUES (%s, %s)"""
            args = [[a_id, portal_id]]

            if qiita_config.portal != 'QIITA':
                qp_id = convert_to_id('QIITA', 'portal_type', 'portal')
                args.append([a_id, qp_id])
            TRN.add(sql, args, many=True)

            # add parent if necessary
            if parent:
                sql = """INSERT INTO qiita.analysis_chain
                            (parent_id, child_id)
                         VALUES (%s, %s)"""
                TRN.add(sql, [parent.id, a_id])

            return cls(a_id)

    @classmethod
    def delete(cls, _id):
        """Deletes an analysis

        Parameters
        ----------
        _id : int
            The analysis id

        Raises
        ------
        QiitaDBUnknownIDError
            If the analysis id doesn't exist
        """
        with TRN:
            # check if the analysis exist
            if not cls.exists(_id):
                raise QiitaDBUnknownIDError(_id, "analysis")

            sql = "DELETE FROM qiita.analysis_filepath WHERE {0} = %s".format(
                cls._analysis_id_column)
            args = [_id]
            TRN.add(sql, args)

            sql = "DELETE FROM qiita.analysis_workflow WHERE {0} = %s".format(
                cls._analysis_id_column)
{'fraction_non_alphanumeric': 0.10252886201209456, 'fraction_numerical': 0.0021990104452996153, 'mean_word_length': 1.6160345137515728, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2184469', 'n_tokens_mistral': 3951, 'n_tokens_neox': 3723, 'n_words': 1161}