[med-svn] [Git][med-team/atropos][master] 6 commits: New upstream version 1.1.24+dfsg

Steffen Möller gitlab at salsa.debian.org
Sat Nov 30 12:04:44 GMT 2019



Steffen Möller pushed to branch master at Debian Med / atropos


Commits:
879e6a06 by Steffen Moeller at 2019-11-30T12:00:22Z
New upstream version 1.1.24+dfsg
- - - - -
3a390e11 by Steffen Moeller at 2019-11-30T12:00:22Z
New upstream version

- - - - -
156debec by Steffen Moeller at 2019-11-30T12:00:25Z
Update upstream source from tag 'upstream/1.1.24+dfsg'

Update to upstream version '1.1.24+dfsg'
with Debian dir b9de02613f8dcc34eaaffcdfdb6983e973ae663c
- - - - -
1265212d by Steffen Moeller at 2019-11-30T12:00:29Z
Trim trailing whitespace.

Fixes lintian: file-contains-trailing-whitespace
See https://lintian.debian.org/tags/file-contains-trailing-whitespace.html for more details.

- - - - -
538c1e6b by Steffen Moeller at 2019-11-30T12:01:33Z
Upload to unstable

- - - - -
8e57e066 by Steffen Moeller at 2019-11-30T12:03:29Z
Eliminated lintian warning

- - - - -


13 changed files:

- .travis.yml
- CHANGES.md
- Makefile
- README.md
- atropos/_version.py
- atropos/align/__init__.py
- atropos/commands/legacy_report.py
- atropos/io/seqio.py
- atropos/util/__init__.py
- debian/changelog
- debian/copyright
- debian/rules
- setup.py


Changes:

=====================================
.travis.yml
=====================================
@@ -1,17 +1,15 @@
 sudo: false
+dist: xenial
 language: python
 cache:
   directories:
   - "$HOME/.cache/pip"
 python:
-- 3.4.3
-- 3.5
-- 3.6
-matrix:
-  include:
-    - python: 3.7
-      dist: xenial
-      sudo: true
+  - "3.4.5"
+  - "3.5"
+  - "3.6"
+  - "3.7"
+  - "3.8"
 install:
 - pip install --upgrade pip wheel
 - pip install Cython


=====================================
CHANGES.md
=====================================
@@ -1,25 +1,34 @@
 # Changes
 
