ipyrad/core/assembly.py
def _paramschecker(self, param, newvalue):
    """ Raises exceptions when params are set to values they should not be"""

    if param == 'assembly_name':
        ## Make sure somebody doesn't try to change their assembly_name, bad
        ## things would happen. Calling set_params on assembly_name only raises
        ## an informative error. Assembly_name is set at Assembly creation time
        ## and is immutable.
        raise IPyradWarningExit(CANNOT_CHANGE_ASSEMBLY_NAME)

    elif param == 'project_dir':
        expandpath = _expander(newvalue)
        if not expandpath.startswith("/"):
            if os.path.exists(expandpath):
                expandpath = _expander(expandpath)
        ## Forbid spaces in path names
        if " " in expandpath:
            raise IPyradWarningExit(BAD_PROJDIR_NAME.format(expandpath))
        self.paramsdict["project_dir"] = expandpath
        self.dirs["project"] = expandpath

    ## `Merged:` in newvalue for raw_fastq_path indicates that this
    ## assembly is a merge of several others, so this param has no
    ## value for this assembly
    elif param == 'raw_fastq_path':
        if newvalue and not "Merged:" in newvalue:
            fullrawpath = _expander(newvalue)
            if os.path.isdir(fullrawpath):
                raise IPyradWarningExit(RAW_PATH_ISDIR.format(fullrawpath))
            ## if something is found in path
            elif glob.glob(fullrawpath):
                self.paramsdict['raw_fastq_path'] = fullrawpath
            ## else allow empty, tho it can still raise an error in step1
            else:
                raise IPyradWarningExit(NO_RAW_FILE.format(fullrawpath))
        else:
            self.paramsdict['raw_fastq_path'] = ""


    ## `Merged:` in newvalue for barcodes_path indicates that this
    ## assembly is a merge of several others, so this param has no
    ## value for this assembly
    elif param == 'barcodes_path':
        ## if a value was entered check that it exists
        if newvalue and not "Merged:" in newvalue:
            ## also allow for fuzzy match in names using glob
            fullbarpath = glob.glob(_expander(newvalue))[0]
            ## raise error if file is not found
            if not os.path.exists(fullbarpath):
                raise IPyradWarningExit(BARCODE_NOT_FOUND.format(fullbarpath))
            else:
                self.paramsdict['barcodes_path'] = fullbarpath
                self._link_barcodes()

        ## if no path was entered then set barcodes path to empty.
        ## this is checked again during step 1 and will raise an error
        ## if you try demultiplexing without a barcodes file
        else:
            self.paramsdict['barcodes_path'] = newvalue


    ## `Merged:` in newvalue for sorted_fastq_path indicates that this
    ## assembly is a merge of several others, so this param has no
    ## value for this assembly
    elif param == 'sorted_fastq_path':
        if newvalue and not "Merged:" in newvalue:
            fullsortedpath = _expander(newvalue)

            if os.path.isdir(fullsortedpath):
                raise IPyradWarningExit(SORTED_ISDIR.format(fullsortedpath))
            elif glob.glob(fullsortedpath):
                self.paramsdict['sorted_fastq_path'] = fullsortedpath

            else:
                raise IPyradWarningExit(SORTED_NOT_FOUND.format(fullsortedpath))
        ## if no value was entered then set to "".
        else:
            self.paramsdict['sorted_fastq_path'] = ""


    elif param == 'assembly_method':
        ## TEMPORARY BLOCK ON DENOVO+REFERENCE METHOD
