<commit_before>"""Tools to manipulate BAM/SAM files

.. autosummary::

    Alignment
    BAM
    SAMFlags

.. note:: BAM being the compressed version of SAM files, we do not
    implement any function to read SAM files. We strongly encourage
    developers to convert their SAM to BAM

"""
import os
import pandas as pd
import pylab
import pysam

"""
#http://www.acgt.me/blog/2014/12/16/understanding-mapq-scores-in-sam-files-does-37-42#
#http://biofinysics.blogspot.fr/2014/05/how-does-bowtie2-assign-mapq-scores.html
#https://gitlab.univ-nantes.fr/a-slide/ContaVect/blob/9a411abfa720064c205c5f6c811afdfea206ed12/pyDNA/pySamTools/Bam.py
# pysamtools
# pysam uses htlib behing the scene and is very fast
# pysam works great for BAM file but with SAM, it needs to read the file after
# each compete iteration, which is not very useful

Interesting commands::

    samtools flagstat contaminant.bam
    samtools stats contaminant.bam
"""

# simple decorator ro rewind the BAM file
from functools import wraps
def seek(f):
    @wraps(f)
    def wrapper(*args, **kargs):
        #args[0] is the self of the method
        # For BAM files only
        args[0].reset()
        return f(*args, **kargs)
    return wrapper

class BAM(pysam.AlignmentFile):
    """BAM data structure

    This is built on top of pysam package and provides functions to retrieve
    useful information or plot various statistical analysis.

    This BAM class can also read a SAM file but some functionalities will not
    work. Besides, you need to create new instances each time a method is
    called. This is inherent to pysam implementation. Python2.7 and 3.5 also
    behave differently and we would recommend the py35 version. For instance,
    :meth:`to_fastq` would work only for that version;

    .. doctest::

        >>> from sequana import BAM, sequana_data
        >>> b = BAM(sequana_data("test.bam", "testing"))
        >>> len(b)
        1000

    .. note:: Once you loop over this data structure,  you must call
        :meth`reset` to rewind the loop. The methods implemented in this data
        structure takes care of that for you thanks to a decorator called seek.
        So if you want to call an iterator yourself you must use the
        :meth:`reset` method yourself. In particular, if you want to use the
        next() funtion.

    """
    def __init__(self, filename,  mode='rb', *args):
        # The mode rb means read-only (r) and that the format is BAM or SAM (b)

        # super()  works for py27 but not py35 probably a missing __init__  or __new__ in
        # AlignmentFile class. See e.g., stackoverflow/questions/
        # 26653401/typeerror-object-takes-no-parameters-but-only-in-python-3

        #self._filename = filename
        pysam.AlignmentFile.__init__(filename, mode=mode, *args)

        self._filename = filename
        # the BAM format can be rewinded but not SAM. This is a pain since one
        # need to reload the instance after each operation. With the BAM, we can
        # do that automatically. We therefore enfore the BAM format.

        # Another issue is that in PY3, there is an attribute **format** that
        # tells us if the input is BAM or SAM but this does not work in PY2
        # where the attribute is missing...

        # another know issue is that we cannot use the method is_bam trustfully.
        # Using AlignmentFile one must provide the proper mode 'e.g. rb will set
        # is_bam to True while 'r' only will set it to False. However, it seems
        # there is not sanity check inside the file. Besides, using
        # AlignmentFile.__init__ instead of the class itself seems to haev also
        # some side effects.

        # Let us save the length (handy and use in other places).
        # If is is a SAM file, the rewind does not work and calling it again wil
        # return 0. This may give us a hint that it is a SAM file
        self.N = len(self)

        # running a second time the len() should return the correct answer with
        # BAM files but the SAM will not work and return 0
        if len(self) == 0:
            raise ValueError("Convert your SAM file to a BAM file please")

        # since we called len(), let us reset the iterator so that if the user
        # decide to use the BAM data structure, it will work at least once as
        # expected by the pysam API
        self.reset()

    @seek
    def get_read_names(self):
        """Return the reads' names"""
        names = [this.qname for this in self]
        return names

    @seek
    def iter_unmapped_reads(self):
        """Return an iterator on the reads that are unmapped"""
        unmapped = (this.qname for this in self if this.is_unmapped)
        return unmapped

    @seek
    def iter_mapped_reads(self):
        """Return an iterator on the reads that are mapped"""
        mapped = (this.qname for this in self if this.is_unmapped is False)
        return mapped

    @seek
    def __len__(self):
        N = len([x for x in self])
        return N

    @seek
    def get_flags(self):
        """Return flags of all reads as a list

        .. seealso:: :meth:`get_flags_as_df`, :class:`SAMFlags`"""
        flags = [s.flag for s in self]
        return flags

    def get_stats(self):
        """Return basic stats about the reads

        :return: dictionary with basic stats:

            - total_reads : number reads
            - mapped_reads : number of mapped reads
            - unmapped_reads : number of unmapped
            - contamination [%]: mapped / unmapped

        .. warning:: works only for BAM files. Use :meth:`get_full_stats_as_df`
            for SAM files.

        """

        """#See samtools stats
        # 1526795 + 0 in total (QC-passed reads + QC-failed reads)
        13 + 0 secondary
        0 + 0 supplementary
        0 + 0 duplicates
        3010 + 0 mapped (0.20% : N/A)
        1526782 + 0 paired in sequencing
        763391 + 0 read1
        763391 + 0 read2
        2700 + 0 properly paired (0.18% : N/A)
        2976 + 0 with itself and mate mapped
        21 + 0 singletons (0.00% : N/A)
        0 + 0 with mate mapped to a different chr
        0 + 0 with mate mapped to a different chr (mapQ>=5)
        """
        d = {}
        d['total_reads'] = len(list(self.iter_unmapped_reads()))
        d['mapped_reads'] = len(list(self.iter_mapped_reads()))
        d['unmapped_reads'] = len(list(self.iter_unmapped_reads()))
        d['contamination [%]'] = float(d['mapped_reads']) /float(d['unmapped_reads'])
        d['contamination [%]'] *= 100
        return d

    def get_full_stats_as_df(self):
        """Return a dictionary with full stats about the BAM file

        .. note:: uses samtools behind the scene
        """
        from easydev import shellcmd
        res = shellcmd("samtools stats %s" % self._filename)
        res = res.decode('utf-8')

        # First, we can extract all data that statrts with SN
        # The format is
        #
        # SN name: value #comment
        #
        # separators are \t tabulation
        #
        # so we split with the : character, remove the starting SN\t characters
        # remove comments and ignore other \t characters. We should end up with
        # only 2 columns; names/values

        # extra all relevnt lines starting with SN
        data = [x for x in res.split("\n") if x.startswith('SN')]
        # remove comments
        data = [x.split('#')[0][3:] for x in data]
        names = [x.split(":")[0] for x in data]
        values = [x.split(":")[1].strip() for x in data]
        df = pd.DataFrame({"description": names, "count": values })
        df = df[['description', 'count']]
        df.sort_values(by='count', inplace=True)
        return df

    def get_flags_as_df(self):
        """Returns flags as a dataframe

        .. doctest::

            >>> from sequana import BAM, sequana_data
            >>> b = BAM(sequana_data('test.bam', "testing"))
            >>> df = b.get_flags_as_df()
            >>> df.sum()
            1       1000
            2        484
            4          2
            8          2
            16       499
            32       500
            64       477
            128      523
            256       64
            512        0
            1024       0
            2048       0
            dtype: int64

        .. seealso:: :class:`SAMFlags` for meaning of each flag
        """
        flags = self.get_flags()
        data = [(this, [flag&this for flag in flags])
            for this in [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048]]
        import pandas as pd
        df = pd.DataFrame(dict(data))
        df = df > 0
        return df

    def plot_bar_flags(self, logy=True, fontsize=16, filename=None):
        """

        .. plot::
            :include-source:

            from sequana import BAM, sequana_data
            b = BAM(sequana_data('test.bam', "testing"))
            b.plot_bar_flags()

        """
        df = self.get_flags_as_df()
        df = df.sum()
        pylab.clf()
        if logy is True:
            df.plot(kind='bar', logy=logy, grid=True)
        else:
            df.plot(kind='bar', grid=True)
        pylab.xlabel("flags", fontsize=fontsize)
        pylab.ylabel("count", fontsize=fontsize)
        pylab.tight_layout()
        if filename:
            pylab.savefig(filename)

    @seek
    def to_fastq(self, filename):
        """Export the BAM to FastQ format

        .. warning:: to be tested
        .. todo:: comments from original reads ?
        """
        with open(filename, "w") as fh:
            for i, this in enumerate(self):
                read = this.qname + "\t" + "???\n"
                read += this.seq + "\n"
                read += "+\n"
                read += this.qual + "\n"
                #if i != self.N-1:
                #    read += "\n"
                fh.write(read)

    @seek
    def get_mapq_as_df(self):
        """Return dataframe with mapq for each read"""
        df = pd.DataFrame({'mapq': [this.mapq for this in self]})
        return df

    def plot_bar_mapq(self, fontsize=16, filename=None):
        """Plots bar plots of the MAPQ (quality) of alignments

            .. plot::
                :include-source:

                from sequana import BAM, sequana_data
                b = BAM(sequana_data('test.bam', "testing"))
                b.plot_bar_mapq()

        """
        df = self.get_mapq_as_df()
        df.plot(kind='hist', bins=60, legend=False, grid=True, logy=True)
        pylab.xlabel("MAPQ", fontsize=fontsize)
        try:
            # This may raise issue on MAC platforms
            pylab.tight_layout()
        except:
            pass
        if filename:
            pylab.savefig(filename)

