[med-svn] [python-cobra] 01/08: Imported Upstream version 0.5.0~b2

Afif Elghraoui afif at moszumanska.debian.org
Sat Oct 22 06:03:51 UTC 2016


This is an automated email from the git hooks/post-receive script.

afif pushed a commit to branch master
in repository python-cobra.

commit cfeed5970b822c938475f40de27e056b50c28e57
Author: Afif Elghraoui <afif at debian.org>
Date:   Sun Oct 9 12:49:37 2016 -0700

    Imported Upstream version 0.5.0~b2
---
 .gitignore                                   |   3 +
 .travis.yml                                  |  96 ++++++---
 CONTRIBUTING.md                              |  25 ---
 CONTRIBUTING.rst                             |  26 +++
 INSTALL.md                                   | 104 ---------
 INSTALL.rst                                  | 134 ++++++++++++
 MANIFEST.in                                  |   2 +-
 README.md                                    |  45 ----
 README.rst                                   |  61 ++++++
 appveyor.yml                                 |   2 +-
 cobra/VERSION                                |   2 +-
 cobra/core/Metabolite.py                     |  11 +-
 cobra/core/Model.py                          |  23 +-
 cobra/core/Reaction.py                       |  14 ++
 cobra/design/design_algorithms.py            | 117 ++++++----
 cobra/flux_analysis/parsimonious.py          |   1 +
 cobra/flux_analysis/phenotype_phase_plane.py |  16 +-
 cobra/flux_analysis/reaction.py              |  49 +++--
 cobra/flux_analysis/summary.py               | 305 ++++++++++++++++-----------
 cobra/solvers/__init__.py                    |   1 +
 cobra/test/flux_analysis.py                  |  97 ++++-----
 cobra/test/unit_tests.py                     |   5 +-
 config.sh                                    |  57 +++++
 manylinux_builder/Dockerfile                 |   6 +
 manylinux_builder/README.md                  |   5 +
 manylinux_builder/build_cobrapy.sh           |  11 +
 manylinux_builder/run_cobrapy_builder.sh     |   2 +
 scripts/deploy-test.sh                       |  15 ++
 scripts/deploy.sh                            |   5 +
 setup.py                                     |  14 +-
 30 files changed, 794 insertions(+), 460 deletions(-)

diff --git a/.gitignore b/.gitignore
index 854e81a..05b658c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -89,3 +89,6 @@ libglpk.a
 .DS_Store
 .eggs/
 *\.swp
+manylinux_builder/wheelhouse
+*~
+venv/
diff --git a/.travis.yml b/.travis.yml
index 7c2df5f..424b609 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,43 +1,77 @@
 language: python
-sudo: false
+python: 3.5
+sudo: required
+dist: trusty
+services: docker
+sudo: required
 cache:
   directories:
     - $HOME/.cache/pip
-python:
-  - "2.7"
-  - "3.4"
-  - "3.5"
 addons:
   apt:
     packages:
-      - gfortran
-      - libatlas-dev
-      - libatlas-base-dev
-      - liblapack-dev
-      - libgmp-dev
-      - libglpk-dev
-      - libmpfr-dev
-
-# command to install dependencies
+      - libfreetype6-dev
+      - libpng12-dev
+
 env:
-  - PIP_CACHE_DIR=$HOME/.cache/pip
+  global:
+    - secure: "hkKBaGLvoDVgktSKR3BmX+mYlGzHw9EO11MRHtiH8D9BbdygOR9p9aSV/OxkaRWhnkSP5/0SXqVgBrvU1g5OsR6cc85UQSpJ5H5jVnLoWelIbTxMCikjxDSkZlseD7ZEWrKZjRo/ZN2qym0HRWpsir3qLpl8W25xHRv/sK7Z6g8="
+    - secure: "DflyBz+QiyhlhBxn4wN00xu248EJUMjKTxUZQN6wq22qV55xO3ToGJTy9i4D6OBfZGAlSXxjjKCJ2+0sAjsghBSDEK56ud3EEg/08TIo7/T8ex/C58FsGoGFz3yDBATmquClEWN8vAMrLdxwniHmQVCBZCP/phdt5dct0AUuDc8="
+    - PLAT=x86_64
+    - UNICODE_WIDTH=32
+
+matrix:
+  exclude:
+      - python: 3.5
+  include:
+    - os: linux
+      env:
+        - MB_PYTHON_VERSION=2.7
+    - os: linux
+      env:
+        - MB_PYTHON_VERSION=3.4
+    - os: linux
+      env:
+        - MB_PYTHON_VERSION=3.5
+    - os: osx
+      language: objective-c
+      env:
+        - MB_PYTHON_VERSION=2.7
+    - os: osx
+      language: objective-c
+      env:
+        - MB_PYTHON_VERSION=3.4
+    - os: osx
+      language: objective-c
+      env:
+        - MB_PYTHON_VERSION=3.5
+
 before_install:
