Loading ci/recipe/meta.yaml +2 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,8 @@ requirements: - biom-format >=2.1.5,<2.2.0 - ijson - h5py - matplotlib 3.1.0 - matplotlib-base 3.1.0 - qiime2 {{ release }}.* test: Loading q2_types/_version.py +3 −3 Original line number Diff line number Diff line Loading @@ -23,9 +23,9 @@ def get_keywords(): # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = " (tag: 2019.7.0)" git_full = "9f31de0c81510fbe6be8b16f95e23b4c974ca002" git_date = "2019-07-30 18:15:54 +0000" git_refnames = " (tag: 2019.10.0)" git_full = "b382dd345500fc2172858ff00638e6bca35760ed" git_date = "2019-11-01 01:04:25 +0000" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords Loading q2_types/feature_data/_format.py +52 −27 Original line number Diff line number Diff line Loading @@ -54,9 +54,6 @@ class TaxonomyFormat(model.TextFileFormat): elif line.lstrip(' ') == '\n': # Blank line continue elif line.startswith('#'): # Comment line continue else: cells = line.split('\t') if len(cells) < 2: Loading Loading @@ -93,41 +90,53 @@ class TSVTaxonomyFormat(model.TextFileFormat): Optionally followed by other arbitrary columns. This format supports comment lines starting with #, and blank lines. The expected header must be the first non-comment, non-blank line. In addition to the header, there must be at least one line of data. This format supports blank lines. The expected header must be the first non-blank line. In addition to the header, there must be at least one line of data. """ HEADER = ['Feature ID', 'Taxon'] def sniff(self): def _check_n_records(self, n=None): with self.open() as fh: data_lines = 0 data_line_count = 0 header = None while data_lines < 10: line = fh.readline() if line == '': # EOF break elif line.lstrip(' ') == '\n': file_ = enumerate(fh) if n is None else zip(range(n), fh) for i, line in file_: # Tracks line number for error reporting i = i + 1 if line.lstrip(' ') == '\n': # Blank line continue elif line.startswith('#'): # Comment line continue cells = line.rstrip('\n').split('\t') cells = line.strip('\n').split('\t') if header is None: if cells[:2] != self.HEADER: return False raise ValidationError( '%s must be the first two header values. The ' 'first two header values provided are: %s (on ' 'line %s).' % (self.HEADER, cells[:2], i)) header = cells else: if len(cells) != len(header): return False data_lines += 1 raise ValidationError( 'Number of values on line %s are not the same as ' 'number of header values. Found %s values ' '(%s), expected %s.' % (i, len(cells), cells, len(self.HEADER))) data_line_count += 1 return header is not None and data_lines > 0 if data_line_count == 0: raise ValidationError('No taxonomy records found, only blank ' 'lines and/or a header row.') def _validate_(self, level): self._check_n_records(n={'min': 10, 'max': None}[level]) TSVTaxonomyDirectoryFormat = model.SingleFileDirectoryFormat( Loading @@ -138,6 +147,7 @@ class DNAFASTAFormat(model.TextFileFormat): def _validate_lines(self, max_lines): FASTADNAValidator = re.compile(r'[ACGTURYKMSWBDHVN]+\r?\n?') last_line_was_ID = False ids = {} with open(str(self), 'rb') as fh: try: Loading @@ -149,8 +159,8 @@ class DNAFASTAFormat(model.TextFileFormat): return if first[0] != ord(b'>'): raise ValidationError("First line of file is not a valid " "FASTA ID. FASTA IDs must start " "with '>'") "description. Descriptions must " "start with '>'") fh.seek(0) for line_number, line in enumerate(fh, 1): if line_number >= max_lines: Loading @@ -158,9 +168,24 @@ class DNAFASTAFormat(model.TextFileFormat): line = line.decode('utf-8-sig') if line.startswith('>'): if last_line_was_ID: raise ValidationError('Multiple consecutive IDs ' 'starting on line ' f'{line_number-1!r}') raise ValidationError('Multiple consecutive ' 'descriptions starting on ' f'line {line_number-1!r}') line = line.split() if line[0] == '>': if len(line) == 1: raise ValidationError( f'Description on line {line_number} is ' 'missing an ID.') else: raise ValidationError( f'ID on line {line_number} starts with a ' 'space. IDs may not start with spaces') if line[0] in ids: raise ValidationError( f'ID on line {line_number} is a duplicate of ' f'another ID on line {ids[line[0]]}.') ids[line[0]] = line_number last_line_was_ID = True elif re.fullmatch(FASTADNAValidator, line): last_line_was_ID = False Loading q2_types/feature_data/_transformer.py +2 −1 Original line number Diff line number Diff line Loading @@ -47,7 +47,7 @@ def _taxonomy_formats_to_dataframe(filepath, has_header=None): """ # Using `dtype=object` and `set_index()` to avoid type casting/inference of # any columns or the index. df = pd.read_csv(filepath, sep='\t', comment='#', skip_blank_lines=True, df = pd.read_csv(filepath, sep='\t', skip_blank_lines=True, header=None, dtype=object) if len(df.columns) < 2: Loading Loading @@ -88,6 +88,7 @@ def _taxonomy_formats_to_dataframe(filepath, has_header=None): "column names are duplicated: %s" % ', '.join(df.columns.get_duplicates())) df['Taxon'] = df['Taxon'].str.strip() return df Loading q2_types/feature_data/tests/data/dna-sequences-duplicate-id.fasta 0 → 100644 +5 −0 Original line number Diff line number Diff line >SEQUENCE1 ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT >SEQUENCE1 ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT ACGTACGTACGTACGTACGTACGT Loading
ci/recipe/meta.yaml +2 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,8 @@ requirements: - biom-format >=2.1.5,<2.2.0 - ijson - h5py - matplotlib 3.1.0 - matplotlib-base 3.1.0 - qiime2 {{ release }}.* test: Loading
q2_types/_version.py +3 −3 Original line number Diff line number Diff line Loading @@ -23,9 +23,9 @@ def get_keywords(): # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = " (tag: 2019.7.0)" git_full = "9f31de0c81510fbe6be8b16f95e23b4c974ca002" git_date = "2019-07-30 18:15:54 +0000" git_refnames = " (tag: 2019.10.0)" git_full = "b382dd345500fc2172858ff00638e6bca35760ed" git_date = "2019-11-01 01:04:25 +0000" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords Loading
q2_types/feature_data/_format.py +52 −27 Original line number Diff line number Diff line Loading @@ -54,9 +54,6 @@ class TaxonomyFormat(model.TextFileFormat): elif line.lstrip(' ') == '\n': # Blank line continue elif line.startswith('#'): # Comment line continue else: cells = line.split('\t') if len(cells) < 2: Loading Loading @@ -93,41 +90,53 @@ class TSVTaxonomyFormat(model.TextFileFormat): Optionally followed by other arbitrary columns. This format supports comment lines starting with #, and blank lines. The expected header must be the first non-comment, non-blank line. In addition to the header, there must be at least one line of data. This format supports blank lines. The expected header must be the first non-blank line. In addition to the header, there must be at least one line of data. """ HEADER = ['Feature ID', 'Taxon'] def sniff(self): def _check_n_records(self, n=None): with self.open() as fh: data_lines = 0 data_line_count = 0 header = None while data_lines < 10: line = fh.readline() if line == '': # EOF break elif line.lstrip(' ') == '\n': file_ = enumerate(fh) if n is None else zip(range(n), fh) for i, line in file_: # Tracks line number for error reporting i = i + 1 if line.lstrip(' ') == '\n': # Blank line continue elif line.startswith('#'): # Comment line continue cells = line.rstrip('\n').split('\t') cells = line.strip('\n').split('\t') if header is None: if cells[:2] != self.HEADER: return False raise ValidationError( '%s must be the first two header values. The ' 'first two header values provided are: %s (on ' 'line %s).' % (self.HEADER, cells[:2], i)) header = cells else: if len(cells) != len(header): return False data_lines += 1 raise ValidationError( 'Number of values on line %s are not the same as ' 'number of header values. Found %s values ' '(%s), expected %s.' % (i, len(cells), cells, len(self.HEADER))) data_line_count += 1 return header is not None and data_lines > 0 if data_line_count == 0: raise ValidationError('No taxonomy records found, only blank ' 'lines and/or a header row.') def _validate_(self, level): self._check_n_records(n={'min': 10, 'max': None}[level]) TSVTaxonomyDirectoryFormat = model.SingleFileDirectoryFormat( Loading @@ -138,6 +147,7 @@ class DNAFASTAFormat(model.TextFileFormat): def _validate_lines(self, max_lines): FASTADNAValidator = re.compile(r'[ACGTURYKMSWBDHVN]+\r?\n?') last_line_was_ID = False ids = {} with open(str(self), 'rb') as fh: try: Loading @@ -149,8 +159,8 @@ class DNAFASTAFormat(model.TextFileFormat): return if first[0] != ord(b'>'): raise ValidationError("First line of file is not a valid " "FASTA ID. FASTA IDs must start " "with '>'") "description. Descriptions must " "start with '>'") fh.seek(0) for line_number, line in enumerate(fh, 1): if line_number >= max_lines: Loading @@ -158,9 +168,24 @@ class DNAFASTAFormat(model.TextFileFormat): line = line.decode('utf-8-sig') if line.startswith('>'): if last_line_was_ID: raise ValidationError('Multiple consecutive IDs ' 'starting on line ' f'{line_number-1!r}') raise ValidationError('Multiple consecutive ' 'descriptions starting on ' f'line {line_number-1!r}') line = line.split() if line[0] == '>': if len(line) == 1: raise ValidationError( f'Description on line {line_number} is ' 'missing an ID.') else: raise ValidationError( f'ID on line {line_number} starts with a ' 'space. IDs may not start with spaces') if line[0] in ids: raise ValidationError( f'ID on line {line_number} is a duplicate of ' f'another ID on line {ids[line[0]]}.') ids[line[0]] = line_number last_line_was_ID = True elif re.fullmatch(FASTADNAValidator, line): last_line_was_ID = False Loading
q2_types/feature_data/_transformer.py +2 −1 Original line number Diff line number Diff line Loading @@ -47,7 +47,7 @@ def _taxonomy_formats_to_dataframe(filepath, has_header=None): """ # Using `dtype=object` and `set_index()` to avoid type casting/inference of # any columns or the index. df = pd.read_csv(filepath, sep='\t', comment='#', skip_blank_lines=True, df = pd.read_csv(filepath, sep='\t', skip_blank_lines=True, header=None, dtype=object) if len(df.columns) < 2: Loading Loading @@ -88,6 +88,7 @@ def _taxonomy_formats_to_dataframe(filepath, has_header=None): "column names are duplicated: %s" % ', '.join(df.columns.get_duplicates())) df['Taxon'] = df['Taxon'].str.strip() return df Loading
q2_types/feature_data/tests/data/dna-sequences-duplicate-id.fasta 0 → 100644 +5 −0 Original line number Diff line number Diff line >SEQUENCE1 ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT >SEQUENCE1 ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT ACGTACGTACGTACGTACGTACGT