class Alignment(object):
    """Helper class to retrieve info about Alignment

    Takes an alignment as read by :class:`BAM` and provide simplified version
    of pysam.Alignment class.

    ::

        >>> from sequana.bamtools import Alignment
        >>> from sequana import BAM, sequana_data
        >>> b = BAM(sequana_data("test.bam", "testing"))
        >>> segment = next(b)
        >>> align = Alignment(segment)
        >>> align.as_dict()
        >>> align.FLAG
        353

    The original data is stored in hidden attribute :attr:`_data` and the
    following values are available as attributes or dictionary:

    * QNAME: a query template name. Reads/segment having same QNAME come from the
      same template. A QNAME set to `*` indicates the information is unavailable.
      In a sam file, a read may occupy multiple alignment
    * FLAG: combination of bitwise flags. See :class:`SAMFlags`
    * RNAME: reference sequence
    * POS
    * MAPQ: mapping quality if segment is mapped. equals -10 log10 Pr
    * CIGAR:
    * RNEXT: reference sequence name of the primary alignment of the NEXT read
      in the template
    * PNEXT: position of primary alignment
    * TLEN: signed observed template length
    * SEQ: segment sequence
    * QUAL: ascii of base quality

    .. note:: A segment is a contiguous sequence. A read is a sequence that
        may consist of multiple segments.

    """
    def __init__(self, alignment):
        """.. rubric:: constructor

        :param alignment: alignment instance from :class:`BAM`

        """
        self._data = alignment
        d = self.as_dict()
        for key in d.keys():
            setattr(self, key, d[key])

    def as_dict(self):
        d = {}
        s = self._data
        d['QNAME'] = s.qname
        d['FLAG'] = s.flag
        d['RNAME'] = s.rname
        d['POS'] = s.pos
        d['MAPQ'] = s.mapq
        d['CIGAR'] = s.cigar
        d['PNEXT'] = s.pnext
        d['RNEXT'] = s.rnext
        d['TLEN'] = s.tlen
        d['SEQ'] = s.seq
        d['QUAL'] = s.qual
        return d

class SAMFlags(object):
    """Utility to extract bits from a SAM flag

    .. doctest::

        >>> from sequana import SAMFlags
        >>> sf = SAMFlags(257)
        >>> sf.get_flags()
        [1, 256]

    You can also print the bits and their description::

        print(sf)

    ======= ====================================================================
    bit     Meaning/description
    ======= ====================================================================
    1       template having multiple segments in sequencing
    2       each segment properly aligned according to the aligner
    4       segment unmapped
    8       next segment in the template unmapped
    16      SEQ being reverse complemented
    32      SEQ of the next segment in the template being reverse complemented
    64      the first segment in the template
    128     the last segment in the template
    256     secondary alignment
    512     not passing filters, such as platform/vendor quality controls
    1024    PCR or optical duplicate
    2048    supplementary alignme
    ======= ====================================================================

    """
    def __init__(self, value=4095):
        self.value = value
        self._flags = {
            1: "template having multiple segments in sequencing",
            2: "each segment properly aligned according to the aligner",
            4: "segment unmapped",
            8: "next segment in the template unmapped",
            16: "SEQ being reverse complemented",
            32: "SEQ of the next segment in the template being reverse complemented",
            64: "the first segment in the template",
            128: "the last segment in the template",
            256: "secondary alignment",
            512: "not passing filters, such as platform/vendor quality controls",
            1024: "PCR or optical duplicate",
            2048: "supplementary alignme"}

    def get_meaning(self):
        """Return all description sorted by bit """
        return [self._flags[k] for k in sorted(self._flags.keys())]

    def get_flags(self):
        """Return the individual bits included in the flag"""
        flags = []
        for this in sorted(self._flags.keys()):
            if self.value & this:
                flags.append(this)
        return flags

    def __str__(self):
        txt = ""
        for this in sorted(self._flags.keys()):
            if self.value & this:
                txt += "%s: %s\n" % (this, self._flags[this])
        return txt

<commit_msg>Fixes #51<commit_after>"""Tools to manipulate BAM/SAM files

.. autosummary::

    Alignment
    BAM
    SAMFlags

.. note:: BAM being the compressed version of SAM files, we do not
    implement any function to read SAM files. We strongly encourage
    developers to convert their SAM to BAM

"""
import os
import pandas as pd
import pylab
import pysam

"""
#http://www.acgt.me/blog/2014/12/16/understanding-mapq-scores-in-sam-files-does-37-42#
#http://biofinysics.blogspot.fr/2014/05/how-does-bowtie2-assign-mapq-scores.html
#https://gitlab.univ-nantes.fr/a-slide/ContaVect/blob/9a411abfa720064c205c5f6c811afdfea206ed12/pyDNA/pySamTools/Bam.py
# pysamtools
# pysam uses htlib behing the scene and is very fast
# pysam works great for BAM file but with SAM, it needs to read the file after
# each compete iteration, which is not very useful

Interesting commands::

    samtools flagstat contaminant.bam
    samtools stats contaminant.bam