-  - pip install pip --upgrade
-  # These get cached
-  - pip install numpy scipy python-libsbml cython coveralls jsonschema six matplotlib pandas
-  - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then pip install lxml glpk pep8 palettable; fi
-  # Download esolver and add it to the path
-  - wget https://opencobra.github.io/pypi_cobrapy_travis/esolver.gz
-  - gzip -d esolver.gz; chmod +x esolver; export PATH=$PATH:$PWD
-  - mkdir -p $HOME/.config/matplotlib
-  - "echo 'backend: Agg' >> $HOME/.config/matplotlib/matplotlibrc"
+  - (git clone https://github.com/matthew-brett/multibuild.git && cd multibuild && git checkout d7ba4ae)
+  # matplotlib non-compatible as testing runs in venv (non-framework)
+  - TEST_DEPENDS="cython codecov coverage numpy scipy python-libsbml jsonschema six pandas tabulate"
+  - BUILD_DEPENDS="cython numpy scipy"
+  - source multibuild/common_utils.sh
+  - source multibuild/travis_steps.sh
+  - before_install
+
+before_cache:
+  - set +e
+
 install:
-  - python setup.py develop
-# # command to run tests
+  - build_wheel . $PLAT
+
 script:
-  - coverage run --source=cobra setup.py test
-  - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then
-    pep8 cobra --exclude=oven,solvers,sbml.py --show-source;
-    fi
+  - if [[ $TRAVIS_OS_NAME == "linux" ]]; then pip install pip --upgrade; pip install pep8; pep8 cobra --exclude=oven,solvers,sbml.py --show-source; fi
+  - install_run $PLAT
+
+deploy:
+  provider: script
+  skip_cleanup: true
+  script: scripts/deploy.sh
+  on:
+    branch: master
+    tags: true
+
 after_success:
-  - coveralls
+  - if [[ $TRAVIS_OS_NAME == "linux" ]]; then pip install pip --upgrade; pip install codecov; codecov; fi
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 5724522..0000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,25 +0,0 @@
-Contribution Guidelines
------------------------
-
-Generally, the following practices are recommended for making contributions to
-cobrapy. These aren't all necessarily hard-and-fast rules, but should serve as
-guidelines in most cases.
-
-1. Please comment code.
-2. All new python code should be pep8 compliant.
-3. Please use git best practices, with a 50 line summary for each commit.
-   Generally, separate features should be made in separate commits so
-   they can be tested and merged independently. For example, adding a new
-   solver would be a separate commit from fixing whitespace in cobra.core.
-4. Documentation is written as IPython/jupyter notebooks in the
-   ```documentation_builder``` directory, which are then converted to
-   rst by the ```autodoc.sh``` script.
-5. Tests are in the ```cobra/test``` directory. They are automatically run
-   through continuous integration services on both python 2 and python 3
-   when pull requests are made.
-6. Please write tests for new functions. Writing documentation as well
-   would also be very helpful.
-7. Ensure code will work with both python 2 and python 3. For example,
-   instead of ```my_dict.iteritems()``` use ```six.iteritems(my_dict)```
-
-Thank you very much for contributing to cobrapy.
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
new file mode 100644
index 0000000..8623ea4
--- /dev/null
+++ b/CONTRIBUTING.rst
@@ -0,0 +1,26 @@
+Contribution Guidelines
+-----------------------
+
+Generally, the following practices are recommended for making
+contributions to cobrapy. These aren't all necessarily hard-and-fast
+rules, but should serve as guidelines in most cases.
+
+1. Please comment code.
+2. All new python code should be pep8 compliant.
+3. Please use git best practices, with a 50 line summary for each
+   commit. Generally, separate features should be made in separate
+   commits so they can be tested and merged independently. For example,
+   adding a new solver would be a separate commit from fixing whitespace
+   in cobra.core.
+4. Documentation is written as IPython/jupyter notebooks in the
+   ``documentation_builder`` directory, which are then converted to rst
+   by the ``autodoc.sh`` script.
+5. Tests are in the ``cobra/test`` directory. They are automatically run
+   through continuous integration services on both python 2 and python 3
+   when pull requests are made.
+6. Please write tests for new functions. Writing documentation as well
+   would also be very helpful.
+7. Ensure code will work with both python 2 and python 3. For example,
+   instead of ``my_dict.iteritems()`` use ``six.iteritems(my_dict)``
+
+Thank you very much for contributing to cobrapy.
diff --git a/INSTALL.md b/INSTALL.md
deleted file mode 100644
index c58d35b..0000000
--- a/INSTALL.md
+++ /dev/null
@@ -1,104 +0,0 @@
-#Installation of cobrapy
-
-For installation help, please use the
-[Google Group](http://groups.google.com/group/cobra-pie).
-For usage instructions, please see the 
-[documentation](https://cobrapy.readthedocs.org/en/latest/).
-
---------------------------------------------------------------------------------
-
-All releases require Python 2.7+ or 3.4+ to be installed before proceeding.
-Mac OS X (10.7+) and Ubuntu ship with Python. Windows users without python 
-can download and install python from the [python 
-website](https://www.python.org/ftp/python/2.7.9/python-2.7.9.amd64.msi).
-Please note that though Anaconda and other python distributions may work with
-cobrapy, they are not explicitly supported (yet!).
-
-## Stable version installation
-
-cobrapy can be installed with any recent installation of pip. Instructions
-for several operating systems are below:
-
-### Mac OS X or Linux
-0. [install pip](http://pip.readthedocs.org/en/latest/installing.html).
-1. In a terminal, run ```sudo pip install cobra```
-
-### Microsoft Windows
-The preferred installation method on Windows is also to use pip. The latest
-Windows installers for Python 2.7 and 3.4 include pip, so if you use those you
-will already have pip.
-
-1. In a terminal, run ```C:\Python27\Scripts\pip.exe install cobra```
-   (you may need to adjust the path accordingly).
-
-To install without pip, you will need to download and use the appropriate
-installer for your version of python from the [python package
-index](https://pypi.python.org/pypi/cobra/).
-
-
-## Hacking version installation
-Use pip to install [Cython](http://cython.org/). Install libglpk 
-using your package manger. This would be
-```brew install homebrew/science/glpk``` on a Mac
-and ```sudo apt-get install libglpk-dev``` on debian-based systems
-(including Ubuntu and Mint). GLPK can also be compiled from the
-released source.
-
-Clone the git repository using your preferred mothod. Cloning from your
-own [github fork](https://help.github.com/articles/fork-a-repo) is recommended!
-Afterwards, open a terminal, enter the cobrapy repository and run the following
-command:
-
-    python setup.py develop --user
-
-# Installation of optional dependencies
-## Optional Dependencies
-On windows, these can downloaded from [this site]
-(http://www.lfd.uci.edu/~gohlke/pythonlibs/). On Mac/Linux, they can be
-installed using pip, or from the OS package manager (e.g brew, apt, yum).
-
-1. [libsbml](http://sbml.org) >= 5.10 to read/write SBML level 2 files
-    * [Windows installer](http://www.lfd.uci.edu/~gohlke/pythonlibs/#libsbml)
-    * Use ```sudo pip install python-libsbml``` on Mac/Linux
-2. [lxml](http://lxml.de/) to speed up read/write of SBML level 3 files.
-3. [numpy](http://numpy.org) >= 1.6.1 for double deletions
-    * [Windows installer](http://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy)
-4. [scipy](http://scipy.org) >= 0.11 for ArrayBasedModel and saving to *.mat files.
-    * [Windows installer](http://www.lfd.uci.edu/~gohlke/pythonlibs/#scipy)
-
-## Other solvers
-cobrapy comes with bindings to the GNU Linear Programming Kit ([glpk]
-(http://www.gnu.org/software/glpk/)) using its own bindings called "cglpk" in
-cobrapy. In addition, cobrapy currently supports these linear programming
-solvers:
-
- * ILOG/CPLEX (available with
-   [Academic](https://www.ibm.com/developerworks/university/academicinitiative/)
-   and
-   [Commercial](http://www.ibm.com/software/integration/optimization/cplex-optimizer/)
-   licenses).
- * [gurobi](http://gurobi.com)
- * [QSopt_ex esolver](http://www.dii.uchile.cl/~daespino/ESolver_doc/main.html)
- * [MOSEK](http://www.mosek.com/)
- * [coin-or clp and cbc](http://coin-or.org/) through
-   [cylp](https://github.com/coin-or/CyLP).
-
-ILOG/CPLEX, MOSEK, and Gurobi are commercial software packages that currently
-provide free licenses for academics and support both linear and quadratic
-programming. GLPK and clp are open source linear programming solvers; however,
-they may not be as robsut as the commercial solvers for mixed-integer and
-quadratic programming. QSopt_ex esolver is also open source, and can solve
-linear programs using rational operations, giving exact solutions.
-
-
-# Testing your installation
-1. Start python
-2. Type the following into the Python shell
-
-```python
-from cobra.test import test_all
-test_all()
-```
-
-You should see some skipped tests and expected failures, and the function should return ```False```.
-
diff --git a/INSTALL.rst b/INSTALL.rst
new file mode 100644
index 0000000..3eba6cd
--- /dev/null
+++ b/INSTALL.rst
@@ -0,0 +1,134 @@
+Installation of cobrapy
+=======================
+
+For installation help, please use the `Google
+Group <http://groups.google.com/group/cobra-pie>`__. For usage
+instructions, please see the
+`documentation <https://cobrapy.readthedocs.org/en/latest/>`__.
+
+--------------
+
+All releases require Python 2.7+ or 3.4+ to be installed before
+proceeding. Mac OS X (10.7+) and Ubuntu ship with Python. Windows users
+without python can download and install python from the `python
+website <https://www.python.org/ftp/python/2.7.9/python-2.7.9.amd64.msi>`__.
+Please note that though Anaconda and other python distributions may work
+with cobrapy, they are not explicitly supported (yet!).
+
+Stable version installation
+---------------------------
+
+cobrapy can be installed with any recent installation of pip.
+Instructions for several operating systems are below:
+
+Mac OS X or Linux
+~~~~~~~~~~~~~~~~~
+
+0. `install
+   pip <http://pip.readthedocs.org/en/latest/installing.html>`__.
+1. In a terminal, run ``sudo pip install cobra``
+
+Microsoft Windows
+~~~~~~~~~~~~~~~~~
+
+The preferred installation method on Windows is also to use pip. The
+latest Windows installers for Python 2.7 and 3.4 include pip, so if you
+use those you will already have pip.
+
+1. In a terminal, run ``C:\Python27\Scripts\pip.exe install cobra`` (you
+   may need to adjust the path accordingly).
+
+To install without pip, you will need to download and use the
+appropriate installer for your version of python from the `python
+package index <https://pypi.python.org/pypi/cobra/>`__.
+
+Hacking version installation
+----------------------------
+
+Use pip to install `Cython <http://cython.org/>`__. Install libglpk
+using your package manger. This would be
+``brew install homebrew/science/glpk`` on a Mac and
+``sudo apt-get install libglpk-dev`` on debian-based systems (including
+Ubuntu and Mint). GLPK can also be compiled from the released source.
+
+Clone the git repository using your preferred mothod. Cloning from your
+own `github fork <https://help.github.com/articles/fork-a-repo>`__ is
+recommended! Afterwards, open a terminal, enter the cobrapy repository
+and run the following command:
+
+::
+
+    python setup.py develop --user
+
+Installation of optional dependencies
+=====================================
+
+Optional Dependencies
+---------------------
+
+On windows, these can downloaded from [this site]
+(http://www.lfd.uci.edu/~gohlke/pythonlibs/). On Mac/Linux, they can be
+installed using pip, or from the OS package manager (e.g brew, apt,
+yum).
+
+1. `libsbml <http://sbml.org>`__ >= 5.10 to read/write SBML level 2
+   files
+
+   -  `Windows
+      installer <http://www.lfd.uci.edu/~gohlke/pythonlibs/#libsbml>`__
+   -  Use ``sudo pip install python-libsbml`` on Mac/Linux
+
+2. `lxml <http://lxml.de/>`__ to speed up read/write of SBML level 3
+   files.
+3. `numpy <http://numpy.org>`__ >= 1.6.1 for double deletions
+
+   -  `Windows
+      installer <http://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy>`__
+
+4. `scipy <http://scipy.org>`__ >= 0.11 for ArrayBasedModel and saving
+   to \*.mat files.
+
+   -  `Windows
+      installer <http://www.lfd.uci.edu/~gohlke/pythonlibs/#scipy>`__
+
+Other solvers
+-------------
+
+cobrapy comes with bindings to the GNU Linear Programming Kit ([glpk]
+(http://www.gnu.org/software/glpk/)) using its own bindings called
+"cglpk" in cobrapy. In addition, cobrapy currently supports these linear
+programming solvers:
+
+-  ILOG/CPLEX (available with
+   `Academic <https://www.ibm.com/developerworks/university/academicinitiative/>`__
+   and
+   `Commercial <http://www.ibm.com/software/integration/optimization/cplex-optimizer/>`__
+   licenses).
+-  `gurobi <http://gurobi.com>`__
+-  `QSopt\_ex
+   esolver <http://www.dii.uchile.cl/~daespino/ESolver_doc/main.html>`__
+-  `MOSEK <http://www.mosek.com/>`__
+-  `coin-or clp and cbc <http://coin-or.org/>`__ through
+   `cylp <https://github.com/coin-or/CyLP>`__.
+
+ILOG/CPLEX, MOSEK, and Gurobi are commercial software packages that
+currently provide free licenses for academics and support both linear
+and quadratic programming. GLPK and clp are open source linear
+programming solvers; however, they may not be as robust as the
+commercial solvers for mixed-integer and quadratic programming.
+QSopt\_ex esolver is also open source, and can solve linear programs
+using rational operations, giving exact solutions.
+
+Testing your installation
+=========================
+
+1. Start python
+2. Type the following into the Python shell
+
+.. code:: python
+
+    from cobra.test import test_all
+    test_all()
+
+You should see some skipped tests and expected failures, and the
+function should return ``False``.
diff --git a/MANIFEST.in b/MANIFEST.in
index a675f66..d1e6a98 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,2 @@
-include README.md INSTALL.md LICENSE ez_setup.py
+include README.rst INSTALL.rst LICENSE ez_setup.py
 include cobra/solvers/cglpk.pyx cobra/solvers/glpk.pxd
diff --git a/README.md b/README.md
deleted file mode 100644
index 75b0e2b..0000000
--- a/README.md
+++ /dev/null
@@ -1,45 +0,0 @@
-cobrapy
-=======
-[![Build Status](https://travis-ci.org/opencobra/cobrapy.svg?branch=master)](https://travis-ci.org/opencobra/cobrapy)
-[![Coverage Status](https://coveralls.io/repos/opencobra/cobrapy/badge.svg?branch=master&service=github)](https://coveralls.io/github/opencobra/cobrapy?branch=master)
-[![Build status](https://ci.appveyor.com/api/projects/status/2o549lhjyukke8nd/branch/master?svg=true)](https://ci.appveyor.com/project/aebrahim/cobrapy/branch/master)
-[![PyPI](https://img.shields.io/pypi/v/cobra.svg)](https://pypi.python.org/pypi/cobra)
-
-
-COnstraint-Based Reconstruction and Analysis (COBRA) methods are widely used
-for genome-scale modeling of metabolic networks in both prokaryotes and
-eukaryotes. COBRApy is a constraint-based modeling package that is designed to
-accommodate the biological complexity of the next generation of COBRA models and
-provides access to commonly used COBRA methods, such as flux balance analysis,
-flux variability analysis, and gene deletion analyses.
-
-To install, please follow the [instructions](INSTALL.md).
-
-The documentation is browseable online at
-[readthedocs](https://cobrapy.readthedocs.org/en/stable/)
-and can also be
-[downloaded](https://readthedocs.org/projects/cobrapy/downloads/).
-
-Please use the [Google Group](http://groups.google.com/group/cobra-pie) for
-help. More information about opencobra is available at the
-[website](http://opencobra.github.io/).
-
-If you use cobrapy in a scientific publication, please cite
-[doi:10.1186/1752-0509-7-74](http://dx.doi.org/doi:10.1186/1752-0509-7-74)
-
-License
--------
-The cobrapy source is released under both the GPL and LGPL licenses.  You may
-choose which license you choose to use the software under. However, please note
-that binary packages which include GLPK (such as the binary wheels distributed
-at https://pypi.python.org/pypi/cobra) will be bound by its license as well.
-
-This program is free software: you can redistribute it and/or modify it under
-the terms of the GNU General Public License or the Lesser GNU General Public
-License as published by the Free Software Foundation, either version 2 of the
-License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful, but WITHOUT ANY
-WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
-PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..ec4e1d2
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,61 @@
+cobrapy
+=======
+
+|Build Status| |Coverage Status| |Build status| |PyPI| |Gitter|
+
+COnstraint-Based Reconstruction and Analysis (COBRA) methods are widely
+used for genome-scale modeling of metabolic networks in both prokaryotes
+and eukaryotes. COBRApy is a constraint-based modeling package that is
+designed to accommodate the biological complexity of the next generation
+of COBRA models and provides access to commonly used COBRA methods, such
+as flux balance analysis, flux variability analysis, and gene deletion
+analyses.
+
+To install, please follow the `instructions <INSTALL.md>`__.
+
+The documentation is browseable online at
+`readthedocs <https://cobrapy.readthedocs.org/en/stable/>`__ and can
+also be
+`downloaded <https://readthedocs.org/projects/cobrapy/downloads/>`__.
+
+Please use the `Google
+Group <http://groups.google.com/group/cobra-pie>`__ for help.
+Alternatively, you can use
+`gitter.im <https://gitter.im/opencobra/cobrapy>`__ for quick questions
+and discussions about cobrapy (faster response times).
+
+More information about opencobra is available at the
+`website <http://opencobra.github.io/>`__.
+
+If you use cobrapy in a scientific publication, please cite
+`doi:10.1186/1752-0509-7-74 <http://dx.doi.org/doi:10.1186/1752-0509-7-74>`__
+
+License
+-------
+
+The cobrapy source is released under both the GPL and LGPL licenses. You
+may choose which license you choose to use the software under. However,
+please note that binary packages which include GLPK (such as the binary
+wheels distributed at https://pypi.python.org/pypi/cobra) will be bound
+by its license as well.
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License or the Lesser GNU
+General Public License as published by the Free Software Foundation,
+either version 2 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+Public License for more details.
+
+.. |Build Status| image:: https://travis-ci.org/opencobra/cobrapy.svg?branch=master
+   :target: https://travis-ci.org/opencobra/cobrapy
+.. |Coverage Status| image:: https://codecov.io/github/opencobra/cobrapy/coverage.svg?branch=master
+   :target: https://codecov.io/github/opencobra/cobrapy/coverage.svg?branch=master
+.. |Build status| image:: https://ci.appveyor.com/api/projects/status/2o549lhjyukke8nd/branch/master?svg=true
+   :target: https://ci.appveyor.com/project/aebrahim/cobrapy/branch/master
+.. |PyPI| image:: https://img.shields.io/pypi/v/cobra.svg
+   :target: https://pypi.python.org/pypi/cobra
+.. |Gitter| image:: https://badges.gitter.im/opencobra/cobrapy.svg
+   :target: https://gitter.im/opencobra/cobrapy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
diff --git a/appveyor.yml b/appveyor.yml
index decb8ff..f54eb72 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -47,7 +47,7 @@ install:
   - ps: Start-FileDownload 'https://bitbucket.org/gutworth/six/raw/default/six.py'
   - "%WITH_COMPILER% %PYTHON%/python appveyor/build_glpk.py"
   - "%PYTHON%/python -m pip install pip setuptools wheel --upgrade"
-  - "%PYTHON%/python -m pip install Cython jsonschema numpy twine pypandoc==1.1.3"
+  - "%PYTHON%/python -m pip install Cython jsonschema twine pypandoc==1.1.3"
 
 build: off
 
diff --git a/cobra/VERSION b/cobra/VERSION
index 9901f66..733fa3f 100644
--- a/cobra/VERSION
+++ b/cobra/VERSION
@@ -1 +1 @@
-0.4.2b1
+0.5.0b2
diff --git a/cobra/core/Metabolite.py b/cobra/core/Metabolite.py
index 0449783..e764e1d 100644
--- a/cobra/core/Metabolite.py
+++ b/cobra/core/Metabolite.py
@@ -28,10 +28,8 @@ class Metabolite(Species):
         name: str
             A human readable name.
 
-        compartment: None or a dictionary indicating the cellular location
-        of the metabolite.  Used when in a cobra.Reaction or Model
-        object
-
+        compartment: str or None
+            Compartment of metabolite.
         """
         Species.__init__(self, id, name)
         self.formula = formula
@@ -159,12 +157,15 @@ class Metabolite(Species):
             If given, fva should be a float between 0 and 1, representing the
             fraction of the optimum objective to be searched.
 
+        floatfmt: string
+            format method for floats, passed to tabulate. Default is '.3g'.
+
         """
         try:
             from ..flux_analysis.summary import metabolite_summary
             return metabolite_summary(self, **kwargs)
         except ImportError:
-            warn('Summary methods require pandas')
+            warn('Summary methods require pandas/tabulate')
 
 elements_and_molecular_weights = {
     'H':   1.007940,
diff --git a/cobra/core/Model.py b/cobra/core/Model.py
index 9c90a60..ab13ed0 100644
--- a/cobra/core/Model.py
+++ b/cobra/core/Model.py
@@ -128,7 +128,7 @@ class Model(Object):
             new_reaction = reaction.__class__()
             for attr, value in iteritems(reaction.__dict__):
                 if attr not in do_not_copy:
-                    new_reaction.__dict__[attr] = value
+                    new_reaction.__dict__[attr] = copy(value)
             new_reaction._model = new
             new.reactions.append(new_reaction)
             # update awareness
@@ -174,15 +174,16 @@ class Model(Object):
         reaction_list: A list of :class:`~cobra.core.Reaction` objects
 
         """
-        # Only add the reaction if one with the same ID is not already
-        # present in the model.
 
-        # This function really should not used for single reactions
-        if not hasattr(reaction_list, "__len__"):
-            reaction_list = [reaction_list]
+        try:
+            reaction_list = DictList(reaction_list)
+        except TypeError:
+            # This function really should not used for single reactions
+            reaction_list = DictList([reaction_list])
             warn("Use add_reaction for single reactions")
 
-        reaction_list = DictList(reaction_list)
+        # Only add the reaction if one with the same ID is not already
+        # present in the model.
         reactions_in_model = [
             i.id for i in reaction_list if self.reactions.has_id(
                 i.id)]
@@ -363,11 +364,13 @@ class Model(Object):
 
         threshold: float
             tolerance for determining if a flux is zero (not printed)
+
         fva: int or None
             Whether or not to calculate and report flux variability in the
             output summary
-        round: int
-            number of digits after the decimal place to print
+
+        floatfmt: string
+            format method for floats, passed to tabulate. Default is '.3g'.
 
         """
 
@@ -375,4 +378,4 @@ class Model(Object):
             from ..flux_analysis.summary import model_summary
             return model_summary(self, **kwargs)
         except ImportError:
-            warn('Summary methods require pandas')
+            warn('Summary methods require pandas/tabulate')
diff --git a/cobra/core/Reaction.py b/cobra/core/Reaction.py
index cdc94cd..a8e5c29 100644
--- a/cobra/core/Reaction.py
+++ b/cobra/core/Reaction.py
@@ -179,6 +179,20 @@ class Reaction(Object):
             raise e  # Not sure what the exact problem was
 
     @property
+    def bounds(self):
+        """ A more convienient bounds structure than seperate upper and lower
+        bounds """
+
+        return (self.lower_bound, self.upper_bound)
+
+    @bounds.setter
+    def bounds(self, value):
+        """ Set the bounds directly from a tuple """
+
+        self.lower_bound = value[0]
+        self.upper_bound = value[1]
+
+    @property
     def reversibility(self):
         """Whether the reaction can proceed in both directions (reversible)
 
diff --git a/cobra/design/design_algorithms.py b/cobra/design/design_algorithms.py
index b355248..6dd3828 100644
--- a/cobra/design/design_algorithms.py
+++ b/cobra/design/design_algorithms.py
@@ -34,31 +34,38 @@ def set_up_optknock(model, chemical_objective, knockable_reactions,
                     n_knockouts_required=True, dual_maximum=1000, copy=True):
     """Set up the OptKnock problem described by Burgard et al., 2003:
 
-        Burgard AP, Pharkya P, Maranas CD. Optknock: a bilevel programming
-        framework for identifying gene knockout strategies for microbial strain
-        optimization.  Biotechnol Bioeng. 2003;84(6):647-57.
-        http://dx.doi.org/10.1002/bit.10803.
+    Burgard AP, Pharkya P, Maranas CD. Optknock: a bilevel programming
+    framework for identifying gene knockout strategies for microbial strain
+    optimization. Biotechnol Bioeng. 2003;84(6):647-57.
+    https://doi.org/10.1002/bit.10803.
 
+    Arguments
+    ---------
 
-    model : :class:`~cobra.core.Model` object.
+    model: :class:`~cobra.core.Model`
+        A COBRA model.
 
-    chemical_objective: str. The ID of the reaction to maximize in the outer
-    problem.
+    chemical_objective: str
+        The ID of the reaction to maximize in the outer problem.
 
-    knockable_reactions: [str]. A list of reaction IDs that can be knocked out.
+    knockable_reactions: [str]
+        A list of reaction IDs that can be knocked out.
 
-    biomass_objective: str. The ID of the reaction to maximize in the inner
-    problem. By default, this is the existing objective function in the passed
-    model.
+    biomass_objective: str
+        The ID of the reaction to maximize in the inner problem. By default,
+        this is the existing objective function in the passed model.
 
-    n_knockouts: int. The number of knockouts allowable.
+    n_knockouts: int
+        The number of knockouts allowable.
 
-    n_knockouts_required: bool. Require exactly the number of knockouts
-    specified by n_knockouts.
+    n_knockouts_required: bool
+        Require exactly the number of knockouts specified by n_knockouts.
 
-    dual_maximum: float or int. The upper bound for dual variables.
+    dual_maximum: float or int
+        The upper bound for dual variables.
 
-    copy: bool. Copy the model before making any modifications.
+    copy: bool
+        Copy the model before making any modifications.
 
 
     Zachary King 2015
@@ -139,15 +146,20 @@ def run_optknock(optknock_problem, solver=None, tolerance_integer=1e-9,
                  **kwargs):
     """Run the OptKnock problem created with set_up_optknock.
 
+    Arguments
+    ---------
 
-    optknock_problem: :class:`~cobra.core.Model` object. The problem generated
-    by set_up_optknock.
+    optknock_problem: :class:`~cobra.core.Model`
+        The problem generated by set_up_optknock.
 
-    solver: str. The name of the preferred solver.
+    solver: str
+        The name of the preferred solver.
 
-    tolerance_integer: float. The integer tolerance for the MILP.
+    tolerance_integer: float
+        The integer tolerance for the MILP.
 
-    **kwargs: Keyword arguments are passed to Model.optimize().
+    **kwargs
+        Keyword arguments are passed to Model.optimize().
 
 
     Zachary King 2015
@@ -179,10 +191,14 @@ def dual_problem(model, objective_sense="maximize",
 
     Make the problem irreversible, then take the dual. Convert the problem:
 
+    .. code-block:: none
+
         Maximize (c^T)x subject to Ax <= b, x >= 0
 
     which is something like this in COBRApy:
 
+    .. code-block:: none
+
         Maximize sum(objective_coefficient_j * reaction_j for all j)
             s.t.
             sum(coefficient_i_j * reaction_j for all j) <= metabolite_bound_i
@@ -191,10 +207,14 @@ def dual_problem(model, objective_sense="maximize",
 
     to the problem:
 
+    .. code-block:: none
+
         Minimize (b^T)w subject to (A^T)w >= c, w >= 0
 
     which is something like this in COBRApy (S matrix is m x n):
 
+    .. code-block:: none
+
         Minimize sum( metabolite_bound_i * dual_i   for all i ) +
                  sum( upper_bound_j *      dual_m+j for all j ) +
             s.t.
@@ -206,41 +226,48 @@ def dual_problem(model, objective_sense="maximize",
     Arguments
     ---------
 
-    model : :class:`~cobra.core.Model` object.
+    model : :class:`~cobra.core.Model`
+        The COBRA model.
 
-    objective_sense: str. The objective sense of the starting problem, either
-    'maximize' or 'minimize'. A minimization problems will be converted to a
-    maximization before taking the dual. This function always returns a
-    minimization problem.
+    objective_sense: str
+        The objective sense of the starting problem, either 'maximize' or
+        'minimize'. A minimization problems will be converted to a maximization
+        before taking the dual. This function always returns a minimization
+        problem.
 
-    iteger_vars_to_maintain: [str]. A list of IDs for Boolean integer variables
-    to be maintained in the dual problem. See 'Maintaining integer variables'
-    below for more details
+    iteger_vars_to_maintain: [str]
+        A list of IDs for Boolean integer variables to be maintained in the
+        dual problem. See 'Maintaining integer variables' below for more
+        details.
 
-    already_irreversible: Boolean. If True, then do not convert the model to
-    irreversible.
+    already_irreversible: bool
+        If True, then do not convert the model to irreversible.
 
-    copy: bool. If True, then make a copy of the model before modifying
-    it. This is not necessary if already_irreversible is True.
+    copy: bool
+        If True, then make a copy of the model before modifying it. This is not
+        necessary if already_irreversible is True.
 
-    dual_maximum: float or int. The upper bound for dual variables.
+    dual_maximum: float or int
+        The upper bound for dual variables.
 
 
-    Maintaining integer variables
-    -----------------------------
+    **Maintaining integer variables**
 
-    The argument integer_vars_to_maintain can be used to specify certin Boolean
-    integer variables that will be maintained in the dual problem. This makes
-    it possible to join outer and inner problems in a bi-level MILP. The method
-    for maintaining integer variables is described by Tepper and Shlomi, 2010:
+    The argument ``integer_vars_to_maintain`` can be used to specify certin
+    Boolean integer variables that will be maintained in the dual problem. This
+    makes it possible to join outer and inner problems in a bi-level MILP. The
+    method for maintaining integer variables is described by Tepper and Shlomi,
+    2010:
 
-        Tepper N, Shlomi T. Predicting metabolic engineering knockout
-        strategies for chemical production: accounting for competing pathways.
-        Bioinformatics.  2010;26(4):536-43. doi:10.1093/bioinformatics/btp704.
+    Tepper N, Shlomi T. Predicting metabolic engineering knockout strategies
+    for chemical production: accounting for competing pathways. Bioinformatics.
+    2010;26(4):536-43. https://doi.org/10.1093/bioinformatics/btp704.
 
     In COBRApy, this roughly translates to transforming (decision variables p,
     integer constraints o):
 
+    .. code-block:: none
+
         Maximize (c^T)x subject to (A_x)x + (A_y)y <= b, x >= 0
 
         (1) Maximize sum(objective_coefficient_j * reaction_j for all j)
@@ -253,10 +280,14 @@ def dual_problem(model, objective_sense="maximize",
 
     to the problem:
 
+    .. code-block:: none
+
         Minimize (b - (A_y)y)^T w subject to (A_x^T)w >= c, w >= 0
 
     which linearizes to (with auxiliary variables z):
 
+    .. code-block:: none
+
         Minimize (b^T)w - { ((A_y)y)^T w with yw --> z }
         subject to (A_x^T)w >= c, linearization constraints, w >= 0
           Linearization constraints: z <= w_max * y, z <= w,
diff --git a/cobra/flux_analysis/parsimonious.py b/cobra/flux_analysis/parsimonious.py
index 4630214..0269044 100644
--- a/cobra/flux_analysis/parsimonious.py
+++ b/cobra/flux_analysis/parsimonious.py
@@ -56,6 +56,7 @@ def optimize_minimal_flux(cobra_model, already_irreversible=False,
         solver.solve_problem(lp, objective_sense='maximize')
         status = solver.get_status(lp)
         if status != "optimal":
+            revert_to_reversible(cobra_model)
             raise ValueError(
                 "pFBA requires optimal solution status, not {}".format(status))
         desired_objective_value = solver.get_objective_value(lp)
diff --git a/cobra/flux_analysis/phenotype_phase_plane.py b/cobra/flux_analysis/phenotype_phase_plane.py
index 742ee2b..92e0333 100644
--- a/cobra/flux_analysis/phenotype_phase_plane.py
+++ b/cobra/flux_analysis/phenotype_phase_plane.py
@@ -64,12 +64,16 @@ class phenotypePhasePlaneData:
         colors = empty(self.growth_rates.shape, dtype=dtype((str, 7)))
         n_segments = self.segments.max()
         # pick colors
-        if get_map is None:
-            color_list = ['#A6CEE3', '#1F78B4', '#B2DF8A', '#33A02C',
-                          '#FB9A99', '#E31A1C', '#FDBF6F', '#FF7F00',
-                          '#CAB2D6', '#6A3D9A', '#FFFF99', '#B15928']
-        else:
-            color_list = get_map(theme, 'Qualitative', n_segments).hex_colors
+        color_list = ['#A6CEE3', '#1F78B4', '#B2DF8A', '#33A02C',
+                      '#FB9A99', '#E31A1C', '#FDBF6F', '#FF7F00',
+                      '#CAB2D6', '#6A3D9A', '#FFFF99', '#B15928']
+        if get_map is not None:
+            try:
+                color_list = get_map(theme, 'Qualitative',
+                                     n_segments).hex_colors
+            except ValueError:
+                from warnings import warn
+                warn('palettable could not be used for this number of phases')
         if n_segments > len(color_list):
             from warnings import warn
             warn("not enough colors to color all detected phases")
diff --git a/cobra/flux_analysis/reaction.py b/cobra/flux_analysis/reaction.py
index f2321fe..7b31e60 100644
--- a/cobra/flux_analysis/reaction.py
+++ b/cobra/flux_analysis/reaction.py
@@ -4,7 +4,7 @@ from ..core.Reaction import Reaction
 from six import iteritems
 
 
-def assess(model, reaction, flux_coefficient_cutoff=0.001):
+def assess(model, reaction, flux_coefficient_cutoff=0.001, solver=None):
     """Assesses the capacity of the model to produce the precursors for the
     reaction and absorb the production of the reaction while the reaction is
     operating at, or above, the specified cutoff.
@@ -16,6 +16,8 @@ def assess(model, reaction, flux_coefficient_cutoff=0.001):
     flux_coefficient_cutoff:  Float.  The minimum flux that reaction must carry
     to be considered active.
 
+    solver : String or solver name. If None, the default solver will be used.
+
     returns: True if the model can produce the precursors and absorb the
     products for the reaction operating at, or above, flux_coefficient_cutoff.
     Otherwise, a dictionary of {'precursor': Status, 'product': Status}.  Where
@@ -24,7 +26,10 @@ def assess(model, reaction, flux_coefficient_cutoff=0.001):
 
     """
     reaction = model.reactions.get_by_id(reaction.id)
-    model.optimize(new_objective={reaction: 1})
+    original_objective = model.objective
+    model.objective = reaction
+    model.optimize(solver=solver)
+    model.objective = original_objective
     if model.solution.f >= flux_coefficient_cutoff:
         return True
     else:
@@ -36,7 +41,8 @@ def assess(model, reaction, flux_coefficient_cutoff=0.001):
         return results
 
 
-def assess_precursors(model, reaction, flux_coefficient_cutoff=0.001):
+def assess_precursors(model, reaction, flux_coefficient_cutoff=0.001,
+                      solver=None):
     """Assesses the ability of the model to provide sufficient precursors for
     a reaction operating at, or beyond, the specified cutoff.
 
@@ -47,6 +53,8 @@ def assess_precursors(model, reaction, flux_coefficient_cutoff=0.001):
     flux_coefficient_cutoff: Float. The minimum flux that reaction must carry
     to be considered active.
 
+    solver : String or solver name. If None, the default solver will be used.
+
     returns: True if the precursors can be simultaneously produced at the
     specified cutoff. False, if the model has the capacity to produce each
     individual precursor at the specified threshold  but not all precursors at
@@ -57,14 +65,17 @@ def assess_precursors(model, reaction, flux_coefficient_cutoff=0.001):
     """
     model = model.copy()
     reaction = model.reactions.get_by_id(reaction.id)
-    model.optimize(new_objective={reaction: 1})
+    original_objective = model.objective
+    model.objective = reaction
+    model.optimize(solver=solver)
+    model.objective = original_objective
     if model.solution.f >= flux_coefficient_cutoff:
         return True
     #
     simulation_results = {}
     # build the sink reactions and add all at once
     sink_reactions = {}
-    for the_component in reaction.get_reactants():
+    for the_component in reaction.reactants:
         # add in a sink reaction for each component
         sink_reaction = Reaction('test_sink_%s' % the_component.id)
         # then simulate production ability
@@ -80,7 +91,9 @@ def assess_precursors(model, reaction, flux_coefficient_cutoff=0.001):
         super_sink += reaction
     super_sink.id = 'super_sink'
     model.add_reactions(sink_reactions.keys() + [super_sink])
-    model.optimize(new_objective=super_sink)
+    model.objective = super_sink
+    model.optimize(solver=solver)
+    model.objective = original_objective
     if flux_coefficient_cutoff <= model.solution.f:
         return True
 
@@ -89,7 +102,9 @@ def assess_precursors(model, reaction, flux_coefficient_cutoff=0.001):
     # reactant for a reaction
     for sink_reaction, (component, coefficient) in iteritems(sink_reactions):
         # Calculate the maximum amount of the
-        model.optimize(new_objective=sink_reaction)
+        model.objective = sink_reaction
+        model.optimize(solver=solver)
+        model.objective = original_objective
         # metabolite that can be produced.
         if flux_coefficient_cutoff > model.solution.f:
             # Scale the results to a single unit
@@ -105,7 +120,8 @@ def assess_precursors(model, reaction, flux_coefficient_cutoff=0.001):
     return simulation_results
 
 
-def assess_products(model, reaction, flux_coefficient_cutoff=0.001):
+def assess_products(model, reaction, flux_coefficient_cutoff=0.001,
+                    solver=None):
     """Assesses whether the model has the capacity to absorb the products of
     a reaction at a given flux rate.  Useful for identifying which components
     might be blocking a reaction from achieving a specific flux rate.
@@ -117,6 +133,8 @@ def assess_products(model, reaction, flux_coefficient_cutoff=0.001):
     flux_coefficient_cutoff:  Float.  The minimum flux that reaction must carry
     to be considered active.
 
+    solver : String or solver name. If None, the default solver will be used.
+
     returns: True if the model has the capacity to absorb all the reaction
     products being simultaneously given the specified cutoff.   False, if the
     model has the capacity to absorb each individual product but not all
@@ -127,14 +145,17 @@ def assess_products(model, reaction, flux_coefficient_cutoff=0.001):
     """
     model = model.copy()
     reaction = model.reactions.get_by_id(reaction.id)
-    model.optimize(new_objective={reaction: 1})
+    original_objective = model.objective
+    model.objective = reaction
+    model.optimize(solver=solver)
+    model.objective = original_objective
     if model.solution.f >= flux_coefficient_cutoff:
         return True
     #
     simulation_results = {}
     # build the sink reactions and add all at once
     source_reactions = {}
-    for the_component in reaction.get_products():
+    for the_component in reaction.products:
         # add in a sink reaction for each component
         source_reaction = Reaction('test_source_%s' % the_component.id)
         # then simulate production ability
@@ -150,7 +171,9 @@ def assess_products(model, reaction, flux_coefficient_cutoff=0.001):
         super_source += reaction
     super_source.id = 'super_source'
     model.add_reactions(source_reactions.keys() + [super_source])
-    model.optimize(new_objective=super_source)
+    model.objective = super_source
+    model.optimize(solver=solver)
+    model.objective = original_objective
     if flux_coefficient_cutoff <= model.solution.f:
         return True
 
@@ -159,7 +182,9 @@ def assess_products(model, reaction, flux_coefficient_cutoff=0.001):
     for source_reaction, (component, coefficient) in \
             iteritems(source_reactions):
         # Calculate the maximum amount of the
-        model.optimize(new_objective=source_reaction)
+        model.objective = source_reaction
+        model.optimize(solver=solver)
+        model.objective = original_objective
         # metabolite that can be produced.
         if flux_coefficient_cutoff > model.solution.f:
             # Scale the results to a single unit
diff --git a/cobra/flux_analysis/summary.py b/cobra/flux_analysis/summary.py
index dfce00a..f705e91 100644
--- a/cobra/flux_analysis/summary.py
+++ b/cobra/flux_analysis/summary.py
@@ -1,7 +1,8 @@
 from six.moves import zip_longest
-from six import iterkeys, print_, text_type
+from six import print_, iteritems
 
 import pandas as pd
+from tabulate import tabulate
 
 from .variability import flux_variability_analysis
 
@@ -13,7 +14,8 @@ def format_long_string(string, max_length):
     return string
 
 
-def metabolite_summary(met, threshold=0.01, fva=False, **solver_args):
+def metabolite_summary(met, threshold=0.01, fva=False, floatfmt='.3g',
+                       **solver_args):
     """Print a summary of the reactions which produce and consume this
     metabolite
 
@@ -25,84 +27,89 @@ def metabolite_summary(met, threshold=0.01, fva=False, **solver_args):
     If given, fva should be a float between 0 and 1, representing the
     fraction of the optimum objective to be searched.
 
+    floatfmt: string
+        format method for floats, passed to tabulate. Default is '.3g'.
+
     """
 
     def rxn_summary(r):
-        return {
-            'id': r.id,
+        out = {
+            'id': format_long_string(r.id, 10),
             'flux': r.x * r.metabolites[met],
-            'reaction': r.reaction,
+            'reaction': format_long_string(r.reaction, 40 if fva else 50),
         }
 
-    flux_summary = pd.DataFrame((rxn_summary(r) for r in met.reactions))
-    assert flux_summary.flux.sum() < 1E-6, "Error in flux balance"
-    producing = flux_summary[flux_summary.flux > 0].copy()
-    consuming = flux_summary[flux_summary.flux < 0].copy()
+        if rxn_summary.fva_results is not False:
+            fmax = rxn_summary.fva_results.loc[r.id, 'maximum']
+            fmin = rxn_summary.fva_results.loc[r.id, 'minimum']
+            imax = r.metabolites[met] * fmax
+            imin = r.metabolites[met] * fmin
 
-    for df in [producing, consuming]:
-        df['percent'] = df.flux / df.flux.sum()
-        df.drop(df[df['percent'] < threshold].index, axis=0,
-                inplace=True)
+            # Correct 'max' and 'min' for negative values
+            out.update({
+                'fmin': imin if abs(imin) <= abs(imax) else imax,
+                'fmax': imax if abs(imin) <= abs(imax) else imin,
+            })
 
-    producing.sort_values('percent', ascending=False, inplace=True)
-    consuming.sort_values('percent', ascending=False, inplace=True)
+        return out
 
-    if not fva:
+    if fva:
+        rxn_summary.fva_results = pd.DataFrame(flux_variability_analysis(
+            met.model, met.reactions, fraction_of_optimum=fva,
+            **solver_args)).T
+    else:
+        rxn_summary.fva_results = False
 
-        producing.flux = producing.flux.apply(
-            lambda x: '{:6.2g}'.format(x))
-        consuming.flux = consuming.flux.apply(
-            lambda x: '{:6.2g}'.format(x))
+    flux_summary = pd.DataFrame((rxn_summary(r) for r in met.reactions))
+    assert flux_summary.flux.sum() < 1E-6, "Error in flux balance"
 
-        flux_len = 6
+    flux_summary = _process_flux_dataframe(flux_summary, fva, threshold,
+                                           floatfmt)
 
-    else:
-        fva_results = pd.DataFrame(
-            flux_variability_analysis(met.model, met.reactions,
-                                      fraction_of_optimum=fva,
-                                      **solver_args)).T
-        half_span = (fva_results.maximum - fva_results.minimum) / 2
-        median = fva_results.minimum + half_span
+    flux_summary['percent'] = 0
+    total_flux = flux_summary[flux_summary.is_input].flux.sum()
 
-        producing.flux = producing.id.apply(
-            lambda x, median=median, err=half_span:
-            u'{0:0.2f} \u00B1 {1:0.2f}'.format(median[x], err[x]))
-        consuming.flux = consuming.id.apply(
-            lambda x, median=median, err=half_span:
-            u'{0:0.2f} \u00B1 {1:0.2f}'.format(median[x], err[x]))
+    flux_summary.loc[flux_summary.is_input, 'percent'] = \
+        flux_summary.loc[flux_summary.is_input, 'flux'] / total_flux
+    flux_summary.loc[~flux_summary.is_input, 'percent'] = \
+        flux_summary.loc[~flux_summary.is_input, 'flux'] / total_flux
 
-        flux_len = max(producing.flux.apply(len).max(),
-                       consuming.flux.apply(len).max()) + 1
+    flux_summary['percent'] = flux_summary.percent.apply(
+        lambda x: '{:.0%}'.format(x))
+
+    if fva:
+        flux_table = tabulate(
+            flux_summary.loc[:, ['percent', 'flux', 'fva_fmt', 'id',
+                                 'reaction']].values, floatfmt=floatfmt,
+            headers=['%', 'FLUX', 'RANGE', 'RXN ID', 'REACTION']).split('\n')
+    else:
+        flux_table = tabulate(
+            flux_summary.loc[:, ['percent', 'flux', 'id', 'reaction']].values,
+            floatfmt=floatfmt, headers=['%', 'FLUX', 'RXN ID', 'REACTION']
+        ).split('\n')
 
-    for df in [producing, consuming]:
+    flux_table_head = flux_table[:2]
 
-        df['reaction'] = df['reaction'].map(
-            lambda x: format_long_string(x, 52))
-        df['id'] = df['id'].map(
-            lambda x: format_long_string(x, 8))
+    met_tag = "{0} ({1})".format(format_long_string(met.name, 45),
+                                 format_long_string(met.id, 10))
 
-    head = "PRODUCING REACTIONS -- " + format_long_string(met.name, 55)
+    head = "PRODUCING REACTIONS -- " + met_tag
     print_(head)
     print_("-" * len(head))
-    print_(("{0:^6} {1:>" + str(flux_len) + "} {2:>8} {3:^54}").format(
-        '%', 'FLUX', 'RXN ID', 'REACTION'))
-
-    for row in producing.iterrows():
-        print_((u"{0.percent:6.1%} {0.flux:>" + str(flux_len) +
-                "} {0.id:>8} {0.reaction:>54}").format(row[1]))
+    print_('\n'.join(flux_table_head))
+    print_('\n'.join(
+        pd.np.array(flux_table[2:])[flux_summary.is_input.values]))
 
     print_()
-    print_("CONSUMING REACTIONS -- " + format_long_string(met.name, 55))
+    print_("CONSUMING REACTIONS -- " + met_tag)
     print_("-" * len(head))
-    print_(("{0:^6} {1:>" + str(flux_len) + "} {2:>8} {3:^54}").format(
-        '%', 'FLUX', 'RXN ID', 'REACTION'))
-
-    for row in consuming.iterrows():
-        print_((u"{0.percent:6.1%} {0.flux:>" + str(flux_len) +
-                "} {0.id:>8} {0.reaction:>54}").format(row[1]))
+    print_('\n'.join(flux_table_head))
+    print_('\n'.join(
+        pd.np.array(flux_table[2:])[~flux_summary.is_input.values]))
 
 
-def model_summary(model, threshold=1E-8, fva=None, digits=2, **solver_args):
+def model_summary(model, threshold=1E-8, fva=None, floatfmt='.3g',
+                  **solver_args):
     """Print a summary of the input and output fluxes of the model.
 
     threshold: float
@@ -112,80 +119,136 @@ def model_summary(model, threshold=1E-8, fva=None, digits=2, **solver_args):
         Whether or not to calculate and report flux variability in the
         output summary
 
-    digits: int
-        number of digits after the decimal place to print
+    floatfmt: string
+        format method for floats, passed to tabulate. Default is '.3g'.
 
     """
-    obj_fluxes = pd.Series({'{:<15}'.format(r.id): '{:.3f}'.format(r.x)
-                            for r in iterkeys(model.objective)})
 
+    # Create a dataframe of objective fluxes
+    obj_fluxes = pd.DataFrame({key: key.x * value for key, value in
+                               iteritems(model.objective)}, index=['flux']).T
+    obj_fluxes['id'] = obj_fluxes.apply(
+        lambda x: format_long_string(x.name.id, 15), 1)
+
+    # Build a dictionary of metabolite production from the boundary reactions
+    boundary_reactions = model.reactions.query(lambda x: x, 'boundary')
+
+    # Calculate FVA results if requested
+    if fva:
+        fva_results = pd.DataFrame(
+            flux_variability_analysis(model, reaction_list=boundary_reactions,
+                                      fraction_of_optimum=fva,
+                                      **solver_args)).T
+
+    metabolite_fluxes = {}
+    for rxn in boundary_reactions:
+        for met, stoich in iteritems(rxn.metabolites):
+            metabolite_fluxes[met] = {
+                'id': format_long_string(met.id, 15),
+                'flux': stoich * rxn.x}
+
+            if fva:
+                imin = stoich * fva_results.loc[rxn.id]['minimum']
+                imax = stoich * fva_results.loc[rxn.id]['maximum']
+
+                # Correct 'max' and 'min' for negative values
+                metabolite_fluxes[met].update({
+                    'fmin': imin if abs(imin) <= abs(imax) else imax,
+                    'fmax': imax if abs(imin) <= abs(imax) else imin,
+                })
+
+    # Generate a dataframe of boundary fluxes
+    metabolite_fluxes = pd.DataFrame(metabolite_fluxes).T
+    metabolite_fluxes = _process_flux_dataframe(
+        metabolite_fluxes, fva, threshold, floatfmt)
+
+    # Begin building string output table
+    def get_str_table(species_df, fva=False):
+        """Formats a string table for each column"""
+
+        if not fva:
+            return tabulate(species_df.loc[:, ['id', 'flux']].values,
+                            floatfmt=floatfmt, tablefmt='plain').split('\n')
+
+        else:
+            return tabulate(
+                species_df.loc[:, ['id', 'flux', 'fva_fmt']].values,
+                floatfmt=floatfmt, tablefmt='simple',
+                headers=['id', 'Flux', 'Range']).split('\n')
+
+    in_table = get_str_table(
+        metabolite_fluxes[metabolite_fluxes.is_input], fva=fva)
+    out_table = get_str_table(
+        metabolite_fluxes[~metabolite_fluxes.is_input], fva=fva)
+    obj_table = get_str_table(obj_fluxes, fva=False)
+
+    # Print nested output table
+    print_(tabulate(
+        [entries for entries in zip_longest(in_table, out_table, obj_table)],
+        headers=['IN FLUXES', 'OUT FLUXES', 'OBJECTIVES'], tablefmt='simple'))
+
+
+def _process_flux_dataframe(flux_dataframe, fva, threshold, floatfmt):
+    """Some common methods for processing a database of flux information into
+    print-ready formats. Used in both model_summary and metabolite_summary. """
+
+    # Drop unused boundary fluxes
     if not fva:
+        flux_dataframe = flux_dataframe[
+            flux_dataframe.flux.abs() > threshold].copy()
+    else:
+        flux_dataframe = flux_dataframe[
+            (flux_dataframe.flux.abs() > threshold) |
+            (flux_dataframe.fmin.abs() > threshold) |
+            (flux_dataframe.fmax.abs() > threshold)].copy()
 
-        out_rxns = model.reactions.query(
-            lambda rxn: rxn.x > threshold, None
-        ).query(lambda x: x, 'boundary')
+    # Make all fluxes positive
+    if not fva:
+        flux_dataframe['is_input'] = flux_dataframe.flux >= 0
+        flux_dataframe.flux = \
+            flux_dataframe.flux.abs().astype('float').round(6)
+    else:
 
-        in_rxns = model.reactions.query(
-            lambda rxn: rxn.x < -threshold, None
-        ).query(lambda x: x, 'boundary')
+        def get_direction(flux, fmin, fmax):
+            """ decide whether or not to reverse a flux to make it positive """
 
-        out_fluxes = pd.Series({r.reactants[0]: r.x for r in out_rxns})
-        in_fluxes = pd.Series({r.reactants[0]: r.x for r in in_rxns})
+            if flux < 0:
+                return -1
+            elif flux > 0:
+                return 1
+            elif (fmax > 0) & (fmin <= 0):
+                return 1
+            elif (fmax < 0) & (fmin >= 0):
+                return -1
+            elif ((fmax + fmin)/2) < 0:
+                return -1
+            else:
+                return 1
 
-        # sort and round
-        out_fluxes.sort_values(ascending=False, inplace=True)
-        out_fluxes = out_fluxes.round(digits)
-        in_fluxes.sort_values(inplace=True)
-        in_fluxes = in_fluxes.round(digits)
+        sign = flux_dataframe.apply(
+            lambda x: get_direction(x.flux, x.fmin, x.fmax), 1)
 
-        table = pd.np.array(
-            [((a if a else ''), (b if b else ''), (c if c else ''))
-             for a, b, c in zip_longest(
-                ['IN FLUXES'] + in_fluxes.to_string().split('\n'),
-                ['OUT FLUXES'] + out_fluxes.to_string().split('\n'),
-                ['OBJECTIVES'] + obj_fluxes.to_string().split('\n'))])
+        flux_dataframe['is_input'] = sign == 1
 
-    else:
-        boundary_reactions = model.reactions.query(lambda x: x, 'boundary')
+        flux_dataframe.loc[:, ['flux', 'fmin', 'fmax']] = \
+            flux_dataframe.loc[:, ['flux', 'fmin', 'fmax']].multiply(
+                sign, 0).astype('float').round(6)
 
-        fva_results = pd.DataFrame(
-            flux_variability_analysis(model, reaction_list=boundary_reactions,
-                                      fraction_of_optimum=fva,
-                                      **solver_args)).T
+        flux_dataframe.loc[:, ['flux', 'fmin', 'fmax']] = \
+            flux_dataframe.loc[:, ['flux', 'fmin', 'fmax']].applymap(
+                lambda x: x if abs(x) > 1E-6 else 0)
+
+    if fva:
+        flux_dataframe['fva_fmt'] = flux_dataframe.apply(
+            lambda x: ("[{0.fmin:" + floatfmt + "}, {0.fmax:" +
+                       floatfmt + "}]").format(x), 1)
+
+        flux_dataframe = flux_dataframe.sort_values(
+            by=['flux', 'fmax', 'fmin', 'id'],
+            ascending=[False, False, False, True])
+
+    else:
+        flux_dataframe = flux_dataframe.sort_values(
+            by=['flux', 'id'], ascending=[False, True])
 
-        half_span = (fva_results.maximum - fva_results.minimum) / 2
-        median = fva_results.minimum + half_span
-        rxn_data = pd.concat([median, half_span], 1)
-        rxn_data.columns = ['x', 'err']
-
-        for r in rxn_data.index:
-            rxn_data.loc[r, 'met'] = model.reactions.get_by_id(r).reactants[0]
-
-        rxn_data.set_index('met', drop=True, inplace=True)
-
-        out_fluxes = rxn_data[rxn_data.x > threshold]
-        in_fluxes = rxn_data[rxn_data.x < -threshold]
-
-        out_fluxes = out_fluxes.sort_values(by='x', ascending=False)
-        out_fluxes = out_fluxes.round(digits)
-        in_fluxes = in_fluxes.sort_values(by='x')
-        in_fluxes = in_fluxes.round(digits)
-
-        in_fluxes_s = in_fluxes.apply(
-            lambda x: u'{0:0.2f} \u00B1 {1:0.2f}'.format(x.x, x.err),
-            axis=1)
-        out_fluxes_s = out_fluxes.apply(
-            lambda x: u'{0:0.2f} \u00B1 {1:0.2f}'.format(x.x, x.err),
-            axis=1)
-        out_fluxes_s = out_fluxes.apply(lambda x: text_type(x.x) +
-                                        u" \u00B1 " + text_type(x.err), axis=1)
-
-        table = pd.np.array(
-            [((a if a else ''), (b if b else ''), (c if c else ''))
-             for a, b, c in zip_longest(
-                ['IN FLUXES'] + in_fluxes_s.to_string().split('\n'),
-                ['OUT FLUXES'] + out_fluxes_s.to_string().split('\n'),
-                ['OBJECTIVES'] + obj_fluxes.to_string().split('\n'))])
-
-    print_(u'\n'.join([u"{a:<30}{b:<30}{c:<20}".format(a=a, b=b, c=c) for
-                       a, b, c in table]))
+    return flux_dataframe
diff --git a/cobra/solvers/__init__.py b/cobra/solvers/__init__.py
index 0d90690..e4a82b1 100644
--- a/cobra/solvers/__init__.py
+++ b/cobra/solvers/__init__.py
@@ -39,6 +39,7 @@ def add_solver(solver_name, use_name=None):
             use_name = solver_name
     solver_dict[use_name] = eval(solver_name)
 
+    
 for i in listdir(path.dirname(path.abspath(__file__))):
     if i.startswith("_") or i.startswith(".") or i.startswith('legacy'):
         continue
diff --git a/cobra/test/flux_analysis.py b/cobra/test/flux_analysis.py
index 0e79699..ecaa3fe 100644
--- a/cobra/test/flux_analysis.py
+++ b/cobra/test/flux_analysis.py
@@ -3,6 +3,7 @@ from unittest import TestCase, TestLoader, TextTestRunner, skipIf
 from warnings import warn
 import sys
 from os.path import join
+from os import name
 from json import load
 from contextlib import contextmanager
 import re
@@ -177,7 +178,8 @@ class TestCobraFluxAnalysis(TestCase):
             [0.858, 0.863, 0.814, 0.000, 0.874, 0.874, 0.874, 0.874],
             [0.858, 0.863, 0.814, 0.000, 0.874, 0.874, 0.874, 0.874],
             [0.858, 0.863, 0.814, 0.000, 0.874, 0.874, 0.874, 0.874]]
-        solution = double_gene_deletion(cobra_model, gene_list1=genes)
+        opts = {"number_of_processes": 1} if name == "nt" else {}
+        solution = double_gene_deletion(cobra_model, gene_list1=genes, **opts)
         self.assertEqual(solution["x"], genes)
         self.assertEqual(solution["y"], genes)
         self.compare_matrices(growth_list, solution["data"])
@@ -338,22 +340,27 @@ class TestCobraFluxAnalysis(TestCase):
 
         # Test model summary methods
         model = create_test_model("textbook")
+        with self.assertRaises(Exception):
+            model.summary()
         model.optimize()
         desired_entries = [
-            u'glc__D_e     -9.76 \u00B1 0.24'
-            u'co2_e       21.81 \u00B1 2.86',
-            u'nh4_e        -4.84 \u00B1 0.32'
-            u'h_e         19.51 \u00B1 2.86',
-            u'pi_e         -3.13 \u00B1 0.08'
-            u'for_e        2.86 \u00B1 2.86',
-            u'ac_e         0.95 \u00B1 0.95',
-            u'acald_e      0.64 \u00B1 0.64',
-            u'pyr_e        0.64 \u00B1 0.64',
-            u'etoh_e       0.55 \u00B1 0.55',
-            u'lac__D_e     0.54 \u00B1 0.54',
-            u'succ_e       0.42 \u00B1 0.42',
-            u'akg_e        0.36 \u00B1 0.36',
-            u'glu__L_e     0.32 \u00B1 0.32'
+            'idFluxRangeidFluxRangeBiomass_Ecol...0.874',
+            'o2_e       21.8   [19.9, 23.7]'
+            'h2o_e       29.2  [25, 30.7]',
+            'glc__D_e   10     [9.52, 10]'
+            'co2_e       22.8  [18.9, 24.7]',
+            'nh4_e       4.77  [4.53, 5.16]'
+            'h_e         17.5  [16.7, 22.4]',
+            'pi_e        3.21  [3.05, 3.21]'
+            'for_e        0    [0, 5.72]',
+            'ac_e         0    [0, 1.91]',
+            'pyr_e        0    [0, 1.27]',
+            'lac__D_e     0    [0, 1.07]',
+            'succ_e       0    [0, 0.837]',
+            'glu__L_e     0    [0, 0.636]',
+            'akg_e        0    [0, 0.715]',
+            'etoh_e       0    [0, 1.11]',
+            'acald_e      0    [0, 1.27]',
         ]
         for solver in solver_dict:
             with captured_output() as (out, err):
@@ -362,14 +369,14 @@ class TestCobraFluxAnalysis(TestCase):
 
         # test non-fva version (these should be fixed for textbook model
         desired_entries = [
-            "o2_e       -21.80",
-            "glc__D_e   -10.00",
-            "nh4_e       -4.77",
-            "pi_e        -3.21",
-            "h2o_e    29.18",
-            "co2_e    22.81",
-            "h_e      17.53",
-            "Biomass_Ecoli_core    0.874"
+            'o2_e      21.8',
+            'glc__D_e  10',
+            'nh4_e      4.77',
+            'pi_e       3.21',
+            'h2o_e  29.2',
+            'co2_e  22.8',
+            'h_e    17.5',
+            'Biomass_Ecol...  0.874',
         ]
         # Need to use a different method here because
         # there are multiple entries per line.
@@ -381,36 +388,32 @@ class TestCobraFluxAnalysis(TestCase):
 
         # Test metabolite summary methods
         desired_entries = [
-            'PRODUCING REACTIONS -- Ubiquinone-8',
-            '-----------------------------------',
-            '%      FLUX   RXN ID'
-            'REACTION',
-            '100.0%     44    CYTBD'
-            '2.0 h_c + 0.5 o2_c + q8h2_c --> h2o_c + 2.0 h_e +...',
-            'CONSUMING REACTIONS -- Ubiquinone-8',
-            '-----------------------------------',
-            '88.4%    -39   NADH16'
-            '4.0 h_c + nadh_c + q8_c --> 3.0 h_e + nad_c + q8h2_c',
-            '11.6%   -5.1    SUCDi'
-            'q8_c + succ_c --> fum_c + q8h2_c',
+            'PRODUCING REACTIONS -- Ubiquinone-8 (q8_c)',
+            '%       FLUX  RXN ID    REACTION',
+            '100%   43.6   CYTBD     '
+            '2.0 h_c + 0.5 o2_c + q8h2_c --> h2o_c + 2.0 h_e...',
+            'CONSUMING REACTIONS -- Ubiquinone-8 (q8_c)',
+            '%       FLUX  RXN ID    REACTION',
+            '88%    38.5   NADH16    '
+            '4.0 h_c + nadh_c + q8_c --> 3.0 h_e + nad_c + q...',
+            '12%     5.06  SUCDi     q8_c + succ_c --> fum_c + q8h2_c',
         ]
         with captured_output() as (out, err):
             model.metabolites.q8_c.summary()
         self.check_entries(out, desired_entries)
 
         desired_entries = [
-            u'PRODUCING REACTIONS -- D-Fructose 1,6-bisphosphate',
-            u'--------------------------------------------------',
-            u'  %            FLUX   RXN ID'
-            u'REACTION',
-            u'100.0%  7.71 \u00B1 1.54      PFK'
-            u'atp_c + f6p_c --> adp_c + fdp_c + h_c',
-            u'CONSUMING REACTIONS -- D-Fructose 1,6-bisphosphate',
-            u'--------------------------------------------------',
-            u'  %            FLUX   RXN ID'
-            u'REACTION',
-            u'100.0%  7.54 \u00B1 1.37      FBA'
-            u'fdp_c <=> dhap_c + g3p_c',
+            'PRODUCING REACTIONS -- D-Fructose 1,6-bisphosphate (fdp_c)',
+            '----------------------------------------------------------',
+            '%       FLUX  RANGE         RXN ID    REACTION',
+            '100%    7.48  [6.17, 9.26]  PFK       '
+            'atp_c + f6p_c --> adp_c + fdp_c + h_c',
+            'CONSUMING REACTIONS -- D-Fructose 1,6-bisphosphate (fdp_c)',
+            '----------------------------------------------------------',
+            '%       FLUX  RANGE         RXN ID    REACTION',
+            '100%    7.48  [6.17, 8.92]  FBA       fdp_c <=> dhap_c + g3p_c',
+            '0%      0     [0, 1.72]     FBP       '
+            'fdp_c + h2o_c --> f6p_c + pi_c',
         ]
         for solver in solver_dict:
             with captured_output() as (out, err):
diff --git a/cobra/test/unit_tests.py b/cobra/test/unit_tests.py
index 7610754..23213f7 100644
--- a/cobra/test/unit_tests.py
+++ b/cobra/test/unit_tests.py
@@ -357,6 +357,9 @@ class TestReactions(CobraTestCase):
         pgi = model.reactions.get_by_id("PGI")
         pgi.reaction = "g6p_c --> f6p_c"
         self.assertEqual(pgi.lower_bound, 0)
+        pgi.bounds = (0, 1000)
+        self.assertEqual(pgi.bounds, (0, 1000))
+        self.assertEqual(pgi.reversibility, False)
         pgi.reaction = "g6p_c <== f6p_c"
         self.assertEqual(pgi.upper_bound, 0)
         self.assertEqual(pgi.reaction.strip(), "g6p_c <-- f6p_c")
@@ -649,7 +652,7 @@ class TestCobraModel(CobraTestCase):
         contained in reactions after adding them to the model.
         """
         _model = self.model_class('test')
-        _model.add_reactions([x.copy() for x in self.model.reactions])
+        _model.add_reactions((x.copy() for x in self.model.reactions))
         _genes = []
         _metabolites = []
         for x in _model.reactions:
diff --git a/config.sh b/config.sh
new file mode 100644
index 0000000..f1bfd61
--- /dev/null
+++ b/config.sh
@@ -0,0 +1,57 @@
+# Define custom utilities
+# Test for OSX with [ -n "$IS_OSX" ]
+
+function pre_build {
+    # Any stuff that you need to do before you start building the wheels
+    # Runs in the root directory of this repository.
+    if [ -n "$IS_OSX" ]; then
+        export CC=clang
+        export CXX=clang++
+		export CFLAGS="-fPIC -O3 -arch i386 -arch x86_64 -g -DNDEBUG -mmacosx-version-min=10.6"
+	else
+		yum install -y libxslt libxml2 libxml2-devel libxslt-devel
+	fi
+	curl -O http://ftp.gnu.org/gnu/glpk/glpk-4.60.tar.gz
+	tar xzf glpk-4.60.tar.gz
+	(cd glpk-4.60 \
+			&& ./configure \
+			&& make \
+			&& make install)
+	pip install cython
+	cython -a cobra/solvers/cglpk.pyx
+	export PATH="$PATH:/usr/local/bin"
+}
+
+function build_wheel {
+    # Set default building method to pip
+    build_bdist_wheel $@
+	(cd glpk-4.60 && make uninstall)
+}
+
+function run_tests_in_repo {
+    # Run tests from within source repo
+	# trick is to run the /installed/ package 
+    # coverage run --source=cobra setup.py test
+	if [ -n "$IS_OSX" ]; then
+		echo -e " testing for mac... "
+	else
+		wget --no-check-certificate \
+			 https://opencobra.github.io/pypi_cobrapy_travis/esolver.gz
+		gzip -f -d esolver.gz
+		chmod +x esolver
+		export PATH=$PWD:$PATH
+		# which pkg-config
+		# pip install matplotlib
+	fi
+	mkdir -p $HOME/.config/matplotlib
+	echo 'backend: Agg' >> $HOME/.config/matplotlib/matplotlibrc
+	echo -e "import cobra.test; import sys; sys.exit(cobra.test.test_all())" > run-tests.py
+	(coverage run --source=cobra --rcfile ../.coveragerc run-tests.py &&
+			coverage xml &&
+			mv coverage.xml ..)
+}
+
+function run_tests {
+    # Runs tests on installed distribution from an empty directory
+    run_tests_in_repo
+}
diff --git a/manylinux_builder/Dockerfile b/manylinux_builder/Dockerfile
new file mode 100644
index 0000000..3784f04
--- /dev/null
+++ b/manylinux_builder/Dockerfile
@@ -0,0 +1,6 @@
+FROM quay.io/pypa/manylinux1_x86_64
+
+ENV GLPK_VER="4.60"
+RUN wget http://ftp.gnu.org/gnu/glpk/glpk-${GLPK_VER}.tar.gz -O - | tar xz
+WORKDIR glpk-${GLPK_VER}
+RUN ./configure && make install
diff --git a/manylinux_builder/README.md b/manylinux_builder/README.md
new file mode 100644
index 0000000..2f3a6ec
--- /dev/null
+++ b/manylinux_builder/README.md
@@ -0,0 +1,5 @@
+This script uses docker to build manylinux wheels for cobrapy. It only
+requires a working docker installation.
+
+To build manylinux wheels, run ```./run_cobrapy_builder.sh```. The
+built wheels will then be placed in the ```wheelhouse``` folder.
diff --git a/manylinux_builder/build_cobrapy.sh b/manylinux_builder/build_cobrapy.sh
new file mode 100755
index 0000000..6703649
--- /dev/null
+++ b/manylinux_builder/build_cobrapy.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+for PYBIN in /opt/python/*/bin; do
+    ${PYBIN}/pip wheel cobra --pre
+done
+
+# Bundle external shared libraries into the wheels
+for whl in cobra*.whl; do
+    auditwheel repair $whl -w /io/wheelhouse/
+done
+
diff --git a/manylinux_builder/run_cobrapy_builder.sh b/manylinux_builder/run_cobrapy_builder.sh
new file mode 100755
index 0000000..3a59861
--- /dev/null
+++ b/manylinux_builder/run_cobrapy_builder.sh
@@ -0,0 +1,2 @@
+docker build -t cobrapy_builder .
+docker run --rm -v `pwd`:/io cobrapy_builder /io/build_cobrapy.sh
diff --git a/scripts/deploy-test.sh b/scripts/deploy-test.sh
new file mode 100755
index 0000000..6db962f
--- /dev/null
+++ b/scripts/deploy-test.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+echo -e " ... running twine to deploy ... "
+echo -e "
+[distutils]
+index-servers=
+    pypirepository
+
+[pypirepository]
+repository: https://testpypi.python.org/pypi
+username: henred
+password: pippi_langstrump
+" > ~/.pypirc
+
+pip install twine
+twine upload --skip-existing ${TRAVIS_BUILD_DIR}/wheelhouse/* -r pypirepository
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
new file mode 100755
index 0000000..10d6876
--- /dev/null
+++ b/scripts/deploy.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+echo -e " ... running twine to deploy ... "
+pip install twine
+
+twine upload --skip-existing --username "${PYPI_USERNAME}" --password "${PYPI_PASSWORD}" ${TRAVIS_BUILD_DIR}/wheelhouse/*
diff --git a/setup.py b/setup.py
index dd88390..173c115 100644
--- a/setup.py
+++ b/setup.py
@@ -136,13 +136,13 @@ extras = {
     'matlab': ["pymatbridge"],
     'sbml': ["python-libsbml", "lxml"],
     'array': ["numpy>=1.6", "scipy>=0.11.0"],
-    'display': ["matplotlib", "palettable", "pandas>=0.17.0"]
+    'display': ["matplotlib", "palettable", "pandas>=0.17.0", "tabulate"]
 }
 
 all_extras = {'Cython>=0.21'}
 for extra in extras.values():
     all_extras.update(extra)
-extras["all"] = list(all_extras)
+extras["all"] = sorted(list(all_extras))
 
 # If using bdist_wininst, the installer will not get dependencies like
 # a setuptools installation does. Therefore, for the one external dependency,
@@ -155,13 +155,13 @@ if "bdist_wininst" in argv:
     setup_kwargs["py_modules"] = ["six"]
 
 try:
-    import pypandoc
-    readme = pypandoc.convert("README.md", "rst")
-    install = pypandoc.convert("INSTALL.md", "rst")
+    with open('README.rst') as handle:
+        readme = handle.read()
+    with open('INSTALL.rst') as handle:
+        install = handle.read()
     setup_kwargs["long_description"] = readme + "\n\n" + install
 except:
-    with open("README.md", "r") as infile:
-        setup_kwargs["long_description"] = infile.read()
+    setup_kwargs["long_description"] = ''
 
 setup(
     name="cobra",

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/python-cobra.git



More information about the debian-med-commit mailing list