-v1.1.22 (dev)
--------------
+v1.1.24 (2019.11.27)
+
+* Fix #87 - Python 3.8 compatibility, time.clock removed
+
+v1.1.23 (2019.11.22)
+
+* Set minimum Python version to 3.4.5
+* Fixed #86 - no trimming performed for single-end BAM file
+
+v1.1.22 (2019.05.20)
+
 * Documentation fixes (#79)
 * Fix for index error when running `detect` command (#80)
 
 v1.1.21 (2018.11.24)
---------------------
+
 * Added long options for paired adapter parameters.
 
 v1.1.20 (2018.11.21)
---------------------
+
 * Fix #74: Make pysam open SAM/BAM files with check_sq=False by default
 * Fixed setup.py to correctly include README for PyPI.
 
 v1.1.19 (2018.05.16)
---------------------
+
 * Fix #68: Error when using insert aligner with adapters of different lengths
 
 v1.1.18 (2018.03.16)
---------------------
+
 * Added two new tools to the benchmarks:
   * fastp
   * Cutadapt
@@ -27,78 +36,78 @@ v1.1.18 (2018.03.16)
 * Fix #64: InsertAligner not respecting match_adapter_wildcards and match_read_wildcards options.
 
 v1.1.17 (2018.01.13)
---------------------
+
 * Fix #51: Reads of different lengths not error corrected.
 
 v1.1.16 (2018.01.07)
---------------------
+
 * Fix for #57: LegacyReport stops on adapter with no trimmed reads, and LegacyReport errors when histogram data is None. Thanks to @cokelaer and @pyMyt1!
 * Fix for #58: NextSeqTrimmer not trimming from both ends. Thanks to @pkMyt1!
 
 v1.1.15 (2017.09.28)
---------------------
+
 * Fix for #41: Error when using progress bar.
 * Fix for #42: Discordance between Cutadapt and Atropos in number of expected events.
 * Added '--alphabet' option. Set to 'dna' to validate input sequences against the allowed DNA characters (A/C/G/T/N). This fixes #43 and partially fixes #44.
 * Fixed #44: Uncaught errors not being logged.
 
 v1.1.14 (2017.09.19)
---------------------
+
 * Fix for #39: miRNA option error (thanks to @mottodora)
 * Fix for #37: fixes overflow error when computing RandomMatchProbability on long reads (>150 bp)
 
 v1.1.13 (2017.09.14)
---------------------
+
 * Fix for #38: Atropos fails with MultiCore error when using OrderPreservingWriterResultsHandler (thanks to @cshenanigans)
 
 v1.1.12 (2017.08.15)
---------------------
+
 * Expose --min-frequency and --min-contaminant-match-frac options to 'detect -d heuristic' command.
 * Expose --min-kmer-match-frac option to 'detect -d known' command.
 * Fixed #35: using incorrect metric to determine match fraction in 'detect -d known' command.
 
 v1.1.11 (2017.08.15)
---------------------
+
 * Fixed #34: JSON report output not working with SRA streaming.
 
 v1.1.10 (2017.08.09)
---------------------
+
 * Improve debugging messages
 
 v1.1.9 (2017.08.01)
--------------------
+
 * Fix #30: failure when using --preserve-order option
 
 v1.1.8 (2017.07.10)
--------------------
+
 * Add --config option for specifying options in a config file.
 * Fix for #29: allow paired-end quality and N trimming without adapter trimming.
 * Removed twine register command from make release
 
 v1.1.7 (2017.06.01)
--------------------
+
 * Stream reads directly from an SRA accession for any atropos command using the
   -sra option.
 * Add detect option to specify the bases that signify when the sequencer has
   read past the end of a fragment.
 
 v1.1.6 (2017.05.30)
--------------------
+
 * Add FASTA output for detect command, and enable json, yaml, and pickle output for all commands.
 
 v1.1.5 (2017.05.18)
--------------------
+
 * Major update to the documentation.
 * Fixed error messages in multi-threaded mode.
 * Fixed bug when generating reports for runs involving error correction.
 
 v1.1.4 (2017.05.02)
--------------------
+
 * Exposed option to set PRNG seed when subsampling reads.
 * Fixed issue #14: 'detect' and 'error' commands were broken. This involved rewriting those commands to use the same pipeline and reporting frameworks as the 'trim' and 'qc' commands.
 
 v1.1.3 (2017.05.01)
--------------------
+
 * Updated Dockerfile to use smaller, Alpine-based image.
 * Added Docker image for v1.1.2 to Docker Hub.
 * Updated Travis config to automatically build Docker images for each release.
@@ -107,7 +116,6 @@ v1.1.3 (2017.05.01)
 * Fixed #13: unnecessary differences in summary output between Cutadapt and Atropos.
 
 v1.1.2 (2017.04.12)
--------------------
 
 * New 'qc' command computes read-level statistics.
 * The 'trim' command can also compute read-level statistic pre- and/or post-trimming using the new '--stats' option.
@@ -125,7 +133,6 @@ v1.1.2 (2017.04.12)
 * Ported some recent enhancments over from Cutadapt.
 
 v1.0.23 (2016.12.07)
---------------------
 
 * Identified a subtle bug having to do with insufficient memory in multi-threaded mode. The main thread appears to hang waiting for the next read from the input file. This appears to occur only under a strictly-regulated memory cap such as on cluster environment. This bug is not fixed, but I added the following:
     * Set the default batch size based on the queue sizes
@@ -133,36 +140,30 @@ v1.0.23 (2016.12.07)
 * Bug fixes
 
 v1.0.22 (2016.12.02)
---------------------
 
 * Abstracted the ErrorEstimator class to enable alternate implementations.
 * Added a new ShadowRegressionErrorEstimator that uses the ShadowRegression R package (Wang et al.) to more accurately estimate sequencing error rate. This requires that R and the [ShadowRegression package](http://bcb.dfci.harvard.edu/~vwang/shadowRegression.html) and its dependencies be installed -- MASS and ReadCount, which in turn depend on a bunch of Bioconductor packages. At some point, this dependency will go away when I reimplement the method in pure python.
 * The error command now reports the longest matching read fragment, which is usually a closer match for the actual adapter sequence than the longest matching k-mer.
 
 v1.0.21 (2016.11.23)
---------------------
 
 * Bugfixes
 
 v1.0.20 (2016.11.22)
---------------------
 
 * Changed the order of trimming operations - OverwriteReadModifier is now after read and quality trimming.
 * Refactored the main Atropos interface to improve testability.
 * Added more unit tests.
 
 v1.0.19 (2016.11.21)
---------------------
 
 * Fixed a major bug in OverwriteReadModifier, and in the unit tests for paired-end trimmers.
 
 v1.0.18 (2016.11.20)
---------------------
 
 * Added OverwriteReadModifier, a paired-end modifier that overwrites one read end with the other if the mean quality over the first N bases (where N is user-specified) of one is below a threshold value and the mean quality of the other is above a second threshold. This dramatically improves the number of high-quality read mappings in data sets where there are systematic problems with one read-end.
 
 v1.0.17 (2016.11.18)
---------------------
 
 * Perform error correction when insert match fails but adapter matches are complementary
 * Improvements to handling of cached adapter lists
@@ -171,7 +172,6 @@ v1.0.17 (2016.11.18)
 * Many
 
 v1.0.16 (2016.09.20)
--------------------
 
 * Migrate to Versioneer for version management.
 * Enable stderr as a valid output using the '\_' shortcut.
@@ -182,14 +182,12 @@ v1.0.16 (2016.09.20)
 * When InsertAdapterCutter.symmetric is True and mismatch_action is not None, insert match fails, at least one adapter match succeeds, and the adapter matches (if there are two) are complementary, then the reads are treated as overlapping and error correction is performed. This leads to substantial improvements when one read is of good quality while the other is other is of poor quality.
 
 v1.0.15 (2016.09.14)
---------------------
 
 * Fixed missing import bug in 'detect' command.
 * Added estimate of fraction of contaminated reads to output of 'detect' command.
 * Optionally cache list of known contaminants rather than re-download it every time.
 
 v1.0.14 (2016.09.13)
---------------------
 
 * Implemented \_align.MultiAligner, which returns all matches that satisfy the overlap and error thresholds. align.InsertAligner now uses MultiAligner for insert matching, and tests all matches in decreasing size order until it finds one with adapter matches (if any).
 * Major improvements to the accuracy of the 'detect' command.
@@ -201,62 +199,52 @@ v1.0.14 (2016.09.13)
 * Sevaral other bugfixes.
 
 v1.0.13 (2016.08.31)
---------------------
 
 * Add options to specify max error rates for insert and adapter matching within insert aligner.
 * Add new command to estimate empirical error rate in data set from base qualities.
 
 v1.0.12 (2016.08.30)
---------------------
 
 * Add ability to correct errors during insert-match adapter trimming.
 * Implement additional adapter-detection algorithms.
 * Fix bug where default output file is force-created in parallel-write mode
 
 v1.0.11 (2016.08.24)
---------------------
 
 * Clarify and fix issues with bisulfite trimming. Notably, rrbs and non-directional are now allowed independently or in combination.
 
 v1.0.10 (2016.08.23)
---------------------
 
 * Introduced new 'detect' command for automatically detecting adapter sequences.
 * Options are now required to specify input files.
 * Major updates to documentation.
 
 v1.0.9 (2016.08.22)
--------------------
 
 * Bugfix release
 
 v1.0.8 (2016.08.19)
--------------------
 
 * Reverted previously introduced (and no longer necessary) dependency on bitarray).
 * Switched the insert aligner back to the default implementation, as the one that ignores indels is not any faster.
 
 v1.0.7 (2016.08.18)
--------------------
 
 * Re-engineered modifiers.py (and all dependent code) to enable use of modifiers that simultaneously edit both reads in a pair.
 * Add --op-order option to enable use to specify order of first four trimming operations.
 * Implemented insert-based alignment for paired-end adapter trimming. This is currently experimental. Benchmarking against SeqPurge and Skewer using simulated reads showed that the method Cutadapt uses to align adapters, while optimal for single-end reads, is much less sensitive and specific than the insert match algorithms used by SeqPurge and Skewer. Our algorithm is similar to the one used by SeqPurge but leverages the dynamic programming model of Cutadapt.
 
 v1.0.6 (2016.08.08)
--------------------
 
 * Based on tests, worker compression is faster than writer compression when more than 8 threads are available, so set this to be the default.
 
 v1.0.5 (2016.08.06)
--------------------
 
 * Interanal code reorganization - compression code moved to separate module
 * Eliminated the --worker-compression option in favor of --compression (whose value is either 'worker' or 'writer')
 * More documentation improvements
 
 v1.0.3 (2016.08.05)
--------------------
 
 * Significant performance improvements:
     * Start an extra worker once the main process is finished loading reads
@@ -267,12 +255,11 @@ v1.0.3 (2016.08.05)
 * Eliminated the --parallel-environment option
 
 v1.0.1 (2016.08.04)
--------------------
 
 * Fix documentation bugs associated with migration from optparse to argparse
 
 v1.0 (2016.07.29)
------------------
+
 * Initial release (forked from cutadapt 1.10)
 * Re-wrote much of filters.py and modifiers.py to separate modifying/filtering from file writing.
     * File writing is now managed by a separate class (seqio.Writers)


=====================================
Makefile
=====================================
@@ -1,22 +1,24 @@
 tests = tests
 module = atropos
-#pytestops = "--full-trace"
-#pytestops = "-v -s"
+#pytestops = --full-trace
+#pytestops = -v -s
 repo = jdidion/$(module)
 desc = Release $(version)
 
-BUILD = python setup.py build_ext -i && python setup.py install $(installargs)
-TEST = py.test $(pytestops) $(tests)
+BUILD =
+TEST =
 
-all:
-	$(BUILD)
-	$(TEST)
+all: clean install test
 
-install:
-	$(BUILD)
+build:
+	python setup.py build_ext -i
+	python setup.py sdist bdist_wheel
+
+install: clean build
+	python setup.py install $(installargs)
 
 test:
-	$(TEST)
+	py.test $(pytestops) $(tests)
 
 docs:
 	make -C doc html
@@ -48,21 +50,14 @@ docker:
 	docker login -u jdidion && \
 	docker push $(repo)
 
-release:
-	$(clean)
-	# tag
+tag:
 	git tag $(version)
-	# build
-	$(BUILD)
-	$(TEST)
-	python setup.py sdist bdist_wheel
-	# release
-	python setup.py sdist upload -r pypi
+
+release: clean tag install test
+	twine upload dist/*
 	git push origin --tags
-	$(github_release)
-	$(docker)
 
-github_release:
+	# github release
 	curl -v -i -X POST \
 		-H "Content-Type:application/json" \
 		-H "Authorization: token $(token)" \


=====================================
README.md
=====================================
@@ -26,8 +26,7 @@ Atropos is available from [pypi](https://pypi.python.org/pypi/atropos) and can b
 First install dependencies:
 
 * Required
-    * Python 3.3+ (python 2.x is NOT supported)
-        - note: we have identified a possible bug in python 3.4.2 that causes random segmentation faults. We think this mainly affects unit testing (and thus specifically test on 3.4.3). If you encounter this bug, we recommend upgrading to a newer python version.
+    * Python 3.4.5+ (python 2.x is NOT supported)
     * Cython 0.25.2+ (`pip install Cython`)
 * Maybe python libraries
     * pytest (for running unit tests)
@@ -197,6 +196,7 @@ The citation for the original Cutadapt paper is:
 * Scythe is an interesting new trimmer. Depending on how the benchmarks look in the forthcoming paper, we will add it to the list of tools we compare against Atropos, and perhaps implement their Bayesian approach for adapter match.
 * Experiment with replacing the multicore implementation with an asyncio-based implementation (using ProcessPoolExecutor and uvloop).
 * Automatic adaptive tuning of queue sizes to maximize the balance between memory usage and latency.
+* FastProNGS has some nice visualizations that could be included, rather than relying on MultiQC: https://github.com/Megagenomics/FastProNGS
 
 While we consider the command-line interface to be stable, the internal code organization of Atropos is likely to change. At this time, we recommend to not directly interface with Atropos as a library (or to be prepared for your code to break). The internal code organization will be stabilized as of version 2.0, which is planned for sometime in 2017.
 


=====================================
atropos/_version.py
=====================================
@@ -23,8 +23,8 @@ 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: 1.1.22)"
-    git_full = "2b15c778f0ccf1d0fb753e4334fa6dc0048a9ee6"
+    git_refnames = " (HEAD -> 1.1, tag: 1.1.24)"
+    git_full = "9281be92f0e52a14085841344a509f7808efcfe1"
     keywords = {"refnames": git_refnames, "full": git_full}
     return keywords
 


=====================================
atropos/align/__init__.py
=====================================
@@ -174,50 +174,6 @@ MatchInfo = namedtuple("MatchInfo", (
     "seq_after", "adapter_name", "qual_before", "qual_adapter", "qual_after",
     "is_front", "asize", "rsize_adapter", "rsize_total"))
 
-# Alternative semi-global alignment (
-# http://www.bioinf.uni-freiburg.de/Lehre/Courses/2013_SS/V_Bioinformatik_1/lecture4.pdf)
-# strategies designed to improve insert matching of paired-end reads.
-#
-# Note: these are currently just prototype implementations. They will need to be optimized
-# using numpy and/or re-written in cython.
-#
-# 1. SeqPurge algorithm: insert match algorithm that performs thresholded exhaustive
-#    comparison to minimize probability of incorrect alignment. Relies on the fact that
-#    overlapping reads share alleles and indels (i.e. no gaps are required) (in C++).
-#    https://github.com/imgag/ngs-bits/tree/master/src/SeqPurge.
-#   * Speed up sequence comparison:
-#     * Between adapters and overhangs:
-#       * http://www.ncbi.nlm.nih.gov/pmc/articles/PMC4080745/pdf/btu177.pdf
-#     * Between reads:
-#       * http://bioinformatics.oxfordjournals.org/content/30/14/2000.abstract
-# 2. Skewer algorithm: bit-masked k-difference matching (in C++).
-#    https://github.com/relipmoc/skewer
-# 3. Quality-aware overlap alignment (in Haskell).
-#    https://hackage.haskell.org/package/bio-0.5.3/docs/Bio-Alignment-QAlign.html
-# 4. FOGSAA, modified for semi-global alignment.
-#    http://www.nature.com/articles/srep01746
-#    http://www.isical.ac.in/~bioinfo_miu/FOGSAA.7z
-# 5. EDLIB: edit distance-based alignment
-#    https://github.com/Martinsos/edlib
-# 6. Phred-adjusted ML for error probability:
-# https://biosails.github.io/pheniqs/glossary.html#phred_adjusted_maximum_likelihood_decoding
-# 7. Adaptive banded alignment: https://github.com/ocxtal/libgaba
-# Also think about different sequence encodings that might enable faster alignment
-# https://github.com/hammerlab/kerseq/blob/master/kerseq/sequence_encoding.py
-# 8. https://github.com/yamada-kd/nepal
-# 9. The SeqAn C++ library implements several alignment algorithms:
-# http://www.sciencedirect.com/science/article/pii/S0168165617315420
-# 10. Could we treat paired end read + adapter alignment as an MSA problem?
-# 11. Look at alignment-free tools for pairwise sequence comparison:
-# * http://www.combio.pl/alfree/tools/
-# * http://www.combio.pl/alfree
-# * http://bioinformatics.org.au/tools/decaf+py/
-# 12. https://github.com/sdu-hpcl/BGSA
-# 13. Train a NN to approximate the pairwise alignment distance
-# https://academic.oup.com/bioinformatics/advance-article-abstract/doi/10.1093/bioinformatics/bty887/5140215?redirectedFrom=fulltext
-# 14. Nucl2vec: https://github.com/prakharg24/Nucl2vec (local alignment only - could
-# it be adapted to semi-global?)
-# * Can re-implement in SpaCy? https://spacy.io/usage/vectors-similarity
 
 class InsertAligner(object):
     """Implementation of an insert matching algorithm.


=====================================
atropos/commands/legacy_report.py
=====================================
@@ -15,7 +15,7 @@ INDENTED = textwrap.TextWrapper(initial_indent=INDENT, subsequent_indent=INDENT)
 
 class Printer(object):
     """Manages printing to a file.
-    
+
     Args:
         outfile: The output file.
         kwargs: Additional keyword arguments passed to the print function.
@@ -24,7 +24,7 @@ class Printer(object):
         self.outfile = outfile
         self.indent = indent
         self.print_args = kwargs
-    
+
     def __call__(self, *args, indent=None, **kwargs):
         if isinstance(indent, int):
             indent = self.indent * indent
@@ -33,7 +33,7 @@ class Printer(object):
         if indent:
             self._print(indent, end='')
         self._print(*args, **kwargs)
-    
+
     def _print(self, *args, **kwargs):
         if self.print_args:
             print_args = self.print_args.copy()
@@ -41,7 +41,7 @@ class Printer(object):
         else:
             print_args = kwargs
         print(*args, file=self.outfile, **print_args)
-    
+
     def newline(self):
         """Print a newline.
         """
@@ -49,7 +49,7 @@ class Printer(object):
 
 class TitlePrinter(Printer):
     """Printer that formats titles.
-    
+
     Args:
         outfile: The output file.
         levels: The formatting associated with different header levels.
@@ -61,10 +61,10 @@ class TitlePrinter(Printer):
             **kwargs):
         super().__init__(outfile, **kwargs)
         self.levels = levels
-    
+
     def __call__(self, *title, level=None, newline=True, **kwargs):
         title = ' '.join(title)
-        
+
         if level is not None:
             if level >= len(self.levels):
                 raise ValueError("Invalid level: {}".format(level))
@@ -74,18 +74,18 @@ class TitlePrinter(Printer):
             width = len(title)
             if overline:
                 self._print(overline * width, **kwargs)
-        
+
         self._print(title, **kwargs)
-        
+
         if level is not None and underline:
             self._print(underline * width, **kwargs)
-        
+
         if newline:
             self.newline()
 
 class RowPrinter(Printer):
     """Priter that formats rows in a table.
-    
+
     Args:
         outfile: The output file.
         colwidths: Column widths.
@@ -94,7 +94,7 @@ class RowPrinter(Printer):
         pct: Whether floats should be formatted as percentages.
         default: Default value for None's.
         kwargs: Additional keyword arguments passed to the print function.
-    
+
     colwidths, justification, and indent can be longer or shorter than the
     number of arguments; if shorter, the last value in the list is repeated;
     if longer, the list is truncated.
@@ -110,10 +110,10 @@ class RowPrinter(Printer):
                 (int, str, str)))
         self.pct = pct
         self.default = default
-    
+
     def print_rows(self, *rows, header=None, **kwargs):
         """Print multiple rows. Automatically figures out column widths.
-        
+
         Args:
             rows: Rows to print.
             header: Header row.
@@ -126,7 +126,7 @@ class RowPrinter(Printer):
                 header_rows = [header]
             else:
                 header_widths = (
-                    max(sizeof(h) for h in header_part) 
+                    max(sizeof(h) for h in header_part)
                     for header_part in header)
                 header_rows = list(zip(*header))
             colwidths = tuple(
@@ -136,13 +136,13 @@ class RowPrinter(Printer):
                 self(*header_row, colwidths=colwidths, header=(i == len(header_rows)), **kwargs)
         for row in rows:
             self(*row, colwidths=colwidths)
-    
+
     def __call__(
             self, *args, colwidths=None, extra_width=None, justification=None,
             extra_justification=None, indent=None, extra_indent=None,
             header=False, underline='-', pct=None, default=None, **kwargs):
         """Print a row.
-        
+
         Args:
             args: Fields in the row.
             colwidths, justification, indent: Row-specific colwidths,
@@ -161,10 +161,10 @@ class RowPrinter(Printer):
         if ncols == 0:
             self.newline()
             return
-        
+
         if pct is None:
             pct = self.pct
-        
+
         def adjust(arr, extra=None):
             """Adjust an array. If longer than the number of columns,
             truncate; if shorter, fill in by repeating the last element.
@@ -176,7 +176,7 @@ class RowPrinter(Printer):
                 return arr[:ncols]
             else:
                 return arr + ((extra or arr[-1],) * (ncols - alen))
-        
+
         colwidths, justification, indent = (
             adjust(arr, extra)
             for arr, extra in zip(
@@ -185,13 +185,13 @@ class RowPrinter(Printer):
                     justification or self.justification,
                     indent or self.indent),
                 (extra_width, extra_justification, extra_indent)))
-        
+
         # adjust colwidths if this is a header
         if header:
             colwidths = tuple(
                 max(w, len(str(a)))
                 for w, a in zip(colwidths, args))
-        
+
         fmt_str = []
         fmt_args = []
         for i, (value, width, just, ind) in enumerate(
@@ -210,10 +210,10 @@ class RowPrinter(Printer):
                 ind + '{' + str(i) + ':' + just + str(width-len(ind)) +
                 typ + '}')
             fmt_args.append(value)
-        
+
         fmt_str = ' '.join(fmt_str)
         self._print(fmt_str.format(*fmt_args), **kwargs)
-        
+
         if header:
             sepline = ' '.join((underline * width) for width in colwidths)
             self._print(sepline, **kwargs)
@@ -228,12 +228,12 @@ class LegacyReportGenerator(BaseReportGenerator):
 
 def generate_report(summary, outfile):
     """Generate a report.
-    
+
     Args:
         summary: The summary dict.
         outfile: The output file name/object.
     """
-    
+
     print_summary_report(summary, outfile)
     if 'trim' in summary:
         print_trim_report(summary, outfile)
@@ -244,21 +244,21 @@ def generate_report(summary, outfile):
 
 def print_summary_report(summary, outfile):
     """Print the top-level summary report.
-    
+
     Args:
         summary: The summary dict.
         outfile: The output file object.
     """
     _print_title = TitlePrinter(outfile)
     _print = Printer(outfile)
-    
+
     _print_title("Atropos", level=0)
     _print("Atropos version: {}".format(summary['version']))
     _print("Python version: {}".format(summary['python']))
     _print("Command line parameters: {} {}".format(
         summary['command'], " ".join(summary['options']['orig_args'])))
     _print()
-    
+
     _print("Sample ID: {}".format(summary['sample_id']))
     _print("Input format: {}".format(summary['derived']['input_format']))
     _print("Input files:")
@@ -266,7 +266,7 @@ def print_summary_report(summary, outfile):
         if infile is not None:
             _print(infile, indent=INDENT)
     _print()
-    
+
     timing = summary['timing']
     total = summary["total_record_count"]
     wctime = ["Wallclock time: {:.2F} s".format(timing["wallclock"])]
@@ -281,7 +281,7 @@ def print_summary_report(summary, outfile):
 
 def print_trim_report(summary, outfile):
     """Print the trimming report.
-    
+
     Args:
         summary: Summary dict.
         outfile: Open output stream.
@@ -292,17 +292,18 @@ def print_trim_report(summary, outfile):
     max_width = len(str(total_bp))
     # account for commas
     max_width += (max_width // 3)
-    
+
     _print_title = TitlePrinter(outfile)
     _print = RowPrinter(outfile, (35, max_width))
-    
+
     total = summary["total_record_count"]
     if total == 0:
-        _print(
+        _print_error = Printer(outfile)
+        _print_error(
             "No reads processed! Either your input file is empty or you "
             "used the wrong -f/--format parameter.")
         return
-    
+
     modifiers, filters, formatters = (
         summary['trim'][key]
         for key in ('modifiers', 'filters', 'formatters'))
@@ -322,7 +323,7 @@ def print_trim_report(summary, outfile):
             trimmers.append((name, mod))
         if correction_enabled and 'records_corrected' in mod:
             corrected = mod
-    
+
     _print_title("Trimming", level=1)
     _print(pairs_or_reads, 'records', 'fraction', header=True)
     _print(
@@ -342,38 +343,38 @@ def print_trim_report(summary, outfile):
                 adapter_cutter['records_with_adapters'][0],
                 adapter_cutter['fraction_records_with_adapters'][0],
                 pct=True)
-    
+
     def _print_filter(name, sep):
         if name in filters:
             _print(
                 "{} {} {}:".format(pairs_or_reads, sep, name.replace('_', ' ')),
                 filters[name]['records_filtered'],
                 filters[name]['fraction_records_filtered'], pct=True)
-    
+
     _print_filter('too_short', 'that were')
     _print_filter('too_long', 'that were')
     _print_filter('too_many_n', 'with')
-    
+
     _print(
         "{} written (passing filters):".format(pairs_or_reads),
         formatters['records_written'], formatters['fraction_records_written'],
         pct=True)
-    
+
     if corrected:
         _print(
             "Pairs corrected:", corrected['records_corrected'],
             corrected['fraction_records_corrected'], pct=True)
-    
+
     _print()
     _print("Base pairs", 'bp', 'fraction', header=True)
-    
+
     _print("Total bp processed:", total_bp)
     if paired:
         for read in range(2):
             _print(
                 "Read {}:".format(read+1), summary['total_bp_counts'][read],
                 indent=(INDENT, ''))
-    
+
     def _print_bp(title, data, key, default=0):
         if paired:
             _print(
@@ -389,15 +390,15 @@ def print_trim_report(summary, outfile):
             _print(
                 title, data[key][0], data['fraction_{}'.format(key)][0],
                 pct=True, default=default)
-    
+
     for name, mod in trimmers:
         _print_bp(mod['desc'], mod, 'bp_trimmed')
-    
+
     _print_bp("Total bp written (filtered):", formatters, 'bp_written')
-    
+
     if error_corrector:
         _print_bp("Total bp corrected:", error_corrector, 'bp_corrected')
-    
+
     if adapter_cutter:
         _print()
         adapters = adapter_cutter['adapters']
@@ -405,12 +406,12 @@ def print_trim_report(summary, outfile):
 
 def print_adapter_report(adapters, outfile, paired, total_records, max_width):
     """Print details for a adapters.
-    
+
     Args:
         adapters: Sequence of adapter info dicts.
         outfile: The output file
         paired: Whether the data is paired-end.
-        
+
         max_width: Max column width.
     """
     adapter_lenghts = []
@@ -424,15 +425,15 @@ def print_adapter_report(adapters, outfile, paired, total_records, max_width):
                 else:
                     adapter_lenghts.append(len(adapter['sequence']))
     max_seq_len = max(adapter_lenghts)
-    
+
     _print = Printer(outfile)
     _print_title = TitlePrinter(outfile)
     _print_adj = RowPrinter(outfile, (12, 5), pct=True, indent=(INDENT, ''))
-    
+
     seq_printer = RowPrinter(
         outfile, (max_seq_len, 14, 3, max_width), ('<', '<', '>'))
     hist_printer = RowPrinter(outfile, justification=('>', '>', '>', '>', '<'))
-    
+
     def print_error_ranges(adapter_length, error_rate):
         """Print max number of errors for ranges of adapter match lengths.
         """
@@ -451,14 +452,14 @@ def print_adapter_report(adapters, outfile, paired, total_records, max_width):
             _print("{0}-{1} bp: {2}".format(
                 prev, adapter_length, int(error_rate * adapter_length)))
         _print()
-    
+
     def print_histogram(
-            data, adapter_length, num_reads, error_rate, errors, 
+            data, adapter_length, num_reads, error_rate, errors,
             match_probabilities):
         """Print a histogram. Also, print the no. of reads expected to be
         trimmed by chance (assuming a uniform distribution of nucleotides in
         the reads).
-        
+
         Args:
             data: A dictionary mapping lengths of trimmed sequences to their
                 respective frequency
@@ -480,9 +481,9 @@ def print_adapter_report(adapters, outfile, paired, total_records, max_width):
                 length, count, estimated,
                 int(error_rate * min(length, adapter_length))])
             hist_errors.append(errors['rows'][length])
-        
+
         col_sizes = [len(str(max(col))) for col in zip(*hist_errors)]
-        
+
         def _format_hist_errors(errs):
             trailing = True
             hist_str = []
@@ -493,26 +494,26 @@ def print_adapter_report(adapters, outfile, paired, total_records, max_width):
                     trailing = False
                     hist_str.append(('{:<' + str(col_sizes[i]) + 'd}').format(e))
             return ' '.join(reversed(hist_str))
-        
+
         for i, errs in enumerate(hist_errors):
             hist[i].append(_format_hist_errors(errs))
-        
+
         error_header = ' '.join(
             ('{:<' + str(e) + 'd}').format(i)
             for i, e in enumerate(col_sizes))
-        
+
         hist_printer.print_rows(
             *hist,
             header=(
                 ("length",""), ("count",""), ("expect",""), ("max.err",""),
                 ("error counts", error_header)))
         hist_printer.newline()
-    
+
     def print_adjacent_bases(bases):
         """Print a summary of the bases preceding removed adapter sequences.
         Print a warning if one of the bases is overrepresented and there are
         at least 20 preceding bases available.
-        
+
         Return:
             True if a warning was printed.
         """
@@ -538,19 +539,19 @@ def print_adapter_report(adapters, outfile, paired, total_records, max_width):
             return True
         _print()
         return False
-        
+
     warning = False
     for pair in range(2 if paired else 1):
         if adapters[pair] is None:
             continue
-        
+
         header = "Adapter {}"
         if paired:
             header = ("First read: " if pair == 0 else "Second read: ") + header
-        
+
         for name, adapter in adapters[pair].items():
             _print_title(header.format(name), level=1)
-            
+
             where_name = adapter["where"]["name"]
             if where_name == "linked":
                 front_len, back_len = [
@@ -576,12 +577,12 @@ def print_adapter_report(adapters, outfile, paired, total_records, max_width):
                         adapter["total"]),
                     header=(
                         "Sequence", "Type", "Length", "Trimmed (x)"))
-            
+
             _print()
-            
+
             if adapter["total"] == 0:
                 continue
-            
+
             if where_name == "anywhere":
                 _print(
                     adapter["total_front"],
@@ -602,7 +603,7 @@ def print_adapter_report(adapters, outfile, paired, total_records, max_width):
                     adapter["lengths_back"], seq_len, total_records,
                     adapter["max_error_rate"], adapter["errors_back"],
                     adapter['match_probabilities'])
-            
+
             elif where_name == "linked":
                 print_error_ranges(front_len, adapter["front_max_error_rate"])
                 print_error_ranges(back_len, adapter["back_max_error_rate"])
@@ -610,7 +611,7 @@ def print_adapter_report(adapters, outfile, paired, total_records, max_width):
                 print_histogram(
                     adapter["front_lengths_front"], front_len, total_records,
                     adapter["front_max_error_rate"],
-                    adapter["front_errors_front"], 
+                    adapter["front_errors_front"],
                     adapter['front_match_probabilities'])
                 _print()
                 _print("Overview of removed sequences at 3' end:")
@@ -618,7 +619,7 @@ def print_adapter_report(adapters, outfile, paired, total_records, max_width):
                     adapter["back_lengths_back"], back_len, total_records,
                     adapter["back_max_error_rate"], adapter["back_errors_back"],
                     adapter['back_match_probabilities'])
-            
+
             elif where_name in ("front", "prefix"):
                 print_error_ranges(seq_len, adapter["max_error_rate"])
                 _print("Overview of removed sequences:")
@@ -626,7 +627,7 @@ def print_adapter_report(adapters, outfile, paired, total_records, max_width):
                     adapter["lengths_front"], seq_len, total_records,
                     adapter["max_error_rate"], adapter["errors_front"],
                     adapter['match_probabilities'])
-            
+
             elif where_name in ("back", "suffix"):
                 print_error_ranges(seq_len, adapter["max_error_rate"])
                 warning = warning or print_adjacent_bases(
@@ -636,7 +637,7 @@ def print_adapter_report(adapters, outfile, paired, total_records, max_width):
                     adapter["lengths_back"], seq_len, total_records,
                     adapter["max_error_rate"], adapter["errors_back"],
                     adapter['match_probabilities'])
-    
+
     if warning:
         _print('WARNING:')
         _print("\n".join(INDENTED.wrap(
@@ -645,7 +646,7 @@ def print_adapter_report(adapters, outfile, paired, total_records, max_width):
 
 def print_pre_trim_report(summary, outfile):
     """Print pre-trimming stats.
-    
+
     Args:
         summary: The summary dict.
         outfile: The output file.
@@ -664,10 +665,10 @@ def print_pre_trim_report(summary, outfile):
             _print("Read {}: {}".format(read, src))
         _print()
         print_stats_report(data, outfile)
-    
+
 def print_post_trim_report(summary, outfile):
     """Print post-trimming stats.
-    
+
     Args:
         summary: The summary dict.
         outfile: The output file.
@@ -691,7 +692,7 @@ def print_post_trim_report(summary, outfile):
 
 def print_stats_report(data, outfile):
     """Print stats.
-    
+
     Args:
         data: The stats dict.
         outfile: The output file.
@@ -703,10 +704,10 @@ def print_stats_report(data, outfile):
     max_width = len(str(max_count))
     # add space for commas and column separation
     max_width += (max_width // 3) + 1
-    
+
     _print_title = TitlePrinter(outfile)
     _print = RowPrinter(outfile, (35, max_width))
-    
+
     def _print_histogram(title, hist1, hist2=None):
         _print_title(title, level=2)
         if hist1 is None:
@@ -720,7 +721,7 @@ def print_stats_report(data, outfile):
             hist = sorted(hist1.items(), key=lambda x: x[0])
         for histbin in hist:
             _print(*histbin)
-    
+
     def _print_base_histogram(title, hist, extra_width=4, index_name='Pos'):
         _print_title(title, level=2)
         if hist is None:
@@ -734,7 +735,7 @@ def print_stats_report(data, outfile):
                 round(count * 100 / total_count, 1)
                 for count in row)
             _print(pos, *base_pcts, extra_width=extra_width)
-    
+
     def _print_tile_histogram(title, hist):
         if hist is None:
             _print_title(title, level=2)
@@ -745,7 +746,7 @@ def print_stats_report(data, outfile):
             4, len(str(math.ceil(data['read1']['counts'] / ncol)))) + 1
         _print_base_histogram(
             title, hist, extra_width=max_tile_width, index_name='Tile')
-    
+
     def _print_tile_base_histogram(title, hist):
         """Print a histogram of position x tile, with values as the median
         base quality.
@@ -768,9 +769,9 @@ def print_stats_report(data, outfile):
                     weighted_median(quals, tile_counts)
                     for tile_counts in tiles.values()),
                 extra_width=max_tile_width)
-    
+
     _print('', 'Read1', 'Read2', header=True)
-    
+
     # Sequence-level stats
     _print(
         "Read pairs:" if paired else "Reads:",
@@ -793,7 +794,7 @@ def print_stats_report(data, outfile):
         data['read1']['gc']['hist'],
         data['read2']['gc']['hist'])
     _print()
-    
+
     if 'tile_sequence_qualities' in data['read1']:
         _print_tile_histogram(
             "Read 1 per-tile sequence qualities (%)",
@@ -803,7 +804,7 @@ def print_stats_report(data, outfile):
             "Read 2 per-tile sequence qualities (%)",
             data['read2']['tile_sequence_qualities'])
         _print()
-    
+
     # Base-level stats
     if 'base_qualities' in data['read1']:
         _print_base_histogram(
@@ -835,7 +836,7 @@ def print_stats_report(data, outfile):
 def sizeof(*x, seps=True, prec=1):
     """Returns the largest string size of all objects in x, where x is a
     sequence of string or numeric values.
-    
+
     Args:
         *x: The objects to test.
         seps: Whether to include separators (,.) in the size.


=====================================
atropos/io/seqio.py
=====================================
@@ -11,9 +11,9 @@ from atropos.io import STDOUT, xopen
 from atropos.io.compression import splitext_compressed
 from atropos.util import Summarizable, truncate_string, ALPHABETS
 
+SINGLE = 0
 READ1 = 1
 READ2 = 2
-SINGLE = READ1
 PAIRED = 1|2
 
 class FormatError(AtroposError):
@@ -1045,16 +1045,16 @@ def get_format(
         elif qualities is False:
             # Same, but we know that we want to write reads without qualities.
             file_format = 'fasta'
+        else:
+            raise UnknownFileType("Could not determine file type.")
 
-    if file_format is None:
-        raise UnknownFileType("Could not determine file type.")
+    file_format = file_format.lower()
 
     if file_format == 'fastq' and qualities is False:
         raise ValueError(
             "Output format cannot be FASTQ since no quality values are "
             "available.")
 
-    file_format = file_format.lower()
     if file_format == 'fasta':
         if colorspace:
             return ColorspaceFastaFormat(line_length)


=====================================
atropos/util/__init__.py
=====================================
@@ -25,23 +25,23 @@ class Alphabet():
             valid_characters.add(default_character)
         self.valid_characters = valid_characters
         self.default_character = default_character
-    
+
     def __contains__(self, character):
         return character in self.valid_characters
-    
+
     def validate(self, character):
         """Raises NotInAlphabetError if the character is not in the alphabet.
         """
         if not character in self:
             raise NotInAlphabetError(character)
-    
+
     def validate_string(self, string):
         """Raises NotInAlphabetError if any character in 'string' is not in the
         alphabet.
         """
         for character in string:
             self.validate(character)
-        
+
     def resolve(self, character):
         """Returns 'character' if it's in the alphabet, otherwise the
         alphabet's default character.
@@ -50,7 +50,7 @@ class Alphabet():
             return character
         else:
             return self.default_character
-    
+
     def resolve_string(self, string):
         """Returns a new string with any non-alphabet characters replaced
         with the default character.
@@ -103,7 +103,7 @@ LOG2 = math.log(2)
 class RandomMatchProbability(object):
     """Class for computing random match probability for DNA sequences based on
     binomial expectation. Maintains a cache of factorials to speed computation.
-    
+
     Args:
         init_size: Initial cache size.
     """
@@ -112,15 +112,15 @@ class RandomMatchProbability(object):
         self.factorials = [1] * init_size
         self.max_n = 1
         self.cur_array_size = init_size
-    
+
     def __call__(self, matches, size, match_prob=0.25, mismatch_prob=0.75):
         """Computes the random-match probability for a given sequence size and
         number of matches.
-        
+
         Args:
             match_prob: Probability of two random bases matching.
             mismatch_prob: Probability of two random bases not matcing.
-        
+
         Returns:
             The probability.
         """
@@ -129,17 +129,17 @@ class RandomMatchProbability(object):
         prob = self.cache.get(key, None)
         if prob:
             return prob
-        
+
         # When there are no mismatches, the probability is
         # just that of observing a specific sequence of the
         # given length by chance.
         if matches == size:
             prob = match_prob ** matches
-        
+
         else:
             nfac = self.factorial(size)
             prob = 0.0
-            
+
             for i in range(matches, size+1):
                 j = size - i
                 # use integer division in the case that the numbers are too
@@ -149,10 +149,10 @@ class RandomMatchProbability(object):
                 except OverflowError:
                     div = nfac // self.factorial(i) // self.factorial(j)
                 prob += (mismatch_prob ** j) * (match_prob ** i) * div
-        
+
         self.cache[key] = prob
         return prob
-    
+
     def factorial(self, num):
         """Returns `num`!.
         """
@@ -191,23 +191,23 @@ class Summarizable(object):
 class Const(Mergeable):
     """A :class:`Mergeable` that is a constant value. Merging simply checks
     that two values are identical.
-    
+
     Args:
         value: The value to treat as a constant.
     """
     def __init__(self, value):
         self.value = value
-    
+
     def merge(self, other):
         """Check that `self==other`.
-        
+
         Raises:
             ValueError
         """
         if self != other:
             raise ValueError("{} != {}".format(self, other))
         return self
-    
+
     def __eq__(self, other):
         """Returns True if `self.value==other` (or `other.value` if `other` is
         a :class:`Const`).
@@ -215,7 +215,7 @@ class Const(Mergeable):
         if isinstance(other, Const):
             other = other.value
         return self.value == other
-    
+
     def __repr__(self):
         return str(self.value)
 
@@ -224,31 +224,31 @@ class Timestamp(object):
     """
     def __init__(self):
         self.dtime = datetime.now()
-        self.clock = time.clock()
-    
+        self.process_time = time.process_time()
+
     def timestamp(self):
         """Returns the unix timestamp.
         """
         return self.dtime.timestamp()
-    
+
     def isoformat(self):
         """Returns the datetime in ISO format.
         """
         return self.dtime.isoformat()
-    
+
     def __sub__(self, other, minval=0.01):
         """Subtract another timestamp from this one.
-        
+
         Args:
             other: The other timestamp.
             minval: The minimum difference.
-        
+
         Returns:
             A dict of {wallclock=<datetime_diff>, cpu=<clock_diff>}.
         """
         return dict(
             wallclock=max(minval, self.timestamp() - other.timestamp()),
-            cpu=max(minval, self.clock - other.clock))
+            cpu=max(minval, self.process_time - other.process_time))
 
 class Timing(Summarizable):
     """Context manager that maintains timing information using
@@ -258,19 +258,19 @@ class Timing(Summarizable):
     def __init__(self):
         self.start_time = None
         self.cur_time = None
-    
+
     def __enter__(self):
         self.start_time = Timestamp()
         return self
-    
+
     def __exit__(self, exception_type, exception_value, traceback):
         self.update()
-    
+
     def update(self):
         """Set :attr:`self.cur_time` to the current time.
         """
         self.cur_time = Timestamp()
-    
+
     def summarize(self):
         """Returns a summary dict
         {start=<start_time>, wallclock=<datetime_diff>, cpu=<clock_diff>}.
@@ -284,7 +284,7 @@ class Timing(Summarizable):
 
 class CountingDict(dict, Mergeable, Summarizable):
     """A dictionary that always returns 0 on get of a missing key.
-    
+
     Args:
         sort_by: Whether summary is sorted by key (0) or value (1).
     """
@@ -295,15 +295,15 @@ class CountingDict(dict, Mergeable, Summarizable):
         if keys:
             for key in keys:
                 self.increment(key)
-    
+
     def __getitem__(self, name):
         return self.get(name, 0)
-    
+
     def increment(self, key, inc=1):
         """Increment the count of `key` by `inc`.
         """
         self[key] += inc
-    
+
     def merge(self, other):
         if not isinstance(other, CountingDict):
             raise ValueError(
@@ -311,13 +311,13 @@ class CountingDict(dict, Mergeable, Summarizable):
         for key, value in other.items():
             self[key] += value
         return self
-    
+
     def get_sorted_items(self):
         """Returns an iterable of (key, value) sorted according to this
         CountingDict's `sort_by` param.
         """
         return sorted(self.items(), key=lambda item: item[self.sort_by])
-    
+
     def summarize(self):
         """Returns an OrderedDict of sorted items.
         """
@@ -332,7 +332,7 @@ class Histogram(CountingDict):
         return dict(
             hist=hist,
             summary=self.get_summary_stats())
-    
+
     def get_summary_stats(self):
         """Returns dict with mean, median, and modes of histogram.
         """
@@ -347,19 +347,19 @@ class Histogram(CountingDict):
 
 class NestedDict(dict, Mergeable, Summarizable):
     """A dict that initalizes :class:`CountingDict`s for missing keys.
-    
+
     Args:
         shape: The flattened shape: 'long' or 'wide'.
     """
     def __init__(self, shape="wide"):
         super().__init__()
         self.shape = shape
-    
+
     def __getitem__(self, name):
         if name not in self:
             self[name] = CountingDict()
         return self.get(name)
-    
+
     def merge(self, other):
         if not isinstance(other, NestedDict):
             raise ValueError(
@@ -370,10 +370,10 @@ class NestedDict(dict, Mergeable, Summarizable):
             else:
                 self[key] = value
         return self
-    
+
     def summarize(self):
         """Returns a flattened version of the nested dict.
-        
+
         Returns:
             When `shape=='long'`, a list of (key1, key2, value) tuples.
             When `shape=='wide'`, a dict of
@@ -410,11 +410,11 @@ def merge_dicts(dest, src):
     """Merge corresponding items in `src` into `dest`. Values in `src` missing
     in `dest` are simply added to `dest`. Values that appear in both `src` and
     `dest` are merged using `merge_values`.
-    
+
     Args:
         dest: The dict to merge into.
         src: The dict to merge from.
-    
+
     Raises:
         ValueError if a value is not one of the accepted types.
     """
@@ -426,7 +426,7 @@ def merge_dicts(dest, src):
 
 def merge_values(v_dest, v_src):
     """Merge two values based on their types, as follows:
-    
+
     - Mergeable: merging is done by the dest object's merge function.
     - dict: merge_dicts is called recursively.
     - Number: values are summed.
@@ -434,11 +434,11 @@ def merge_values(v_dest, v_src):
     they must be the same length. Then, corresponding values are handled as
     above. The value is returned as a list.
     - Otherwise: Treated as a Const (i.e. must be identical).
-    
+
     Args:
         v_dest: The dest value.
         v_src: The src value.
-    
+
     Returns:
         The merged value.
     """
@@ -482,10 +482,10 @@ def reverse_complement(seq):
 
 def sequence_complexity(seq):
     """Computes a simple measure of sequence complexity.
-    
+
     Args:
         seq: The sequence to measure.
-    
+
     Returns:
         Complexity, as a value [0,2], where 0 = a homopolymer and
         2 = completely random.
@@ -502,12 +502,12 @@ def sequence_complexity(seq):
 
 def qual2int(qual, base=33):
     """Convert a quality charater to a phred-scale int.
-    
+
     Args:
         q: The quality value.
         base: The offset of the first quality value (Old Illumina = 64,
             new Illumina = 33).
-    
+
     Returns:
         The integer quality.
     """
@@ -515,12 +515,12 @@ def qual2int(qual, base=33):
 
 def quals2ints(quals, base=33):
     """Convert an iterable of quality characters to phred-scale ints.
-    
+
     Args:
         quals: The qualities.
         base: The offset of the first quality value (Old Illumina = 64,
             new Illumina = 33).
-    
+
     Returns:
         A tuple of integer qualities.
     """
@@ -533,14 +533,14 @@ def qual2prob(qchar):
 
 def enumerate_range(collection, start, end):
     """Generates an indexed series:  (0,coll[0]), (1,coll[1]) ...
-    
+
     Only up to (start-end+1) items will be yielded.
-    
+
     Args:
         collection: The collection to enumerate.
         start: The starting index.
         end: The ending index.
-    
+
     Yields:
         (index, item) tuples.
     """
@@ -552,10 +552,10 @@ def enumerate_range(collection, start, end):
 
 def mean(values):
     """Computes the mean of a sequence of numeric values.
-    
+
     Args:
         values: Sequence of numeric values.
-    
+
     Returns:
         The mean (floating point).
     """
@@ -565,10 +565,10 @@ def mean(values):
 
 def weighted_mean(values, counts):
     """Computes the mean of a sequence of numeric values weighted by counts.
-    
+
     Args:
         values: Sequence of numeric values.
-    
+
     Returns:
         The weighted mean (floating point).
     """
@@ -606,26 +606,26 @@ def weighted_stdev(values, counts, mu0=None):
         mu0 = weighted_mean(values, counts)
     return math.sqrt(
         sum(
-            ((val - mu0) ** 2) * count 
-            for val, count in zip(values, counts)) / 
+            ((val - mu0) ** 2) * count
+            for val, count in zip(values, counts)) /
         sum(counts))
 
 def median(values):
     """Median function borrowed from python statistics module, and sped up by
     in-place sorting of the array.
-    
+
     Args:
         data: Sequence of numeric values.
-    
+
     Returns:
         The median (floating point).
     """
     datalen = len(values)
     if datalen == 0:
         raise ValueError("Cannot determine the median of an empty sequence")
-    
+
     values.sort()
-    
+
     idx = datalen // 2
     if datalen % 2 == 1:
         return values[idx]
@@ -634,12 +634,12 @@ def median(values):
 
 def weighted_median(values, counts):
     """Compute the median of `values` weighted by `counts`.
-    
+
     Args:
         values: Sequence of unique values.
         counts: Sequence of counts, where each count is the number of times the
             value at the corresponding position appears in the sample.
-    
+
     Returns:
         The weighted median.
     """
@@ -711,16 +711,16 @@ def truncate_string(string, max_len=100):
 
 def run_interruptible(func, *args, **kwargs):
     """Run a function, gracefully handling keyboard interrupts.
-    
+
     Args:
         func: The function to execute.
         args, kwargs: Positional and keyword arguments to pass to `func`.
-    
+
     Returns:
         A (unix-style) return code (0=normal, anything else is an error).
-    
+
     Raises:
-        
+
     """
     retcode = 0
     try:


=====================================
debian/changelog
=====================================
@@ -1,3 +1,11 @@
+atropos (1.1.24+dfsg-1) unstable; urgency=medium
+
+  * Team upload.
+  * New upstream version
+  * Trim trailing whitespace.
+
+ -- Steffen Moeller <moeller at debian.org>  Sat, 30 Nov 2019 13:00:31 +0100
+
 atropos (1.1.22+dfsg-1) unstable; urgency=medium
 
   * Team upload.


=====================================
debian/copyright
=====================================
@@ -18,7 +18,8 @@ Files: *
 Copyright: 2010-2016 Marcel Martin <marcel.martin at scilifelab.se>
            2016-2019 John P Didion, and are subject to
 License: Expat
-Comment: Atropos began as a fork of Cutadapt [1]. Specifically, it is a fork of commit
+Comment:
+ Atropos began as a fork of Cutadapt [1]. Specifically, it is a fork of commit
  2f3cc0717aa9ff1e0326ea6bcb36b712950d4999 on June 22, 2016
  .
  All additions and non-trivial modifications (which can be discovered by comparing
@@ -35,7 +36,8 @@ Comment: Atropos began as a fork of Cutadapt [1]. Specifically, it is a fork of
  [2] https://github.com/jdidion/atropos
  [3] https://github.com/marcelm/cutadapt/tree/2f3cc0717aa9ff1e0326ea6bcb36b712950d4999
  [4] https://creativecommons.org/publicdomain/zero/1.0/
-Comment: On Debian systems the full text of the CCO license can be found at
+ .
+ On Debian systems the full text of the CCO license can be found at
  /usr/share/common-licenses/CC0-1.0
 
 Files: bin/_preamble.py


=====================================
debian/rules
=====================================
@@ -25,4 +25,3 @@ override_dh_auto_clean:
 	rm -rf  .pytest_cache
 	rm -rf atropos.egg-info
 	rm -f atropos/align/_align.c atropos/commands/trim/_qualtrim.c atropos/io/_seqio.c
-


=====================================
setup.py
=====================================
@@ -6,6 +6,7 @@ Cython is run when
 * or the pre-generated C sources are out of date,
 * or when --cython is given on the command line.
 """
+import codecs
 import os.path
 import sys
 
@@ -24,13 +25,6 @@ if sys.version_info < (3, 3):
     sys.exit(1)
 
 
-with open(
-    os.path.join(os.path.abspath(os.path.dirname(__file__)), "README.md"),
-    encoding="utf-8"
-) as f:
-    long_description = f.read()
-
-
 def out_of_date(_extensions):
     """
     Check whether any pyx source is newer than the corresponding generated
@@ -145,9 +139,16 @@ setup(
     author_email="john.didion at nih.gov",
     url="https://atropos.readthedocs.org/",
     description="trim adapters from high-throughput sequencing reads",
-    long_description=long_description,
+    long_description=codecs.open(
+        os.path.join(
+            os.path.dirname(os.path.realpath(__file__)),
+            "README.md"
+        ),
+        "rb",
+        "utf-8"
+    ).read(),
     long_description_content_type="text/markdown",
-    license="Original Cutadapt code is under MIT license; improvements and additions are in the Public Domain",
+    license="MIT",
     ext_modules=extensions,
     packages=find_packages(),
     scripts=["bin/atropos"],
@@ -176,7 +177,6 @@ setup(
         "License :: Public Domain",
         "Natural Language :: English",
         "Programming Language :: Cython",
-        "Programming Language :: Python :: 3.3",
         "Programming Language :: Python :: 3.4",
         "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6"



View it on GitLab: https://salsa.debian.org/med-team/atropos/compare/92bc7e6ea0645bddb5397b2827a68b0b227ef7a0...8e57e0667a00a6ba5c01b38225a6ca6fcd9eebc2

-- 
View it on GitLab: https://salsa.debian.org/med-team/atropos/compare/92bc7e6ea0645bddb5397b2827a68b0b227ef7a0...8e57e0667a00a6ba5c01b38225a6ca6fcd9eebc2
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20191130/0cc1442a/attachment-0001.html>


More information about the debian-med-commit mailing list