"""

# simple decorator ro rewind the BAM file
from functools import wraps
def seek(f):
    @wraps(f)
    def wrapper(*args, **kargs):
        #args[0] is the self of the method
        # For BAM files only
        args[0].reset()
        return f(*args, **kargs)
    return wrapper

class BAM(pysam.AlignmentFile):
    """BAM data structure

    This is built on top of pysam package and provides functions to retrieve
    useful information or plot various statistical analysis.

    This BAM class can also read a SAM file but some functionalities will not
    work. Besides, you need to create new instances each time a method is
    called. This is inherent to pysam implementation. Python2.7 and 3.5 also
    behave differently and we would recommend the py35 version. For instance,
    :meth:`to_fastq` would work only for that version;

    .. doctest::

        >>> from sequana import BAM, sequana_data
        >>> b = BAM(sequana_data("test.bam", "testing"))
        >>> len(b)
        1000

    .. note:: Once you loop over this data structure,  you must call
        :meth`reset` to rewind the loop. The methods implemented in this data
        structure takes care of that for you thanks to a decorator called seek.
        So if you want to call an iterator yourself you must use the
        :meth:`reset` method yourself. In particular, if you want to use the
        next() funtion.

    """
    def __init__(self, filename,  mode='rb', *args):
        # The mode rb means read-only (r) and that the format is BAM or SAM (b)

        # super()  works for py27 but not py35 probably a missing __init__  or __new__ in
        # AlignmentFile class. See e.g., stackoverflow/questions/
        # 26653401/typeerror-object-takes-no-parameters-but-only-in-python-3

        #self._filename = filename
        pysam.AlignmentFile.__init__(filename, mode=mode, *args)

        self._filename = filename
        # the BAM format can be rewinded but not SAM. This is a pain since one
        # need to reload the instance after each operation. With the BAM, we can
        # do that automatically. We therefore enfore the BAM format.

        # Another issue is that in PY3, there is an attribute **format** that
        # tells us if the input is BAM or SAM but this does not work in PY2
        # where the attribute is missing...

        # another know issue is that we cannot use the method is_bam trustfully.
        # Using AlignmentFile one must provide the proper mode 'e.g. rb will set
        # is_bam to True while 'r' only will set it to False. However, it seems
        # there is not sanity check inside the file. Besides, using
        # AlignmentFile.__init__ instead of the class itself seems to haev also
        # some side effects.

        # Let us save the length (handy and use in other places).
        # If is is a SAM file, the rewind does not work and calling it again wil
        # return 0. This may give us a hint that it is a SAM file
        self.reset()
        print("Computing length of the BAM file")
        self.N = sum(1 for _ in self)
        self.reset()

        # running a second time the len() should return the correct answer with
        # BAM files but the SAM will not work and return 0
        if len(self) == 0:
            raise ValueError("Convert your SAM file to a BAM file please")

    @seek
    def get_read_names(self):
        """Return the reads' names"""
        names = [this.qname for this in self]
        return names

    @seek
    def iter_unmapped_reads(self):
        """Return an iterator on the reads that are unmapped"""
        unmapped = (this.qname for this in self if this.is_unmapped)
        return unmapped

    @seek
    def iter_mapped_reads(self):
        """Return an iterator on the reads that are mapped"""
        mapped = (this.qname for this in self if this.is_unmapped is False)
        return mapped

    def __len__(self):
        return self.N

    @seek
    def get_flags(self):
        """Return flags of all reads as a list

        .. seealso:: :meth:`get_flags_as_df`, :class:`SAMFlags`"""
        flags = [s.flag for s in self]
        return flags

    def get_stats(self):
        """Return basic stats about the reads

        :return: dictionary with basic stats:

            - total_reads : number reads
            - mapped_reads : number of mapped reads
            - unmapped_reads : number of unmapped
            - contamination [%]: mapped / unmapped

        .. warning:: works only for BAM files. Use :meth:`get_full_stats_as_df`
            for SAM files.

        """

        """#See samtools stats
        # 1526795 + 0 in total (QC-passed reads + QC-failed reads)
        13 + 0 secondary
        0 + 0 supplementary
        0 + 0 duplicates
        3010 + 0 mapped (0.20% : N/A)
        1526782 + 0 paired in sequencing
        763391 + 0 read1
        763391 + 0 read2
        2700 + 0 properly paired (0.18% : N/A)
        2976 + 0 with itself and mate mapped
        21 + 0 singletons (0.00% : N/A)
        0 + 0 with mate mapped to a different chr
        0 + 0 with mate mapped to a different chr (mapQ>=5)
        """
        d = {}
        d['total_reads'] = len(list(self.iter_unmapped_reads()))
        d['mapped_reads'] = len(list(self.iter_mapped_reads()))
        d['unmapped_reads'] = len(list(self.iter_unmapped_reads()))
        d['contamination [%]'] = float(d['mapped_reads']) /float(d['unmapped_reads'])
        d['contamination [%]'] *= 100
        return d

    def get_full_stats_as_df(self):
        """Return a dictionary with full stats about the BAM file

        .. note:: uses samtools behind the scene
        """
        from easydev import shellcmd
        res = shellcmd("samtools stats %s" % self._filename)
        res = res.decode('utf-8')

        # First, we can extract all data that statrts with SN
        # The format is
        #
        # SN name: value #comment
        #
        # separators are \t tabulation
        #
        # so we split with the : character, remove the starting SN\t characters
        # remove comments and ignore other \t characters. We should end up with
        # only 2 columns; names/values

        # extra all relevnt lines starting with SN
        data = [x for x in res.split("\n") if x.startswith('SN')]
        # remove comments
        data = [x.split('#')[0][3:] for x in data]
        names = [x.split(":")[0] for x in data]
        values = [x.split(":")[1].strip() for x in data]
        df = pd.DataFrame({"description": names, "count": values })
        df = df[['description', 'count']]
        df.sort_values(by='count', inplace=True)
        return df

    def get_flags_as_df(self):
        """Returns flags as a dataframe

        .. doctest::

            >>> from sequana import BAM, sequana_data
            >>> b = BAM(sequana_data('test.bam', "testing"))
            >>> df = b.get_flags_as_df()
            >>> df.sum()
            1       1000
            2        484
            4          2
            8          2
            16       499
            32       500
            64       477
            128      523
            256       64
            512        0
            1024       0
            2048       0
            dtype: int64

        .. seealso:: :class:`SAMFlags` for meaning of each flag
        """
        flags = self.get_flags()
        data = [(this, [flag&this for flag in flags])
            for this in [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048]]
        import pandas as pd
        df = pd.DataFrame(dict(data))
        df = df > 0
        return df

    def plot_bar_flags(self, logy=True, fontsize=16, filename=None):
        """

        .. plot::
            :include-source:

            from sequana import BAM, sequana_data
            b = BAM(sequana_data('test.bam', "testing"))
            b.plot_bar_flags()

        """
        df = self.get_flags_as_df()
        df = df.sum()
        pylab.clf()
        if logy is True:
            df.plot(kind='bar', logy=logy, grid=True)
        else:
            df.plot(kind='bar', grid=True)
        pylab.xlabel("flags", fontsize=fontsize)
        pylab.ylabel("count", fontsize=fontsize)
        pylab.tight_layout()
        if filename:
            pylab.savefig(filename)

    @seek
    def to_fastq(self, filename):
        """Export the BAM to FastQ format

        .. warning:: to be tested
        .. todo:: comments from original reads ?
        """
        with open(filename, "w") as fh:
            for i, this in enumerate(self):
                read = this.qname + "\t" + "???\n"
                read += this.seq + "\n"
                read += "+\n"
                read += this.qual + "\n"
                #if i != self.N-1:
                #    read += "\n"
                fh.write(read)

    @seek
    def get_mapq_as_df(self):
        """Return dataframe with mapq for each read"""
        df = pd.DataFrame({'mapq': [this.mapq for this in self]})
        return df

    def plot_bar_mapq(self, fontsize=16, filename=None):
        """Plots bar plots of the MAPQ (quality) of alignments

            .. plot::
                :include-source:

                from sequana import BAM, sequana_data
                b = BAM(sequana_data('test.bam', "testing"))
                b.plot_bar_mapq()

        """
        df = self.get_mapq_as_df()
        df.plot(kind='hist', bins=60, legend=False, grid=True, logy=True)
        pylab.xlabel("MAPQ", fontsize=fontsize)
        try:
            # This may raise issue on MAC platforms
            pylab.tight_layout()
        except:
            pass
        if filename:
            pylab.savefig(filename)