#        if newvalue == "denovo+reference":
#            raise IPyradWarningExit("""
#    Error: The 'denovo+reference' method is temporarily blocked while we 
#    refactor it to greatly improve the speed. You can either revert to an
#    older version (pre v.0.7.0) or wait for the next update to resume using
#    this method. 
#    """)

        methods = ["denovo", "reference", "denovo+reference", "denovo-reference"]
        assert newvalue in methods, BAD_ASSEMBLY_METHOD.format(newvalue)
        self.paramsdict['assembly_method'] = newvalue


    elif param == 'reference_sequence':
        if newvalue:
            fullrawpath = _expander(newvalue)
            if not os.path.isfile(fullrawpath):
                LOGGER.info("reference sequence file not found.")
                raise IPyradWarningExit(REF_NOT_FOUND.format(fullrawpath))
            self.paramsdict['reference_sequence'] = fullrawpath
        ## if no value was entered the set to "". Will be checked again
        ## at step3 if user tries to do refseq and raise error
        else:
            self.paramsdict['reference_sequence'] = ""


    elif param == 'datatype':
        ## list of allowed datatypes
        datatypes = ['rad', 'gbs', 'ddrad', 'pairddrad',
                     'pairgbs', 'merged', '2brad', 'pair3rad']
        ## raise error if something else
        if str(newvalue) not in datatypes:
            raise IPyradError("""
    datatype {} not recognized, must be one of: {}
    """.format(newvalue, datatypes))
        else:
            self.paramsdict['datatype'] = str(newvalue)
            ## link_barcodes is called before datatypes is set
            ## we need to know the datatype so we can read in
            ## the multiplexed barcodes for 3rad. This seems
            ## a little annoying, but it was better than any
            ## alternatives I could think of.
            if "3rad" in self.paramsdict['datatype'] and not \
            self.paramsdict['sorted_fastq_path'].strip():
                if not "Merged:" in self.paramsdict['barcodes_path']:
                    self._link_barcodes()

    elif param == 'restriction_overhang':
        newvalue = _tuplecheck(newvalue, str)
        assert isinstance(newvalue, tuple), """
    cut site must be a tuple, e.g., (TGCAG, '') or (TGCAG, CCGG)"""
        ## Handle the special case where the user has 1
        ## restriction overhang and does not include the trailing comma
        if len(newvalue) == 1:
            ## for gbs users might not know to enter the second cut site
            ## so we do it for them.
            if self.paramsdict["datatype"] == "gbs":
                newvalue += newvalue
            else:
                newvalue += ("",)
        #=======
        #    newvalue = (newvalue[0], "")
        #>>>>>>> d40a5d5086a0d0aace04dd08338ec4ba5341d1f2

        ## Handle 3rad datatype with only 3 cutters
        if len(newvalue) == 3:
            newvalue = (newvalue[0], newvalue[1], newvalue[2], "")
        assert len(newvalue) <= 4, """
    most datasets require 1 or 2 cut sites, e.g., (TGCAG, '') or (TGCAG, CCGG).
    For 3rad/seqcap may be up to 4 cut sites."""
        self.paramsdict['restriction_overhang'] = newvalue

    elif param == 'max_low_qual_bases':
        assert isinstance(int(newvalue), int), """
    max_low_qual_bases must be an integer."""
        self.paramsdict['max_low_qual_bases'] = int(newvalue)

    elif param == 'phred_Qscore_offset':
        assert isinstance(int(newvalue), int), \
            "phred_Qscore_offset must be an integer."
        self.paramsdict['phred_Qscore_offset'] = int(newvalue)

    elif param == 'mindepth_statistical':
        assert isinstance(int(newvalue), int), \
            "mindepth_statistical must be an integer."
        ## do not allow values below 5
        if int(newvalue) < 5:
            raise IPyradError("""
    mindepth statistical cannot be set < 5. Use mindepth_majrule.""")
        else:
            self.paramsdict['mindepth_statistical'] = int(newvalue)

    elif param == 'mindepth_majrule':
        assert isinstance(int(newvalue), int), \
            "mindepth_majrule must be an integer."
        self.paramsdict['mindepth_majrule'] = int(newvalue)

    elif param == 'maxdepth':
        self.paramsdict['maxdepth'] = int(newvalue)

    elif param == 'clust_threshold':
        newvalue = float(newvalue)
        assert (newvalue < 1) & (newvalue > 0), \
        "clust_threshold must be a decimal value between 0 and 1."
        self.paramsdict['clust_threshold'] = newvalue

    elif param == 'max_barcode_mismatch':
        self.paramsdict['max_barcode_mismatch'] = int(newvalue)

    elif param == 'filter_adapters':
        self.paramsdict['filter_adapters'] = int(newvalue)

    elif param == 'filter_min_trim_len':
        self.paramsdict["filter_min_trim_len"] = int(newvalue)

    elif param == 'max_alleles_consens':
        self.paramsdict['max_alleles_consens'] = int(newvalue)

    elif param == 'max_Ns_consens':
        newvalue = _tuplecheck(newvalue, int)
        assert isinstance(newvalue, tuple), \
        "max_Ns_consens should be a tuple e.g., (8, 8)"
        self.paramsdict['max_Ns_consens'] = newvalue

    elif param == 'max_Hs_consens':
        newvalue = _tuplecheck(newvalue, int)
        assert isinstance(newvalue, tuple), \
        "max_Hs_consens should be a tuple e.g., (5, 5)"
        self.paramsdict['max_Hs_consens'] = newvalue

    elif param == 'min_samples_locus':
        self.paramsdict['min_samples_locus'] = int(newvalue)

    elif param == 'max_shared_Hs_locus':
        if isinstance(newvalue, str):
            if newvalue.isdigit():
                newvalue = int(newvalue)
            else:
                try:
                    newvalue = float(newvalue)
                except Exception as inst:
                    raise IPyradParamsError("""
    max_shared_Hs_locus must be int or float, you put: {}""".format(newvalue))
        self.paramsdict['max_shared_Hs_locus'] = newvalue

    elif param == 'max_SNPs_locus':
        newvalue = _tuplecheck(newvalue, int)
        assert isinstance(newvalue, tuple), \
        "max_SNPs_locus should be a tuple e.g., (20, 20)"
        self.paramsdict['max_SNPs_locus'] = newvalue

    elif param == 'max_Indels_locus':
        newvalue = _tuplecheck(newvalue, int)
        assert isinstance(newvalue, tuple), \
        "max_Indels_locus should be a tuple e.g., (5, 100)"
        self.paramsdict['max_Indels_locus'] = newvalue

    ## deprecated but retained for legacy, now uses trim_reads (below)
    elif param == 'edit_cutsites':
        ## Force into a string tuple
        newvalue = _tuplecheck(newvalue)
        ## try converting each tup element to ints
        newvalue = list(newvalue)
        for i in range(2):
            try:
                newvalue[i] = int(newvalue[i])
            except (ValueError, IndexError):
                newvalue.append(0)
                pass
        newvalue = tuple(newvalue)
        ## make sure we have a nice tuple
        if not isinstance(newvalue, tuple):
            raise IPyradWarningExit("""
    Error: edit_cutsites should be a tuple e.g., (0, 5) or ('TGCAG', 6),
    you entered {}
    """.format(newvalue))
        self.paramsdict['edit_cutsites'] = newvalue

    elif param == 'trim_reads':
        ## Force into a string tuple
        newvalue = _tuplecheck(newvalue)
        ## try converting each tup element to ints
        newvalue = list(newvalue)
        for i in range(4):
            try:
                newvalue[i] = int(newvalue[i])
            except (ValueError, IndexError):
                newvalue.append(0)
                pass
        newvalue = tuple(newvalue)
        ## make sure we have a nice tuple
        if not isinstance(newvalue, tuple):
            raise IPyradWarningExit("""
    Error: trim_reads should be a tuple e.g., (0, -5, -5, 0) 
    or (0, 90, 0, 90), or (0, 0, 0, 0). 
    You entered {}\n""".format(newvalue))
        self.paramsdict['trim_reads'] = newvalue        

    ## deprecated but retained for legacy, now named trim_loci 
    elif param == 'trim_overhang':
        newvalue = _tuplecheck(newvalue, str)
        assert isinstance(newvalue, tuple), \
        "trim_overhang should be a tuple e.g., (4, *, *, 4)"
        self.paramsdict['trim_overhang'] = tuple([int(i) for i in newvalue])

    elif param == 'trim_loci':
        newvalue = _tuplecheck(newvalue, str)
        assert isinstance(newvalue, tuple), \
        "trim_overhang should be a tuple e.g., (0, -5, -5, 0)"
        self.paramsdict['trim_loci'] = tuple([int(i) for i in newvalue])


    elif param == 'output_formats':
        ## let's get whatever the user entered as a tuple of letters
        allowed = assemble.write_outfiles.OUTPUT_FORMATS.keys()

        #<<<<<<< HEAD
        ## Handle the case where output formats is an empty string
        if isinstance(newvalue, str):
            ## strip commas and spaces from string so we have only letters
            newvalue = newvalue.replace(",", "").replace(" ", "")
            newvalue = list(newvalue)
            if not newvalue:
                newvalue = ["*"]
        if isinstance(newvalue, tuple):
            newvalue = list(newvalue)
        #=======
        #if isinstance(newvalue, tuple):
        #    newvalue = list(newvalue)
        #if isinstance(newvalue, str):
        #    newvalue = [i.strip() for i in newvalue.split(",")]
        #    ## Handle the case where output formats is empty
        #    if not any(newvalue):
        #        newvalue = "*"
        #>>>>>>> 488144d1d97240b8b6f6caf9cfb6c023bb6ebb36
        if isinstance(newvalue, list):
            ## if more than letters, raise an warning
            if any([len(i) > 1 for i in newvalue]):
                LOGGER.warning("""
    'output_formats' params entry is malformed. Setting to * to avoid errors.""")
                newvalue = allowed
            newvalue = tuple(newvalue)
            #newvalue = tuple([i for i in newvalue if i in allowed])
        if "*" in newvalue:
            newvalue = allowed

        ## set the param
        self.paramsdict['output_formats'] = newvalue


    elif param == 'pop_assign_file':
        fullpoppath = _expander(newvalue)

        ## if a path is entered, raise exception if not found
        if newvalue:
            if not os.path.isfile(fullpoppath):
                LOGGER.warn("Population assignment file not found.")
                raise IPyradWarningExit("""
    Warning: Population assignment file not found. This must be an
    absolute path (/home/wat/ipyrad/data/my_popfile.txt) or relative to
    the directory where you're running ipyrad (./data/my_popfile.txt)
    You entered: {}\n""".format(fullpoppath))
        ## should we add a check here that all pop samples are in samples?

            self.paramsdict['pop_assign_file'] = fullpoppath
            self._link_populations()
        else:
            self.paramsdict['pop_assign_file'] = ""
            ## Don't forget to possibly blank the populations dictionary
            self.populations = {}

    return self