class Alignment(object):
    """Helper class to retrieve info about Alignment

    Takes an alignment as read by :class:`BAM` and provide simplified version
    of pysam.Alignment class.

    ::

        >>> from sequana.bamtools import Alignment
        >>> from sequana import BAM, sequana_data
        >>> b = BAM(sequana_data("test.bam", "testing"))
        >>> segment = next(b)
        >>> align = Alignment(segment)
        >>> align.as_dict()
        >>> align.FLAG
        353

    The original data is stored in hidden attribute :attr:`_data` and the
    following values are available as attributes or dictionary:

    * QNAME: a query template name. Reads/segment having same QNAME come from the
      same template. A QNAME set to `*` indicates the information is unavailable.
      In a sam file, a read may occupy multiple alignment
    * FLAG: combination of bitwise flags. See :class:`SAMFlags`
    * RNAME: reference sequence
    * POS
    * MAPQ: mapping quality if segment is mapped. equals -10 log10 Pr
    * CIGAR:
    * RNEXT: reference sequence name of the primary alignment of the NEXT read
      in the template
    * PNEXT: position of primary alignment
    * TLEN: signed observed template length
    * SEQ: segment sequence
    * QUAL: ascii of base quality

    .. note:: A segment is a contiguous sequence. A read is a sequence that
        may consist of multiple segments.

    """
    def __init__(self, alignment):
        """.. rubric:: constructor

        :param alignment: alignment instance from :class:`BAM`

        """
        self._data = alignment
        d = self.as_dict()
        for key in d.keys():
            setattr(self, key, d[key])

    def as_dict(self):
        d = {}
        s = self._data
        d['QNAME'] = s.qname
        d['FLAG'] = s.flag
        d['RNAME'] = s.rname
        d['POS'] = s.pos
        d['MAPQ'] = s.mapq
        d['CIGAR'] = s.cigar
        d['PNEXT'] = s.pnext
        d['RNEXT'] = s.rnext
        d['TLEN'] = s.tlen
        d['SEQ'] = s.seq
        d['QUAL'] = s.qual
        return d

class SAMFlags(object):
    """Utility to extract bits from a SAM flag

    .. doctest::

        >>> from sequana import SAMFlags
        >>> sf = SAMFlags(257)
        >>> sf.get_flags()
        [1, 256]

    You can also print the bits and their description::

        print(sf)

    ======= ====================================================================
    bit     Meaning/description
    ======= ====================================================================
    1       template having multiple segments in sequencing
    2       each segment properly aligned according to the aligner
    4       segment unmapped
    8       next segment in the template unmapped
    16      SEQ being reverse complemented
    32      SEQ of the next segment in the template being reverse complemented
    64      the first segment in the template
    128     the last segment in the template
    256     secondary alignment
    512     not passing filters, such as platform/vendor quality controls
    1024    PCR or optical duplicate
    2048    supplementary alignme
    ======= ====================================================================

    """
    def __init__(self, value=4095):
        self.value = value
        self._flags = {
            1: "template having multiple segments in sequencing",
            2: "each segment properly aligned according to the aligner",
            4: "segment unmapped",
            8: "next segment in the template unmapped",
            16: "SEQ being reverse complemented",
            32: "SEQ of the next segment in the template being reverse complemented",
            64: "the first segment in the template",
            128: "the last segment in the template",
            256: "secondary alignment",
            512: "not passing filters, such as platform/vendor quality controls",
            1024: "PCR or optical duplicate",
            2048: "supplementary alignme"}

    def get_meaning(self):
        """Return all description sorted by bit """
        return [self._flags[k] for k in sorted(self._flags.keys())]

    def get_flags(self):
        """Return the individual bits included in the flag"""
        flags = []
        for this in sorted(self._flags.keys()):
            if self.value & this:
                flags.append(this)
        return flags

    def __str__(self):
        txt = ""
        for this in sorted(self._flags.keys()):
            if self.value & this:
                txt += "%s: %s\n" % (this, self._flags[this])
        return txt

<|endoftext|>


<commit_before># Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# vim: tabstop=4 shiftwidth=4 softtabstop=4

import unittest
import logging
import six
import socket
from struct import *
from nose.tools import *
from ryu.ofproto.ofproto_v1_3_parser import *
from ryu.ofproto import ofproto_v1_3_parser
from ryu.ofproto import ofproto_v1_3
from ryu.ofproto import ofproto_protocol
from ryu.ofproto import ether
from ryu.ofproto.ofproto_parser import MsgBase
from ryu import utils
from ryu.lib import addrconv

LOG = logging.getLogger('test_ofproto_v13')

_Datapath = ofproto_protocol.ProtocolDesc(version=ofproto_v1_3.OFP_VERSION)