def _paramschecker(self, param, newvalue):
    """ Raises exceptions when params are set to values they should not be"""

    if param == 'assembly_name':
        ## Make sure somebody doesn't try to change their assembly_name, bad
        ## things would happen. Calling set_params on assembly_name only raises
        ## an informative error. Assembly_name is set at Assembly creation time
        ## and is immutable.
        raise IPyradWarningExit(CANNOT_CHANGE_ASSEMBLY_NAME)

    elif param == 'project_dir':
        expandpath = _expander(newvalue)
        if not expandpath.startswith("/"):
            if os.path.exists(expandpath):
                expandpath = _expander(expandpath)
        ## Forbid spaces in path names
        if " " in expandpath:
            raise IPyradWarningExit(BAD_PROJDIR_NAME.format(expandpath))
        self.paramsdict["project_dir"] = expandpath
        self.dirs["project"] = expandpath

    ## `Merged:` in newvalue for raw_fastq_path indicates that this
    ## assembly is a merge of several others, so this param has no
    ## value for this assembly
    elif param == 'raw_fastq_path':
        if newvalue and not "Merged:" in newvalue:
            fullrawpath = _expander(newvalue)
            if os.path.isdir(fullrawpath):
                raise IPyradWarningExit(RAW_PATH_ISDIR.format(fullrawpath))
            ## if something is found in path
            elif glob.glob(fullrawpath):
                self.paramsdict['raw_fastq_path'] = fullrawpath
            ## else allow empty, tho it can still raise an error in step1
            else:
                raise IPyradWarningExit(NO_RAW_FILE.format(fullrawpath))
        else:
            self.paramsdict['raw_fastq_path'] = ""


    ## `Merged:` in newvalue for barcodes_path indicates that this
    ## assembly is a merge of several others, so this param has no
    ## value for this assembly
    elif param == 'barcodes_path':
        ## if a value was entered check that it exists
        if newvalue and not "Merged:" in newvalue:
            ## also allow for fuzzy match in names using glob
            fullbarpath = glob.glob(_expander(newvalue))[0]
            ## raise error if file is not found
            if not os.path.exists(fullbarpath):
                raise IPyradWarningExit(BARCODE_NOT_FOUND.format(fullbarpath))
            else:
                self.paramsdict['barcodes_path'] = fullbarpath
                self._link_barcodes()

        ## if no path was entered then set barcodes path to empty.
        ## this is checked again during step 1 and will raise an error
        ## if you try demultiplexing without a barcodes file
        else:
            self.paramsdict['barcodes_path'] = newvalue


    ## `Merged:` in newvalue for sorted_fastq_path indicates that this
    ## assembly is a merge of several others, so this param has no
    ## value for this assembly
    elif param == 'sorted_fastq_path':
        if newvalue and not "Merged:" in newvalue:
            fullsortedpath = _expander(newvalue)

            if os.path.isdir(fullsortedpath):
                raise IPyradWarningExit(SORTED_ISDIR.format(fullsortedpath))
            elif glob.glob(fullsortedpath):
                self.paramsdict['sorted_fastq_path'] = fullsortedpath

            else:
                raise IPyradWarningExit(SORTED_NOT_FOUND.format(fullsortedpath))
        ## if no value was entered then set to "".
        else:
            self.paramsdict['sorted_fastq_path'] = ""


    elif param == 'assembly_method':
        ## TEMPORARY BLOCK ON DENOVO+REFERENCE METHOD
#        if newvalue == "denovo+reference":
#            raise IPyradWarningExit("""
#    Error: The 'denovo+reference' method is temporarily blocked while we 
#    refactor it to greatly improve the speed. You can either revert to an
#    older version (pre v.0.7.0) or wait for the next update to resume using
#    this method. 
#    """)

        methods = ["denovo", "reference", "denovo+reference", "denovo-reference"]
        assert newvalue in methods, BAD_ASSEMBLY_METHOD.format(newvalue)
        self.paramsdict['assembly_method'] = newvalue


    elif param == 'reference_sequence':
        if newvalue:
            fullrawpath = _expander(newvalue)
            if not os.path.isfile(fullrawpath):
                LOGGER.info("reference sequence file not found.")
                raise IPyradWarningExit(REF_NOT_FOUND.format(fullrawpath))
            self.paramsdict['reference_sequence'] = fullrawpath
        ## if no value was entered the set to "". Will be checked again
        ## at step3 if user tries to do refseq and raise error
        else:
            self.paramsdict['reference_sequence'] = ""


    elif param == 'datatype':
        ## list of allowed datatypes
        datatypes = ['rad', 'gbs', 'ddrad', 'pairddrad',
                     'pairgbs', 'merged', '2brad', 'pair3rad']
        ## raise error if something else
        if str(newvalue) not in datatypes:
            raise IPyradError("""
    datatype {} not recognized, must be one of: {}
    """.format(newvalue, datatypes))
        else:
            self.paramsdict['datatype'] = str(newvalue)
            ## link_barcodes is called before datatypes is set
            ## we need to know the datatype so we can read in
            ## the multiplexed barcodes for 3rad. This seems
            ## a little annoying, but it was better than any
            ## alternatives I could think of.
            if "3rad" in self.paramsdict['datatype'] and not \
            self.paramsdict['sorted_fastq_path'].strip():
                if not "Merged:" in self.paramsdict['barcodes_path']:
                    self._link_barcodes()

    elif param == 'restriction_overhang':
        newvalue = _tuplecheck(newvalue, str)
        assert isinstance(newvalue, tuple), """
    cut site must be a tuple, e.g., (TGCAG, '') or (TGCAG, CCGG)"""
        ## Handle the special case where the user has 1
        ## restriction overhang and does not include the trailing comma
        if len(newvalue) == 1:
            ## for gbs users might not know to enter the second cut site
            ## so we do it for them.
            if self.paramsdict["datatype"] == "gbs":
                newvalue += newvalue
            else:
                newvalue += ("",)
        #=======
        #    newvalue = (newvalue[0], "")
        #>>>>>>> d40a5d5086a0d0aace04dd08338ec4ba5341d1f2

        ## Handle 3rad datatype with only 3 cutters
        if len(newvalue) == 3:
            newvalue = (newvalue[0], newvalue[1], newvalue[2], "")
        assert len(newvalue) <= 4, """
    most datasets require 1 or 2 cut sites, e.g., (TGCAG, '') or (TGCAG, CCGG).
    For 3rad/seqcap may be up to 4 cut sites."""
        self.paramsdict['restriction_overhang'] = newvalue

    elif param == 'max_low_qual_bases':
        assert isinstance(int(newvalue), int), """
    max_low_qual_bases must be an integer."""
        self.paramsdict['max_low_qual_bases'] = int(newvalue)

    elif param == 'phred_Qscore_offset':
        assert isinstance(int(newvalue), int), \
            "phred_Qscore_offset must be an integer."
        self.paramsdict['phred_Qscore_offset'] = int(newvalue)

    elif param == 'mindepth_statistical':
        assert isinstance(int(newvalue), int), \
            "mindepth_statistical must be an integer."
        ## do not allow values below 5
        if int(newvalue) < 5:
            raise IPyradError("""
    mindepth statistical cannot be set < 5. Use mindepth_majrule.""")
        else:
            self.paramsdict['mindepth_statistical'] = int(newvalue)

    elif param == 'mindepth_majrule':
        assert isinstance(int(newvalue), int), \
            "mindepth_majrule must be an integer."
        self.paramsdict['mindepth_majrule'] = int(newvalue)

    elif param == 'maxdepth':
        self.paramsdict['maxdepth'] = int(newvalue)

    elif param == 'clust_threshold':
        newvalue = float(newvalue)
        assert (newvalue < 1) & (newvalue > 0), \
        "clust_threshold must be a decimal value between 0 and 1."
        self.paramsdict['clust_threshold'] = newvalue

    elif param == 'max_barcode_mismatch':
        self.paramsdict['max_barcode_mismatch'] = int(newvalue)

    elif param == 'filter_adapters':
        self.paramsdict['filter_adapters'] = int(newvalue)

    elif param == 'filter_min_trim_len':
        self.paramsdict["filter_min_trim_len"] = int(newvalue)

    elif param == 'max_alleles_consens':
        self.paramsdict['max_alleles_consens'] = int(newvalue)

    elif param == 'max_Ns_consens':
        newvalue = _tuplecheck(newvalue, int)
        assert isinstance(newvalue, tuple), \
        "max_Ns_consens should be a tuple e.g., (8, 8)"
        self.paramsdict['max_Ns_consens'] = newvalue

    elif param == 'max_Hs_consens':
        newvalue = _tuplecheck(newvalue, int)
        assert isinstance(newvalue, tuple), \
        "max_Hs_consens should be a tuple e.g., (5, 5)"
        self.paramsdict['max_Hs_consens'] = newvalue

    elif param == 'min_samples_locus':
        self.paramsdict['min_samples_locus'] = int(newvalue)

    elif param == 'max_shared_Hs_locus':
        if isinstance(newvalue, str):
            if newvalue.isdigit():
                newvalue = int(newvalue)
            else:
                try:
                    newvalue = float(newvalue)
                except Exception as inst:
                    raise IPyradParamsError("""
    max_shared_Hs_locus must be int or float, you put: {}""".format(newvalue))
        self.paramsdict['max_shared_Hs_locus'] = newvalue

    elif param == 'max_SNPs_locus':
        newvalue = _tuplecheck(newvalue, int)
        assert isinstance(newvalue, tuple), \
        "max_SNPs_locus should be a tuple e.g., (20, 20)"
        self.paramsdict['max_SNPs_locus'] = newvalue

    elif param == 'max_Indels_locus':
        newvalue = _tuplecheck(newvalue, int)
        assert isinstance(newvalue, tuple), \
        "max_Indels_locus should be a tuple e.g., (5, 100)"
        self.paramsdict['max_Indels_locus'] = newvalue

    ## deprecated but retained for legacy, now uses trim_reads (below)
    elif param == 'edit_cutsites':
        ## Force into a string tuple
        newvalue = _tuplecheck(newvalue)
        ## try converting each tup element to ints
        newvalue = list(newvalue)
        for i in range(2):
            try:
                newvalue[i] = int(newvalue[i])
            except (ValueError, IndexError):
                newvalue.append(0)
                pass
        newvalue = tuple(newvalue)
        ## make sure we have a nice tuple
        if not isinstance(newvalue, tuple):
            raise IPyradWarningExit("""
    Error: edit_cutsites should be a tuple e.g., (0, 5) or ('TGCAG', 6),
    you entered {}
    """.format(newvalue))
        self.paramsdict['edit_cutsites'] = newvalue

    elif param == 'trim_reads':
        ## Force into a string tuple
        newvalue = _tuplecheck(newvalue)
        ## try converting each tup element to ints
        newvalue = list(newvalue)
        for i in range(4):
            try:
                newvalue[i] = int(newvalue[i])
            except (ValueError, IndexError):
                newvalue.append(0)
                pass
        newvalue = tuple(newvalue)
        ## make sure we have a nice tuple
        if not isinstance(newvalue, tuple):
            raise IPyradWarningExit("""
    Error: trim_reads should be a tuple e.g., (0, -5, -5, 0) 
    or (0, 90, 0, 90), or (0, 0, 0, 0). 
    You entered {}\n""".format(newvalue))
        self.paramsdict['trim_reads'] = newvalue        

    ## deprecated but retained for legacy, now named trim_loci 
    elif param == 'trim_overhang':
        newvalue = _tuplecheck(newvalue, str)
        assert isinstance(newvalue, tuple), \
        "trim_overhang should be a tuple e.g., (4, *, *, 4)"
        self.paramsdict['trim_overhang'] = tuple([int(i) for i in newvalue])

    elif param == 'trim_loci':
        newvalue = _tuplecheck(newvalue, str)
        assert isinstance(newvalue, tuple), \
        "trim_overhang should be a tuple e.g., (0, -5, -5, 0)"
        self.paramsdict['trim_loci'] = tuple([int(i) for i in newvalue])


    elif param == 'output_formats':
        ## let's get whatever the user entered as a tuple of letters
        allowed = assemble.write_outfiles.OUTPUT_FORMATS.keys()

        #<<<<<<< HEAD
        ## Handle the case where output formats is an empty string
        if isinstance(newvalue, str):
            ## strip commas and spaces from string so we have only letters
            newvalue = newvalue.replace(",", "").replace(" ", "")
            newvalue = list(newvalue)
            if not newvalue:
                newvalue = ["*"]
        if isinstance(newvalue, tuple):
            newvalue = list(newvalue)
        #=======
        #if isinstance(newvalue, tuple):
        #    newvalue = list(newvalue)
        #if isinstance(newvalue, str):
        #    newvalue = [i.strip() for i in newvalue.split(",")]
        #    ## Handle the case where output formats is empty
        #    if not any(newvalue):
        #        newvalue = "*"
        #>>>>>>> 488144d1d97240b8b6f6caf9cfb6c023bb6ebb36
        if isinstance(newvalue, list):
            ## if more than letters, raise an warning
            if any([len(i) > 1 for i in newvalue]):
                LOGGER.warning("""
    'output_formats' params entry is malformed. Setting to * to avoid errors.""")
                newvalue = allowed
            newvalue = tuple(newvalue)
            #newvalue = tuple([i for i in newvalue if i in allowed])
        if "*" in newvalue:
            newvalue = allowed

        ## set the param
        self.paramsdict['output_formats'] = newvalue


    elif param == 'pop_assign_file':
        fullpoppath = _expander(newvalue)

        ## if a path is entered, raise exception if not found
        if newvalue:
            if not os.path.isfile(fullpoppath):
                LOGGER.warn("Population assignment file not found.")
                raise IPyradWarningExit("""
    Warning: Population assignment file not found. This must be an
    absolute path (/home/wat/ipyrad/data/my_popfile.txt) or relative to
    the directory where you're running ipyrad (./data/my_popfile.txt)
    You entered: {}\n""".format(fullpoppath))
        ## should we add a check here that all pop samples are in samples?

            self.paramsdict['pop_assign_file'] = fullpoppath
            self._link_populations()
        else:
            self.paramsdict['pop_assign_file'] = ""
            ## Don't forget to possibly blank the populations dictionary
            self.populations = {}

    return self