class TestOFPMatch(unittest.TestCase):

    """ Test case for ofproto_v1_3_parser.OFPMatch
    """

    def test_init(self):
        res = OFPMatch()

        # wc check
        eq_(res._wc.vlan_vid_mask, 0)

        # flow check
        eq_(res._flow.vlan_vid, 0)

    def _test_serialize_and_parser(self, match, header, value, mask=None):
        cls_ = OFPMatchField._FIELDS_HEADERS.get(header)
        pack_str = cls_.pack_str.replace('!', '')
        fmt = '!HHI' + pack_str

        # serialize
        buf = bytearray()
        length = match.serialize(buf, 0)
        eq_(length, len(buf))
        if mask and len(buf) > calcsize(fmt):
            fmt += pack_str

        res = list(unpack_from(fmt, six.binary_type(buf), 0)[3:])
        if type(value) is list:
            res_value = res[:calcsize(pack_str) // 2]
            eq_(res_value, value)
            if mask:
                res_mask = res[calcsize(pack_str) // 2:]
                eq_(res_mask, mask)
        else:
            res_value = res.pop(0)
            if cls_.__name__ == 'MTVlanVid':
                eq_(res_value, value | ofproto.OFPVID_PRESENT)
            else:
                eq_(res_value, value)
            if mask and res and res[0]:
                res_mask = res[0]
                eq_(res_mask, mask)

        # parser
        res = match.parser(six.binary_type(buf), 0)
        eq_(res.type, ofproto.OFPMT_OXM)
        eq_(res.fields[0].header, header)
        eq_(res.fields[0].value, value)
        if mask and res.fields[0].mask is not None:
            eq_(res.fields[0].mask, mask)

        # to_jsondict
        jsondict = match.to_jsondict()

        # from_jsondict
        match2 = match.from_jsondict(jsondict["OFPMatch"])
        buf2 = bytearray()
        match2.serialize(buf2, 0)
        eq_(six.binary_type(match), six.binary_type(match2))
        eq_(buf, buf2)

    # set_vlan_vid
    def _test_set_vlan_vid(self, vid, mask=None):
        header = ofproto.OXM_OF_VLAN_VID
        match = OFPMatch()
        if mask is None:
            match.set_vlan_vid(vid)
        else:
            header = ofproto.OXM_OF_VLAN_VID_W
            match.set_vlan_vid_masked(vid, mask)
        self._test_serialize_and_parser(match, header, vid, mask)

    def _test_set_vlan_vid_none(self):
        header = ofproto.OXM_OF_VLAN_VID
        match = OFPMatch()
        match.set_vlan_vid_none()
        value = ofproto.OFPVID_NONE
        cls_ = OFPMatchField._FIELDS_HEADERS.get(header)
        pack_str = cls_.pack_str.replace('!', '')
        fmt = '!HHI' + pack_str

        # serialize
        buf = bytearray()
        length = match.serialize(buf, 0)
        eq_(length, len(buf))

        res = list(unpack_from(fmt, six.binary_type(buf), 0)[3:])
        res_value = res.pop(0)
        eq_(res_value, value)

        # parser
        res = match.parser(six.binary_type(buf), 0)
        eq_(res.type, ofproto.OFPMT_OXM)
        eq_(res.fields[0].header, header)
        eq_(res.fields[0].value, value)

        # to_jsondict
        jsondict = match.to_jsondict()

        # from_jsondict
        match2 = match.from_jsondict(jsondict["OFPMatch"])
        buf2 = bytearray()
        match2.serialize(buf2, 0)
        eq_(six.binary_type(match), six.binary_type(match2))
        eq_(buf, buf2)

    def test_set_vlan_vid_mid(self):
        self._test_set_vlan_vid(2047)

    def test_set_vlan_vid_max(self):
        self._test_set_vlan_vid(0xfff)

    def test_set_vlan_vid_min(self):
        self._test_set_vlan_vid(0)

    def test_set_vlan_vid_masked_mid(self):
        self._test_set_vlan_vid(2047, 0xf0f)

    def test_set_vlan_vid_masked_max(self):
        self._test_set_vlan_vid(2047, 0xfff)

    def test_set_vlan_vid_masked_min(self):
        self._test_set_vlan_vid(2047, 0)

    def test_set_vlan_vid_none(self):
        self._test_set_vlan_vid_none()
<commit_msg>python3: Restore original str()s where six.binary_type shouldn't be used<commit_after># Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# vim: tabstop=4 shiftwidth=4 softtabstop=4

import unittest
import logging
import six
import socket
from struct import *
from nose.tools import *
from ryu.ofproto.ofproto_v1_3_parser import *
from ryu.ofproto import ofproto_v1_3_parser
from ryu.ofproto import ofproto_v1_3
from ryu.ofproto import ofproto_protocol
from ryu.ofproto import ether
from ryu.ofproto.ofproto_parser import MsgBase
from ryu import utils
from ryu.lib import addrconv

LOG = logging.getLogger('test_ofproto_v13')

_Datapath = ofproto_protocol.ProtocolDesc(version=ofproto_v1_3.OFP_VERSION)

class TestOFPMatch(unittest.TestCase):

    """ Test case for ofproto_v1_3_parser.OFPMatch
    """

    def test_init(self):
        res = OFPMatch()

        # wc check
        eq_(res._wc.vlan_vid_mask, 0)

        # flow check
        eq_(res._flow.vlan_vid, 0)

    def _test_serialize_and_parser(self, match, header, value, mask=None):
        cls_ = OFPMatchField._FIELDS_HEADERS.get(header)
        pack_str = cls_.pack_str.replace('!', '')
        fmt = '!HHI' + pack_str

        # serialize
        buf = bytearray()
        length = match.serialize(buf, 0)
        eq_(length, len(buf))
        if mask and len(buf) > calcsize(fmt):
            fmt += pack_str

        res = list(unpack_from(fmt, six.binary_type(buf), 0)[3:])
        if type(value) is list:
            res_value = res[:calcsize(pack_str) // 2]
            eq_(res_value, value)
            if mask:
                res_mask = res[calcsize(pack_str) // 2:]
                eq_(res_mask, mask)
        else:
            res_value = res.pop(0)
            if cls_.__name__ == 'MTVlanVid':
                eq_(res_value, value | ofproto.OFPVID_PRESENT)
            else:
                eq_(res_value, value)
            if mask and res and res[0]:
                res_mask = res[0]
                eq_(res_mask, mask)

        # parser
        res = match.parser(six.binary_type(buf), 0)
        eq_(res.type, ofproto.OFPMT_OXM)
        eq_(res.fields[0].header, header)
        eq_(res.fields[0].value, value)
        if mask and res.fields[0].mask is not None:
            eq_(res.fields[0].mask, mask)

        # to_jsondict
        jsondict = match.to_jsondict()

        # from_jsondict
        match2 = match.from_jsondict(jsondict["OFPMatch"])
        buf2 = bytearray()
        match2.serialize(buf2, 0)
        eq_(str(match), str(match2))
        eq_(buf, buf2)

    # set_vlan_vid
    def _test_set_vlan_vid(self, vid, mask=None):
        header = ofproto.OXM_OF_VLAN_VID
        match = OFPMatch()
        if mask is None:
            match.set_vlan_vid(vid)
        else:
            header = ofproto.OXM_OF_VLAN_VID_W
            match.set_vlan_vid_masked(vid, mask)
        self._test_serialize_and_parser(match, header, vid, mask)

    def _test_set_vlan_vid_none(self):
        header = ofproto.OXM_OF_VLAN_VID
        match = OFPMatch()
        match.set_vlan_vid_none()
        value = ofproto.OFPVID_NONE
        cls_ = OFPMatchField._FIELDS_HEADERS.get(header)
        pack_str = cls_.pack_str.replace('!', '')
        fmt = '!HHI' + pack_str

        # serialize
        buf = bytearray()
        length = match.serialize(buf, 0)
        eq_(length, len(buf))

        res = list(unpack_from(fmt, six.binary_type(buf), 0)[3:])
        res_value = res.pop(0)
        eq_(res_value, value)

        # parser
        res = match.parser(six.binary_type(buf), 0)
        eq_(res.type, ofproto.OFPMT_OXM)
        eq_(res.fields[0].header, header)
        eq_(res.fields[0].value, value)

        # to_jsondict
        jsondict = match.to_jsondict()

        # from_jsondict
        match2 = match.from_jsondict(jsondict["OFPMatch"])
        buf2 = bytearray()
        match2.serialize(buf2, 0)
        eq_(str(match), str(match2))
        eq_(buf, buf2)

    def test_set_vlan_vid_mid(self):
        self._test_set_vlan_vid(2047)

    def test_set_vlan_vid_max(self):
        self._test_set_vlan_vid(0xfff)

    def test_set_vlan_vid_min(self):
        self._test_set_vlan_vid(0)

    def test_set_vlan_vid_masked_mid(self):
        self._test_set_vlan_vid(2047, 0xf0f)

    def test_set_vlan_vid_masked_max(self):
        self._test_set_vlan_vid(2047, 0xfff)

    def test_set_vlan_vid_masked_min(self):
        self._test_set_vlan_vid(2047, 0)

    def test_set_vlan_vid_none(self):
        self._test_set_vlan_vid_none()
<|endoftext|>


<commit_before>__version_info__ = {
    'major': 2,
    'minor': 0,
    'micro': 13,
    'releaselevel': 'beta',
    'serial': 1
}

def get_version(short=False):
    assert __version_info__['releaselevel'] in ('alpha', 'beta', 'final')
    vers = ["%(major)i.%(minor)i" % __version_info__, ]
    if __version_info__['micro']:
        vers.append(".%(micro)i" % __version_info__)
    if __version_info__['releaselevel'] != 'final' and not short:
        vers.append('%s%i' % (__version_info__['releaselevel'][0], __version_info__['serial']))
    return ''.join(vers)

__version__ = get_version()
<commit_msg>2.0.13 Release<commit_after>__version_info__ = {
    'major': 2,
    'minor': 0,
    'micro': 13,
    'releaselevel': 'final',
    'serial': 1
}

def get_version(short=False):
    assert __version_info__['releaselevel'] in ('alpha', 'beta', 'final')
    vers = ["%(major)i.%(minor)i" % __version_info__, ]
    if __version_info__['micro']:
        vers.append(".%(micro)i" % __version_info__)
    if __version_info__['releaselevel'] != 'final' and not short:
        vers.append('%s%i' % (__version_info__['releaselevel'][0], __version_info__['serial']))
    return ''.join(vers)

__version__ = get_version()
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
##############################################################################
# For copyright and license notices, see __openerp__.py file in module root
# directory
##############################################################################
from openerp import api, models
import logging

_logger = logging.getLogger(__name__)

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    @api.multi
    def run_invoicing_atomation(self):
        for so in self:
            invoicing_atomation = so.type_id.invoicing_atomation
            if invoicing_atomation != 'none':
                # we add this check just because if we call
                # action_invoice_create and nothing to invoice, it raise
                # an error
                if not any(
                        line.qty_to_invoice for line in so.order_line):
                    _logger.warning('Noting to invoice')
                    return True
                # a list is returned but only one invoice should be returned
                invoices = so.env['account.invoice'].browse(
                    self.action_invoice_create())
                # for any different from none and create_invoice, validate
                if invoices and invoicing_atomation != 'create_invoice':
                    invoices.signal_workflow('invoice_open')
                    # if different from validate, open and create, then
                    # pay it
                    if invoicing_atomation != 'validate_invoice':
                        for inv in invoices:
                            pay_context = {
                                'to_pay_move_line_ids': (
                                    inv.open_move_line_ids.ids),
                                # 'pop_up': True,
                                'default_company_id': inv.company_id.id,
                            }
                            payment_group = so.env[
                                'account.payment.group'].with_context(
                                pay_context).create({})
                            payment_group.payment_ids.create({
                                'payment_group_id': payment_group.id,
                                'payment_type': 'inbound',
                                'partner_type': 'customer',
                                'company_id': inv.company_id.id,
                                'partner_id': payment_group.partner_id.id,
                                'amount': payment_group.payment_difference,
                                'journal_id': so.type_id.payment_journal_id.id,
                                'payment_method_id': self.env.ref(
                                    'account.account_payment_method_manual_in'
                                ).id,
                            })
                            # if invoice_payment then we validate payment
                            if invoicing_atomation == 'invoice_payment':
                                payment_group.post()

    @api.multi
    def run_picking_atomation(self):
        for so in self:
            if so.type_id.picking_atomation == 'validate' and\
                    so.procurement_group_id:
                picking = self.env['stock.picking'].search(
                    [('group_id', '=', so.procurement_group_id.id)], limit=1)
                picking.force_assign()
                for pack in picking.pack_operation_ids:
                    if pack.product_qty > 0:
                        pack.write({'qty_done': pack.product_qty})
                    else:
                        pack.unlink()
                picking.do_transfer()

    @api.multi
    def action_confirm(self):
        res = super(SaleOrder, self).action_confirm()
        self.run_picking_atomation()
        self.run_invoicing_atomation()
        return res
<commit_msg>FIX refund auto invoice<commit_after># -*- coding: utf-8 -*-
##############################################################################
# For copyright and license notices, see __openerp__.py file in module root
# directory
##############################################################################
from openerp import api, models
import logging

_logger = logging.getLogger(__name__)

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    @api.multi
    def run_invoicing_atomation(self):
        for so in self:
            invoicing_atomation = so.type_id.invoicing_atomation
            if invoicing_atomation != 'none':
                # we add this check just because if we call
                # action_invoice_create and nothing to invoice, it raise
                # an error
                if not any(
                        line.qty_to_invoice for line in so.order_line):
                    _logger.warning('Noting to invoice')
                    return True
                # a list is returned but only one invoice should be returned
                # usamos final para que reste adelantos y tmb por ej
                # por si se usa el modulo de facturar las returns
                invoices = so.env['account.invoice'].browse(
                    self.action_invoice_create(final=True))
                # for any different from none and create_invoice, validate
                if invoices and invoicing_atomation != 'create_invoice':
                    invoices.signal_workflow('invoice_open')
                    # if different from validate, open and create, then
                    # pay it
                    if invoicing_atomation != 'validate_invoice':
                        for inv in invoices:
                            pay_context = {
                                'to_pay_move_line_ids': (
                                    inv.open_move_line_ids.ids),
                                # 'pop_up': True,
                                'default_company_id': inv.company_id.id,
                            }
                            payment_group = so.env[
                                'account.payment.group'].with_context(
                                pay_context).create({})
                            payment_group.payment_ids.create({
                                'payment_group_id': payment_group.id,
                                'payment_type': 'inbound',
                                'partner_type': 'customer',
                                'company_id': inv.company_id.id,
                                'partner_id': payment_group.partner_id.id,
                                'amount': payment_group.payment_difference,
                                'journal_id': so.type_id.payment_journal_id.id,
                                'payment_method_id': self.env.ref(
                                    'account.account_payment_method_manual_in'
                                ).id,
                            })
                            # if invoice_payment then we validate payment
                            if invoicing_atomation == 'invoice_payment':
                                payment_group.post()

    @api.multi
    def run_picking_atomation(self):
        for so in self:
            if so.type_id.picking_atomation == 'validate' and\
                    so.procurement_group_id:
                picking = self.env['stock.picking'].search(
                    [('group_id', '=', so.procurement_group_id.id)], limit=1)
                picking.force_assign()
                for pack in picking.pack_operation_ids:
                    if pack.product_qty > 0:
                        pack.write({'qty_done': pack.product_qty})
                    else:
                        pack.unlink()
                picking.do_transfer()

    @api.multi
    def action_confirm(self):
        res = super(SaleOrder, self).action_confirm()
        self.run_picking_atomation()
        self.run_invoicing_atomation()
        return res
<|endoftext|>


<commit_before>import os
import redis
import threading
import ConfigParser
import paho.mqtt.client as mqtt
import time
import random
import datetime
from Tts import Tts
from Room import Room
from Template import TemplateMatcher
from Mpd import Mpd

class MqttRulez(threading.Thread):

    def _readConfig(self):
        update = False

        if not os.path.isdir(self._homeDir):
            print "Creating homeDir"
            os.makedirs(self._homeDir)

        if os.path.isfile(self._configFileName):
            self._config.read(self._configFileName)
        else:
            print "Config file not found"
            update = True

        if not self._config.has_section('MQTT'):
            print "Adding MQTT part"
            update = True
            self._config.add_section("MQTT")

        if not self._config.has_option("MQTT", "ServerAddress"):
            print "No Server Address"
            update = True
            self._config.set("MQTT", "ServerAddress", "<ServerAddress>")

        if not self._config.has_option("MQTT", "ServerPort"):
            print "No Server Port"
            update = True
            self._config.set("MQTT", "ServerPort", "1883")

        if not self._config.has_section('REDIS'):
            print "Adding Redis part"
            update = True
            self._config.add_section("REDIS")

        if not self._config.has_option("REDIS", "ServerAddress"):
            print "No Server Address"
            update = True
            self._config.set("REDIS", "ServerAddress", "<ServerAddress>")

        if not self._config.has_option("REDIS", "ServerPort"):
            print "No Server Port"
            update = True
            self._config.set("REDIS", "ServerPort", "6379")

        if update:
            with open(self._configFileName, 'w') as f:
                self._config.write(f)

    def _process(self, k, v):

        keys = k.split("/")

        if keys[0] == Room.BATH_ROOM:

            if keys[1] == "temp":
                print "temp"

            if keys[1] == "button":
                print "button"

                #Wasching machine
                if v == "1":
                    if self._redis.exists("Waschingmachine"):
                        print "Ack empty wasching machine"
                        self._redis.delete("Waschingmachine")
                        self._tts.createWavFile(self._template.getAcknowledgeEndWashingMachine(), Room.BATH_ROOM)
                    else:
                        print "Ack start wasching machine"
                        self._redis.setex("Waschingmachine", 60 * 60 * 24 * 1, time.time())
                        self._tts.createWavFile(self._template.getAcknowledgeStartWashingMachine(), Room.BATH_ROOM)

                #Ansi shower
                if v == "2":
                    if self._redis.exists("shower"):
                        print "Stop shower"
                        self._tts.createWavFile(self._template.getAcknowledgeEndShower('Ansi'), Room.BATH_ROOM)
                        self._redis.delete("shower")
                        Mpd().getServerbyName("Bath").stop()
                        self._mqclient.publish("bathroom/light/rgb/r","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","0")

                    else:
                        print "Start shower"
                        self._tts.createWavFile(self._template.getAcknowledgeStartShower('Ansi'), Room.BATH_ROOM)
                        self._redis.setex("shower", 60 * 60 * 2, time.time())
                        s = Mpd().getServerbyName("Bath")
                        s.emptyPlaylist()
                        if datetime.datetime.now().hour > 11:
                            s.loadPlaylist(random.choice(s.getPlaylists('Die drei ???')))
                            s.randomize(0)
                        else:
                            for i in s.getPlaylists('Starred'):
                                s.loadPlaylist(i)
                            s.randomize(1)
                        s.volume(80)
                        s.play()
                        self._mqclient.publish("bathroom/light/rgb/r","255")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","255")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","255")

                #Tiffy shower
                if v == "3":
                    if self._redis.exists("shower"):
                        print "Stop shower"
                        self._tts.createWavFile(self._template.getAcknowledgeEndShower('Phawx'), Room.BATH_ROOM)
                        self._redis.delete("shower")
                        self._mqclient.publish("bathroom/light/rgb/r","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","0")
                    else:
                        print "Start shower"
                        self._tts.createWavFile(self._template.getAcknowledgeStartShower('Phawx'), Room.BATH_ROOM)
                        self._redis.setex("shower", 60 * 60 * 2, time.time())
                        self._mqclient.publish("bathroom/light/rgb/r","255")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","255")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","255")

                #Ansi bath
                if v == "4":
                    if self._redis.exists("bath"):
                        print "Stop bath"
                        self._tts.createWavFile(self._template.getAcknowledgeEndBath('Ansi'), Room.BATH_ROOM)
                        self._redis.delete("bath")
                        Mpd().getServerbyName("Bath").stop()
                        self._mqclient.publish("bathroom/light/rgb/r","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","0")
                    else:
                        print "Start bath"
                        self._tts.createWavFile(self._template.getAcknowledgeStartBath('Ansi'), Room.BATH_ROOM)
                        self._redis.setex("bath", 60 * 60 * 5, time.time())
                        s = Mpd().getServerbyName("Bath")
                        s.emptyPlaylist()
                        for i in s.getPlaylists('Starred'):
                            s.loadPlaylist(i)
                        s.randomize(1)
                        s.volume(10)
                        s.play()
                        self._mqclient.publish("bathroom/light/rgb/r","25")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","25")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","23")

                #Tiffy bath
                if v == "5":
                    if self._redis.exists("bath"):
                        print "Stop bath"
                        self._tts.createWavFile(self._template.getAcknowledgeEndBath('Phawx'), Room.BATH_ROOM)
                        self._redis.delete("bath")
                        self._mqclient.publish("bathroom/light/rgb/r","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","0")
                    else:
                        print "Start bath"
                        self._tts.createWavFile(self._template.getAcknowledgeStartBath('Phawx'), Room.BATH_ROOM)
                        self._redis.setex("bath", 60 * 60 * 5, time.time())
                        self._mqclient.publish("bathroom/light/rgb/r","23")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","25")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","15")

            if keys[1] == "motion":
                print "motion in bath detected"
                self._redis.setex(Room.BATH_ROOM+"/populated", 60 * 60, time.time())

        if keys[0] == Room.LIVING_ROOM:

            if keys[1] == "motion":
                print "motion in livingroom detected"
                self._redis.setex(Room.LIVING_ROOM+"/populated", 60 * 60, time.time())

        if keys[0] == Room.ANSI_ROOM:

            if keys[1] == "motion":
                print "motion in ansi room detected"
                self._redis.setex(Room.ANSI_ROOM+"/populated", 60 * 60, time.time())

        if keys[0] == Room.TIFFY_ROOM:

            if keys[1] == "motion":
                print "motion in tiffy room detected"
                self._redis.setex(Room.TIFFY_ROOM+"/populated", 60 * 60, time.time())

    def __init__(self):
        random.seed
        threading.Thread.__init__(self)
        self.setDaemon(True)

        self._homeDir        = os.path.expanduser("~/.sensomatic")
        self._configFileName = self._homeDir + '/config.ini'
        self._config         = ConfigParser.ConfigParser()
        self._readConfig()

        self._mqclient = mqtt.Client("MqttRulez", clean_session=True)
        self._redis    = redis.StrictRedis(host=self._config.get("REDIS", "ServerAddress"), port=self._config.get("REDIS", "ServerPort"), db=0)
        self._tts      = Tts()
        self._template = TemplateMatcher()

    def _on_connect(self, client, userdata, rc, msg):
        print "Connected with result code %s" % rc
        self._mqclient.subscribe("#")

    def _on_message(self, client, userdata, msg):
        #print "Mq Received on channel %s -> %s" % (msg.topic, msg.payload)
        self._process(msg.topic, msg.payload)

    def _on_disconnect(self, client, userdata, msg):
        print "Disconnect"

    def run(self):
        self._mqclient.connect(self._config.get("MQTT","ServerAddress"), self._config.get("MQTT","ServerPort"), 60)
        self._mqclient.on_connect = self._on_connect
        self._mqclient.on_message = self._on_message
        self._mqclient.on_disconnect = self._on_disconnect
        self._mqclient.loop_forever()

if __name__ == '__main__':
    print "Start"

    m = MqttRulez()
    m.start()

    time.sleep(10)

    print "End"<commit_msg>Changing volume fuer shower and bath sound<commit_after>import os
import redis
import threading
import ConfigParser
import paho.mqtt.client as mqtt
import time
import random
import datetime
from Tts import Tts
from Room import Room
from Template import TemplateMatcher
from Mpd import Mpd

class MqttRulez(threading.Thread):

    def _readConfig(self):
        update = False

        if not os.path.isdir(self._homeDir):
            print "Creating homeDir"
            os.makedirs(self._homeDir)

        if os.path.isfile(self._configFileName):
            self._config.read(self._configFileName)
        else:
            print "Config file not found"
            update = True

        if not self._config.has_section('MQTT'):
            print "Adding MQTT part"
            update = True
            self._config.add_section("MQTT")

        if not self._config.has_option("MQTT", "ServerAddress"):
            print "No Server Address"
            update = True
            self._config.set("MQTT", "ServerAddress", "<ServerAddress>")

        if not self._config.has_option("MQTT", "ServerPort"):
            print "No Server Port"
            update = True
            self._config.set("MQTT", "ServerPort", "1883")

        if not self._config.has_section('REDIS'):
            print "Adding Redis part"
            update = True
            self._config.add_section("REDIS")

        if not self._config.has_option("REDIS", "ServerAddress"):
            print "No Server Address"
            update = True
            self._config.set("REDIS", "ServerAddress", "<ServerAddress>")

        if not self._config.has_option("REDIS", "ServerPort"):
            print "No Server Port"
            update = True
            self._config.set("REDIS", "ServerPort", "6379")

        if update:
            with open(self._configFileName, 'w') as f:
                self._config.write(f)

    def _process(self, k, v):

        keys = k.split("/")

        if keys[0] == Room.BATH_ROOM:

            if keys[1] == "temp":
                print "temp"

            if keys[1] == "button":
                print "button"

                #Wasching machine
                if v == "1":
                    if self._redis.exists("Waschingmachine"):
                        print "Ack empty wasching machine"
                        self._redis.delete("Waschingmachine")
                        self._tts.createWavFile(self._template.getAcknowledgeEndWashingMachine(), Room.BATH_ROOM)
                    else:
                        print "Ack start wasching machine"
                        self._redis.setex("Waschingmachine", 60 * 60 * 24 * 1, time.time())
                        self._tts.createWavFile(self._template.getAcknowledgeStartWashingMachine(), Room.BATH_ROOM)

                #Ansi shower
                if v == "2":
                    if self._redis.exists("shower"):
                        print "Stop shower"
                        self._tts.createWavFile(self._template.getAcknowledgeEndShower('Ansi'), Room.BATH_ROOM)
                        self._redis.delete("shower")
                        Mpd().getServerbyName("Bath").stop()
                        self._mqclient.publish("bathroom/light/rgb/r","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","0")

                    else:
                        print "Start shower"
                        self._tts.createWavFile(self._template.getAcknowledgeStartShower('Ansi'), Room.BATH_ROOM)
                        self._redis.setex("shower", 60 * 60 * 2, time.time())
                        s = Mpd().getServerbyName("Bath")
                        s.emptyPlaylist()
                        if datetime.datetime.now().hour > 11:
                            s.loadPlaylist(random.choice(s.getPlaylists('Die drei ???')))
                            s.randomize(0)
                        else:
                            for i in s.getPlaylists('Starred'):
                                s.loadPlaylist(i)
                            s.randomize(1)
                        s.volume(90)
                        s.play()
                        self._mqclient.publish("bathroom/light/rgb/r","255")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","255")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","255")

                #Tiffy shower
                if v == "3":
                    if self._redis.exists("shower"):
                        print "Stop shower"
                        self._tts.createWavFile(self._template.getAcknowledgeEndShower('Phawx'), Room.BATH_ROOM)
                        self._redis.delete("shower")
                        self._mqclient.publish("bathroom/light/rgb/r","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","0")
                    else:
                        print "Start shower"
                        self._tts.createWavFile(self._template.getAcknowledgeStartShower('Phawx'), Room.BATH_ROOM)
                        self._redis.setex("shower", 60 * 60 * 2, time.time())
                        self._mqclient.publish("bathroom/light/rgb/r","255")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","255")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","255")

                #Ansi bath
                if v == "4":
                    if self._redis.exists("bath"):
                        print "Stop bath"
                        self._tts.createWavFile(self._template.getAcknowledgeEndBath('Ansi'), Room.BATH_ROOM)
                        self._redis.delete("bath")
                        Mpd().getServerbyName("Bath").stop()
                        self._mqclient.publish("bathroom/light/rgb/r","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","0")
                    else:
                        print "Start bath"
                        self._tts.createWavFile(self._template.getAcknowledgeStartBath('Ansi'), Room.BATH_ROOM)
                        self._redis.setex("bath", 60 * 60 * 5, time.time())
                        s = Mpd().getServerbyName("Bath")
                        s.emptyPlaylist()
                        for i in s.getPlaylists('Starred'):
                            s.loadPlaylist(i)
                        s.randomize(1)
                        s.volume(15)
                        s.play()
                        self._mqclient.publish("bathroom/light/rgb/r","255")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","42")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","23")

                #Tiffy bath
                if v == "5":
                    if self._redis.exists("bath"):
                        print "Stop bath"
                        self._tts.createWavFile(self._template.getAcknowledgeEndBath('Phawx'), Room.BATH_ROOM)
                        self._redis.delete("bath")
                        self._mqclient.publish("bathroom/light/rgb/r","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","0")
                    else:
                        print "Start bath"
                        self._tts.createWavFile(self._template.getAcknowledgeStartBath('Phawx'), Room.BATH_ROOM)
                        self._redis.setex("bath", 60 * 60 * 5, time.time())
                        self._mqclient.publish("bathroom/light/rgb/r","0")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/g","25")
                        time.sleep(1)
                        self._mqclient.publish("bathroom/light/rgb/b","255")

            if keys[1] == "motion":
                print "motion in bath detected"
                self._redis.setex(Room.BATH_ROOM+"/populated", 60 * 60, time.time())

        if keys[0] == Room.LIVING_ROOM:

            if keys[1] == "motion":
                print "motion in livingroom detected"
                self._redis.setex(Room.LIVING_ROOM+"/populated", 60 * 60, time.time())

        if keys[0] == Room.ANSI_ROOM:

            if keys[1] == "motion":
                print "motion in ansi room detected"
                self._redis.setex(Room.ANSI_ROOM+"/populated", 60 * 60, time.time())

        if keys[0] == Room.TIFFY_ROOM:

            if keys[1] == "motion":
                print "motion in tiffy room detected"
                self._redis.setex(Room.TIFFY_ROOM+"/populated", 60 * 60, time.time())

    def __init__(self):
        random.seed
        threading.Thread.__init__(self)
        self.setDaemon(True)

        self._homeDir        = os.path.expanduser("~/.sensomatic")
        self._configFileName = self._homeDir + '/config.ini'
        self._config         = ConfigParser.ConfigParser()
        self._readConfig()

        self._mqclient = mqtt.Client("MqttRulez", clean_session=True)
        self._redis    = redis.StrictRedis(host=self._config.get("REDIS", "ServerAddress"), port=self._config.get("REDIS", "ServerPort"), db=0)
        self._tts      = Tts()
        self._template = TemplateMatcher()

    def _on_connect(self, client, userdata, rc, msg):
        print "Connected with result code %s" % rc
        self._mqclient.subscribe("#")

    def _on_message(self, client, userdata, msg):
        #print "Mq Received on channel %s -> %s" % (msg.topic, msg.payload)
        self._process(msg.topic, msg.payload)

    def _on_disconnect(self, client, userdata, msg):
        print "Disconnect"

    def run(self):
        self._mqclient.connect(self._config.get("MQTT","ServerAddress"), self._config.get("MQTT","ServerPort"), 60)
        self._mqclient.on_connect = self._on_connect
        self._mqclient.on_message = self._on_message
        self._mqclient.on_disconnect = self._on_disconnect
        self._mqclient.loop_forever()