def _paramschecker ( self , param , newvalue ) : if param == 'assembly_name' : ## Make sure somebody doesn't try to change their assembly_name, bad ## things would happen. Calling set_params on assembly_name only raises ## an informative error. Assembly_name is set at Assembly creation time ## and is immutable. raise IPyradWarningExit ( CANNOT_CHANGE_ASSEMBLY_NAME ) elif param == 'project_dir' : expandpath = _expander ( newvalue ) if not expandpath . startswith ( "/" ) : if os . path . exists ( expandpath ) : expandpath = _expander ( expandpath ) ## Forbid spaces in path names if " " in expandpath : raise IPyradWarningExit ( BAD_PROJDIR_NAME . format ( expandpath ) ) self . paramsdict [ "project_dir" ] = expandpath self . dirs [ "project" ] = expandpath ## `Merged:` in newvalue for raw_fastq_path indicates that this ## assembly is a merge of several others, so this param has no ## value for this assembly elif param == 'raw_fastq_path' : if newvalue and not "Merged:" in newvalue : fullrawpath = _expander ( newvalue ) if os . path . isdir ( fullrawpath ) : raise IPyradWarningExit ( RAW_PATH_ISDIR . format ( fullrawpath ) ) ## if something is found in path elif glob . glob ( fullrawpath ) : self . paramsdict [ 'raw_fastq_path' ] = fullrawpath ## else allow empty, tho it can still raise an error in step1 else : raise IPyradWarningExit ( NO_RAW_FILE . format ( fullrawpath ) ) else : self . paramsdict [ 'raw_fastq_path' ] = "" ## `Merged:` in newvalue for barcodes_path indicates that this ## assembly is a merge of several others, so this param has no ## value for this assembly elif param == 'barcodes_path' : ## if a value was entered check that it exists if newvalue and not "Merged:" in newvalue : ## also allow for fuzzy match in names using glob fullbarpath = glob . glob ( _expander ( newvalue ) ) [ 0 ] ## raise error if file is not found if not os . path . exists ( fullbarpath ) : raise IPyradWarningExit ( BARCODE_NOT_FOUND . format ( fullbarpath ) ) else : self . paramsdict [ 'barcodes_path' ] = fullbarpath self . _link_barcodes ( ) ## if no path was entered then set barcodes path to empty. ## this is checked again during step 1 and will raise an error ## if you try demultiplexing without a barcodes file else : self . paramsdict [ 'barcodes_path' ] = newvalue ## `Merged:` in newvalue for sorted_fastq_path indicates that this ## assembly is a merge of several others, so this param has no ## value for this assembly elif param == 'sorted_fastq_path' : if newvalue and not "Merged:" in newvalue : fullsortedpath = _expander ( newvalue ) if os . path . isdir ( fullsortedpath ) : raise IPyradWarningExit ( SORTED_ISDIR . format ( fullsortedpath ) ) elif glob . glob ( fullsortedpath ) : self . paramsdict [ 'sorted_fastq_path' ] = fullsortedpath else : raise IPyradWarningExit ( SORTED_NOT_FOUND . format ( fullsortedpath ) ) ## if no value was entered then set to "". else : self . paramsdict [ 'sorted_fastq_path' ] = "" elif param == 'assembly_method' : ## TEMPORARY BLOCK ON DENOVO+REFERENCE METHOD #        if newvalue == "denovo+reference": #            raise IPyradWarningExit(""" #    Error: The 'denovo+reference' method is temporarily blocked while we  #    refactor it to greatly improve the speed. You can either revert to an #    older version (pre v.0.7.0) or wait for the next update to resume using #    this method.  #    """) methods = [ "denovo" , "reference" , "denovo+reference" , "denovo-reference" ] assert newvalue in methods , BAD_ASSEMBLY_METHOD . format ( newvalue ) self . paramsdict [ 'assembly_method' ] = newvalue elif param == 'reference_sequence' : if newvalue : fullrawpath = _expander ( newvalue ) if not os . path . isfile ( fullrawpath ) : LOGGER . info ( "reference sequence file not found." ) raise IPyradWarningExit ( REF_NOT_FOUND . format ( fullrawpath ) ) self . paramsdict [ 'reference_sequence' ] = fullrawpath ## if no value was entered the set to "". Will be checked again ## at step3 if user tries to do refseq and raise error else : self . paramsdict [ 'reference_sequence' ] = "" elif param == 'datatype' : ## list of allowed datatypes datatypes = [ 'rad' , 'gbs' , 'ddrad' , 'pairddrad' , 'pairgbs' , 'merged' , '2brad' , 'pair3rad' ] ## raise error if something else if str ( newvalue ) not in datatypes : raise IPyradError ( """
    datatype {} not recognized, must be one of: {}
    """ . format ( newvalue , datatypes ) ) else : self . paramsdict [ 'datatype' ] = str ( newvalue ) ## link_barcodes is called before datatypes is set ## we need to know the datatype so we can read in ## the multiplexed barcodes for 3rad. This seems ## a little annoying, but it was better than any ## alternatives I could think of. if "3rad" in self . paramsdict [ 'datatype' ] and not self . paramsdict [ 'sorted_fastq_path' ] . strip ( ) : if not "Merged:" in self . paramsdict [ 'barcodes_path' ] : self . _link_barcodes ( ) elif param == 'restriction_overhang' : newvalue = _tuplecheck ( newvalue , str ) assert isinstance ( newvalue , tuple ) , """
    cut site must be a tuple, e.g., (TGCAG, '') or (TGCAG, CCGG)""" ## Handle the special case where the user has 1 ## restriction overhang and does not include the trailing comma if len ( newvalue ) == 1 : ## for gbs users might not know to enter the second cut site ## so we do it for them. if self . paramsdict [ "datatype" ] == "gbs" : newvalue += newvalue else : newvalue += ( "" , ) #======= #    newvalue = (newvalue[0], "") #>>>>>>> d40a5d5086a0d0aace04dd08338ec4ba5341d1f2 ## Handle 3rad datatype with only 3 cutters if len ( newvalue ) == 3 : newvalue = ( newvalue [ 0 ] , newvalue [ 1 ] , newvalue [ 2 ] , "" ) assert len ( newvalue ) <= 4 , """
    most datasets require 1 or 2 cut sites, e.g., (TGCAG, '') or (TGCAG, CCGG).
    For 3rad/seqcap may be up to 4 cut sites.""" self . paramsdict [ 'restriction_overhang' ] = newvalue elif param == 'max_low_qual_bases' : assert isinstance ( int ( newvalue ) , int ) , """
    max_low_qual_bases must be an integer.""" self . paramsdict [ 'max_low_qual_bases' ] = int ( newvalue ) elif param == 'phred_Qscore_offset' : assert isinstance ( int ( newvalue ) , int ) , "phred_Qscore_offset must be an integer." self . paramsdict [ 'phred_Qscore_offset' ] = int ( newvalue ) elif param == 'mindepth_statistical' : assert isinstance ( int ( newvalue ) , int ) , "mindepth_statistical must be an integer." ## do not allow values below 5 if int ( newvalue ) < 5 : raise IPyradError ( """
    mindepth statistical cannot be set < 5. Use mindepth_majrule.""" ) else : self . paramsdict [ 'mindepth_statistical' ] = int ( newvalue ) elif param == 'mindepth_majrule' : assert isinstance ( int ( newvalue ) , int ) , "mindepth_majrule must be an integer." self . paramsdict [ 'mindepth_majrule' ] = int ( newvalue ) elif param == 'maxdepth' : self . paramsdict [ 'maxdepth' ] = int ( newvalue ) elif param == 'clust_threshold' : newvalue = float ( newvalue ) assert ( newvalue < 1 ) & ( newvalue > 0 ) , "clust_threshold must be a decimal value between 0 and 1." self . paramsdict [ 'clust_threshold' ] = newvalue elif param == 'max_barcode_mismatch' : self . paramsdict [ 'max_barcode_mismatch' ] = int ( newvalue ) elif param == 'filter_adapters' : self . paramsdict [ 'filter_adapters' ] = int ( newvalue ) elif param == 'filter_min_trim_len' : self . paramsdict [ "filter_min_trim_len" ] = int ( newvalue ) elif param == 'max_alleles_consens' : self . paramsdict [ 'max_alleles_consens' ] = int ( newvalue ) elif param == 'max_Ns_consens' : newvalue = _tuplecheck ( newvalue , int ) assert isinstance ( newvalue , tuple ) , "max_Ns_consens should be a tuple e.g., (8, 8)" self . paramsdict [ 'max_Ns_consens' ] = newvalue elif param == 'max_Hs_consens' : newvalue = _tuplecheck ( newvalue , int ) assert isinstance ( newvalue , tuple ) , "max_Hs_consens should be a tuple e.g., (5, 5)" self . paramsdict [ 'max_Hs_consens' ] = newvalue elif param == 'min_samples_locus' : self . paramsdict [ 'min_samples_locus' ] = int ( newvalue ) elif param == 'max_shared_Hs_locus' : if isinstance ( newvalue , str ) : if newvalue . isdigit ( ) : newvalue = int ( newvalue ) else : try : newvalue = float ( newvalue ) except Exception as inst : raise IPyradParamsError ( """
    max_shared_Hs_locus must be int or float, you put: {}""" . format ( newvalue ) ) self . paramsdict [ 'max_shared_Hs_locus' ] = newvalue elif param == 'max_SNPs_locus' : newvalue = _tuplecheck ( newvalue , int ) assert isinstance ( newvalue , tuple ) , "max_SNPs_locus should be a tuple e.g., (20, 20)" self . paramsdict [ 'max_SNPs_locus' ] = newvalue elif param == 'max_Indels_locus' : newvalue = _tuplecheck ( newvalue , int ) assert isinstance ( newvalue , tuple ) , "max_Indels_locus should be a tuple e.g., (5, 100)" self . paramsdict [ 'max_Indels_locus' ] = newvalue ## deprecated but retained for legacy, now uses trim_reads (below) elif param == 'edit_cutsites' : ## Force into a string tuple newvalue = _tuplecheck ( newvalue ) ## try converting each tup element to ints newvalue = list ( newvalue ) for i in range ( 2 ) : try : newvalue [ i ] = int ( newvalue [ i ] ) except ( ValueError , IndexError ) : newvalue . append ( 0 ) pass newvalue = tuple ( newvalue ) ## make sure we have a nice tuple if not isinstance ( newvalue , tuple ) : raise IPyradWarningExit ( """
    Error: edit_cutsites should be a tuple e.g., (0, 5) or ('TGCAG', 6),
    you entered {}
    """ . format ( newvalue ) ) self . paramsdict [ 'edit_cutsites' ] = newvalue elif param == 'trim_reads' : ## Force into a string tuple newvalue = _tuplecheck ( newvalue ) ## try converting each tup element to ints newvalue = list ( newvalue ) for i in range ( 4 ) : try : newvalue [ i ] = int ( newvalue [ i ] ) except ( ValueError , IndexError ) : newvalue . append ( 0 ) pass newvalue = tuple ( newvalue ) ## make sure we have a nice tuple if not isinstance ( newvalue , tuple ) : raise IPyradWarningExit ( """
    Error: trim_reads should be a tuple e.g., (0, -5, -5, 0) 
    or (0, 90, 0, 90), or (0, 0, 0, 0). 
    You entered {}\n""" . format ( newvalue ) ) self . paramsdict [ 'trim_reads' ] = newvalue ## deprecated but retained for legacy, now named trim_loci  elif param == 'trim_overhang' : newvalue = _tuplecheck ( newvalue , str ) assert isinstance ( newvalue , tuple ) , "trim_overhang should be a tuple e.g., (4, *, *, 4)" self . paramsdict [ 'trim_overhang' ] = tuple ( [ int ( i ) for i in newvalue ] ) elif param == 'trim_loci' : newvalue = _tuplecheck ( newvalue , str ) assert isinstance ( newvalue , tuple ) , "trim_overhang should be a tuple e.g., (0, -5, -5, 0)" self . paramsdict [ 'trim_loci' ] = tuple ( [ int ( i ) for i in newvalue ] ) elif param == 'output_formats' : ## let's get whatever the user entered as a tuple of letters allowed = assemble . write_outfiles . OUTPUT_FORMATS . keys ( ) #<<<<<<< HEAD ## Handle the case where output formats is an empty string if isinstance ( newvalue , str ) : ## strip commas and spaces from string so we have only letters newvalue = newvalue . replace ( "," , "" ) . replace ( " " , "" ) newvalue = list ( newvalue ) if not newvalue : newvalue = [ "*" ] if isinstance ( newvalue , tuple ) : newvalue = list ( newvalue ) #======= #if isinstance(newvalue, tuple): #    newvalue = list(newvalue) #if isinstance(newvalue, str): #    newvalue = [i.strip() for i in newvalue.split(",")] #    ## Handle the case where output formats is empty #    if not any(newvalue): #        newvalue = "*" #>>>>>>> 488144d1d97240b8b6f6caf9cfb6c023bb6ebb36 if isinstance ( newvalue , list ) : ## if more than letters, raise an warning if any ( [ len ( i ) > 1 for i in newvalue ] ) : LOGGER . warning ( """
    'output_formats' params entry is malformed. Setting to * to avoid errors.""" ) newvalue = allowed newvalue = tuple ( newvalue ) #newvalue = tuple([i for i in newvalue if i in allowed]) if "*" in newvalue : newvalue = allowed ## set the param self . paramsdict [ 'output_formats' ] = newvalue elif param == 'pop_assign_file' : fullpoppath = _expander ( newvalue ) ## if a path is entered, raise exception if not found if newvalue : if not os . path . isfile ( fullpoppath ) : LOGGER . warn ( "Population assignment file not found." ) raise IPyradWarningExit ( """
    Warning: Population assignment file not found. This must be an
    absolute path (/home/wat/ipyrad/data/my_popfile.txt) or relative to
    the directory where you're running ipyrad (./data/my_popfile.txt)
    You entered: {}\n""" . format ( fullpoppath ) ) ## should we add a check here that all pop samples are in samples? self . paramsdict [ 'pop_assign_file' ] = fullpoppath self . _link_populations ( ) else : self . paramsdict [ 'pop_assign_file' ] = "" ## Don't forget to possibly blank the populations dictionary self . populations = { } return self
Raises exceptions when params are set to values they should not be
Raises exceptions when params are set to values they should not be