if __name__ == '__main__':
    print "Start"

    m = MqttRulez()
    m.start()

    time.sleep(10)

    print "End"<|endoftext|>


<commit_before>import datetime

from flask import jsonify, request

from server import app, sqldb
from server.models import Account, AnalyticsEvent, User

@app.route('/feed/analytics', methods=['POST'])
def send_analytics():
    try:
        user = User.get_user()
    except ValueError as e:
        return jsonify({'success': False, 'error': str(e)}), 400

    try:
        account = Account.get_account()
        account_id = account.id
    except ValueError:
        account_id = None

    data = request.get_json()
    events = list(data)

    for event_json in events:
        timestamp_str = event_json.get('timestamp')

        # Some timestamps malformed as '2019-09-08T4:18:24.709 PM'
        if 'AM' in timestamp_str or 'PM' in timestamp_str:
            timestamp_str = timestamp_str.split(' ')[0]

        timestamp = datetime.datetime.strptime(timestamp_str, '%Y-%m-%dT%H:%M:%S.%f')
        type = event_json.get('cell_type')
        index = int(event_json.get('index'))
        post_id = event_json.get('id')
        flag = bool(event_json.get('is_interaction'))
        if any(x == type for x in ['news', 'post']):
            # Only log news and post events
            event = AnalyticsEvent(user=user.id, account_id=account_id, timestamp=timestamp, type=type, index=index,
                                   post_id=post_id, is_interaction=flag)
            sqldb.session.add(event)
    sqldb.session.commit()

    return jsonify({'success': True, 'error': None})
<commit_msg>undo analytics<commit_after>import datetime

from flask import jsonify, request

from server import app, sqldb
from server.models import Account, AnalyticsEvent, User

@app.route('/feed/analytics', methods=['POST'])
def send_analytics():
    try:
        user = User.get_user()
    except ValueError as e:
        return jsonify({'success': False, 'error': str(e)}), 400

    try:
        account = Account.get_account()
        account_id = account.id
    except ValueError:
        account_id = None

    data = request.get_json()
    events = list(data)

    for event_json in events:
        timestamp_str = event_json.get('timestamp')

        # Some timestamps malformed as '2019-09-08T4:18:24.709 PM'
        if 'AM' in timestamp_str or 'PM' in timestamp_str:
            timestamp_str = timestamp_str.split(' ')[0]

        timestamp = datetime.datetime.strptime(timestamp_str, '%Y-%m-%dT%H:%M:%S.%f')
        type = event_json.get('cell_type')
        index = int(event_json.get('index'))
        post_id = event_json.get('id')
        flag = bool(event_json.get('is_interaction'))
        # if any(x == type for x in ['news', 'post']):
            # Only log news and post events
        event = AnalyticsEvent(user=user.id, account_id=account_id, timestamp=timestamp, type=type, index=index,
                               post_id=post_id, is_interaction=flag)
        sqldb.session.add(event)
    sqldb.session.commit()

    return jsonify({'success': True, 'error': None})
<|endoftext|>