[taurus] 01/04: New upstream version 4.0.3

Frédéric-Emmanuel Picca picca at moszumanska.debian.org
Mon Jan 23 19:12:50 UTC 2017


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

picca pushed a commit to branch master
in repository taurus.

commit b0d850882c51b8556b93b3fcb454105f3adb2071
Author: Picca Frédéric-Emmanuel <picca at debian.org>
Date:   Sat Jan 21 11:54:58 2017 +0100

    New upstream version 4.0.3
---
 CHANGELOG.md                                       |   66 +-
 PKG-INFO                                           |    4 +-
 doc/source/devel/examples.rst                      |    4 +-
 doc/source/devel/taurusgui_newgui.rst              |    4 +-
 doc/source/docs.rst                                |    2 +
 doc/source/index.rst                               |    9 +-
 doc/source/tep/TEP0.md                             |  358 +++
 doc/source/tep/TEP13.md                            |  374 ++++
 doc/source/tep/TEP14.md                            |  232 ++
 doc/source/tep/TEP15.md                            |  114 +
 doc/source/tep/TEP16.md                            |  201 ++
 doc/source/tep/TEP3.md                             |  414 ++++
 doc/source/tep/TEP7.md                             |  190 ++
 doc/source/tep/TEP8.md                             |  250 +++
 doc/source/tep/index.md                            |   40 +
 doc/source/tep/index.rst                           |   23 +
 doc/source/tep/res/tep0_workflow.png               |  Bin 0 -> 22871 bytes
 doc/source/tep/res/tep14_merge_Attr_and_Conf.png   |  Bin 0 -> 24672 bytes
 doc/source/users/getting_started.rst               |    2 +-
 lib/taurus.egg-info/PKG-INFO                       |    4 +-
 lib/taurus.egg-info/SOURCES.txt                    |   12 +
 lib/taurus/core/epics/epicsattribute.py            |    5 -
 lib/taurus/core/epics/epicsvalidator.py            |    3 +-
 lib/taurus/core/epics/test/test_epicsvalidator.py  |   12 +-
 lib/taurus/core/evaluation/evalattribute.py        |    6 -
 lib/taurus/core/evaluation/evalfactory.py          |   29 -
 lib/taurus/core/release.py                         |   20 +-
 lib/taurus/core/tango/starter.py                   |    8 +-
 lib/taurus/core/tango/tangoattribute.py            |  140 +-
 lib/taurus/core/tango/tangofactory.py              |   35 +-
 lib/taurus/core/tango/test/res/TangoSchemeTest     |   26 +-
 lib/taurus/core/tango/test/test_tangoattribute.py  |  234 +-
 lib/taurus/core/tango/util/tango_taurus.py         |    4 +-
 lib/taurus/core/taurusattribute.py                 |   24 +-
 lib/taurus/core/taurusauthority.py                 |    7 -
 lib/taurus/core/taurusdevice.py                    |    7 -
 lib/taurus/core/taurusfactory.py                   |  146 +-
 lib/taurus/core/taurushelper.py                    |   38 +-
 lib/taurus/core/taurusmodel.py                     |    6 +-
 lib/taurus/core/taurusvalidator.py                 |    4 +-
 lib/taurus/external/argparse/LICENSE.txt           |   20 +
 lib/taurus/external/argparse/argparse_local.py     | 2362 ++++++++++++++++++++
 lib/taurus/external/enum/enum/__init__.py          |  809 +++++++
 lib/taurus/external/ordereddict/__init__.py        |    6 +
 lib/taurus/external/ordereddict/ordereddict.py     |  153 ++
 lib/taurus/external/pint/pint_local/AUTHORS        |   41 +
 lib/taurus/external/pint/pint_local/LICENSE        |   33 +
 lib/taurus/external/pint/pint_local/__init__.py    |  126 ++
 .../external/pint/pint_local/compat/__init__.py    |  129 ++
 .../external/pint/pint_local/compat/chainmap.py    |  153 ++
 .../external/pint/pint_local/compat/lrucache.py    |  177 ++
 .../external/pint/pint_local/compat/nullhandler.py |   32 +
 .../external/pint/pint_local/compat/tokenize.py    |  640 ++++++
 .../external/pint/pint_local/constants_en.txt      |   51 +
 lib/taurus/external/pint/pint_local/context.py     |  239 ++
 lib/taurus/external/pint/pint_local/converters.py  |   82 +
 lib/taurus/external/pint/pint_local/default_en.txt |  377 ++++
 lib/taurus/external/pint/pint_local/definitions.py |  157 ++
 lib/taurus/external/pint/pint_local/errors.py      |  113 +
 lib/taurus/external/pint/pint_local/formatting.py  |  200 ++
 lib/taurus/external/pint/pint_local/measurement.py |   99 +
 lib/taurus/external/pint/pint_local/pint_eval.py   |  258 +++
 lib/taurus/external/pint/pint_local/quantity.py    | 1337 +++++++++++
 lib/taurus/external/pint/pint_local/systems.py     |  423 ++++
 lib/taurus/external/pint/pint_local/unit.py        | 1375 ++++++++++++
 lib/taurus/external/pint/pint_local/util.py        |  643 ++++++
 lib/taurus/qt/qtgui/compact/basicswitcher.py       |    2 +-
 lib/taurus/qt/qtgui/display/taurusled.py           |    4 +-
 .../qt/qtgui/display/test/test_tauruslabel.py      |    6 +-
 lib/taurus/qt/qtgui/editor/tauruseditor.py         |  120 +-
 lib/taurus/qt/qtgui/extra_guiqwt/curvesmodel.py    |    6 +-
 lib/taurus/qt/qtgui/graphic/taurusgraphic.py       |   34 +-
 lib/taurus/qt/qtgui/input/tauruslineedit.py        |   29 +-
 lib/taurus/qt/qtgui/input/taurusspinbox.py         |   54 +-
 lib/taurus/qt/qtgui/input/tauruswheel.py           |    9 +-
 lib/taurus/qt/qtgui/panel/taurusvalue.py           |   16 +-
 lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py |    4 +-
 lib/taurus/qt/qtgui/taurusgui/appsettingswizard.py |   13 +-
 lib/taurus/qt/qtgui/taurusgui/macrolistener.py     |   53 +-
 lib/taurus/qt/qtgui/taurusgui/taurusgui.py         |    2 +-
 lib/taurus/qt/qtgui/test/base.py                   |    6 +
 lib/taurus/qt/qtgui/util/taurusaction.py           |    5 +
 lib/taurus/qt/qtgui/util/taurusropepatch.py        |   10 +-
 setup.cfg                                          |    1 -
 setup.py                                           |    2 +-
 85 files changed, 12965 insertions(+), 503 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e75c6d..c705c28 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,54 @@ Note: changes in the [support-3.x] branch (which was split from
 the master branch after [3.7.1] and maintained in parallel to the 
 develop branch)  won't be reflected in this file. 
 
-## [Unreleased]
+
+## Unreleased
+
+
+## [4.0.3] - 2017-01-16
+[Jan17 milestone](https://github.com/taurus-org/taurus/milestone/1)
+Bugfix release.
+For a full log of commits since Jul16, run (in your git repo):
+`git log 4.0.1..4.0.3` 
+
+### Added
+- Generic Attribute, Device and  Authority getters in TaurusFactory`
+- spyder >=3 support (#343)
+- bumpversion support (for maintainers) (#347)
+- Contribution policy explicited in CONTRIBUTING.md
+- Continuous Integration for Windows support (Appveyor) (PR#10)
+
+### Changed
+- TangoAttribute now decodes uchars as integers instead of strings (#367)
+- Allow empty path in Attr and Dev URIs (#269)
+- Project migrated to Github (TEP16)
+- Versioning policy (use of `-alpha` suffix for unreleased branches)
+
+### Deprecated
+- `taurus.Release.version_info` and `taurus.Release.revision` variables
+- `TaurusAttribute.isState` (#2)
+- `taurus.external.ordereddict` (#8)
+
+### Fixed
+- Taurus4 regressions in:
+    - TangoAttribute (when handling Tango config errors) (#365)
+    - TaurusValueSpinBox (#7)
+    - taurusgui --new-gui (#275)
+    - TaurusGui Sardana instrument panels (#372)
+    - Macrolistener (affects sardana) (#373)
+    - Synoptics (#363)
+    - TaurusValueLineEdit (#265)
+    - taurusgui.macrolistener` (#260)
+    - TaurusEditor (#343)
+- Bug causing high CPU usage in TaurusForms (#247)
+- Deprecation warnings in `TaurusWheelEdit` (#337)
+- Exceptions in `taurusconfigurationpanel` for non-tango models (#354)
+- Exception when creating non-exported tango devices (#262)
+- Bug causing random failures in the test suite(#261)
+- Documentation issues(#351, #350, #349)
+
+### Removed
+- `TaurusBaseEditor2` class
 
 
 ## [4.0.1] - 2016-07-19
@@ -146,14 +193,15 @@ and several other places](https://sf.net/p/tauruslib/tickets/milestone/Jul15/)
 
 
 [keepachangelog.com]: http://keepachangelog.com
-[TEP3]: https://sf.net/p/tauruslib/wiki/TEP3/
-[TEP14]: https://sf.net/p/tauruslib/wiki/TEP14/
-[Unreleased]: https://sf.net/p/tauruslib/taurus.git/ci/develop/tree/
-[4.0.1]: https://sf.net/p/tauruslib/taurus.git/ci/4.0.0/tree/
-[3.7.1]: https://sf.net/p/tauruslib/taurus.git/ci/3.7.1/tree/
-[3.7.0]: https://sf.net/p/tauruslib/taurus.git/ci/3.7.0/tree/
-[3.6.0]: https://sf.net/p/tauruslib/taurus.git/ci/3.6.0/tree/
-[support-3.x]: https://sf.net/p/tauruslib/taurus.git/ci/support-3.x/tree/
+[TEP3]: http://www.taurus-scada.org/tep/?TEP3.md
+[TEP14]: http://www.taurus-scada.org/tep/?TEP14.md
+[Unreleased]: https://github.com/taurus-org/taurus/tree/develop
+[4.0.3]: https://github.com/taurus-org/taurus/tree/4.0.3
+[4.0.1]: https://github.com/taurus-org/taurus/tree/4.0.1
+[3.7.1]: https://github.com/taurus-org/taurus/tree/3.7.1
+[3.7.0]: https://github.com/taurus-org/taurus/tree/3.7.0
+[3.6.0]: https://github.com/taurus-org/taurus/tree/3.6.0
+[support-3.x]: https://github.com/taurus-org/taurus/tree/support-3.x
 
 
 
diff --git a/PKG-INFO b/PKG-INFO
index 81b85ab..e22ba94 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: taurus
-Version: 4.0.1
+Version: 4.0.3
 Summary: A framework for scientific/industrial CLIs and GUIs
 Home-page: http://www.taurus-scada.org
 Author: Taurus Community
@@ -9,7 +9,7 @@ License: LGPL
 Download-URL: http://pypi.python.org/packages/source/t/taurus
 Description: Taurus is a python framework for control and data
         acquisition CLIs and GUIs in scientific/industrial environments.
-        It supports multiple control systems or data sources: Tango, EPICS, spec...
+        It supports multiple control systems or data sources: Tango, EPICS,...
         New control system libraries can be integrated through plugins.
 Keywords: CLI,GUI,PyTango,Tango,Shell,Epics
 Platform: Linux
diff --git a/doc/source/devel/examples.rst b/doc/source/devel/examples.rst
index e352709..a909657 100644
--- a/doc/source/devel/examples.rst
+++ b/doc/source/devel/examples.rst
@@ -142,7 +142,9 @@ Interactively display attribute
 
 Humm... Now suppose the user wants to change this value. :class:`input.TaurusValueLineEdit`
 does this job well (and so does :class:`input.TaurusValueSpinBox` and 
-:class:`input.TaurusWheelEdit` :-)
+:class:`input.TaurusWheelEdit` |smile| )
+
+.. |smile| unicode:: U+1F603 .. smiling face with open mouth
 
 .. figure:: /_static/edit01.png
   :align: center
diff --git a/doc/source/devel/taurusgui_newgui.rst b/doc/source/devel/taurusgui_newgui.rst
index 2ae5a2f..b76fdf9 100644
--- a/doc/source/devel/taurusgui_newgui.rst
+++ b/doc/source/devel/taurusgui_newgui.rst
@@ -21,8 +21,8 @@ The taurus GUI thus created can be populated with *panels* containing
 any Qt widget (typically from Taurus, but it may also be from any
 other arbitrary module).
 
-The GUI can also be modified and extended at execution time asexplained
-in :ref:`this section of the user guide <taurusgui_ui>`
+The GUI can also be modified and extended at execution time as explained
+in :ref:`this section of the user guide <taurusgui_ui>`.
 
 The Taurus widgets can be associated with models when they are added
 to the GUI and, in many cases, they may also accept drag & drop of the
diff --git a/doc/source/docs.rst b/doc/source/docs.rst
index 8900855..3079d40 100644
--- a/doc/source/docs.rst
+++ b/doc/source/docs.rst
@@ -15,6 +15,8 @@ acquisition.
 
    users/index.rst
    devel/index.rst
+   tep/index.rst
+
 
 * :ref:`genindex`
 * :ref:`modindex`
diff --git a/doc/source/index.rst b/doc/source/index.rst
old mode 100644
new mode 100755
index f16fbdd..f069dd5
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -20,12 +20,12 @@ and economical API which abstracts data sources as "models".
 
 Of course, Taurus is Free Software (under LGPL). You can download it from PyPi_,
 access its Documentation_ or get support from its community and the latest code
-from the `project page <http://sourceforge.net/projects/sardana>`_.
+from the `project page <https://github.com/taurus-org/taurus>`_.
 
 Projects related to Taurus
 ---------------------------
 
-- Taurus uses PyQt_ for the GUIs (Pyside_ support planned)
+- Taurus uses PyQt_ for the GUIs 
 - Tango_ is supported vis PyTango_ 
 - Taurus is part of the Sardana_ suite
 
@@ -38,7 +38,7 @@ Projects related to Taurus
     :hidden:
 
     Home Page <http://www.taurus-scada.org>
-    Project Page <http://sourceforge.net/projects/tauruslib>
+    Project Page <https://github.com/taurus-org/taurus>
     Download from PyPI <http://pypi.python.org/pypi/taurus>
     docs
 
@@ -50,8 +50,7 @@ Projects related to Taurus
 .. _EPICS: http://www.aps.anl.gov/epics/
 .. _PyQt: http://www.riverbankcomputing.co.uk/software/pyqt/
 .. _Sardana: http://sardana-controls.org
-.. _PySide: http://pyside.org
 .. _LGPL: http://www.gnu.org/licenses/lgpl.html
 .. _PyPi: http://pypi.python.org/pypi/taurus 
-.. _Documentation: http://taurus.readthedocs.org
+.. _Documentation: http://taurus-scada.org/docs.html
 
diff --git a/doc/source/tep/TEP0.md b/doc/source/tep/TEP0.md
new file mode 100644
index 0000000..b8b37df
--- /dev/null
+++ b/doc/source/tep/TEP0.md
@@ -0,0 +1,358 @@
+    Title: Introducing Taurus Enhancement Proposals (TEPs)
+    TEP: 0
+    State: OBSOLETE
+    Reason: 
+     TEP16 obsoletes TEP0. https://sourceforge.net/p/tauruslib/wiki/TEP is no 
+     longer the index for TEPs, nor it is a wiki. The "Creating a TEP section" of 
+     TEP0 is superseded by the one with the same name in TEP16.
+    Date: 2016-04-07
+    Drivers: Carlos Pascual-Izarra <cpascual at cells.es>, Tiago Coutinho <coutinho at esrf.fr>
+    URL: http://www.taurus-scada.org/tep/?TEP0.md
+    License: http://www.jclark.com/xml/copying.txt
+    Abstract:
+     Workflow for managing discussions about improvements to Taurus
+     and archiving their outcomes.
+
+
+Introduction
+------------
+
+*This TEP is an adaptation of [SEP0](https://sourceforge.net/p/sardana/wiki/SEP0)*
+
+This is a proposal to organize discussions about Taurus enhancements, reflect
+their current status and, in particular, to archive their outcomes, via a new
+lightweight process based on Taurus Enhancement Proposals (TEPs). This idea is
+a copy of the Sardana Enhancement Proposal system which is itself a copy of the
+Debian Enhancement Proposal system with a few adjustments to the Taurus project
+reality.
+
+
+Motivation
+----------
+
+The main reason for using TEPs is to provide a central index in which to list
+such proposals, which would be useful to see at a glance what open fronts
+there are at a given moment in Taurus, and who is taking care of them and, 
+additionally, to serve as a storage place for successfully completed proposals,
+documenting the outcome of the discussion and the details of the implementation.
+
+
+Workflow
+--------
+
+A "Taurus enhancement" can be pretty much any change to Taurus, technical or 
+otherwise. Examples of situations when the TEP process might be or might have
+been used include:
+
+* Introducing a new feature in Taurus (e.g. Visualization widgets)
+* Introducing/modifying a policy or workflow for the community
+
+The workflow is very simple, and is intended to be quite lightweight: an
+enhancement to Taurus is suggested, discussed, implemented, and becomes
+accepted practice (or policy, if applicable), in the normal Taurus way. As the
+discussion progresses, the enhancement is assigned certain states, as explained
+ below. During all the process, a single URL maintained by the proposers can be
+used to check the status of the proposal.
+
+The result of all this is:
+
+  1. an implementation of the enhancement and
+  2. a document that can be referred to later on without having to dig
+     up and read through large discussions.
+
+The actual discussions should happen in the taurus mailing lists (normally 
+taurus-devel, unless the discussion may benefit from getting input from the 
+wider audience of taurus-users). This way, TEPs do not act asyet another forum
+to be followed.
+
+In the same way, TEPs do not give any extra powers or authority to anyone: they
+rely on reaching consensus, by engaging in discussions on mailing lists, IRC,
+or real life meetings as appropriate. In case of dispute, the ultimate decision
+lies in the Taurus Executive Committee defined in the Taurus MoU.
+
+The person or people who do the suggestion are the "drivers" of the proposal
+and have the responsibility of writing the initial draft, and of updating it 
+during the discussions, see below.
+
+
+Proposal states
+---------------
+
+![TEP state diagram](res/tep0_workflow.png)
+
+A given TEP can be in one of the following *states*:
+
+* DRAFT
+* CANDIDATE
+* ACCEPTED
+* REJECTED
+* OBSOLETE
+
+The ideal progression of states is DRAFT -> CANDIDATE -> ACCEPTED, but
+reality requires a couple of other states and transitions as well.
+
+### DRAFT state: discussion
+
+* every new proposal starts as a DRAFT
+* anyone can propose a draft
+* each draft has a number (next free one from document index)
+* normal discussion and changes to the text happen in this state
+* drafts should include *extra* criteria for success (in addition to
+  having obtained consensus, see below), that is, requirements to
+  finally become ACCEPTED
+
+#### DRAFT -> CANDIDATE: rough consensus
+
+In order for a TEP to become CANDIDATE, the following condition should
+be met:
+
+* consensus exists for *what* should be done, and *how* it should be
+  done (agreement needs to be expressed by all affected parties, not
+  just the drivers; silence is not agreement, but unanimity is not
+  required, either)
+
+### CANDIDATE: implementation + testing
+
+The CANDIDATE state is meant to prove, via a suitable implementation
+and its testing, that a given TEP is feasible.
+
+* of course, implementation can start in earlier states
+* changes to the text can happen also in this period, primarily based
+  on feedback from implementation
+* this period must be long enough that there is consensus that the
+  enhancement works (on the basis of implementation evaluation)
+* since TEP are not necessarily technical, "implementation" does not
+  necessarily mean coding
+
+#### CANDIDATE -> ACCEPTED: working implementation
+
+In order for a TEP to become ACCEPTED, the following condition should
+be met:
+
+* consensus exists that the implementation has been a success
+
+### ACCEPTED: have fun
+
+Once accepted:
+
+* the final version of the TEP text is archived on the Taurus wiki
+* if applicable, the proposed TEP change is integrated into
+  authoritative texts such as policy, developer's reference, etc.
+
+#### {DRAFT, CANDIDATE} -> REJECTED
+
+A TEP can become REJECTED in the following cases:
+
+* the drivers are no longer interested in pursuing the TEP and
+  explicitly acknowledge so
+* there are no modifications to a TEP in DRAFT state for 6 months or
+  more
+* there is no consensus either on the draft text or on the fact that
+  the implementation is working satisfactorily
+
+#### ACCEPTED -> OBSOLETE: no longer relevant
+
+A TEP can become OBSOLETE when it is no longer relevant, for example:
+
+* a new TEP gets accepted overriding previous TEPs (in that case the
+  new TEP should refer to the one it OBSOLETE-s)
+* the object of the enhancement is no longer in use
+
+### {REJECTED, OBSOLETE}
+
+In one of these states, no further actions are needed.
+
+It is recommended that TEPs in one of these states carry a reason
+describing why they have moved to such a state.
+
+
+What the drivers should do
+--------------------------
+
+The only additional burden of the TEP process falls on the shoulders of its
+drivers. They have to take care of all the practical work of writing
+and maintaining the text, so that everyone else can just continue
+discussing things as before.  Driver's burden can be summarized as:
+
+* Write the draft text and update it during discussion.
+* Determine when (rough) consensus in discussion has been reached.
+* Implement, or find volunteers to implement.
+* Determine when consensus of implementation success has been reached,
+  when the testing of the available implementation has been satisfactory.
+* Update the TEP with progress updates at suitable intervals, until the
+  TEP has been accepted (or rejected).
+
+If the drivers go missing in action, other people may step in and
+courteously take over the driving position.
+
+**Note**: the drivers can of course participate in the discussion as
+everybody else, but have no special authority to impose their ideas to
+others. <q>TEP gives pencils, not hammers.</q>
+
+
+Format and content
+------------------
+
+A TEP is basically a free-form plain text file, except that it must
+start with a paragraph of the following RFC822-style headers:
+
+* Title: the full title of the document
+* TEP: the number for this TEP
+* State: the current state of this revision
+* Date: the date of this revision
+* Drivers: a list of drivers (names and e-mail addresses), in RFC822
+  syntax for the To: header
+* URL: during DRAFT state, a link to the wiki place of the draft
+  (typically probably https://sourceforge.net/p/tauruslib/wiki/TEPxxx)
+* Abstract: a short paragraph describing the TEP
+
+(Additionally, REJECTED TEPs can carry a "Reason:" field describing
+why they were rejected.)
+
+The rest of the file is free form. Since the TEP is kept in a wiki, using
+its markup syntax is, of course, a good idea.
+
+Suggested document contents:
+
+* An introduction, giving an overview of the situation and the motivation
+  for the TEP.
+* A plan for implementation, especially indicating what parts of Taurus need
+  to be changed, and preferably indicating who will do the work.
+* Preferably a list of criteria to judge whether the implementation has been
+  a success.
+* Links to mailing list threads, perhaps highlighting particularly important
+  messages.
+
+
+License
+-------
+
+The TEP must have a license that is DFSG free. You may choose the
+license freely, but the "Expat" license is recommended. The
+official URL for it is <http://www.jclark.com/xml/copying.txt> and
+the license text is:
+
+    Copyright (c) <year>  <your names>
+    
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+    
+    The above copyright notice and this permission notice shall be included
+    in all copies or substantial portions of the Software.
+    
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+The justification for this recommendation is that this license is one
+of the most permissive of the well-known licenses. By using this
+license, it is easy to copy parts of the TEP to other places, such as
+documentation for Taurus development or embedded in individual
+packages.
+
+
+
+Creating a TEP
+--------------
+
+The procedure to create a TEP is simple: send an e-mail to
+`taurus-devel at lists.sourceforge.net`, stating that you're taking the next
+available number, and including the first paragraph of the TEP as
+explained above. It is very important to include the list of drivers,
+and the URL where the draft will be kept up to date. The next available
+TEP number can be obtained by consulting 
+https://sourceforge.net/p/tauruslib/wiki/TEP.
+
+It is also a very good idea to mention in this mail the place where the
+discussion is going to take place, with a pointer to the thread in the
+mailing list archives if it has already started.
+
+The actual place where the TEP draft is going to be published is up to the TEP
+driver (e.g., it can be a plain text file or sphinx file in a code repository)
+but the taurus project provides infrastructure to host it in its wiki for
+convenience. If you decide to host the TEP draft in the taurus wiki, just
+create a new wiki page named https://sourceforge.net/p/tauruslib/wiki/TEPxxx,
+where xxx is the TEP number.
+
+Independently of where the draft is hosted you should edit the list of TEPs in
+https://sourceforge.net/p/tauruslib/wiki/TEP to add a link to the new TEP.
+
+Revising an accepted TEP
+------------------------
+
+If the feature, or whatever, of the TEP needs further changing later,
+the process can start over with the accepted version of the TEP document
+as the initial draft. The new draft will get a new TEP number. Once the
+new TEP is accepted, the old one should move to OBSOLETE state.
+
+As an exception, **trivial** changes may be done in the same TEP without
+requiring a new TEP number as long as:
+
+- the intention to change is communicated by the usual channels, and
+- the change is approved by the community, and
+- the change gets registered in the document (e.g., in a "Changes" 
+section of the document)
+
+**Note:** A *trivial change* here is understood as a *small modification* that 
+*does not alter the intention* of the previous text and simply *corrects* 
+something that is clearly an *unintended* mistake (e.g., fixing a typo, 
+fixing a broken link, fixing a formatting mistake). *Format translations* (e.g. 
+adapting the Markdown formatting to reStructuredText format), can also be considered
+trivial changes. In case of doubt or discrepancies, it is always better
+to opt for the standard procedure of creating a new TEP that obsoletes 
+the current one.
+
+License
+-------
+
+The following copyright statement and license apply to TEP0 (this
+document).
+
+Copyright (c) 2014 Carlos Pascual-Izarra
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Changes
+-------
+
+
+* 2016-11-16:
+  [mrosanes](https://github.com/sagiss/) Adapt TEP format, modify URL and obsolete TEP0 according to TEP16.
+  
+* 2016-04-07:
+  [cpascual](https://sourceforge.net/u/cpascual/) Pass from CANDIDATE to ACCEPTED (it was in candidate for testing its application with several real cases, but its contents have been basically unaltered since its creation)
+
+* 2015-05-13:
+  [cpascual](https://sourceforge.net/u/cpascual/) Fixed date in header. Fixed state (it was outdated as DRAFT, when it should be CANDIDATE)
+
+* 2014-01-27:
+  [tiagocoutinho](https://sourceforge.net/u/tiagocoutinho/) Change main author and copyright
+
+* 2014-01-23:
+  [tiagocoutinho](https://sourceforge.net/u/tiagocoutinho/) Initial version written as a copy of SEP0
diff --git a/doc/source/tep/TEP13.md b/doc/source/tep/TEP13.md
new file mode 100644
index 0000000..82df40c
--- /dev/null
+++ b/doc/source/tep/TEP13.md
@@ -0,0 +1,374 @@
+    Title: plugins support 
+    TEP: 13
+    State: DRAFT
+    Date: 2015-03-25
+    Drivers: Carlos Pascual-Izarra <cpascual at cells.es>
+    URL: http://www.taurus-scada.org/tep?TEP13.md
+    License: http://www.jclark.com/xml/copying.txt
+    Abstract:
+     Implement support for managing third-party code 
+     (plugins) to extends Taurus. Identify, document,
+     unify and systematise the various existing 
+     approaches to plugins already found in Taurus, 
+     and provide conventions and tools for creating more
+     entry points in the future.
+    
+
+IMPORTANT
+=========
+
+This is still in pre-draft stage. For now we are using this this wiki page to collect various preparatory notes in order to write the proper drafts for TEP13
+
+
+Introduction
+============
+
+At this moment, different mechanisms to support extensions are already implemented in various subsystems of Taurus (see APPENDIX IV). Most of these mechanisms, being ad-hoc implementations, are quite specific and present limitations such as requiring the plugin to have privileged access to Taurus installation directories, or not having a well defined interface, or not allowing to define dependencies/incompatibilities among plugins.
+
+This situation can be improved by adopting a generic extension mechanism (e.g., [stevedore][] or [yapsy][]) and using it throughout the whole Taurus library.
+
+The expected benefits are:
+
+- facilitate the maintainability of the code (removing multiple different implementations and APIs)
+- Increase Taurus modularity (since many subpackages that are currently monolithic could be reimplemented as a collection of optional extensions to be loaded on-demand).
+- Sardana would also benefit from an "official" extension API in Taurus (first, by formally registering itself as a taurus extension and second, by using the same API internally for its own plugins.
+
+
+Goals and constraints
+=====================
+
+(To be written)
+
+APPENDIX I: Notes on terms and concepts used
+============================================
+
+Regardless of which solution we end up proposing in the draft, we find that the analysis and naming conventions used in the [stevedore docs][] are quite useful for analising the various aspects to consider of a plugin system.
+
+We will therefore be using concepts such as "Driver", "Hook", "Extension", "Loading pattern", "Enabling pattern", etc. as defined in the [stevedore docs][].
+
+More specifically, you should read the following parts of stevedore docs:
+
+First an overview of what was out there in 2013:
+
+- [discovery](http://docs.openstack.org/developer/stevedore/essays/pycon2013.html#discovery)
+- [enabling](http://docs.openstack.org/developer/stevedore/essays/pycon2013.html#enabling)
+- [importing](http://docs.openstack.org/developer/stevedore/essays/pycon2013.html#importing)
+- [integration](http://docs.openstack.org/developer/stevedore/essays/pycon2013.html#application-plugin-integration)
+- [api-enforcement](http://docs.openstack.org/developer/stevedore/essays/pycon2013.html#api-enforcement)
+- [invocation](http://docs.openstack.org/developer/stevedore/essays/pycon2013.html#invocation)
+
+And then some more formal description of patterns:
+
+- [Loading Pattern](http://docs.openstack.org/developer/stevedore/patterns_loading.html)
+- [Enabling Pattern](http://docs.openstack.org/developer/stevedore/patterns_enabling.html)
+
+APPENDIX II: Notes on basic considerations when evaluating a solution
+=====================================================================
+
+What we look for in a plugin system:
+
+- able to handle all existing and foreseen entry points in Taurus and Sardana
+- not adding dependencies (as yapsy), ... or embedable... or as a worst case, adding light well-known, well supported deps (e.g. setuptools in the case of stevedore)
+- well supported (if it is external) or simple enough to self-support
+- must allow 3rd parties to provide plugins that can be installed, discovered, enabled, etc. without assuming direct coordination between the 3rd party and the sardana/Taurus authors
+
+APPENDIX III: Possible candidates being explored
+=================================================
+
+- [stevedore][]
+- [yapsy][]
+- PCA (pyutilib component architecture) from [pyutilib](https://software.sandia.gov/trac/pyutilib)
+- [Marty Alchin's 6-line plugin framework](http://martyalchin.com/2008/jan/10/simple-plugin-framework/)
+- Custom implementation
+
+APPENDIX IV: List of (potential) plugin systems in Taurus and Sardana
+=====================================================================
+
+The following is a list of Taurus & Sardana subsistems or APIs which are already implemented as plugins or which may benefit from being implemented as plugins.
+
+Each system/API is described according to the following template:
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+<System/API name>
+------------------
+- **Description:** <what does it provide?>
+- **Pointer**: <Path(s)/entry point(s) in current code where it is implemented>
+- **Existing plugins**: <which plugins are already available for this system?>
+- **Foreseen plugins**: <which plugins would we want to be implemented for this system?>
+- **Current discovery method**: <explicit VS scanned? ... File Path VS Py Object Name?>
+- **Current activation method**: <explicit VS installation VS self-enabled>
+- **Proposed Loading Patterns**: <Driver VS Hook VS Extension>
+- **Proposed Enabling Patterns**: <explicit VS installation VS self-enabled>
+- **Priority**: <MoSCoW-style priotization for this system to be adapted to sep13>
+- **Notes**: <anything worth mentioning and not covered above>
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Schemes
+-------
+- **Description**: access abstraction to data sources via data models
+- **Pointer**: taurus.core (taurus factory, helper, etc. Refer to [SEP3](https://sourceforge.net/p/sardana/wiki/SEP3/))
+- **Existing plugins**: Tango, Eval, Epics, Spec, Simulation
+- **Foreseen plugins**: Hdf5, Ascii tables, xls/ods files, SQL, archiving, ...
+- **Current discovery method**: Path based. Uses both scanning and explicit (via tauruscustomsettings.SCHEMES)
+- **Current activation method**: self-enabled (based on availability of 3rd party modules)
+- **Proposed Loading Patterns**: Drivers 
+- **Proposed Enabling Patterns**: installation (maybe with the possibility of explicitly disabling them to save resources?)
+- **Priority**: Should
+
+Codecs
+------
+- **Description:** Codecs for the DEV_ENCODED data type
+- **Pointer**: taurus.core.util.codecs
+- **Existing plugins**: Null, Zip, BZ2, Pickle, Json, VideoImage (Lima),...
+- **Foreseen plugins**: ?
+- **Current discovery method**: scan import reference (functions defined in taurus.core.util.codecs)
+- **Current activation method**: install
+- **Proposed Loading Patterns**: Driver
+- **Proposed Enabling Patterns**: installation
+- **Priority**: Would
+
+widgets
+-------
+- **Description:** adding new widgets, e.g. sardana's TaurusMacroExecutor
+- **Pointer**: subdirs of taurus.qt.qtgui (except those that do not define QWidget-derived classes, such as base, test, ui,...)
+- **Existing plugins**: all submodules of taurus.qt.qtgui which define classes that inherit from QWidget could be considered as plugins of this type. Still some widgets can be considered more "core" than others (e.g. those in the extra_XXXX subdirs are already considered "optional"). Also, sardana already defines several plugins of this type in its sardana.taurus.qt.qtgui module.
+- **Foreseen plugins**: any taurus widget of sufficient general interest that someone may want to share
+- **Current discovery method**: scan import reference 
+- **Current activation method**: installation and in some cases self-enabled (based on availability of 3rd party modules)
+- **Proposed Loading Patterns**: Extension
+- **Proposed Enabling Patterns**: self-enabled
+- **Priority**: Should
+
+external modules
+------------------
+- **Description:** modules that unify APIs from 3rd party modules (e.g. provide a common access to PySide and PyQt, or to unittest across the various supported versions of Python, etc)
+- **Pointer**: taurus.external
+- **Existing plugins**: argparse, enum, ordereddict, pint, qt, unittest
+- **Foreseen plugins**: any dependency that requires backporting for some version of Python supported by Taurus
+- **Current discovery method**: scan import reference
+- **Current activation method**: installation
+- **Proposed Loading Patterns**: Extension
+- **Proposed Enabling Patterns**: installation
+- **Priority**: would
+
+Tango FactoryExtension API
+--------------------------
+- **Description:** API for (un)registering custom classes to be returned by the factory when a Device or Attribute is requested. These are based on the Tango Device Class name and the tango attribute.
+- **Pointer**: TangoFactory.{un,}register{Device,Attribute}Class methods (note, previous to SEP3, this was implemented at the TaurusFactory level, but it is moved to tango scheme folder by SEP3). 
+- **Existing plugins**: 
+    - those registered by spock: SpockMacroServer, QSpockDoor, SpockDoor
+    - those registered by
+      sardana.taurus.core.tango.sardana.macroserver.registerExtensions():  
+      MacroServer, Door,...
+    - those registered by sardana.taurus.core.tango.sardana.pool.registerExtensions():
+      Pool, Controller, ComChannel, Motor,...
+    - those registered by taurus.core.tango.img.registerExtensions(): PyImageViewer,
+      ImgGrabber, Falcon,...
+- **Foreseen plugins**: ?
+- **Current discovery method**: explicit import reference
+- **Current activation method**: self-enabled
+- **Proposed Loading Patterns**: Driver
+- **Proposed Enabling Patterns**: explicit
+- **Priority**: could
+- **Notes**: the {un,}registerAttributeClass methods are implemented but they do not seem to be used at all yet (only the {un,}registerDeviceClass are)
+
+The TaurusModelChooser widget (support for non-tango schemes)
+-------------------------------------------------------------
+- **Description:** The ModelChooser uses a TaurusDbTreeWidget which is, 
+  nowadays, Tango-centric. Other schemes may define alternative 
+  widgets to interact with their models (e.g. the eval scheme may provide
+  an eval attribute editor)
+- **Pointer**: .../qtgui/panel/taurusmodelchooser.py (see also
+  taurus.qt.qtgui.panel.QRawDataWidget which is used in plots together with
+  TaurusModelChooser)
+- **Existing plugins**: TaurusDbTreeWidget (and its associated QModel), 
+- **Foreseen plugins**: Evaluation Attribute editor
+- **Current discovery method**: N/A
+- **Current activation method**: N/A
+- **Proposed Loading Patterns**: Extension
+- **Proposed Enabling Patterns**: installation (with scheme)
+- **Priority**: Could
+- **Notes**: Apart from allowing certain schemes to provide an extension for 
+  the ModelChooser, it would be wise to implement a scheme-agnostic Tree View Widget
+
+Icons
+-------
+- **Description**: adding icons for the standard Taurus icon catalog
+- **Pointer**: implemented in taurus.qt.qtgui.resource. Also "build_resources" command from setup.py
+- **Existing plugins**: tango-icons, rrze-icons, external, extra-icons, large
+- **Foreseen plugins**: icon folders added by other plugins (e.g. sardana-icons)
+- **Current discovery method**: Path based. Scanning (at install time) the subdirs of...gtgui/resource/ to generate qrc and rcc files that are later used used by the methods in taurus.qt.qtgui.resource.taurus_resource_utils
+- **Current activation method**: install
+- **Proposed Loading Patterns**: Extension 
+- **Proposed Enabling Patterns**: installation
+- **Priority**: Could
+- **Notes**: There have been informal talks about implementing direct load of icons instead of using qrc/rcc files. This may affect the design of this plugin system.
+
+Loggers
+-------
+- **Description**: Set the Taurus logger system (multiples loggers could be registered). 
+- **Pointer**:  taurus.core.util.log, taurus.core, taurus.qt (refer to [TEP8](http://www.taurus-scada.org/tep?TEP8.md))
+- **Existing plugins**:  Taurus logger only
+- **Foreseen plugins**: loggers from external applications using Taurus, custom loggers, ...
+- **Current discovery method**: Not discovered. Hardcoded in  taurus.core.util.log
+- **Current activation method**: Not activated. TEP8 implementation proposes explicit mechanism, via tauruscustomsetting variable (ENABLE_TAURUS_LOGGER)
+- **Proposed Loading Patterns**: Hook 
+- **Proposed Enabling Patterns**: Explicit
+- **Priority**: Could
+- **Notes**:  The Taurus models use the Factory pattern, which creates Mementos for logged events, so the use of a Logger is a requirement.
+
+TestSuite
+-------
+- **Description:** The unit test infrastructure (Implemented in [SEP5](https://sourceforge.net/p/sardana/wiki/SEP5/)). New tests for features or modules can be added. Particular tests may be (de)activated dynamically.
+- **Pointer**:  taurus.test and test submodules of taurus modules.
+- **Existing plugins**: all "test" submodules taurus modules
+- **Foreseen plugins**: New tests.
+- **Current discovery method**: unittest discovery (started from test suite in taurus.test)
+- **Current activation method**: installation, explicit defined via variable in taurus custom settings, and in some cases self-enabled (based on availability of resources, or execution conditions. e.g.  Taurus Qt tests (widgets) are not executed if the X is not exported.
+- **Proposed Loading Patterns**: Extension
+- **Proposed Enabling Patterns**: self-enabled (e.g. based on the activation of other plugins or availability of resources), explicit (white/black lists could be used to deactivate certain tests).
+- **Priority**: Could
+- **Notes**:  The tests could be grouped by feature/module. e.g. taurus.core.test, taurus.core.tango, taurus.qt.qtgui.panel, etc. (maybe using particular test suites)
+
+Macros
+------------------
+- **Description:** adding new macros to the MacroServer 
+- **Pointer**: Paths set in MacroPath property of the MacroServer Device. (Class MacroManager: msmacromanager.py )
+- **Existing plugins**: specific macros
+- **Foreseen plugins**: all macros which are not provided by Sardana
+- **Current discovery method**: Path based. Scanning the directories listed in the MacroPath property of the MacroServer
+- **Current activation method**: Explicit (when the macro is executed).
+- **Proposed Loading Patterns**: Drivers
+- **Proposed Enabling Patterns**: Explicit
+- **Priority**: Should
+- **Notes**: 
+
+
+Controllers
+------------------
+- **Description:** adding new controller libraries to the Pool.  
+- **Pointer**: Paths set in PoolPath property of the Pool Device  (Class ControllerManager: poolcontrollermanager.py)
+- **Existing plugins**: specific controllers which are not distributed by default. 
+- **Foreseen plugins**: all controllers which are not provided by Sardana.
+- **Current discovery method**: Path based. Scanning the directories listed in the PoolPath property of the PoolDevice
+- **Current activation method**: Explicit (when an instance is created)
+- **Proposed Loading Patterns**: Drivers
+- **Proposed Enabling Patterns**: Explicit
+- **Priority**: Should
+- **Notes**: 
+
+
+Recorders
+------------------
+- **Description:** adding new recorders to the MacroServer.  
+- **Pointer**: Paths set in RecorderPath property of the MacroServer Device (**TODO**: point to relevant modules involved in the implementation)
+- **Existing plugins**: Spec, Json, NXscan, NXsax, FIO, output, SharedMemory, ... 
+- **Foreseen plugins**: Other specific recorders which are not provided by default. 
+- **Current discovery method**: Path based. Scanning the folder (no the subdirs)
+- **Current activation method**: Explicit (The output recorder is active by default)
+- **Proposed Loading Patterns**: Extension 
+- **Proposed Enabling Patterns**: Explicit
+- **Priority**: Should
+- **Notes**:  More information ticket: https://sourceforge.net/p/sardana/tickets/380/
+
+
+
+
+
+(APIs/Sytems to be expanded with the above template)
+----------------------------------------------------
+- <s>schemes (adding new taurus.core schemes)</s>
+- <s>codecs (extend taurus.core.util.codecs)</s>
+- <s>widgets (adding new widgets, e.g. sardana's TaurusMacroExecutor)</s>
+- <s>external modules (unifying interfaces e.g. taurus.external.unittest, or taurus.external.qt)</s>
+- <s>Tango FactoryExtension API (API of TaurusFactory, used e.g. in sardana.taurus.core.tango.sardana.pool)</s>
+- Extendable Taurus widgets: some Taurus widgets may provide entry
+  points to be extended:
+    - <s>The ModelBrowser widget (each scheme may provide an extension to support
+       browsing/selection of its models)</s>
+    - The entries in the panel catalog (when selecting "new panel" in a TaurusGUI)
+    - The pages of the new-gui wizard (e.g. the sardana-related page should be 
+      provided by sardana)
+    - TaurusForm's Custom Widget Map (see tauruscustomsettings.T_FORM_CUSTOM_WIDGET_MAP)
+    - Choice of alternative subwidgets in TaurusValue (see mechanism from 
+      TaurusValue.getDefaultXXXXWidgetClass())
+    - ...
+- <s>Icons (adding icons for the standard Taurus icon catalog)</s>
+- EventFilters (extend taurus.core.util.eventfilter)
+- <s>Loggers (consider this in relation to [SEP8](https://sourceforge.net/p/sardana/wiki/SEP8/))</s>
+- <s>testsuite (may be interesting for enabling/disabling certain tests in the testsuite... </s>
+  (e.g for substituting the lib.taurus.test.skip mechanism)
+- Qt Designer Pluggins (see https://sourceforge.net/p/tauruslib/tickets/144/)
+- Synoptic extensions (e.g., jdraw allows to declare custom extensions...)
+- Plot mechanisms (maybe too ambitious... but plugins could be used to provide the 
+  plotting... like PyMca5 does when abstracting access to switch from pyqtgraph and
+  matplotlib backends)
+- The mechanism for loading/installing specific TaurusGUI (using `taurusgui <modulename>`)
+  could based on plugins (maybe only if the plugin system is coupled with the installation
+  system as in stevedore)
+- widgets backend (e.g. taurus.qt VS taurus.web VS taurus.gtk,...) 
+  Ideally, we could define a set of basic Taurus building blocks such as TaurusForm, 
+  Taurus Label, TaurusPlot, TaurusGui,...etc and make them "back-end agnostic" so 
+  that  a Taurus GUI app could be defined transparently and be ported to any 
+  of those back-ends. This sounds nice but it means a huge refactoring and may meet 
+  many practical issues, so for the moment it is not to be considered.
+
+Sardana:
+
+- <s>macros (discovering 3rd party macros)</s>
+- <s>controllers (discovering 3rd party controllers)</s>
+- <s>recorders (registering recorders. see gscan's  of DataHandler.addRecorder() )</s>
+- custom macro executor widgets (see 
+  sardana.taurus.qt.qtgui.extra_macroexecutor.macroparameterseditor.customeditors)
+- hook API for macros? (maybe it makes sense defining the hookpoints of macros as 
+  internal entry points)
+- macro execution clients (does it make sense to define an entry point for the 
+  macro execution clients (e.g. spock vs TaurusMacroExecutor))
+- spock support for diffferent versions of ipython (i.e. the code forks in the 
+  genutils submodule).
+- spock's InputHandler support/selection (see sardanacustomsettings.SPOCK_INPUT_HANDLER)
+
+
+
+
+Links to more details and discussions
+=====================================
+
+The main discussions for this SEP take place in the [sardana-devel mailing list](https://sourceforge.net/p/sardana/mailman/).
+
+This SEP uses concepts and nomenclature from the [stevedore docs](http://docs.openstack.org/developer/stevedore/index.html)
+
+License
+=======
+
+Copyright (c) 2015  Carlos Pascual-Izarra
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Changes
+=======
+* 2015-10-15: [cpascual](https://sourceforge.net/u/cpascual/) Moved pre-draft notes from [sardana:wiki:SEP13]
+* 2015-01-27: [cpascual](https://sourceforge.net/u/cpascual/) 
+  Initial pre-draft notes collection started
+* 2016-11-16: [mrosanes](https://github.com/sagiss/) Adapt TEP format and URL according TEP16
+  
+[stevedore docs]: http://docs.openstack.org/developer/stevedore/index.html
+[stevedore]: https://pypi.python.org/pypi/stevedore
+[yapsy]: https://pypi.python.org/pypi/Yapsy
diff --git a/doc/source/tep/TEP14.md b/doc/source/tep/TEP14.md
new file mode 100644
index 0000000..918aa67
--- /dev/null
+++ b/doc/source/tep/TEP14.md
@@ -0,0 +1,232 @@
+    Title: core refactoring (quantities and configuration)
+    TEP: 14
+    State: ACCEPTED
+    Date: 2016-03-16
+    Drivers: Carlos Pascual-Izarra <cpascual at cells.es>, Carlos Falcon-Torres <cfalcon at cells.es>
+    URL: http://www.taurus-scada.org/tep?TEP14.md
+    License: http://www.jclark.com/xml/copying.txt
+    Abstract:
+     This proposal picks the tango.core refactoring from TEP3 and takes it a 
+     step further. While TEP3 eliminates the PyTango module dependencies, 
+     this TEP aims to get rid some of the most evident "tangoisms" still left 
+     in the code by TEP3. Among other things it implements support for 
+     Quantities and eliminates the attribute configuration classes 
+
+
+
+Introduction & motivation
+=========================
+
+This TEP is a spin-off of [TEP3][]. It proposes a refactoring of taurus.core to get rid of some tango-centric implementation details that were left by [TEP3][]. The objective is to obtain a cleaner and more scheme-agnostic API for creation of new schemes. Several inter-related aspects of the taurus core are refactored:
+
+- the  xxxConfiguration classes are merged into xxxAttribute
+- The AttributeValue members and their types are more strictly defined and unified among schemes (e.g. physical values are expected to be Quantity objects)
+- Several tango-centric enumerations are substituted by scheme-agnostic ones 
+- A new API for  "model fragments" replaces the "configuration" API, extending its scope
+- A new API for deprecation messages is implemented
+
+An explicit objective of this TEP is to **provide as much backwards-compatibility as possible**. Some of the changes unavoidably break the backwards compatibility, but, whenever it is possible, a compatibility API is left in place which uses the new deprecation mechanisms to inform the developers of the new recommended alternative.
+
+
+Merge of xxxConfiguration into xxxAttribute
+============================================
+
+One of the main changes of this TEP involves the removal of the xxxConfiguration,
+and xxxConfigurationValue classes, and their role being taken by xxxAttribute.
+
+The resulting class relationships is then simplified, as seen in the following figure:
+
+![Attr-Conf merge figure](res/tep14_merge_Attr_and_Conf.png)
+
+From the point of view of application code (e.g. widgets), this implies that:
+
+- where a xxxConfiguration was used/received, a xxxAttribute will be used/received instead. This xxxAttribute now provides the configuration API too.
+- The Value associated to Configuration events will now be a xxxAttrValue. In the case of Tango, the TangoAttrValue provides backwards-compatibility (though deprecated) by allowing transparent 
+access to the configuration API implemented in the TangoAttribute
+
+Scheme-agnostic API for xxxAttribute and xxxAttributeValue
+==============================
+
+Previous to this proposal, the API for xxxAttribute and xxxAttributeValue (as well the configuration API) was not defined, and the tango scheme was used as a loose reference by other schemes. 
+
+This proposal defines, via the TaurusAttributeValue and TaurusAttribute classes, a base API for all schemes to use, so that it can be relied upon when writing scheme-agnostic code.
+
+
+Allowed data types
+-------------------------
+
+Apart of defining standard members for common concepts (e.g. "range" for the `min,max` tuple in which a value can vary), the types allowed for these members are also restricted by this TEP:
+
+  -  all members of xxxAttribute and xxxAttributeValue representing physical magnitudes  must use Quantity objects (from the `taurus.external.pint` module). Note that Quantity objects allow for both scalar and non-scalar magnitudes to be represented. See table below.
+  - members representing strings, booleans or bytes, must use the standard python types (including numpy types). See table below
+  - members containg enumerations should use Enum or IntEnum `from taurus.external.enum` (preferred) or Enumeration from (`taurus.core.util.enumeration`)
+  - as a general rule for other cases, only standard python types (inc. numpy, Quantitys and Enum) or taurus-defined scheme-agnostic types should be used by members of the common API
+
+Schemes must use encode/decode methods to ensure that the common API is respected even if the scheme internally uses scheme-specific types. Of course, it is allowed for a scheme to extend the API with its own scheme-specific members which would not be subject to the rules defined here, but it should be kept in mind that those extensions will not be usable by scheme-agnostic code.
+
+The following table summarizes the allowed types for the read/write values, limits, etc. depending on the type and format of the attribute:
+
+DataType           | _0D         | _1D                   | _2D
+------------------- | ------------ | ------------------- | ---------------------- 
+Boolean            | bool         | `ndarray<bool>` | `ndarray<bool>` 
+Integer, Float    | Quantity  | Quantity            | Quantity
+String                | str           | seq<str>          | `seq<seq<str>>`
+Bytes                 | bytes      |                          |
+
+Some important remarks:
+
+- units is no longer a common member of the TaurusAttribute. Each each physical value, being represented as a Quantity object, has its own units (e.g. the rvalue, wvalue or the range values units are not necessarily identical, although they should be compatible among them)
+- The use of Quantity objects blurs the difference between integer and float numbers because unit transformations may transform a quantity whose magnitude initially was an integer into a "float"-based quantity. But this is not essentially different from the implicit type conversion between int and float objects.
+- while numpy arrays are the only allowed types for representing non-scalar booleans (and the same for integers and float within Quantities), we do **not** require numpy arrays for arrays of strings because `numpy.ndarray` imposes fixed lengths of its items. Instead, for strings we recomend using (nested) lists of `str`.
+
+Agnostic configuration attributes
+-----------------------------------------
+
+As explained before, the information previously available via the xxxConfiguration API is now part of the xxxAttribute scheme-agnostic API. However in order to avoid using tango-centric enumerations/concepts the new API relies on newly defined enumerations and/or standard python objects for some information that was previously encoded with tango-centric types:
+
+  - Instead of using `PyTango.AttrDataFormat`, the dimensionality of a given attribute is now described by the `taurus.DataFormat` enumeration, which currently defines `_0D`, `_1D`, and `_2D`, corresponding to the scalars, spectrum and image types of Tango, respectively. More formats may eventually be added if support for them is introduced in taurus.
+  - Instead of using `PyTango.CmdArgType`, the data type of a given attribute is now described by the `taurus.DataType` enumeration, which currently defines  `Boolean`, `Integer`, `Float`, `String` and `Bytes` types (plus some other deprecated types as well as a type for opaque objects). A map of Tango to Taurus data types can be found in `taurus.core.tango.util.tango_taurus.FROM_TANGO_TO_TAURUS_TYPE`
+  - The quality of a given xxxAttribute value is now described by the `taurus.AttrQuality` enumeration instead of the `PyTango.AttrQuality`
+  - The error status is now described by either a None (for no errors) or a python Exception object in case of errors.
+  - The access mode for an attribute (previously described with `PyTango.AttrWriteType`)  is now described by a boolean stored in the `writable` attribute/property of a xxxAttribute.
+  - The timestamp is now stored as a TaurusTimeVal.
+
+
+Scheme-agnostic API for xxxDevice
+===========================
+
+Just as for xxxAttribute, this proposal defines, via the TaurusDevice class, a base API for all schemes to use, so that it can be relied upon when writing scheme-agnostic code.
+
+Previous to this proposal the xxxDevice objects were heavily influenced by the TangoDevice implementation. In particular the concept of a "device state" was linked to the tango-centric assumption that all xxxDevices had at least one associated xxxAttribute called "state", which reflected the device's state using the `PyTango.DevState`. Also, other tango-influenced enumerations were used by Taurus in relation to the state of the connection state/health of a device (`TaurusDevSWState`,   ` [...]
+
+This TEP avoids those tango-specific assumptions and defines a simpler (albeit less informative) API for dealing with the device state: now "state" is an attribute/property of the xxxDevice which uses the `taurus.TaurusDevState` enumeration to describe the device state in a scheme-agnostic way.
+
+Note that each scheme is still alowed to provide its own specific API to handle richer state information but, of course, it will not be used by scheme-agnostic code (e.g., in a tango-specific context one could still get PyTango.DevState info by using `TangoDevice.stateObj.read()`).
+
+Note that this change is one of the possible sources of unavoidable backwards incompatibilities introduced by this TEP.
+
+
+Scheme-agnostic API for xxxAuthority
+============================
+
+The concept of "Authority" models type was already introduced in [TEP3][] to generalise the tango-centric "Database". This TEP defines a base API for xxxAuthority via the TaurusAuthority base class. This is the api that scheme-agnostic code can use for dealing with Authority models. 
+
+
+Interpretation of fragments in model names
+==========================
+
+Since [TEP3][], the concept of "URI fragments" was introduced in the model names (a model name may contain a "#" symbol followed by a fragment name). In [TEP3][] only a limited use was done of this syntax. This proposal generalises it: 
+
+- The model name (i.e., without the fragment name) is an identifier for a model object
+- The fragment name is an identifier of a member of the model object. 
+
+For example, "tango:a/b/c/d" refers to the TangoAttribute object (which is a subclass of TaurusModel) representing the "a/b/c/d" Tango attribute, and  "tango:a/b/c/d#label" refers to the object's "label" attribute.
+
+It is important to note that **the  fragment name is not a part of the model name**. In the Model-View-Controller pattern used in Taurus, the fragment name is only known by the view/controller parts, but not by the model.
+
+Certain fragment names may be reserved in some or all model types to provide a common scheme-agnostic API (e.g., widgets will expect that suffixing "#label" to any attribute name will be interpreted as a reference to the "label" of the attribute).
+
+Other fragment names may be used to access scheme-specific members of the given model object. For example, this will allow TaurusCommand button (which is for now actually a Tango-specific widget) to refer to methods of the TangoDevice object which execute Tango commands (it is easy to imagine that this could open the door to supporting the concept of "Taurus commands" in general).
+
+Currently, the list of reserved fragment names corresponds to the public properties/attributes of the base model classes (TaurusAttribute, TaurusDevice, TaurusFactory):
+
+- For all models
+    - name 
+    - fullname 
+    - description
+- For TaurusAttribute
+    - label
+    - writable
+    - data_format
+    - type
+    - range
+    - warnings 
+    - alarms
+    - rvalue (uses the last value)
+    - wvalue (uses the last value)
+    - time (uses the last value)
+    - quality (uses the last value)
+- For TaurusDevice
+    - state
+- For TaurusAuthority
+
+Finally, note that dot-syntax is supported by the fragment mechanism: e.g. on can refer to the units of the read value of the "a/b/c/d" tango attribute using "tango:a/b/c/d#rvalue.units"
+
+
+Summary of API changes
+===============
+
+The deprecations introduced by this proposal can be identified in the code by the use of the `@tep14_deprecation` decorator and the calls to the `deprecated` function with the `rel='tep14'` argument.
+
+The [Taurus4-API_changes](https://sourceforge.net/p/tauruslib/wiki/Taurus4-API_changes/) document is an attempt to explicitly list all the changes (it is not considered part of this TEP because it is likely to be modified to include more comments or missing changes)
+
+
+Links to more details and discussions
+======================================
+
+The proposed implementation was done for the most part internally at the ALBA synchrotron and therefore most of the discussion took place either offline or with internal mechanisms for which unfortunately no external access is available.
+
+Some discussions/announcements about TEP14 were held on the tauruslib-devel and taurus-user mailing list:
+
+- https://sourceforge.net/p/tauruslib/taurus-devel/message/34641275/
+- https://sourceforge.net/p/tauruslib/taurus-devel/message/34643400/
+
+
+Implementation and Follow on
+===================
+
+A proposal of implementation was pushed to the `taurus4-preview` branch at the official repository.
+
+The mentioned branch also includes changes belonging to [TEP3][], since that is the starting implementation point for TEP14. It also includes the minimal necessary changes in Taurus widgets in order for them to work with the changes of the core due to TEP14 (note that these changes in widgets are technically out of the scope of TEP14, but they are convenient in order to properly evaluate the implementation proposal).
+
+Apart of the minimal changes just mentioned, the taurus widgets require further changes in order to take advantage of the new features provided by this TEP (e.g., plots currently use only the magnitude information from their model values, when they should use the units info as well). Another TEP (or feature-request-driven development) should be started for that.
+
+
+License
+==================
+
+The following copyright statement and license apply to SEP3 (this
+document).
+
+Copyright (c) 2015 CELLS / ALBA Synchrotron, Bellaterra, Spain
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+Changes
+========
+
+
+2015-04-28
+[cpascual](http://sf.net/u/cpascual/) First draft based on parts moved from [TEP3][] and previous discussions with [tiagocoutinho](http://sf.net/u/tiagocoutinho/), [cfalcon](https://sf.net/u/cmft/) and [zreszela](http://sf.net/u/zreszela/)
+
+2015-11-23
+[cpascual](http://sf.net/u/cpascual/) Implementation proposal uploaded to taurus4-preview branch of official repo
+
+2016-03-11
+[cpascual](http://sf.net/u/cpascual/) Completed missing sections, rewritted existing ones and passed to CANDIDATE
+
+2016-03-16
+[cpascual](http://sf.net/u/cpascual/) Passing to  ACCEPTED
+
+2016-11-16
+[mrosanes](https://github.com/sagiss/) Adapt TEP format and URL according TEP16
+
+[TEP3]: http://www.taurus-scada.org/tep?TEP3.md
diff --git a/doc/source/tep/TEP15.md b/doc/source/tep/TEP15.md
new file mode 100644
index 0000000..4d92c24
--- /dev/null
+++ b/doc/source/tep/TEP15.md
@@ -0,0 +1,114 @@
+    Title: fragment-based slicing support in URIs
+    TEP: 15
+    State: DRAFT
+    Date: 2016-06-16
+    Drivers: Carlos Pascual-Izarra <cpascual at cells.es>
+    URL: http://www.taurus-scada.org/tep?TEP15.md
+    License: http://www.jclark.com/xml/copying.txt
+    Abstract:
+     This proposal aims at defining a way for Taurus URIs to support data slicing.
+
+
+
+Introduction & motivation
+=========================
+
+A Taurus user may use a URI to identify a source of data which, when read is resolved into an iterable object. A common request by Taurus users is to be able to use URI fragments to refer to a particular index (or slice) of such iterable data values.
+
+Note that particular schemes may already provide access to slices of data (i.e., a reference to an object that already is a slice of an iterable).  The particulars of how a given scheme supports that are out of the scope of this TEP. This proposal is only concerned with the use of fragments for referencing slices when the scheme provides an iterable object. 
+
+For example, at this moment, if we define the following URI: `uri = "tango:sys/tg_test/1/wave#rvalue"`and we run `v = taurus.Attribute(uri).read()`, the object `v` would be a Quantity object with shape (256,). The objective of this proposal is to define a way to reference a certain slice of `v` via a fragment.
+
+Considered alternatives
+======================
+
+Consider this example:
+
+```
+v = taurus.Attribute("eval:arange(5)").read()
+```
+
+We will now discuss different ways of referring to, e.g. `v[0:2]`
+
+(note that we leave out the possibility of demanding it to the scheme itself as in `eval:arange(5)[0:2]`)
+
+Using  brackets (python slices notation)
+------------------------------------------------------
+One possibility is to use `"eval:arange(5)#[0:2]"`, or  `"eval:arange(5)#rvalue[0:2]"`
+
+This seems the most natural API given the current support for fragments: just as  `"eval:arange(5)#rvalue.magnitude` returns the magnitude of the rvalue, the slice notation would return the slice.
+
+The main drawback is that square brackets are **not** allowed by [RFC3986][] into the fragment (or the path) of an URI unless they are percent-encoded.
+
+Note that this problem also affects to the currently allowed use of reserved characters in the path of the `eval` scheme. 
+
+Use a explicit slice function
+-----------------------------------------
+In the process of retrieving the fragment object from a model object, we could have a reserved name for slices (e.g. `_slice(...)`) that would return `v[slice(...)]`:
+
+ `"eval:arange(5)#_slice(0,2)"`
+ `"eval:arange(5)#rvalue._slice(0,2)"`
+
+Note that the arguments to `_slice` would be the same as for python's `slice()` class constructor.
+
+This does overcome the problem of using square brackets, but feels too verbose and much less natural.
+
+Use of `@(...)`
+-------------------
+
+Use the python slice notation but with parenthesis instead of square brackets and using some allowed character for conveying this special meaning (e.g., "@", "!", "?" or "&"). Just for this example I assume that the choice is "@":
+
+`"eval:arange(5)#@(0:2)"`
+ `"eval:arange(5)#rvalue@(0:2)"`
+ 
+The use of parenthesis allows us to delimit  the start and end of the slice so that nested fragments could still be used, e.g.:
+ 
+ `"eval:arange(5)#rvalue@(0:2).magnitude"`
+
+The use of the special character is desirable to avoid ambiguities if we ever support method calling in fragments (not yet supported but foreseen).
+
+This overcomes the isssue of the use of a reserved square bracket and is reasonably concise, but it certainly seems less natural than the brackets.
+
+
+License
+==================
+
+The following copyright statement and license apply to SEP3 (this
+document).
+
+Copyright (c) 2015 CELLS / ALBA Synchrotron, Bellaterra, Spain
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+Changes
+========
+
+
+2016-06-16
+[cpascual][] First draft triggered by a [discussion about URIs][1] in the tauruslib-devel mailing list.
+
+2016-11-16:
+[mrosanes](https://github.com/sagiss/) Adapt TEP format and URL according TEP16
+
+[cpascual]: http://sf.net/u/cpascual/
+[RFC3986]: https://tools.ietf.org/html/rfc3986
+[1]: https://sourceforge.net/p/tauruslib/taurus-devel/message/35184319/
+
diff --git a/doc/source/tep/TEP16.md b/doc/source/tep/TEP16.md
new file mode 100644
index 0000000..c254b28
--- /dev/null
+++ b/doc/source/tep/TEP16.md
@@ -0,0 +1,201 @@
+    Title: Moving Taurus to GitHub
+    TEP: 16
+    State: ACCEPTED
+    Date: 2016-11-17
+    Drivers: Carlos Pascual-Izarra cpascual at cells.es
+    URL: http://www.taurus-scada.org/tep?TEP16.md
+    License: http://www.jclark.com/xml/copying.txt
+    Abstract: 
+     Move Taurus project from its current hosting in SourceForge to 
+     GitHub. The move affects the code repository, the ticket tracker and the 
+     wiki pages. It also proposes to change the contribution and the TEP 
+     workflow to make use of the Pull Request feature.
+ 
+## Introduction
+
+This TEP proposes the migration of the taurus project from its
+current hosting in SourceForge (SF) to the GitHub (GH) service, and to change 
+the contribution workflow (defined in TEP7) to one based on Pull Requests (PR).
+
+The following reasons are considered in favour of migrating to GH:
+
+- It would alleviate the current saturation of the -devel mailing list 
+  (since the code review would be done via PR)
+- A PR-based workflow for contributions is preferred by all the integrators and
+  most of the current contributors and is expected to attract more new 
+  contributors
+- It would enable the use of Travis for *public* Continuous Integration and 
+  Deployment
+- GH is perceived as more user friendly than SF
+- GH is perceived as providing more visibility than SF
+- Tango (with which we share a lot of developers and users) is currently doing
+  a similar transition.
+- Most developers already have an account in GH
+
+
+The following reasons were considered against migrating to GH:
+
+- GH is a closed-source product (which may raise ethic concerns and increase 
+  the risk of lock-in). Gitlab would be preferred in this particular aspect.
+
+
+## Relationship with other Enhancement Proposals
+
+This TEP obsoletes totally or partially some previous Enhancement 
+Proposals (EP), as summarized here:
+
+- TEP7: most of the contribution procedure is no longer applicable due 
+  to the adoption of PR-based workflow.
+- TEP0: `https://sourceforge.net/p/tauruslib/wiki/TEP` is no longer the 
+  index for TEPs, nor it is a wiki. The "Creating a TEP section" of TEP0 
+  is superseded by the one with the same name in this TEP 
+- TEP10 / SEP10:references to SF
+- SEP1: references to SF
+  
+A similar Enhancement proposal should be done to the Sardana Community, 
+once the implementation is tested for this one.
+
+## Goals
+
+The goals are roughly described in order of priority:
+
+1. create a taurus repo within a Taurus GH organization
+2. define the new contribution policy
+3. define the policy for bug reports / feature requests
+4. migrate SF tickets to GH Issues
+5. move the TEP pages to a service-independent URL
+6. define what to do with the mailing lists
+
+## Implementation
+
+The implementation steps to accomplish each of the goals are listed 
+below:
+
+### New taurus repo within a Taurus GH organization
+
+- A GH organization called `taurus-org` will be created (the `taurus`
+  name is already taken in GH)
+
+- A `taurus` project will be created within `taurus-org` and the current `master`,
+  `develop` and `support-3.x` branches pushed to it
+
+- The Travis and Appveyor continuous integration services will be enabled 
+  for this repo.
+
+### New contribution policy
+
+- The new contribution policy will be detailed in the `CONTRIBUTING.md` file 
+  at the root of the repository. It should be based on Pull Requests 
+  instead of the current email-based policy described in TEP7.
+
+### New policy for bug reports and feature requests
+
+- Bugs and feature requests will be reported via [GitHub Issues][] instead of
+  using SF tickets.
+
+### Migration of SF tickets to GH Issues
+
+- Existing tickets in the ticket tracker for the tauruslib project in SF 
+  will be migrated using the same tools and procedure described for 
+  migrating the tickets of the Tango projects. To this purpose, some tools
+  from https://github.com/taurus-org/svn2git-migration will be used.
+
+- The SF ticket tracker will be locked to prevent further ticket 
+  creation, and its SF tool menu entry will be renamed to "Old Tickets". A 
+  new SF tool menu entry called "Tickets" will be added pointing to 
+  the new taurus GH issues URL
+  
+- Prominent notices will be added in the SF ticket tracker indicating that
+  the new GH tracker shouold be used instead.
+
+### New policy for TEPs
+
+- All TEPs will be stored as files in `<new_tep_location>` in the repository. 
+  We propose `<new_tep_location>` to be defined as `doc/source/tep`.
+
+- To start a new TEP, the TEP driver submits a PR containing, at least, one
+  file called `<new_tep_location>/TEPX[.md|.txt|.rst|...]`, where X is the 
+  TEP number and the extension depends on the the markup language used 
+  (as of today, we recommend `.md`). 
+  
+- The discussion for this new TEP should take place using the comments and
+  similar tools within the PR itself. 
+  
+- If the TEP includes some proposal of implementation in the form of code, 
+  the changes should be committed as part of the same PR (and reviewed with it).
+  
+- If the TEP reaches the ACCEPTED stage, the PR is merged (which, at the 
+  same time, will bring the source code implementation changes, if any). 
+  If the TEP is REJECTED, the integrator will issue a commit in the PR 
+  reverting any implementation changes (if any) and then he/she will 
+  merge the PR so that the whole discussion history is not lost. 
+
+### Migration of existing TEP pages and index
+
+- A file called `<new_tep_location>/TEPX.md` will be created for each 
+  existing TEP (X being the TEP  number).
+
+- A file called `index.md` will be created in `<new_tep_location>`, 
+  containing the info currently in `https://sourceforge.net/p/tauruslib/wiki/TEP`. 
+  The provisions in TEP0 for that page now apply to `index.md` (i.e., 
+  **TEP drivers are required to update the status of their TEP in this 
+  page**).
+
+- Service-provider independent URLs will be configured to redirect to the new 
+  locations of the TEPs and the index.
+  This service-agnostic URLs should be used instead of the SF or GH specific location
+  in all documentation from now on (this allows us to change the location in the 
+  future without altering the docs). In the proposed implementation, these URLs are:
+  
+  - `https://www.taurus-scada.org/tep/` (for the TEP index)
+  - `https://www.taurus-scada.org/tep/?<TEP_FILE_NAME>` (e.g., for TEP16, this agnostic 
+    URL would be https://www.taurus-scada.org/tep/?TEP16.md)
+
+- The wiki pages currently served under `https://sourceforge.net/p/tauruslib/wiki/TEP/`
+  should redirect to the new location
+
+### Mailing lists
+
+GH does not provide mailing list hosting. For now, continue using 
+the existing mailing lists provided by SF. 
+
+## Links to more details and discussions
+
+Discussions for this TEP are conducted in its associated Pull Request:
+https://github.com/taurus-org/taurus/pull/1
+
+
+## License
+
+Copyright (c) 2016 Carlos Pascual-Izarra
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+## Changes
+
+- 2016-11-17 [cpascual][]. Changed state to ACCEPTED
+- 2016-11-15 [cpascual][]. Reflect the latest implementation decisions
+- 2016-11-02 [cpascual][]. Changed state from DRAFT to CANDIDATE
+- 2016-10-26 [cpascual][]. Several changes to get it ready for first announcement of DRAFT
+- 2016-10-21 [cpascual][]. Initial version
+
+
+[GitHub Issues]: https://guides.github.com/features/issues/
+[cpascual]: https://github.com/cpascual
diff --git a/doc/source/tep/TEP3.md b/doc/source/tep/TEP3.md
new file mode 100644
index 0000000..83054ad
--- /dev/null
+++ b/doc/source/tep/TEP3.md
@@ -0,0 +1,414 @@
+    Title: Tango-Independent
+    TEP: 3
+    State: ACCEPTED
+    Date: 2016-03-16
+    Drivers: Carlos Falcon-Torres <cfalcon at cells.es>, Carlos Pascual-Izarra <cpascual at cells.es>
+    URL: http://www.taurus-scada.org/tep/?TEP3.md
+    License: http://www.jclark.com/xml/copying.txt
+    Abstract:
+     The goal of this TEP is to refactor the Taurus core to make the Tango 
+     dependency optional instead of mandatory. The idea is to have a generic
+     core that accepts any scheme without forcing PyTango. 
+
+
+Introduction
+==================
+
+This TEP describes an enhancement proposal for a refactoring of Taurus to make the Tango dependency optional instead of mandatory.
+
+PS: see also [TEP14][], which extends and complements this proposal.
+
+The TEP3 was created from [SEP3][] after the split of Taurus from sardana according to [SEP10][]. 
+
+Motivation
+==================
+
+Taurus evolved from Tau which was conceived as a library for creating GUIs & CLIs on top of a Tango control system. As such, Taurus models incorporate concepts such as "Device", "Attribute", etc that are heavily influenced by Tango.
+
+Lately, however, Taurus models have been extended to allow the interaction with other control systems and/or hardware via the "schemes" extensions (current schemes are: "tango", "simulation", "eval", "epics", "spec").
+
+The tango scheme was the one initially developed, and is not completely isolated from the rest of Taurus. Taurus uses objects from the PyTango module, and assumes naming conventions in many parts of the code, making it impossible to even import taurus if Tango is not installed.
+
+The objective of this proposal is to define a pure Taurus interface so that at least the Taurus core treats Tango just as another (optional) scheme.
+
+Requirements
+==================
+
+* It should be possible to use all the features of taurus.core without having PyTango installed. PyTango should not be imported and PyTango objects should not be assumed outside taurus.core.tango
+
+* A clear API should be offered to facilitate the creation of new schemes
+
+* A minimum set of functionality should be defined in an abstract Taurus API so that code using taurus.core can work transparently regardless of the type of scheme that provides the model object
+
+* The refactored taurus.core should be backwards-compatible in general, and in particular it must be backwards-compatible with the tango and evaluation schemes. In case of object name or other API changes, the old names/API should still be usable too (although deprecation warnings may be issued)
+
+API for taurus schemes
+======================
+
+Taurus Base/Abstract API for schemes
+-------------------------------------
+
+taurus.core must provide the following Base/Abstract classes to be inherited or used in each scheme:   
+
+* TaurusFactory
+
+* TaurusAuthority (corresponding to which was previously known as TaurusDatabase)
+
+* TaurusDevice
+
+* TaurusAttribute  
+
+* TaurusConfiguration        
+
+* TaurusAuthorityNameValidator 
+
+* TaurusDeviceNameValidator
+
+* TaurusAttributeNameValidator
+
+* TaurusConfigurationNameValidator
+
+
+Specific scheme API
+--------------------
+
+A scheme to be used in Taurus must provide the following classes (xxx is the name of the scheme, and the corresponding base class to use is given in parenthesis):
+
+* xxxFactory(TaurusFactory)
+
+* xxxAuthority(TaurusAuthority)
+
+* xxxDevice(TaurusDevice)
+
+* xxxAttribute(TaurusAttribute)
+
+* xxxConfiguration(TaurusConfiguration)
+
+* xxxAuthorityNameValidator(TaurusAuthorityNameValidator) 
+
+* xxxDeviceNameValidator(TaurusDeviceNameValidator)
+
+* xxxAttributeNameValidator(TaurusAttributeNameValidator)
+
+* xxxConfigurationNameValidator(TaurusConfigurationNameValidator)
+
+Keep in mind the following notes:
+
+- The value object returned when reading an attribute model object must be an instance of TaurusAttributeValue (or of a subclass of it).
+
+- The value object returned when reading a configuration model object must be an instance of TaurusConfigValue (or of a subclass of it).
+
+- Note: the validator classes should ideally be implemented using name parsing only (e.g. avoiding to instantiate TaurusModel ojects), and, if possible, avoid requiring the import of optional modules. The base classes provided for validators allow defining a new validator by providing just a few partial regexps for each URI component.
+
+
+Generic ("scheme-agnostic") Taurus helpers
+-------------------------------------------
+
+Users of Taurus are encouraged to write "scheme-agnostic" code whenever it is possible (e.g., code using the taurus.core should try to avoid assuming that a given attribute is of a certain scheme type). 
+Checks should be done (whenever it is possible) against the Base/Abstract classes instead of scheme-specific.
+
+Also, taurus.core must provide the following scheme-agnostic factory functions:
+
+* Factory(modelname). Returns a xxxFactory instance.
+
+* Authority(modelname). Returns a xxxAuthority instance.
+
+* Device(modelname). Returns a xxxDevice instance.
+
+* Attribute(modelname). Returns a xxxAttribute instance.
+
+* Configuration(modelname). Returns a xxxConfiguration instance.
+
+taurus.core should also provide utility functions for the following tasks:
+
+- find out cheaply whether a certain model name corresponds to a given Taurus Element Type (Attribute, Device, Database or Configuration). It can be achieved with taurus.isValidName()
+
+- find the scheme associated to a given model name. It can be achieved with taurus.getSchemeFromName()
+
+- find the supported schemes that are available.It can be achieved with taurus.Manager().getPlugins()
+
+Case-sensitivity in models
+--------------------------
+
+Tango models are case-insensitive, but this is not generally the case for other schemes. Case insensitivity is assumed in many parts of the code (e.g. through the use of CaselessList and CaselessDict containers to store models). The Factory classes should declare whether the scheme naming is case sensitive or not via a xxxFactory.caseSensitive class attribute (or property).
+
+
+Refactoring of Model name syntax
+================================
+
+The refactoring of the validator classes brings the opportunity of making the model names proper [URIs][1] (i.e., to comply with [RFC3986][2].
+
+Parsing any Taurus model name must yield at least a dictionary with the 
+following keys: 'scheme', 'authority', 'path', 'query', 'fragment', 
+corresponding to the basic URI components. Additionally, the dictionary 
+resulting from parsing the URI of a Taurus device should contain the device name
+under the key 'devname'. Similarly for the attribute name in the case of a 
+Taurus Attribute (under 'attrname') and for the configuration key in the case of 
+Configuration objects (under 'cfgkey'). 
+
+Apart from the keys mentioned above, the dictionary resulting from parsing a 
+given URI can contain also other keys for convenience (e.g. for scheme-specific 
+definitions) but it is recommended that those keys are prefixed with an 
+underscore ("_") to differentiate them from the generic ones.
+
+The validator classes must provide an attribute called 
+'namePattern' containing a regexp which validates the model names and which may 
+define named groups to help in constructing the dictionary with the above 
+mentioned keys.
+
+In the proposed implementation, the validator base classes are created assuming 
+the following RFC3986-compliant structure of the names for the Authority, Device, 
+Attribute and Configuration element types respectively (note we use Auth instead 
+of Database to be scheme-agnostic):
+
+~~~~
+Auth: <scheme>:<authority>[/<path>][?<query>][#<fragment>]   
+Dev:  <scheme>:[<authority>/]<path>[?<query>][#<fragment>]
+Attr: <scheme>:[<authority>/]<path>[?<query>][#<fragment>]
+Conf: <scheme>:[<authority>/]<path>[?<query>]#<fragment>
+~~~~
+
+Note about syntax: square brackets indicate "optionality" and segment names 
+are represented between angle brackets. The rest of the characters are literal)
+
+The proposed base validator classes can then use the mentioned structure to simplify the creation of the 'namePattern'. A base implementation of 'namePattern' exists as a property that returns the name pattern by constructing it from the following attributes:
+    
+    - scheme
+    - authority
+    - path
+    - query
+    - fragment
+
+Each of these attributes should contain the regexp for the same-named segment of the URI
+
+For example, the validator for a Tango database name is:
+
+~~~~
+class TangoAuthorityNameValidator(TaurusAuthorityNameValidator):
+    '''Validator for Tango authority names. Apart from the standard named 
+    groups (scheme, authority, path, query and fragment), the following named 
+    groups are created:
+    
+     - host: tango host name, without port.
+     - port: port number
+    '''
+
+    scheme = 'tango'
+    authority = '//(?P<host>([\w\-_]+\.)*[\w\-_]+):(?P<port>\d{1,5})'
+    path = '(?!)'
+    query = '(?!)'
+    fragment = '(?!)'
+~~~~
+
+and the resulting 'namePattern' is:
+
+~~~~
+^(?P<scheme>tango):(?P<authority>//(?P<host>([\w\-_]+\.)*[\w\-_]+):(?P<port>\d{1,5}))((?=/)(?P<path>(?!)))?(\?(?P<query>(?!)))?(#(?P<fragment>(?!)))?$
+~~~~
+
+
+Handling backwards compatibility 
+--------------------------------
+
+A mechanism should be implemented to provide compatibility with previous syntax.
+
+For example, in pre-TEP3 implementations, '//' was used as part of a separator between the scheme and the rest of the model name instead of as a prefix of authority (which is what RFC3986 dictates).
+
+Usage of old-style model names should be accepted to the maximum possible extent (although deprecation warnings may be issued) 
+
+The proposed API for this is that those validator classes that need to provide backwards compatibility should expose an attribute called 'nonStrictNamePattern' containing a regexp that matches the "old-style" names. Then, the validator's isValid() methods accept a 'strict' flag which, in case of being False, would allow to fall back to the nonStrictNamePattern if the regular validation failed.
+
+
+New evaluation syntax
+---------------------
+
+In order to be RFC3986-compliant, the evaluation scheme model names 
+need a radical transformation. 
+
+In the proposed implementation, the following changes have been made:
+
+- 'evaluation' is no longer accepted as an alias of 'eval' as a scheme name
+- the model names conform to the structure given in the base validator
+classes 
+- substitution expressions are handled as part of the path, not the query, 
+and must precede the expression itself
+
+So, for example, the old-style model name 
+
+'evaluation://dev=foo;x+y?x=1;y=2' 
+
+would now be written as:
+
+'eval:@foo/x=1;y=2;x+y'
+
+
+Change in the syntax for TangoConfiguration and EvaluationConfiguration names
+-----------------------------------------------------------------------------
+
+The proposed implementation changes the model name syntax for the configuration objects of both the tango and eval schemes. Where before the suffix "?configuration" was used, now "#" is used. Similarly, when a configuration key was defined with the suffix "?configuration=`<cfg_key>`", now it is defined as "`#<cfg_key>`". 
+
+This new syntax has the following advantages: 
+
+- it yields more compact names
+- it is easier to parse and handle
+- it prepares the field for [TEP14][]'s merge of TaurusConfiguration objects into TaurusAttribute objects.
+
+
+Adapting existing schemes to TEP3
+=================================
+
+Previous to TEP3, five schemes were included in Taurus. The following list summarizes how they are adapted to the TEP3 changes:
+
+- *tango*: the proposed implementation completely ports the tango scheme, including providing backwards compatibility.
+
+- *eval*: the proposed implementation completely ports the eval scheme, including providing backwards compatibility. 
+
+- *simulation*: the proposed implementation removes the simulation scheme (which was used only by the taurusdesigner to provide an offline mock for tango and which was already broken). The necessary changes have been done in taurusdesigner.
+
+- *epics*: the epics scheme is left in an unfinished (unusable) state after the proposed implementation of TEP3. This scheme was just a proof-of-concept and not used in production, so it can be adapted on a later moment (e.g. after [TEP14][]) without blocking the TEP3 acceptance.
+
+- *spec*: the spec scheme is left in an unfinished (unusable) state after the proposed implementation of TEP3. This scheme was just a proof-of-concept and not used in production, so it can be adapted on a later moment (e.g. after [TEP14][]) without blocking the TEP3 acceptance.
+
+- *res*: the res scheme (which provides a model indirection mechanism) is left in an unfinished (unusable) state after the proposed implementation of TEP3. This scheme is not used in production, so it can be adapted on a later moment (e.g. after [TEP14][]) without blocking the TEP3 acceptance.
+
+
+Refactoring of Value Types, enumerations, etc
+=============================================
+
+A description of a refactoring of other taurus.core features such as TaurusAttributeValue and TaurusConfigValue was included into the draft of TEP3, but it has now been moved to [TEP14][].
+
+
+Links to more details and discussions
+======================================
+
+The initial discussions about the SEP3 itself are in the sardana-devel mailing list.
+Further (post [SEP10][] split) discussions about TEP3 are held on the tauruslib-devel mailing list.
+
+
+Follow on and relations with other Enhancement Proposals
+========================================================
+
+- This TEP effectively makes the Taurus core tango-independent at the import level (i.e., it makes it possible to import taurus without importing PyTango). But there many implementations details remain that are inherited from the Tango-dependency history. The [TEP14][] proposes a further taurus.core refactoring that eliminates some of the most evident of these "tangoisms", thus simplifying even more the creation of new schemes.
+
+- Once the taurus.core is independent of Tango (and maybe also after [TEP14][]), other proposals should be made to a) make as many widgets from taurus.qt both scheme-agnostic and Tango-independent; and b) to identify and document those that are for some reason bound to one or another scheme.
+
+- The TEP3 was created from [SEP3][] after the split of Taurus from sardana according to [SEP10][]. The [SEP3][] still remains to cover the changes necessary in sardana to adapt to the TEP3 changes.
+
+
+APPENDIX: Implementation details history
+=======================================
+
+Implementation is currently in the tep3 branch of git://git.code.sf.net/p/tauruslib/taurus
+
+The following compilation of tasks was started on 12/08/2014 (by then, many of the requirements from this TEP were already implemented) and was used as a checklist of pending issues before submitting the implementation of TEP3 for approval. The list is kept in this appendix of the TEP3 to provide a historical reference:
+
+- <s>Refactor validators: move code from Taurus to Tango scheme and clean base/abstract validator classes (see [Taurus_URIRefactoring]). Remove some previous implementations. Deprecate "matchLevel" keyword of isValid implementations.</s>
+- <s>Tests: provide tests for TDD: Create tests for case (in)sensitivity; clean, document, refactor and/or remove existing tests in taurus.core.test; Implement basic test for tango-independence</s>
+- <s>Adapt evaluation scheme to the new design (make more use of Taurus abstract classes)</s>
+- <s>Adapt (or remove) simulation scheme (at the moment it is used by the taurusdesigner). Update: the Simulation scheme has been removed</s>
+- <s>Clean git history / commit messages and prepare for integration (pushing a cleaner history to sep3 branch in the canonical repo)</s>
+- <s>Move Tango-centric code still existing in taurusdatabase.py to the Tango scheme (the code is already clean of PyTango imports, but many tango concepts are still implemented in there and could be moved to the Tango scheme).</s>
+- <s>Remove PyTango dependency from TaurusExceptionListener (specific schemes should transform their own specific exceptions into agnostic Taurus-supported exception types if they need to be handled in an special way by Taurus)</s>
+- <s>Remove PyTango dependency from the release info of Taurus</s> 
+- <s>Check documentation and explain the models in a scheme-agnostic way (see, e.g. http://taurus-scada.org/devel/core_tutorial.html#model-concept)</s>
+- <s>TaurusPollingTimer.addAttribute treats tango attributes in a special way (regarding case-sensitivity) by a hardcoded check of the tango scheme. This should be generic. Maybe using a property declared by the scheme.</s>
+- <s>Use "authority" instead of "database" everywhere. Still support (but deprecate) the use of "Database". Consider also substituting "Device" by "object"</s>
+- <s>Change configuration model name syntax (substitute "?configuration[=cfgkey]" by "#[cfgkey]")</s>
+- <s>make sure that the installation procedure is independent of PyTango.</s>
+
+The following  tasks were previously part of the above requirements list, but have been moved to the [TEP14][]:
+
+- Implement quantities support 
+- TaurusConfiguration class still has some potentially tango-centric code (concepts such as "Standard unit vs Display unit" or classification into spectrum/image/scalars may require to be re-thought).
+
+The following tasks were previously part of the above requirements list, but have been left out of TEP3 for not being critical. They can be handled as feature requests after approval of TEP3:
+
+- Adapt epics scheme (not strictly needed for TEP3 since epics scheme is a proof-of-concept)
+- Adapt spec scheme (not strictly needed for TEP3 since spec scheme is a proof-of-concept)
+
+License
+==================
+
+The following copyright statement and license apply to TEP3 (this
+document).
+
+Copyright (c) 2013 CELLS / ALBA Synchrotron, Bellaterra, Spain
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Changes
+========
+
+2013-06-26
+[cmft](http://sf.net/u/cmft/) First draft based on previous documents and discussions with [tiagocoutinho](https://sourceforge.net/u/https://sf.net/u/tiagocoutinho/) and [cpascual](http://sf.net/u/cpascual/)
+
+2013-11-04
+[cpascual](http://sf.net/u/cpascual/) Partial rewrite of section on implementation. Also some spellchecking.
+
+2013-11-04
+[cpascual](http://sf.net/u/cpascual/) Partial rewrite of section on implementation. Also some spellchecking.
+
+2013-11-06
+[cmft](http://sf.net/u/cmft/) Including  "getting things done" section
+
+2014-04-28
+[cmft](http://sf.net/u/cmft/) Changed API description for validators
+
+2014-08-13
+[cpascual](http://sf.net/u/cpascual/) General update of the document based on code review and discussions with [cmft](https://sf.net/u/cmft/). Also added the "Changes" section.
+
+2014-08-14
+[cpascual](http://sf.net/u/cpascual/) Added some more tasks to implementation plan and reference to [Taurus_URIRefactoring](https://sourceforge.net/p/sardana/wiki/Taurus_URIRefactoring/) document
+
+2014-10-03
+[cpascual](http://sf.net/u/cpascual/) Updated pending tasks and completed some info about validators
+
+2015-04-28
+[cpascual](http://sf.net/u/cpascual/) Adapted latest version of [SEP3][] to update TEP3 and move some parts to [TEP14][]
+
+2015-04-30
+[cpascual](http://sf.net/u/cpascual/) Added 'Refactoring of Model name syntax' section using info from [Taurus_URIRefactoring](https://sourceforge.net/p/sardana/wiki/Taurus_URIRefactoring/). Also several other minor changes and improvements.
+
+2015-04-30
+[cpascual](http://sf.net/u/cpascual/) passing to CANDIDATE
+
+2015-05-06
+[cpascual](http://sf.net/u/cpascual/) Introduced some minor changes according to comments from [zreszela](http://sf.net/u/zreszela/)
+
+2015-05-06
+[cpascual](http://sf.net/u/cpascual/) Added the adapting existing schemes to TEP3, and updated the Implementation plan
+
+2015-05-08
+[cpascual](http://sf.net/u/cpascual/) Updated the Implementation plan (cross last task) and added section about change in configuration name syntax.
+
+2016-03-16
+[cpascual](http://sf.net/u/cpascual/) Passing to ACCEPTED
+
+2016-06-13
+[cpascual](http://sf.net/u/cpascual/) Minor formatting fix
+
+2016-11-16
+[mrosanes](https://github.com/sagiss/) Adapt TEP format and URL according TEP16
+
+[1]: https://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
+[2]: https://tools.ietf.org/html/rfc3986
+[TEP14]: http://www.taurus-scada.org/tep/?TEP14.md
+[SEP3]: https://sourceforge.net/p/sardana/wiki/SEP3/
+[SEP10]: https://sourceforge.net/p/sardana/wiki/SEP10/
diff --git a/doc/source/tep/TEP7.md b/doc/source/tep/TEP7.md
new file mode 100644
index 0000000..08c7cd9
--- /dev/null
+++ b/doc/source/tep/TEP7.md
@@ -0,0 +1,190 @@
+    Title: Code contribution workflow
+    TEP: 7
+    State: OBSOLETE
+    Reason: 
+     TEP16 obsoletes TEP7. Most of the contribution procedure is 
+     no longer applicable due to the adoption of a workflow based on Pull Requests.
+    Date: 2014-01-23
+    Drivers: Carlos Pascual-Izarra <cpascual at cells.es>, Tiago Coutinho <coutinho at esrf.fr>
+    URL: http://www.taurus-scada.org/tep?TEP7.md
+    License: http://www.jclark.com/xml/copying.txt
+    Abstract:
+     Define the procedures for contributing code to taurus. It covers git 
+     repository conventions and organization as well as workflows and tools
+     for reviewing code contributions.
+
+
+Introduction
+============
+
+*This TEP is a "translation" of [SEP7](https://sourceforge.net/p/sardana/wiki/SEP7 "Sardana Enhancement Proposal 7"). References to sardana have been changed to taurus*
+
+This is a proposal to define the mechanisms for contributing code to Taurus. It describes the agreed conventions for using the git repository as well as the workflow(s) and tools used for reviewing code prior to its acceptance into the official taurus repository.
+
+This proposal tries to answer the following questions:
+
+- Which conventions (e.g., naming, organization...) are used in the official git repository?
+- How should one submit a proposed contribution?
+- Who approves/rejects proposed contributions?
+- Which tools/workflows are used for reviewing the proposed contributed code?
+
+
+Goals and constraints
+=====================
+
+The following goals and constraints are taken into consideration for this proposal:
+
+General:
+
+- **Open development**: we want to encourage participation and contribution. We want an open development project (not just open source). 
+- **Code review**: we want taurus to be robust. Contributed code should be reviewed.
+- **Integration manager availability**: currently none of the involved people can dedicate 100% of time to coordination tasks. We need to minimize and share the load of coordination/integration.
+
+Specific/technical:
+
+- **SF account required**: we assume that all contributors already have a sourceforge.net account
+- **Minimise platform lock-down**: it should be possible to move the project to a different platform if needed in the future, without data loss and without forcing big workflow changes.
+- **Minimise imposed log-ins**: contributors and reviewers should be able to do most (if possible, all) their daily work without needing to log into SourceForge. Workflows of contribution/code review which integrate a mailing-list interface are preferred.
+- **Contributions traceability**: We would like to have a way of tracking the status of contributions (e.g., proposed / changes needed / accepted / rejected).
+
+
+Which conventions (e.g., naming, organization...) are used in the official git repository?
+==========================================================================================
+
+Branching model for the core repository of taurus
+--------------------------------------------------
+
+The official repository of taurus (from now on also called "origin") is organised following the [gitflow](http://nvie.com/posts/a-successful-git-branching-model/) branching model, in which there are two main long-running branches (*master* and *develop*) and a number of support finite-life branches (feature, release and hotfix branches). 
+
+Please refer to http://nvie.com/posts/a-successful-git-branching-model for a full description of the gitflow. The following are notes to complement the gitflow general information with specific details on the implementation of the gitflow model in Taurus:
+
+- The *master* branch reflects the latest official Taurus release. Only the Integration/Release Managers can push to the *master* branch.
+- The *develop* branch reflects the latest development changes that have already been integrated for the next release. Only the Integration Managers can push to the *develop* branch.
+- New features, bug fixes, etc. must be developed in *feature* branches. They branch off *develop*. Once they are ready and the code passed the review, the feature branch can be merged into *develop* by an Integration Manager. These branches may exist only in local clones of contributors, or in repositories forked from development or, in certain cases, in the official repository (see below).
+- The two other types of supporting branches (release branches and hotfix branches) are managed by the Integration/Release managers for the purpose of preparing official releases.
+
+In the Taurus project, we use a special type of *feature* branches called *tepX* branches: unlike other *feature* branches which typically only exist in the contributor local repository (or maybe in a public fork of the official repository), the *tepX* feature branches are hosted in the oficial repository. The *tepX* branch may be created if required during the DRAFT or CANDIDATE phases of the *X*th Taurus Enhancement Proposal, and is merged to *develop* if the TEPX is APPROVED. Only the [...]
+
+**Tip**: You can find a set of practical examples on working with the taurus branching model in the [taurus git recipes][]
+
+How should one submit a proposed contribution?
+==============================================
+
+In general, code submissions for inclusion in the taurus repositories should take the following into account:
+
+- It must comply with the [**Taurus coding conventions**](http://www.taurus-scada.org/devel/coding_guide.html).
+- The **contributor must be clearly identified** and provide a valid email address which can be used to contact him/her.
+- Commit messages  should be [properly formatted](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
+- The **licensing terms** for the contributed code must be compatible with (and preferably the same as) the license chosen for the Taurus project (at the time of writing this TEP, it is the [LGPL](http://www.gnu.org/licenses/lgpl.html), version 3 *or later*).
+
+
+Submitting code for the core repository of taurus
+-------------------------------------------------
+
+The discussion and public tracking of contributed code is done on the [tauruslib-devel mailing list](https://lists.sf.net/lists/listinfo/tauruslib-devel).
+
+Code contributions should be sent to tauruslib-devel at lists.sourceforge.net either in form of patches (formatted with **git format-patch**, as explained [here](http://www.git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Public-Large-Project)) or as a pull request (formatted with **git request-pull** as explained [here](http://www.git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Public-Small-Project)).
+
+Specific notes for contributing via patches:
+
+- The preferred way of sending the patch formatted with *git format-patch* is using *git send-email*
+- Please read http://www.git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Public-Large-Project (and use it as a guide)
+
+
+Specific notes for contributing via pull requests:
+
+- Please read http://www.git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Public-Small-Project (and use it as a guide)
+- Important: prepend the subject of your email to the mailing list with **`[PULL]`**
+- If the changes are not too big, consider using the "-p" option to *git request-pull* (it includes the diff info in the body of the email)
+
+**Tip**: You can find a set of practical examples on how to submit code according to the TEP7 specifications in the [taurus git recipes][]
+
+Who approves/rejects proposed contributions?
+============================================
+
+The Taurus community elects a group of people to act as "Integration Managers".
+
+For a given contribution to be accepted into the *develop* branch of the official repository, it has to be submitted to the tauruslib-devel mailing list (as explained before) and approved by at least one **Integration Manager**. If the contributor happens to be an Integration Manager, it is considered good practice to get the approval of *another* Integration Manager before accepting it (although this can be relaxed for trivial contributions).
+
+For a given contribution to be accepted into the *tepX* branch of the official repository, it has to be submitted to the tauruslib-devel mailing list (as explained before) and approved by at least one **TepX Integration Lieutenant**. If the contributor happens to be a TepX Integration Lieutenant, the previous rule can be relaxed, and direct pushes may be allowed. Note that ultimately, the approval of an **Integration Manager** is required once the *tepX* branch is to be merged into the * [...]
+
+
+Which tools/workflows are used for reviewing the proposed contributed code?
+===========================================================================
+
+The code review process for contributions to the official taurus **core** repository is as follows:
+
+1- The contributor submits a contribution to the mailing list (see "How should one submit a proposed contribution?" above).
+2- The contribution is publicly reviewed in the mailing list (everyone is encouraged to participate in the review). 
+3- During this phase, the contributor may be asked for further clarifications and/or corrections to the contributed code (in which case a resubmission may be required).
+4- Eventually, an Integration Manager (or a TepX Integration Lieutenant if the contribution is for a *tepX* branch) may either accept the contribution and integrate it into the official repository, or reject it. In both cases, he/she is posts a message in the mailing list informing of the decision.
+
+**Tip**: You can find a set of practical examples on how to integrate contributed code according to the TEP7 specifications in the [taurus git recipes][]
+
+
+Naming convention for feature branches
+--------------------------------------
+
+The integration of contributed code by an Integration Manager (or Lieutenant) usually involves merging some local branch (let's call it *A*) into the branch that tracks the official repository. Although the *A* branch itself stays local, its name appears in the merge commit message (ending up in the official history). Therefore the following naming convention should be used:
+
+- If the contributed code is related to a bug in the ticket tracker, the branch *A* should be called *bug-N*, where *N* is the ticket number.
+
+- If the contributed code is related to a feature-request in the ticket tracker, the branch *A* should be called *feature-N*, where *N* is the ticket number.
+
+- In the remaining cases, any descriptive name can be used for branch *A* (preferably lower case and reasonably short) provided that it doesn't use any of the reserved names (i.e. master, develop, release-\*, hotfix-\*, tepX, bug-N, feature-N)
+
+Note that those who contribute code via patches do not need to worry about this convention since their local branch names do not affect the official repository history. Nevertheless, it can be a good practice to follow anyway.
+ 
+
+
+Links to more details and discussions
+=====================================
+
+The main discussions affecting this proposal were held for SEP7 in the [sardana-devel mailing list](https://sourceforge.net/p/sardana/mailman/).
+
+This TEP uses concepts and nomenclature from [chapter 5 of Pro-Git book](http://git-scm.com/book/en/Distributed-Git)
+
+License
+=======
+
+Copyright (c) 2014 Carlos Pascual-Izarra
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+Changes
+=======
+* 2016-11-16: [mrosanes](https://github.com/sagiss/) Adapt format, 
+  modify URL and change state to OBSOLETE according to TEP16.
+  
+* 2015-05-13: [cpascual](https://sourceforge.net/u/cpascual/). Made final 
+  adaptations in the "translation" from SEP7. Fixed state in header 
+  (it was outdated as DRAFT, when it really inherited the ACCEPTED state 
+  from SEP7). 
+
+* 2014-01-27: [tiagocoutinho](https://sourceforge.net/u/tiagocoutinho/) 
+  Change main author and copyright
+
+* 2014-01-23: [tiagocoutinho](https://sourceforge.net/u/tiagocoutinho/) 
+  Initial version written (from SEP7)
+  
+
+
+  [taurus git recipes]: http://sf.net/p/tauruslib/wiki/git-recipes/
diff --git a/doc/source/tep/TEP8.md b/doc/source/tep/TEP8.md
new file mode 100644
index 0000000..a75f102
--- /dev/null
+++ b/doc/source/tep/TEP8.md
@@ -0,0 +1,250 @@
+    Title: Remove from Taurus objects the direct Logger dependence.
+    TEP: 8
+    State: CANDIDATE
+    Date: 2013-10-30
+    Drivers: Carlos Falcon Torres <cfalcon at cells.es>; Tiago Coutinho Macara <tiago.coutinho at esrf.fr>
+    URL: http://www.taurus-scada.org/tep?TEP8.md
+    License: http://www.jclark.com/xml/copying.txt
+    Abstract:
+     This document describes the process to remove from Taurus objects 
+     the direct Logger dependence.
+
+Introduction
+-----
+Nowadays, the most of Taurus objects inherited from Logger. This inheritance provide to those objects unnecessary functions and attributes that are using for internal Logger business. Moreover the current Logger design does not allow integrate Taurus with others libraries.
+
+The goal of TEP8 is simplify the Taurus objects inheritance, abstracting the Taurus Logger control functions and attributes, and adapt Taurus Logger API to be able to use external  logger system.
+
+We propose to transfer of the the control Logger functions and attributes to a new intermediate class, using a [Delegation pattern](http://en.wikipedia.org/wiki/Delegation_pattern). With it we will move part of the  inherited features to an inherited (private) object. Besides we propose separate into two step the Logger system: Initialization and Instantiation, doing this second part optional. 
+
+
+Motivation
+-----
+The main reason for this change is solve the mixed feature. For the one hand, these changes will simplify the objects, doing their more "light" and easy to understand. For example, a Tango device will reduce the number of attributes and functions visible by a third. For the other hand, removing the direct dependence of Taurus Logger and not assuming the automatically instantiation we could use an external logger, doing Taurus more friendly and compatible with other libraries.
+
+
+Requirements
+-----
+Taurus [TEP8] and Sardana [SEP8](https://sourceforge.net/p/sardana/wiki/SEP8/) classes must be backward compatibility. For this reason, the evolution of every change will be prove in a high level, through the cross checking with the current Sardana's applications: Sardana, spock, taurusdesigner, taurusgui, ...
+
+Taurus Logger has to be created in two steps: Taurus Logger initialization and Taurus Logger instantiation. Moreover, to have a Logger system more standard is convenient to use the python logging module as much as possible.
+
+The gross of changes have to be done in log.py file but also the Logger dependencies have to be removed from other files:
+```
+src/sardana/macroserver/macro.py
+src/sardana/macroserver/macros/examples/hooks.py
+src/sardana/macroserver/macros/standard.py
+src/sardana/macroserver/msdoor.py
+src/sardana/macroserver/scan/recorder/output.py
+src/sardana/pool/controller.py
+src/sardana/pool/pool.py
+src/sardana/pool/poolacquisition.py
+src/sardana/pool/poolcontroller.py
+src/sardana/spock/ipython_00_11/genutils.py
+src/sardana/tango/core/util.py
+src/sardana/tango/macroserver/Door.py
+src/sardana/tango/macroserver/_init_.py
+taurus/lib/taurus/core/taurusconfiguration.py
+taurus/lib/taurus/core/taurushelper.py
+taurus/lib/taurus/core/taurusmanager.py
+taurus/lib/taurus/core/taurusmodel.py
+taurus/lib/taurus/core/util/log.py
+taurus/lib/taurus/core/util/threadpool.py
+taurus/lib/taurus/qt/qtcore/util/emitter.py
+taurus/lib/taurus/qt/qtdesigner/tauruspluginplugin.py
+taurus/lib/taurus/qt/qtgui/application/taurusapplication.py
+taurus/lib/taurus/qt/qtgui/base/taurusbase.py
+taurus/lib/taurus/qt/qtgui/display/taurusvaluelabel.py
+taurus/lib/taurus/qt/qtgui/graphic/taurusgraphic.py
+taurus/lib/taurus/qt/qtgui/table/qlogtable.py
+taurus/lib/taurus/qt/qtgui/taurusgui/taurusgui.py
+taurus/lib/taurus/qt/qtgui/tree/taurusdevicetree.py
+```
+
+
+This is the current Taurus Logger API.
+
+|                            |  Current Taurus Logger API |                            |
+| ---------------------------|----------------------------|----------------------------|
+|Logger.Critical             |Logger.addLogHandler        |Logger.error                |
+|Logger.getParent            |Logger.root_inited          |Logger.exception            |   
+|Logger.Debug                |Logger.addRootLogHandler    |Logger.root_init_lock       |
+|Logger.getRootLog           |Logger.setLogFormat         |Logger.resetLogLevel        |
+|Logger.DftLogFormat         |Logger.call__init__         |Logger.fatal                |
+|Logger.info                 |Logger.setLogLevel          |Logger.DftLogLevel          |
+|Logger.call__init__wo_kw    |Logger.flushOutput          |Logger.initRoot             |
+|Logger.stack                |Logger.DftLogMessageFormat  |Logger.changeLogName        |
+|Logger.getAttrDict          |Logger.log                  |Logger.stream_handler       |
+|Logger.Error                |Logger.cleanUp              |Logger.getChildren          |
+|Logger.log_format           |Logger.syncLog              |Logger.Fatal                |
+|Logger.copyLogHandlers      |Logger.getLogFormat         |Logger.log_level            |
+|Logger.trace                |Logger.Info                 |Logger.critical             |
+|Logger.getLogFullName       |Logger.mro                  |Logger.traceback            |
+|Logger.Trace                |Logger.debug                |Logger.getLogLevel          |
+|Logger.removeRootLogHandler |Logger.updateAttrDict       |Logger.Warning              |
+|Logger.deprecated           |Logger.getLogName           |Logger.resetLogFormat       |
+|Logger.warning              |Logger.addChild             |Logger.disableLogOutput     |
+|Logger.getLogObj            |Logger.addLevelName         |Logger.enableLogOutput      |
+|Logger.getLogger            |
+
+
+
+Implementation
+----
+The main changes have been done in taurus/lib/taurus/core/util/log.py. 
+If we compare [fork](https://sourceforge.net/u/cmft/sardana_logger/ci/master/tree/taurus/lib/taurus/core/util/log.py) version with the [current](https://sourceforge.net/p/tauruslib/taurus.git/ci/develop/tree/taurus/lib/taurus/core/util/log.py) version you can see a new class LoggingHelper where was moved the control functions and attributes of Logger class. This solution is 99.99% backward compatible.
+
+Following the patter design, the rest of changes corresponding to change the code for access to the new class members. Logger was replaced by _LoggerHelper and obj.FUNCTION_OR_ATTRIBUTE by obj._logger.FUNCTION_OR_ATTRIBUTE
+
+
+The Logger functions flushOutput and syncLog were considered deprecated. Them were used internally and were removed from the code. 
+
+The Taurus Logger Trace level was market as deprecated. Right now the Tace level was setting to Debug. The callings of trace function were remplaced by debug.
+
+To uncouple the Taurus Logger initialization and instantiation, The old function initRoot was split in __init__ and  initLogger functions. 
+
+Following one of the requirement Taurus Logger uses internally logging.getLogger instead of getLogger function to access to logger.
+
+To be backward compatible, the variable ENABLE_TAURUS_LOGGER  was added to TaurusCustomSettings. This  variable is used in the functions, __getrootlogger , and in the __init__ of _LoggerHelper class, both defined in taurus.core.util.log. Allowing instantiate the logger class when the variable is equal True or does not defined. In the other case Taurus Logger asume an external logger system. 
+
+    #!/usr/bin/python
+    # Code goes here ...
+    # Use Taurus Logger API: 
+    # True (or commented out) enables the initiatialization of Taurus logger.
+    # False Taurus assumed an external Logger system; If it does not exist, 
+    #       the Logger API will not work. Showing the following message 
+    #       the first time that any Logger message (warning, debug, info, etc)
+    #       was executed.
+    #
+    #    i.e.    Logger.warning('asd')
+    #            No handlers could be found for logger "TaurusRootLogger"
+    
+    ENABLE_TAURUS_LOGGER = True
+
+
+This is the new Taurus Logger API after the changes:
+
+|                            |   New Taurus Logger API    |                            | 
+| ---------------------------|----------------------------|----------------------------|
+|Logger.call__init__         |Logger.debug                |Logger.exception            |
+|Logger.log                  |Logger.traceback            |Logger.call__init__wo_kw    |
+|Logger.deprecated           |Logger.getAttrDict          |Logger.mro                  |
+|Logger.updateAttrDict       |Logger.critical             |Logger.error                |
+|Logger.info                 |Logger.trace                |Logger.warning              |
+
+
+Recommendations
+----
+Nowadays Taurus can work with an external Logger, but if we assumed that Taurus will use its own Logger we recommend manage it from Taurus API instead of the Logger API. This last option was removed (hide) in the new API.
+
+    #!/usr/bin/python
+    # Code goes here ...
+    import taurus
+    taurus.getLogLevel()
+    taurus.setLogLevel(LEVEL)
+    taurus.getLogFormat()
+    taurus.setLogFormat(FORMAT)
+    taurus.resetLogLevel()
+    taurus.resetLogFormat()
+    taurus.enableLogOutput() 
+    taurus.disableLogOutput()
+
+  
+How to use the new API
+----
+
+The code below show how Taurus Logger could be used:
+
+1-If ENABLE_TAURUS_LOGGER = True or does not exist (backward compatibility)
+
+    #!/usr/bin/python
+    # Code goes here ...
+    import taurus
+    taurus.warning('asd')
+    MainThread     WARNING  2014-04-24 17:11:03,511 TaurusRootLogger: asd
+
+
+2-If ENABLE_TAURUS_LOGGER = True and exists external Logger.
+
+    #!/usr/bin/python
+    # Code goes here ...
+    import taurus
+    # External logger
+    import logging
+    logging.basicConfig()
+    taurus.warning('asd')
+    WARNING:TaurusRootLogger:asd
+    MainThread     WARNING  2014-04-24 17:15:01,619 TaurusRootLogger: asd
+
+
+
+3-If ENABLE_TAURUS_LOGGER = False and does not exist external Logger.
+
+    #!/usr/bin/python
+    # Code goes here ...
+    import taurus
+    taurus.warning('asd')
+    No handlers could be found for logger "TaurusRootLogger"
+
+
+
+4-If ENABLE_TAURUS_LOGGER = False and exists external Logger.
+
+    #!/usr/bin/python
+    # Code goes here ...
+    import taurus
+    import logging             # External logger module
+    logging.basicConfig()
+    taurus.warning('asd')
+    WARNING:TaurusRootLogger:asd
+
+
+5-If ENABLE_TAURUS_LOGGER = False and manual initialization:
+
+    #!/usr/bin/python
+    # Code goes here ...
+    import taurus
+    taurus.initLogger()
+    taurus.warning('asd')
+    MainThread     WARNING  2014-04-24 17:02:03,881 TaurusRootLogger: asd
+
+
+License
+-----
+The following copyright statement and license apply to TEP8 (this
+document).
+
+Copyright (c) 2013 Carlos Falcon Torres - Tiago Coutinho Macara
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+Changes
+-----
+
+2016-11-16 [mrosanes](https://github.com/sagiss/) Adapt format and URL according to TEP16
+
+2015-05-15 Create TEP8 using SEP8, Fix links according to [TEP10], pass TEP8 to CANDIDATE
+
+2014-04-24 Code review and SEP documentation
+
+2013-10-30
+[cmft](https://sourceforge.net/u/cfalcon/) Creation of SEP8
+
diff --git a/doc/source/tep/index.md b/doc/source/tep/index.md
new file mode 100644
index 0000000..825396f
--- /dev/null
+++ b/doc/source/tep/index.md
@@ -0,0 +1,40 @@
+Taurus Enhancement Proposals
+=============================
+
+This is the main index of the Taurus Enhancement Proposals (TEP).
+
+Each proposal should be in a separate file and be linked in the following table
+
+Proposals list
+--------------
+
+  Link        |  status   |        Title                                              
+  ------------| --------- | ---------------------------------------------------------
+  [TEP0][]   | OBSOLETE | Introducing Taurus Enhancement Proposal                  
+  [TEP3][]   | ACCEPTED | Tango-Independent                                        
+  [SEP5][]   | ACCEPTED  | Implementation of tests infrastructure                   
+  [TEP7][]   | OBSOLETE  | Code contribution workflow                               
+  [TEP8][]   | CANDIDATE | Remove from Taurus objects the direct Logger dependence  
+  [SEP9][]   | ACCEPTED  | Compact Read+Write widgets in Taurus                     
+  [SEP10][] | ACCEPTED  | Taurus separation                                        
+  [SEP11][] | ACCEPTED  | Direct load of .ui files                                       
+  [SEP12][] | CANDIDATE | Use python Enum instead of taurus Enumeration
+  [TEP13][] | DRAFT     | Plugins support 
+  [TEP14][] | ACCEPTED  | Core refactoring (quantities and configuration)
+  [TEP15][] | DRAFT     | fragment-based slicing support in URIs
+  [TEP16][] | ACCEPTED     | Moving Taurus to Github
+
+
+[TEP0]: http://www.taurus-scada.org/tep/?TEP0.md
+[TEP3]: http://www.taurus-scada.org/tep/?TEP3.md
+[SEP5]: http:/www.sardana-controls.org/sep/?SEP5.md
+[TEP7]: http://www.taurus-scada.org/tep/?TEP7.md
+[TEP8]: http://www.taurus-scada.org/tep/?TEP8.md
+[SEP9]:  http:/www.sardana-controls.org/sep/?SEP9.md
+[SEP10]: http:/www.sardana-controls.org/sep/?SEP10.md
+[SEP11]: http:/www.sardana-controls.org/sep/?SEP11.md
+[SEP12]: http:/www.sardana-controls.org/sep/?SEP12.md
+[TEP13]: http://www.taurus-scada.org/tep/?TEP13.md
+[TEP14]: http://www.taurus-scada.org/tep/?TEP14.md
+[TEP15]: http://www.taurus-scada.org/tep/?TEP15.md
+[TEP16]: http://www.taurus-scada.org/tep/?TEP16.md
diff --git a/doc/source/tep/index.rst b/doc/source/tep/index.rst
new file mode 100755
index 0000000..9162ad2
--- /dev/null
+++ b/doc/source/tep/index.rst
@@ -0,0 +1,23 @@
+Taurus Enhancement Proposals
+=============================
+
+.. raw:: html
+
+    <script>
+    //Function to redirect to the TEPs website
+    function redirect()
+    {
+        // Base url: URL where are hosted the TEPs
+        var baseurl = "https://github.com/taurus-org/taurus/tree/develop/doc/source/tep/"
+        // Get query if exist:
+        var query = location.search.substring(1)
+        if (typeof query === 'undefined' || !query)
+        {
+            // If there is not a query redirect to the index page
+            query = "index.md"
+        }
+        var url = baseurl.concat(query);
+        window.location = url;
+    }
+    redirect()
+    </script>
\ No newline at end of file
diff --git a/doc/source/tep/res/tep0_workflow.png b/doc/source/tep/res/tep0_workflow.png
new file mode 100755
index 0000000..e8cbf8c
Binary files /dev/null and b/doc/source/tep/res/tep0_workflow.png differ
diff --git a/doc/source/tep/res/tep14_merge_Attr_and_Conf.png b/doc/source/tep/res/tep14_merge_Attr_and_Conf.png
new file mode 100755
index 0000000..6248caf
Binary files /dev/null and b/doc/source/tep/res/tep14_merge_Attr_and_Conf.png differ
diff --git a/doc/source/users/getting_started.rst b/doc/source/users/getting_started.rst
index 3a2a734..1c51f8f 100644
--- a/doc/source/users/getting_started.rst
+++ b/doc/source/users/getting_started.rst
@@ -92,7 +92,7 @@ to re-install on each change.
 
 You can clone taurus from our main git repository::
 
-    git clone git://git.code.sf.net/p/tauruslib/taurus.git taurus
+    git clone https://github.com/taurus-org/taurus.git taurus
 
 Then, to work on develop mode, just do::
 
diff --git a/lib/taurus.egg-info/PKG-INFO b/lib/taurus.egg-info/PKG-INFO
index 81b85ab..e22ba94 100644
--- a/lib/taurus.egg-info/PKG-INFO
+++ b/lib/taurus.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: taurus
-Version: 4.0.1
+Version: 4.0.3
 Summary: A framework for scientific/industrial CLIs and GUIs
 Home-page: http://www.taurus-scada.org
 Author: Taurus Community
@@ -9,7 +9,7 @@ License: LGPL
 Download-URL: http://pypi.python.org/packages/source/t/taurus
 Description: Taurus is a python framework for control and data
         acquisition CLIs and GUIs in scientific/industrial environments.
-        It supports multiple control systems or data sources: Tango, EPICS, spec...
+        It supports multiple control systems or data sources: Tango, EPICS,...
         New control system libraries can be integrated through plugins.
 Keywords: CLI,GUI,PyTango,Tango,Shell,Epics
 Platform: Linux
diff --git a/lib/taurus.egg-info/SOURCES.txt b/lib/taurus.egg-info/SOURCES.txt
index b0280bf..11b4757 100644
--- a/lib/taurus.egg-info/SOURCES.txt
+++ b/lib/taurus.egg-info/SOURCES.txt
@@ -127,6 +127,18 @@ doc/source/devel/examples/taurusplot03.py
 doc/source/devel/examples/taurustrend01.py
 doc/source/devel/examples/taurusvalue01.py
 doc/source/sphinxext/taurusextension.py
+doc/source/tep/TEP0.md
+doc/source/tep/TEP13.md
+doc/source/tep/TEP14.md
+doc/source/tep/TEP15.md
+doc/source/tep/TEP16.md
+doc/source/tep/TEP3.md
+doc/source/tep/TEP7.md
+doc/source/tep/TEP8.md
+doc/source/tep/index.md
+doc/source/tep/index.rst
+doc/source/tep/res/tep0_workflow.png
+doc/source/tep/res/tep14_merge_Attr_and_Conf.png
 doc/source/users/getting_started.rst
 doc/source/users/index.rst
 doc/source/users/introduction.rst
diff --git a/lib/taurus/core/epics/epicsattribute.py b/lib/taurus/core/epics/epicsattribute.py
index ffdba7a..d5e0f6c 100644
--- a/lib/taurus/core/epics/epicsattribute.py
+++ b/lib/taurus/core/epics/epicsattribute.py
@@ -125,11 +125,6 @@ class EpicsAttribute(TaurusAttribute):
     # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
     # Necessary to overwrite from TaurusAttribute
     # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
-    def isNumeric(self):
-        return self.type in (DataType.Integer, DataType.Float)
-
-    def isState(self):
-        return False  # TODO implement generic
 
     def encode(self, value):
         """encodes the value passed to the write method into
diff --git a/lib/taurus/core/epics/epicsvalidator.py b/lib/taurus/core/epics/epicsvalidator.py
index 1ca6429..4a8d748 100644
--- a/lib/taurus/core/epics/epicsvalidator.py
+++ b/lib/taurus/core/epics/epicsvalidator.py
@@ -68,8 +68,7 @@ class EpicsDeviceNameValidator(TaurusDeviceNameValidator):
 
     scheme = '(ca|epics)'
     authority = EpicsAuthorityNameValidator.authority
-    devname = r'(?P<devname>)'  # (only empty string allowed for now)
-    path = devname
+    path = r'/(?P<devname>)'  # (only empty string allowed for now)
     query = '(?!)'
     fragment = '(?!)'
 
diff --git a/lib/taurus/core/epics/test/test_epicsvalidator.py b/lib/taurus/core/epics/test/test_epicsvalidator.py
index 931a007..7d38bdd 100644
--- a/lib/taurus/core/epics/test/test_epicsvalidator.py
+++ b/lib/taurus/core/epics/test/test_epicsvalidator.py
@@ -57,12 +57,14 @@ class EpicsAuthValidatorTestCase(AbstractNameValidatorTestCase,
 # ==============================================================================
 # Tests for Epics Device name validation
 # ==============================================================================
- at valid(name='ca:', groups=dict(authority=None, devname=''))
- at valid(name='epics:', groups=dict(authority=None, devname=''))
- at invalid(name='ca:/')
+ at valid(name='ca:/', groups=dict(authority=None, devname='', path='/'))
+ at valid(name='epics:/', groups=dict(authority=None, devname='', path='/'))
+ at valid(name='ca:///', groups=dict(authority='//', devname='', path='/'))
+ at invalid(name='ca:')  # device requires absolute non-empty path
+ at invalid(name='epics:')  # device requires absolute non-empty path
 @invalid(name='ca://')  # this is an auth
- at invalid(name='ca:///')
- at invalid(name='ca:foo')
+ at invalid(name='ca:foo')  #  device requires absolute path
+ at invalid(name='ca:/foo')  # devname must be empty (for now)
 @invalid(name='ca:@foo')
 @unittest.skipIf(sys.modules.has_key('epics') is False,
                  "epics module is not available")
diff --git a/lib/taurus/core/evaluation/evalattribute.py b/lib/taurus/core/evaluation/evalattribute.py
index 2b06c08..73571e4 100644
--- a/lib/taurus/core/evaluation/evalattribute.py
+++ b/lib/taurus/core/evaluation/evalattribute.py
@@ -358,15 +358,9 @@ class EvaluationAttribute(TaurusAttribute):
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
     # Necessary to overwrite from TaurusAttribute
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
-    def isNumeric(self):
-        return self.type in [DataType.Integer, DataType.Float]
-
     def isBoolean(self):
         return isinstance(self._value.rvalue, bool)
 
-    def isState(self):
-        return False
-
     def getDisplayValue(self, cache=True):
         return str(self.read(cache=cache).rvalue)
 
diff --git a/lib/taurus/core/evaluation/evalfactory.py b/lib/taurus/core/evaluation/evalfactory.py
index 686c15b..bf0bad0 100755
--- a/lib/taurus/core/evaluation/evalfactory.py
+++ b/lib/taurus/core/evaluation/evalfactory.py
@@ -35,7 +35,6 @@ from evalattribute import EvaluationAttribute
 from evalauthority import EvaluationAuthority
 from evaldevice import EvaluationDevice
 from taurus.core.taurusexception import TaurusException
-from taurus.core.tauruspollingtimer import TaurusPollingTimer
 from taurus.core.util.log import Logger
 from taurus.core.util.singleton import Singleton
 from taurus.core.taurusfactory import TaurusFactory
@@ -219,34 +218,6 @@ class EvaluationFactory(Singleton, TaurusFactory, Logger):
                 raise DoubleRegistration
         self.eval_configs[name] = config
 
-    def addAttributeToPolling(self, attribute, period, unsubscribe_evts=False):
-        """Activates the polling (client side) for the given attribute with the
-           given period (seconds).
-
-           :param attribute: (taurus.core.tango.TangoAttribute) attribute name.
-           :param period: (float) polling period (in seconds)
-           :param unsubscribe_evts: (bool) whether or not to unsubscribe from events
-        """
-        tmr = self.polling_timers.get(period, TaurusPollingTimer(period))
-        self.polling_timers[period] = tmr
-        tmr.addAttribute(attribute, self.isPollingEnabled())
-
-    def removeAttributeFromPolling(self, attribute):
-        """Deactivate the polling (client side) for the given attribute. If the
-           polling of the attribute was not previously enabled, nothing happens.
-
-           :param attribute: (str) attribute name.
-        """
-        p = None
-        for period, timer in self.polling_timers.iteritems():
-            if timer.containsAttribute(attribute):
-                timer.removeAttribute(attribute)
-                if timer.getAttributeCount() == 0:
-                    p = period
-                break
-        if p:
-            del self.polling_timers[period]
-
     def getAuthorityNameValidator(self):
         """Return EvaluationAuthorityNameValidator"""
         import evalvalidator
diff --git a/lib/taurus/core/release.py b/lib/taurus/core/release.py
index df9a09c..00c0395 100644
--- a/lib/taurus/core/release.py
+++ b/lib/taurus/core/release.py
@@ -46,21 +46,25 @@ Release data for the taurus project. It contains the following members:
 # the tarballs and RPMs made by distutils, so it's best to lowercase it.
 name = 'taurus'
 
-# For versions with substrings (like 0.6.16.svn), use an extra . to separate
-# the new substring.  We have to avoid using either dashes or underscores,
-# because bdist_rpm does not accept dashes (an RPM) convention, and
-# bdist_deb does not accept underscores (a Debian convention).
+# we use semantic versioning (http://semver.org/) and we update it using the
+# bumpversion script (https://github.com/peritus/bumpversion)
+version = '4.0.3'
 
+# generate version_info and revision (**deprecated** since version 4.0.2-dev).
+if '-' in version:
+    _v, _rel = version.split('-')
+else:
+    _v, _rel = version, ''
+_v = tuple([int(n) for n in _v.split('.')])
+version_info = _v + (_rel, 0)   # deprecated, do not use
+revision = str(version_info[4])  # deprecated, do not use
 
-version_info = (4, 0, 1, '', 0)
-version = '.'.join(map(str, version_info[:3]))
-revision = str(version_info[4])
 
 description = "A framework for scientific/industrial CLIs and GUIs"
 
 long_description = """Taurus is a python framework for control and data
 acquisition CLIs and GUIs in scientific/industrial environments.
-It supports multiple control systems or data sources: Tango, EPICS, spec...
+It supports multiple control systems or data sources: Tango, EPICS,...
 New control system libraries can be integrated through plugins."""
 
 license = 'LGPL'
diff --git a/lib/taurus/core/tango/starter.py b/lib/taurus/core/tango/starter.py
index bb5b6b0..290dcc7 100644
--- a/lib/taurus/core/tango/starter.py
+++ b/lib/taurus/core/tango/starter.py
@@ -112,8 +112,12 @@ class Starter(object):
             if self.isRunning():
                 _log.info('Server %s has been started' % self.ds_name)
                 ##############################################################
-                # TODO: this workaround doesn't seem necessary (see isRunning)
-                # time.sleep(3)
+                # Workaround to avoid race conditions
+                # TODO: Find root cause of race condition and fix
+                _wait = float(os.environ.get('TAURUS_STARTER_WAIT', 0))
+                if _wait:
+                    _log.info('Waiting %g s after start' % _wait)
+                    time.sleep(_wait)
                 ##############################################################
                 return
             else:
diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py
index 2339581..fba86af 100755
--- a/lib/taurus/core/tango/tangoattribute.py
+++ b/lib/taurus/core/tango/tangoattribute.py
@@ -88,8 +88,11 @@ class TangoAttrValue(TaurusAttrValue):
         if self._attrRef is None:
             return
 
-        numerical = PyTango.is_numerical_type(self._attrRef._tango_data_type,
-                                              inc_array=True)
+        numerical = (PyTango.is_numerical_type(self._attrRef._tango_data_type,
+                                              inc_array=True) or
+                     p.type == PyTango.CmdArgType.DevUChar
+                     )
+
         if p.has_failed:
             self.error = PyTango.DevFailed(*p.get_err_stack())
         else:
@@ -117,13 +120,6 @@ class TangoAttrValue(TaurusAttrValue):
                 wvalue = Quantity(wvalue, units=units)
         elif isinstance(rvalue, PyTango._PyTango.DevState):
             rvalue = DevState[str(rvalue)]
-        elif p.type == PyTango.CmdArgType.DevUChar:
-            if self._attrRef.data_format == DataFormat._0D:
-                rvalue = chr(rvalue)
-                wvalue = chr(wvalue)
-            else:
-                rvalue = rvalue.view('S1')
-                wvalue = wvalue.view('S1')
 
         self.rvalue = rvalue
         self.wvalue = wvalue
@@ -266,8 +262,6 @@ class TangoAttribute(TaurusAttribute):
 
         self.call__init__(TaurusAttribute, name, parent, **kwargs)
 
-        self._events_working = False
-
         attr_info = None
         if parent:
             attr_name = self.getSimpleName()
@@ -379,12 +373,7 @@ class TangoAttribute(TaurusAttribute):
                 except:
                     attrvalue = str(magnitude).lower() == 'true'
             elif tgtype == PyTango.CmdArgType.DevUChar:
-                try:
-                    # assume value to be a 1-character string repr of a byte
-                    attrvalue = ord(magnitude)
-                except TypeError:
-                    # but also support uint8 values (use ord-chr to make sure)
-                    attrvalue = ord(chr(magnitude))
+                attrvalue = int(magnitude)
             elif tgtype in (PyTango.CmdArgType.DevState,
                             PyTango.CmdArgType.DevEncoded):
                 attrvalue = magnitude
@@ -611,14 +600,14 @@ class TangoAttribute(TaurusAttribute):
             self.__subscription_state = SubscriptionState.Subscribing
             self.__chg_evt_id = self.__dev_hw_obj.subscribe_event(
                 attr_name, PyTango.EventType.CHANGE_EVENT,
-                self, [])
+                self, [])  # connects to self.push_event callback
 
         except:
             self.__subscription_state = SubscriptionState.PendingSubscribe
             self._activatePolling()
             self.__chg_evt_id = self.__dev_hw_obj.subscribe_event(
                 attr_name, PyTango.EventType.CHANGE_EVENT,
-                self, [], True)
+                self, [], True)  # connects to self.push_event callback
 
     def _unsubscribeEvents(self):
         # Careful in this method: This is intended to be executed in the cleanUp
@@ -659,7 +648,7 @@ class TangoAttribute(TaurusAttribute):
             self.__cfg_evt_id = self.__dev_hw_obj.subscribe_event(
                 attr_name,
                 PyTango.EventType.ATTR_CONF_EVENT,
-                self, [], True)
+                self, [], True)  # connects to self.push_event callback
         except PyTango.DevFailed, e:
             self.debug("Error trying to subscribe to CONFIGURATION events.")
             self.traceback()
@@ -690,57 +679,88 @@ class TangoAttribute(TaurusAttribute):
                 self.trace(str(e))
 
     def push_event(self, event):
-        """Method invoked by the PyTango layer when a change event occurs.
-           Default implementation propagates the event to all listeners."""
+        """Method invoked by the PyTango layer when an event occurs.
+        It propagates the event to listeners and delegates other tasks to
+        specific handlers for different event types.
+        """
+        # if it is a configuration event
+        if isinstance(event, PyTango.AttrConfEventData):
+            etype, evalue = self._pushConfEvent(event)
+        # if it is an attribute event
+        else:
+            etype, evalue = self._pushAttrEvent(event)
 
-        curr_time = time.time()
+        # notify the listeners if required (i.e, if etype is not None)
+        if etype is None:
+            return
         manager = Manager()
         sm = self.getSerializationMode()
+        listeners = tuple(self._listeners)
+        if sm == TaurusSerializationMode.Concurrent:
+            manager.addJob(self.fireEvent, None, etype, evalue,
+                           listeners=listeners)
+        else:
+            self.fireEvent(etype, evalue, listeners=listeners)
+
+    def _pushAttrEvent(self, event):
+        """Handler of (non-configuration) events from the PyTango layer.
+        It handles the subscription and the (de)activation of polling
+
+        :param event: (A PyTango event)
+
+        :return: (evt_type, evt_value)  Tuple containing the event type and the
+                 event value. evt_type is a `TaurusEventType` (or None to
+                 indicate that there should not be notification to listeners).
+                 evt_value is a TaurusValue, an Exception, or None.
+        """
         if not event.err:
-            # if it is a configuration event
-            if isinstance(event, PyTango.AttrConfEventData):
-                event_type = TaurusEventType.Config
-                self._decodeAttrInfoEx(event.attr_conf)
-                # make sure that there is a self.__attr_value
-                if self.__attr_value is None:
-                    # TODO: maybe we can avoid this read?
-                    self.__attr_value = self.getValueObj(cache=False)
-            # if it is an attribute event
-            else:
-                event_type = TaurusEventType.Change
-                self.__attr_value, self.__attr_err = self.decode(
-                    event.attr_value), None
-                self.__subscription_state = SubscriptionState.Subscribed
-                self.__subscription_event.set()
-                if not self.isPollingForced():
-                    self._deactivatePolling()
-            # notify the listeners
-            listeners = tuple(self._listeners)
-            if sm == TaurusSerializationMode.Concurrent:
-                manager.addJob(self.fireEvent, None, event_type,
-                               self.__attr_value, listeners=listeners)
-            else:
-                self.fireEvent(event_type, self.__attr_value,
-                               listeners=listeners)
+            self.__attr_value, self.__attr_err = self.decode(
+                event.attr_value), None
+            self.__subscription_state = SubscriptionState.Subscribed
+            self.__subscription_event.set()
+            if not self.isPollingForced():
+                self._deactivatePolling()
+            return TaurusEventType.Change, self.__attr_value
+
         elif event.errors[0].reason in EVENT_TO_POLLING_EXCEPTIONS:
-            if self.isPollingActive():
-                return
-            self.info("Activating polling. Reason: %s", event.errors[0].reason)
-            self.__subscription_state = SubscriptionState.PendingSubscribe
-            self._activatePolling()
+            if not self.isPollingActive():
+                self.info("Activating polling. Reason: %s",
+                          event.errors[0].reason)
+                self.__subscription_state = SubscriptionState.PendingSubscribe
+                self._activatePolling()
+            return None, None
+
         else:
             self.__attr_value, self.__attr_err = None, PyTango.DevFailed(
                 *event.errors)
             self.__subscription_state = SubscriptionState.Subscribed
             self.__subscription_event.set()
             self._deactivatePolling()
-            listeners = tuple(self._listeners)
-            if sm == TaurusSerializationMode.Concurrent:
-                manager.addJob(self.fireEvent, None, TaurusEventType.Error,
-                               self.__attr_err, listeners=listeners)
-            else:
-                self.fireEvent(TaurusEventType.Error, self.__attr_err,
-                               listeners=listeners)
+            return TaurusEventType.Error, self.__attr_err
+
+    def _pushConfEvent(self, event):
+        """Handler of AttrConfEventData events from the PyTango layer.
+
+        :param event: (PyTango.AttrConfEventData)
+
+        :return: (evt_type, evt_value)  Tuple containing the event type and the
+                 event value. evt_type is a `TaurusEventType` (or None to
+                 indicate that there should not be notification to listeners).
+                 evt_value is a TaurusValue, an Exception, or None.
+        """
+        if not event.err:
+            # update conf-related attributes
+            self._decodeAttrInfoEx(event.attr_conf)
+            # make sure that there is a self.__attr_value
+            if self.__attr_value is None:
+                # TODO: maybe we can avoid this read?
+                self.__attr_value = self.getValueObj(cache=False)
+            return TaurusEventType.Config, self.__attr_value
+
+        else:
+            self.__attr_value, self.__attr_err = None, PyTango.DevFailed(
+                *event.errors)
+            return TaurusEventType.Error, self.__attr_err
 
     def isWrite(self, cache=True):
         return self.getTangoWritable(cache) == PyTango.AttrWriteType.WRITE
diff --git a/lib/taurus/core/tango/tangofactory.py b/lib/taurus/core/tango/tangofactory.py
index 6e71b25..fd87287 100644
--- a/lib/taurus/core/tango/tangofactory.py
+++ b/lib/taurus/core/tango/tangofactory.py
@@ -43,7 +43,6 @@ from taurus.core.taurusbasetypes import TaurusElementType
 from taurus.core.taurusfactory import TaurusFactory
 from taurus.core.taurusbasetypes import OperationMode
 from taurus.core.taurusexception import TaurusException, DoubleRegistration
-from taurus.core.tauruspollingtimer import TaurusPollingTimer
 from taurus.core.util.log import Logger, taurus4_deprecation
 from taurus.core.util.singleton import Singleton
 from taurus.core.util.containers import CaselessWeakValueDict, CaselessDict
@@ -419,7 +418,11 @@ class TangoFactory(Singleton, TaurusFactory, Logger):
         else:
             if '/' not in dev_name:  # we got an alias... find the devslashname
                 dev_name = db.getElementFullName(dev_name)
-            tango_dev_klass = db.get_class_for_device(dev_name)
+            try:
+                tango_dev_klass = db.get_class_for_device(dev_name)
+            except PyTango.DevFailed:
+                # sometimes we can't get the class (e.g. dev_name not defined)
+                return _Device
             return self.tango_dev_klasses.get(tango_dev_klass, _Device)
 
     def _storeDevice(self, dev):
@@ -497,34 +500,6 @@ class TangoFactory(Singleton, TaurusFactory, Logger):
         if self.tango_attrs.has_key(full_name):
             del self.tango_attrs[full_name]
 
-    def addAttributeToPolling(self, attribute, period, unsubscribe_evts=False):
-        """Activates the polling (client side) for the given attribute with the
-           given period (seconds).
-
-           :param attribute: (taurus.core.tango.TangoAttribute) attribute name.
-           :param period: (float) polling period (in seconds)
-           :param unsubscribe_evts: (bool) whether or not to unsubscribe from events
-        """
-        tmr = self.polling_timers.get(period, TaurusPollingTimer(period))
-        self.polling_timers[period] = tmr
-        tmr.addAttribute(attribute, self.isPollingEnabled())
-
-    def removeAttributeFromPolling(self, attribute):
-        """Deactivate the polling (client side) for the given attribute. If the
-           polling of the attribute was not previously enabled, nothing happens.
-
-           :param attribute: (str) attribute name.
-        """
-        p = None
-        for period, timer in self.polling_timers.iteritems():
-            if timer.containsAttribute(attribute):
-                timer.removeAttribute(attribute)
-                if timer.getAttributeCount() == 0:
-                    p = period
-                break
-        if p:
-            del self.polling_timers[period]
-
     def isPollingEnabled(self):
         """Tells if the local tango polling is enabled
 
diff --git a/lib/taurus/core/tango/test/res/TangoSchemeTest b/lib/taurus/core/tango/test/res/TangoSchemeTest
index 1e267ef..1237e52 100755
--- a/lib/taurus/core/tango/test/res/TangoSchemeTest
+++ b/lib/taurus/core/tango/test/res/TangoSchemeTest
@@ -76,7 +76,7 @@ class TangoSchemeTest(Device):
                       'int': 123,
                       'float': 1.23,
                       'string': 'hello world',
-                      'uchar': ord('A'),
+                      'uchar': 1,
                       }
 
     default_unit = {'int': "mm",
@@ -109,7 +109,8 @@ class TangoSchemeTest(Device):
              'double_scalar': dict(unit=default_unit["float"],
                                    dtype=numpy.float64),
              'string_scalar': dict(dtype=str),
-             'uchar_scalar': dict(dtype=numpy.uint8),
+             'uchar_scalar': dict(unit=default_unit["int"],
+                                  dtype=numpy.uint8),
              'bool_spectrum': dict(dtype=(bool,), max_dim_x=MAXDIMX),
              'short_spectrum': dict(unit=default_unit["int"],
                                     dtype=(numpy.int16,),
@@ -121,7 +122,9 @@ class TangoSchemeTest(Device):
                                      dtype=(numpy.float64,),
                                      max_dim_x=MAXDIMX),
              'string_spectrum': dict(dtype=(str,), max_dim_x=MAXDIMX),
-             'uchar_spectrum': dict(dtype=(numpy.uint8,), max_dim_x=MAXDIMX),
+             'uchar_spectrum': dict(unit=default_unit["int"],
+                                    dtype=(numpy.uint8,),
+                                    max_dim_x=MAXDIMX),
              'bool_image': dict(dtype=[(bool,)], max_dim_x=MAXDIMX,
                                 max_dim_y=MAXDIMY),
              'short_image': dict(unit=default_unit["int"],
@@ -135,8 +138,9 @@ class TangoSchemeTest(Device):
                                   max_dim_x=MAXDIMX, max_dim_y=MAXDIMY),
              'string_image': dict(dtype=[(str,)], max_dim_x=MAXDIMX,
                                   max_dim_y=MAXDIMY),
-             'uchar_image': dict(dtype=[(numpy.uint8,)], max_dim_x=MAXDIMX,
-                                 max_dim_y=MAXDIMY),
+             'uchar_image': dict(unit=default_unit["int"],
+                                 dtype=[(numpy.uint8,)],
+                                 max_dim_x=MAXDIMX, max_dim_y=MAXDIMY),
              }
 
     extra_cfg = {'short_scalar': dict(min_value=default_ranges['int'][0],
@@ -170,8 +174,12 @@ class TangoSchemeTest(Device):
                                    **attrs['short_scalar_nu'])
     float_scalar_ro = attribute(access=AttrWriteType.READ,
                                 **attrs['float_scalar'])
+    double_scalar_ro = attribute(access=AttrWriteType.READ,
+                                **attrs['double_scalar'])
     string_scalar_ro = attribute(access=AttrWriteType.READ,
                                  **attrs['string_scalar'])
+    uchar_scalar_ro = attribute(access=AttrWriteType.READ,
+                                **attrs['uchar_scalar'])
     # SPECTRUMS
     boolean_spectrum_ro = attribute(access=AttrWriteType.READ,
                                     **attrs['bool_spectrum'])
@@ -182,6 +190,8 @@ class TangoSchemeTest(Device):
     string_spectrum_ro = attribute(access=AttrWriteType.READ,
                                    **attrs['string_spectrum'])
     # IMAGES
+    uchar_image_ro = attribute(access=AttrWriteType.READ,
+                                 **attrs['uchar_image'])
     boolean_image_ro = attribute(access=AttrWriteType.READ,
                                  **attrs['bool_image'])
     short_image_ro = attribute(access=AttrWriteType.READ,
@@ -260,6 +270,9 @@ class TangoSchemeTest(Device):
     def read_string_scalar_ro(self):
         return self.default_rvalue['string']
 
+    def read_uchar_scalar_ro(self):
+        return self.default_rvalue['uchar']
+
     # SPECTRUMS
     def read_boolean_spectrum_ro(self):
         return [self.default_rvalue['bool']] * self.DIMX
@@ -292,6 +305,9 @@ class TangoSchemeTest(Device):
     def read_string_image_ro(self):
         return [[self.default_rvalue['string']] * self.DIMX] * self.DIMY
 
+    def read_uchar_image_ro(self):
+        return [[self.default_rvalue['uchar']] * self.DIMX] * self.DIMY
+
     # READ/WRITE METHODS
     # SCALARS
     def read_boolean_scalar(self):
diff --git a/lib/taurus/core/tango/test/test_tangoattribute.py b/lib/taurus/core/tango/test/test_tangoattribute.py
index 33a53fe..4ee6ecf 100644
--- a/lib/taurus/core/tango/test/test_tangoattribute.py
+++ b/lib/taurus/core/tango/test/test_tangoattribute.py
@@ -48,65 +48,60 @@ _FLOAT_SPE = _INT_SPE * .1
 _BOOL_IMG = numpy.array([[True, False], [False, True]])
 _BOOL_SPE = [True, False]
 _STR = 'foo BAR |-+#@!?_[]{}'
-_UCHAR_IMG = numpy.array([['a', 'b'], ['c', 'd']], dtype='S1')
-_UCHAR_SPE = _UCHAR_IMG[1, :]
-_UINT8_IMG = _UCHAR_IMG.view('uint8')
+_UINT8_IMG = numpy.array([[1, 2], [3, 4]], dtype='uint8')
 _UINT8_SPE = _UINT8_IMG[1, :]
 
 # ==============================================================================
 # Test writing fragment values
-##
-# DISABLED: these tests break test isolation (looks like Reset is not working
-#           for configurations). Until we solve it, we disable all these tests
-#
-# @insertTest(helper_name='write_read_conf', attr_name='short_scalar_nu',
-#             cfg='range', value=[float('-inf'), float('inf')],
-#             expected=[Quantity(float('-inf')), Quantity(float('inf'))]
-#             )
-# @insertTest(helper_name='write_read_conf', attr_name='short_scalar_nu',
-#             cfg='range', value=[Quantity(float('-inf')),
-#                                 Quantity(float('inf'))],
-#             expected=[Quantity(float('-inf')), Quantity(float('inf'))])
-# @insertTest(helper_name='write_read_conf', attr_name='short_scalar_nu',
-#             cfg='range', value=[100, 300],
-#             expected=[Quantity(100), Quantity(300)])
-# @insertTest(helper_name='write_read_conf', attr_name='short_scalar_nu',
-#             cfg='range', value=[Quantity(100), Quantity(300)],
-#             expected=[Quantity(100), Quantity(300)])
-# @insertTest(helper_name='write_read_conf', attr_name='float_scalar',
-#             cfg='range', value=[Quantity(-5, 'mm'), Quantity(5, 'mm')],
-#             expected=[Quantity(-0.005, 'm'), Quantity(5, 'mm')])
-# @insertTest(helper_name='write_read_conf', attr_name='short_spectrum',
-#             cfg='label', value='Just a Test',
-#             expected='Just a Test')
-# @insertTest(helper_name='write_read_conf', attr_name='boolean_spectrum',
-#             cfg='label', value='Just_a_Test',
-#             expected='Just_a_Test')
-# @insertTest(helper_name='write_read_conf', attr_name='short_scalar',
-#             cfg='warnings', value=[Quantity(-2, 'mm'), Quantity(2, 'mm')],
-#             expected=[Quantity(-2, 'mm'), Quantity(0.002, 'm')])
-# @insertTest(helper_name='write_read_conf', attr_name='short_image',
-#             cfg='warnings', value=[Quantity(-2, 'mm'), Quantity(2, 'mm')],
-#             expected=[Quantity(-0.002, 'm'), Quantity(2, 'mm')])
-# @insertTest(helper_name='write_read_conf', attr_name='float_image',
-#             cfg='warnings', value=[Quantity(-0.75, 'mm'),
-#                                         Quantity(0.75, 'mm')],
-#             expected=[Quantity(-0.00075, 'm'), Quantity(0.75, 'mm')])
-# @insertTest(helper_name='write_read_conf', attr_name='short_scalar_nu',
-#             cfg='warnings', value=[100, 300],
-#             expected=[Quantity(100), Quantity(300)])
-# @insertTest(helper_name='write_read_conf', attr_name='short_scalar',
-#             cfg='alarms', value=[Quantity(-50, 'mm'), Quantity(50, 'mm')],
-#             expected=[Quantity(-50, 'mm'), Quantity(50, 'mm')])
-# @insertTest(helper_name='write_read_conf', attr_name='short_image',
-#             cfg='alarms', value=[Quantity(-2, 'mm'), Quantity(2, 'mm')],
-#             expected=[Quantity(-0.002, 'm'), Quantity(2, 'mm')])
-# @insertTest(helper_name='write_read_conf', attr_name='float_image',
-#             cfg='alarms', value=[Quantity(-0.75, 'mm'), Quantity(0.75, 'mm')],
-#             expected=[Quantity(-0.00075, 'm'), Quantity(0.75, 'mm')])
-# @insertTest(helper_name='write_read_conf', attr_name='short_scalar_nu',
-#             cfg='alarms', value=[100, 300],
-#             expected=[Quantity(100), Quantity(300)])
+
+ at insertTest(helper_name='write_read_conf', attr_name='short_scalar_nu',
+            cfg='range', value=[float('-inf'), float('inf')],
+            expected=[Quantity(float('-inf')), Quantity(float('inf'))]
+            )
+ at insertTest(helper_name='write_read_conf', attr_name='short_scalar_nu',
+            cfg='range', value=[Quantity(float('-inf')),
+                                Quantity(float('inf'))],
+            expected=[Quantity(float('-inf')), Quantity(float('inf'))])
+ at insertTest(helper_name='write_read_conf', attr_name='short_scalar_nu',
+            cfg='range', value=[100, 300],
+            expected=[Quantity(100), Quantity(300)])
+ at insertTest(helper_name='write_read_conf', attr_name='short_scalar_nu',
+            cfg='range', value=[Quantity(100), Quantity(300)],
+            expected=[Quantity(100), Quantity(300)])
+ at insertTest(helper_name='write_read_conf', attr_name='float_scalar',
+            cfg='range', value=[Quantity(-5, 'mm'), Quantity(5, 'mm')],
+            expected=[Quantity(-0.005, 'm'), Quantity(5, 'mm')])
+ at insertTest(helper_name='write_read_conf', attr_name='short_spectrum',
+            cfg='label', value='Just a Test',
+            expected='Just a Test')
+ at insertTest(helper_name='write_read_conf', attr_name='boolean_spectrum',
+            cfg='label', value='Just_a_Test',
+            expected='Just_a_Test')
+ at insertTest(helper_name='write_read_conf', attr_name='short_scalar',
+            cfg='warnings', value=[Quantity(-2, 'mm'), Quantity(2, 'mm')],
+            expected=[Quantity(-2, 'mm'), Quantity(0.002, 'm')])
+ at insertTest(helper_name='write_read_conf', attr_name='short_image',
+            cfg='warnings', value=[Quantity(-2, 'mm'), Quantity(2, 'mm')],
+            expected=[Quantity(-0.002, 'm'), Quantity(2, 'mm')])
+ at insertTest(helper_name='write_read_conf', attr_name='float_image',
+            cfg='warnings', value=[Quantity(-0.75, 'mm'),
+                                        Quantity(0.75, 'mm')],
+            expected=[Quantity(-0.00075, 'm'), Quantity(0.75, 'mm')])
+ at insertTest(helper_name='write_read_conf', attr_name='short_scalar_nu',
+            cfg='warnings', value=[100, 300],
+            expected=[Quantity(100), Quantity(300)])
+ at insertTest(helper_name='write_read_conf', attr_name='short_scalar',
+            cfg='alarms', value=[Quantity(-50, 'mm'), Quantity(50, 'mm')],
+            expected=[Quantity(-50, 'mm'), Quantity(50, 'mm')])
+ at insertTest(helper_name='write_read_conf', attr_name='short_image',
+            cfg='alarms', value=[Quantity(-2, 'mm'), Quantity(2, 'mm')],
+            expected=[Quantity(-0.002, 'm'), Quantity(2, 'mm')])
+ at insertTest(helper_name='write_read_conf', attr_name='float_image',
+            cfg='alarms', value=[Quantity(-0.75, 'mm'), Quantity(0.75, 'mm')],
+            expected=[Quantity(-0.00075, 'm'), Quantity(0.75, 'mm')])
+ at insertTest(helper_name='write_read_conf', attr_name='short_scalar_nu',
+            cfg='alarms', value=[100, 300],
+            expected=[Quantity(100), Quantity(300)])
 
 # ==============================================================================
 # Test encode-decode of empty arrays
@@ -161,99 +156,99 @@ _UINT8_SPE = _UINT8_IMG[1, :]
 # Test encode-decode of strings, booleans and uchars
 @insertTest(helper_name='write_read_attr',
             attrname='uchar_image',
-            setvalue=_UCHAR_IMG,
-            expected=dict(rvalue=_UCHAR_IMG,
-                          wvalue=_UCHAR_IMG,
-                          type=DataType.Bytes,
+            setvalue=Quantity(_UINT8_IMG, 'mm'),
+            expected=dict(rvalue=Quantity(_UINT8_IMG, 'mm'),
+                          wvalue=Quantity(_UINT8_IMG, 'mm'),
+                          type=DataType.Integer,
                           label='uchar_image',
                           writable=True,
                           ),
-            expected_attrv=dict(rvalue=_UCHAR_IMG,
-                                value=_UCHAR_IMG,
-                                wvalue=_UCHAR_IMG,
-                                w_value=_UCHAR_IMG,
+            expected_attrv=dict(rvalue=Quantity(_UINT8_IMG, 'mm'),
+                                value=_UINT8_IMG,
+                                wvalue=Quantity(_UINT8_IMG, 'mm'),
+                                w_value=_UINT8_IMG,
                                 quality=AttrQuality.ATTR_VALID
                                 )
             )
 @insertTest(helper_name='write_read_attr',
             attrname='uchar_spectrum',
-            setvalue=_UCHAR_SPE,
-            expected=dict(rvalue=_UCHAR_SPE,
-                          wvalue=_UCHAR_SPE,
-                          type=DataType.Bytes,
+            setvalue=Quantity(_UINT8_SPE, 'mm'),
+            expected=dict(rvalue=Quantity(_UINT8_SPE, 'mm'),
+                          wvalue=Quantity(_UINT8_SPE, 'mm'),
+                          type=DataType.Integer,
                           writable=True,
                           ),
-            expected_attrv=dict(rvalue=_UCHAR_SPE,
-                                value=_UCHAR_SPE,
-                                wvalue=_UCHAR_SPE,
-                                w_value=_UCHAR_SPE,
+            expected_attrv=dict(rvalue=Quantity(_UINT8_SPE, 'mm'),
+                                value=_UINT8_SPE,
+                                wvalue=Quantity(_UINT8_SPE, 'mm'),
+                                w_value=_UINT8_SPE,
                                 quality=AttrQuality.ATTR_VALID
                                 )
             )
 @insertTest(helper_name='write_read_attr',
             attrname='uchar_scalar',
-            setvalue='a',
-            expected=dict(rvalue='a',
-                          wvalue='a',
-                          type=DataType.Bytes,
+            setvalue=Quantity(12, 'mm'),
+            expected=dict(rvalue=Quantity(12, 'mm'),
+                          wvalue=Quantity(12, 'mm'),
+                          type=DataType.Integer,
                           writable=True,
                           range=[None, None],
                           alarms=[None, None],
                           warnings=[None, None]
                           ),
-            expected_attrv=dict(rvalue='a',
-                                value='a',
-                                wvalue='a',
-                                w_value='a',
+            expected_attrv=dict(rvalue=Quantity(12, 'mm'),
+                                value=12,
+                                wvalue=Quantity(12, 'mm'),
+                                w_value=12,
                                 quality=AttrQuality.ATTR_VALID
                                 )
             )
 @insertTest(helper_name='write_read_attr',
             attrname='uchar_image',
-            setvalue=_UINT8_IMG,
-            expected=dict(rvalue=_UCHAR_IMG,
-                          wvalue=_UCHAR_IMG,
-                          type=DataType.Bytes,
+            setvalue=Quantity(_UINT8_IMG, 'mm'),
+            expected=dict(rvalue=Quantity(_UINT8_IMG, 'mm'),
+                          wvalue=Quantity(_UINT8_IMG, 'mm'),
+                          type=DataType.Integer,
                           label='uchar_image',
                           writable=True,
                           ),
-            expected_attrv=dict(rvalue=_UCHAR_IMG,
-                                value=_UCHAR_IMG,
-                                wvalue=_UCHAR_IMG,
-                                w_value=_UCHAR_IMG,
+            expected_attrv=dict(rvalue=Quantity(_UINT8_IMG, 'mm'),
+                                value=_UINT8_IMG,
+                                wvalue=Quantity(_UINT8_IMG, 'mm'),
+                                w_value=_UINT8_IMG,
                                 quality=AttrQuality.ATTR_VALID
                                 )
             )
 @insertTest(helper_name='write_read_attr',
             attrname='uchar_spectrum',
-            setvalue=_UINT8_SPE,
-            expected=dict(rvalue=_UCHAR_SPE,
-                          wvalue=_UCHAR_SPE,
-                          type=DataType.Bytes,
+            setvalue=Quantity(_UINT8_SPE, 'mm'),
+            expected=dict(rvalue=Quantity(_UINT8_SPE, 'mm'),
+                          wvalue=Quantity(_UINT8_SPE, 'mm'),
+                          type=DataType.Integer,
                           writable=True,
                           ),
-            expected_attrv=dict(rvalue=_UCHAR_SPE,
-                                value=_UCHAR_SPE,
-                                wvalue=_UCHAR_SPE,
-                                w_value=_UCHAR_SPE,
+            expected_attrv=dict(rvalue=Quantity(_UINT8_SPE, 'mm'),
+                                value=_UINT8_SPE,
+                                wvalue=Quantity(_UINT8_SPE, 'mm'),
+                                w_value=_UINT8_SPE,
                                 quality=AttrQuality.ATTR_VALID
                                 )
             )
 @insertTest(helper_name='write_read_attr',
             attrname='uchar_scalar',
-            setvalue=97,  # ord('a')-->97
-            expected=dict(rvalue='a',
-                          wvalue='a',
-                          type=DataType.Bytes,
+            setvalue=Quantity(12, 'mm'),
+            expected=dict(rvalue=Quantity(12, 'mm'),
+                          wvalue=Quantity(12, 'mm'),
+                          type=DataType.Integer,
                           writable=True,
                           range=[None, None],
                           alarms=[None, None],
                           warnings=[None, None]
                           ),
-            expected_attrv=dict(rvalue='a',
-                                value='a',
-                                wvalue='a',
-                                w_value='a',
+            expected_attrv=dict(rvalue=Quantity(12, 'mm'),
+                                value=12,
+                                wvalue=Quantity(12, 'mm'),
+                                w_value=12,
                                 quality=AttrQuality.ATTR_VALID
                                 )
             )
@@ -534,6 +529,36 @@ _UINT8_SPE = _UINT8_IMG[1, :]
 # ==============================================================================
 # Test read of tango attributes
 @insertTest(helper_name='write_read_attr',
+            attrname='uchar_image_ro',
+            expected=dict(rvalue=Quantity([[1] * 3] * 3, 'mm'),
+                          wvalue=None,
+                          type=DataType.Integer
+                          ),
+            expected_attrv=dict(value=[[1] * 3] * 3,
+                                w_value=None,
+                                quality=AttrQuality.ATTR_VALID
+                                ),
+            expectedshape=(3, 3),
+            )
+
+ at insertTest(helper_name='write_read_attr',
+            attrname='uchar_scalar_ro',
+            expected=dict(rvalue=Quantity(1, 'mm'),
+                          wvalue=None,
+                          type=DataType.Integer,
+                          data_format=DataFormat._0D,
+                          writable=False,
+                          range=[None, None],
+                          alarms=[None, None],
+                          warnings=[None, None]
+                          ),
+            expected_attrv=dict(rvalue=Quantity(1, 'mm'),
+                                value=1,
+                                quality=AttrQuality.ATTR_VALID,
+                                wvalue=None,
+                                w_value=None)
+            )
+ at insertTest(helper_name='write_read_attr',
             attrname='string_image_ro',
             expected=dict(rvalue=(('hello world',) * 3,) * 3,
                           wvalue=None,
@@ -764,7 +789,8 @@ class AttributeTestCase(TangoSchemeTestLauncher, unittest.TestCase):
         if setvalue is None:
             read_value = a.read()
         else:
-            read_value = a.write(setvalue,  with_read=True)
+            a.write(setvalue)
+            read_value = a.read(cache=False)
 
         msg = ('read() for "%s" did not return a TangoAttrValue (got a %s)' %
                (attrname, read_value.__class__.__name__))
diff --git a/lib/taurus/core/tango/util/tango_taurus.py b/lib/taurus/core/tango/util/tango_taurus.py
index 0577c42..5913582 100644
--- a/lib/taurus/core/tango/util/tango_taurus.py
+++ b/lib/taurus/core/tango/util/tango_taurus.py
@@ -47,7 +47,7 @@ FROM_TANGO_TO_TAURUS_TYPE = {PyTango.CmdArgType.DevVoid: None,
                              PyTango.CmdArgType.DevUShort: DataType.Integer,
                              PyTango.CmdArgType.DevULong: DataType.Integer,
                              PyTango.CmdArgType.DevString: DataType.String,
-                             PyTango.CmdArgType.DevVarCharArray: DataType.Integer,  # maybe should be Bytes?
+                             PyTango.CmdArgType.DevVarCharArray: DataType.Integer,
                              PyTango.CmdArgType.DevVarShortArray: DataType.Integer,
                              PyTango.CmdArgType.DevVarLongArray: DataType.Integer,
                              PyTango.CmdArgType.DevVarFloatArray: DataType.Float,
@@ -60,7 +60,7 @@ FROM_TANGO_TO_TAURUS_TYPE = {PyTango.CmdArgType.DevVoid: None,
                              PyTango.CmdArgType.DevState: DataType.DevState,
                              PyTango.CmdArgType.ConstDevString: DataType.String,
                              PyTango.CmdArgType.DevVarBooleanArray: DataType.Boolean,
-                             PyTango.CmdArgType.DevUChar: DataType.Bytes,
+                             PyTango.CmdArgType.DevUChar: DataType.Integer,
                              PyTango.CmdArgType.DevLong64: DataType.Integer,
                              PyTango.CmdArgType.DevULong64: DataType.Integer,
                              PyTango.CmdArgType.DevVarLong64Array: DataType.Integer,
diff --git a/lib/taurus/core/taurusattribute.py b/lib/taurus/core/taurusattribute.py
index 08a69ae..6ae7eb8 100644
--- a/lib/taurus/core/taurusattribute.py
+++ b/lib/taurus/core/taurusattribute.py
@@ -31,9 +31,9 @@ __docformat__ = "restructuredtext"
 
 import weakref
 
-from .taurushelper import Factory
 from .taurusmodel import TaurusModel
 from taurus.core.taurusbasetypes import TaurusElementType, DataType
+from taurus.core.util.log import deprecation_decorator
 from taurus.external.pint import Quantity, UR
 
 
@@ -70,7 +70,7 @@ class TaurusAttribute(TaurusModel):
 
         self.writable = None
         self.data_format = None
-        self._label = None
+        self._label = self.getSimpleName()
         self.type = None
         self._range = [None, None]
         self._alarm = [None, None]
@@ -81,12 +81,6 @@ class TaurusAttribute(TaurusModel):
         self._unsubscribeEvents()
         TaurusModel.cleanUp(self)
 
-    @classmethod
-    def factory(cls):
-        if cls._factory is None:
-            cls._factory = Factory(scheme=cls._scheme)
-        return cls._factory
-
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
     # TaurusModel implementation
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
@@ -120,16 +114,16 @@ class TaurusAttribute(TaurusModel):
     def getNameValidator(cls):
         return cls.factory().getAttributeNameValidator()
 
-    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
-    # Necessary to overwrite in subclass
-    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
     def isNumeric(self):
-        raise NotImplementedError("Not allowed to call AbstractClass" +
-                                  " TaurusAttribute.isNumeric")
+        return self.type in [DataType.Float, DataType.Integer]
 
+    @deprecation_decorator(rel='>4.0.1', alt='.type==DataType.DevState')
     def isState(self):
-        raise NotImplementedError("Not allowed to call AbstractClass" +
-                                  " TaurusAttribute.isState")
+        return False
+
+    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
+    # Necessary to overwrite in subclass
+    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
 
     def encode(self, value):
         raise NotImplementedError("Not allowed to call AbstractClass" +
diff --git a/lib/taurus/core/taurusauthority.py b/lib/taurus/core/taurusauthority.py
index 9054ed1..b0b8878 100644
--- a/lib/taurus/core/taurusauthority.py
+++ b/lib/taurus/core/taurusauthority.py
@@ -32,7 +32,6 @@ __docformat__ = "restructuredtext"
 
 from .taurusbasetypes import TaurusElementType
 from .taurusmodel import TaurusModel
-from .taurushelper import Factory
 
 
 class TaurusAuthority(TaurusModel):
@@ -50,12 +49,6 @@ class TaurusAuthority(TaurusModel):
         TaurusModel.cleanUp(self)
 
     @classmethod
-    def factory(cls):
-        if cls._factory is None:
-            cls._factory = Factory(scheme=cls._scheme)
-        return cls._factory
-
-    @classmethod
     def getTaurusElementType(cls):
         return TaurusElementType.Authority
 
diff --git a/lib/taurus/core/taurusdevice.py b/lib/taurus/core/taurusdevice.py
index 2f1fe91..af4e909 100755
--- a/lib/taurus/core/taurusdevice.py
+++ b/lib/taurus/core/taurusdevice.py
@@ -31,7 +31,6 @@ __docformat__ = "restructuredtext"
 
 from .taurusbasetypes import TaurusDevState, TaurusElementType
 from .taurusmodel import TaurusModel
-from .taurushelper import Factory
 
 
 class TaurusDevice(TaurusModel):
@@ -52,12 +51,6 @@ class TaurusDevice(TaurusModel):
         if storeCallback:
             storeCallback(self)
 
-    @classmethod
-    def factory(cls):
-        if cls._factory is None:
-            cls._factory = Factory(scheme=cls._scheme)
-        return cls._factory
-
     def __contains__(self, key):
         """Reimplement in schemes if you want to support membership testing for
         attributes of the device
diff --git a/lib/taurus/core/taurusfactory.py b/lib/taurus/core/taurusfactory.py
index 2f05fa1..b140dfc 100644
--- a/lib/taurus/core/taurusfactory.py
+++ b/lib/taurus/core/taurusfactory.py
@@ -61,19 +61,27 @@ __all__ = ["TaurusFactory"]
 __docformat__ = "restructuredtext"
 
 import atexit
+from weakref import WeakValueDictionary
 from taurusbasetypes import TaurusElementType
 from taurusauthority import TaurusAuthority
 from taurusdevice import TaurusDevice
 from taurusattribute import TaurusAttribute
 from taurusconfiguration import TaurusConfiguration, TaurusConfigurationProxy
+from taurusexception import TaurusException
+from taurus.core.tauruspollingtimer import TaurusPollingTimer
 
 
 class TaurusFactory(object):
     """The base class for valid Factories in Taurus."""
 
-    schemes = ()  # reimplement in derived classes to provide the supported sche
+    schemes = ()  # reimplement in derived classes to declare supported schemes
     caseSensitive = True  # reimplement if your scheme is case insensitive
 
+    elementTypesMap = None  # reimplement in derived classes to profit from
+                            # generic implementations of getAuthority,
+                            # getDevice, getAttribute, findObjectClass, etc.
+                            # see findObjectClass for more details
+
     DefaultPollingPeriod = 3000
 
     def __init__(self):
@@ -81,10 +89,14 @@ class TaurusFactory(object):
         self._polling_period = self.DefaultPollingPeriod
         self.polling_timers = {}
         self._polling_enabled = True
+        self._attrs = WeakValueDictionary()
+        self._devs = WeakValueDictionary()
+        self._auths = WeakValueDictionary()
 
         import taurusmanager
         manager = taurusmanager.TaurusManager()
         self._serialization_mode = manager.getSerializationMode()
+
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
     # API for cleanUp at exit
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
@@ -114,61 +126,109 @@ class TaurusFactory(object):
         return self._serialization_mode
 
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
-    # Methods that must be implemented by the specific Factory
+    # API to get objects. Generic implementation. You may want to reimplement
+    # it in your scheme factory
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
 
     def getAuthority(self, name=None):
-        """getAuthority(string db_name) -> taurus.core.taurusauthority.TaurusAuthority
-
-        Obtain the object corresponding to the given authority name or the
-        default authority if db_name is None.
-        If the corresponding authority object already exists, the existing
-        instance is returned. Otherwise a new instance is stored and returned.
+        """Obtain the model object corresponding to the given authority name.
+        If the corresponding authority already exists, the existing instance
+        is returned. Otherwise a new instance is stored and returned.
 
-        :param db_name: [in] authority name string. It should be formed like:
-                           <scheme>://<authority>. If <scheme> is ommited then
-                           it will use the default scheme. if db_name is None,
-                           the default authority is used
+        :param name: (str) authority name
 
         :return: a taurus.core.taurusauthority.TaurusAuthority object
         :raises: :TaurusException: if the given name is invalid.
         """
-        raise NotImplementedError("getAuthority cannot be called for abstract"
-                                  " TaurusFactory")
-
-    def getDevice(self, dev_name, **kw):
-        """getDevice(string dev_name) -> taurus.core.taurusdevice.TaurusDevice
-
-        Obtain the object corresponding to the given device name. If the
-        corresponding device already exists, the existing instance is returned.
-        Otherwise a new instance is stored and returned.
+        v = self.getAuthorityNameValidator()
+        if not v.isValid(name):
+            msg = "Invalid {scheme} authority name '{name}'".format(
+                    scheme=self.schemes[0], name=name)
+            raise TaurusException(msg)
+
+        fullname, _, _ = v.getNames(name)
+        auth = self._devs.get(fullname)
+        if auth is not None:
+            return auth
+
+        cls = self.elementTypesMap[TaurusElementType.Authority]
+        auth = cls(name=fullname)
+        self._auths[fullname] = auth
+        return auth
+
+    def getDevice(self, name, **kw):
+        """Obtain the model object corresponding to the given device name.
+        If the corresponding device already exists, the existing instance
+        is returned. Otherwise a new instance is stored and returned.
 
-        :param dev_name: [in] the device name string. It should be formed like:
-                            <scheme>://<authority>/<device name>. If <scheme>
-                            is ommited then it will use the default scheme.
-                            If authority is ommited then it will use the
-                            default authority for the scheme.
+        :param name: (str) device name
 
         :return: a taurus.core.taurusdevice.TaurusDevice object
         :raises: :TaurusException: if the given name is invalid.
         """
-        raise NotImplementedError("getDevice cannot be called for abstract"
-                                  " TaurusFactory")
+        v = self.getDeviceNameValidator()
+        if not v.isValid(name):
+            msg = "Invalid {scheme} device name '{name}'".format(
+                    scheme=self.schemes[0], name=name)
+            raise TaurusException(msg)
 
-    def getAttribute(self, attr_name):
-        """getAttribute(string attr_name) -> taurus.core.taurusattribute.TaurusAttribute
+        fullname, _, _ = v.getNames(name)
+        dev = self._devs.get(fullname)
+        if dev is not None:
+            return dev
 
-        Obtain the object corresponding to the given attribute name.
+        try:
+            # this works if the authority name is present in the dev full name
+            # (which in principle should always be the case)
+            authname = v.getUriGroups(fullname)['authority']
+            auth = self.getAuthority(authname)
+        except:
+            self.debug('Cannot get device parent from name "%s"', fullname)
+            auth = None
+
+        cls = self.elementTypesMap[TaurusElementType.Device]
+        dev = cls(name=fullname, parent=auth)
+        self._devs[fullname] = dev
+        return dev
+
+    def getAttribute(self, name):
+        """ Obtain the model object corresponding to the given attribute name.
         If the corresponding attribute already exists, the existing instance
         is returned. Otherwise a new instance is stored and returned.
 
-        :param attr_name: [in] string attribute name
+        :param name: (str) attribute name
 
         :return: a taurus.core.taurusattribute.TaurusAttribute object
         :raises: :TaurusException: if the given name is invalid.
         """
-        raise NotImplementedError("getAttribute cannot be called for abstract"
-                                  " TaurusFactory")
+        v = self.getAttributeNameValidator()
+        if not v.isValid(name):
+            msg = "Invalid {scheme} attribute name '{name}'".format(
+                    scheme=self.schemes[0], name=name)
+            raise TaurusException(msg)
+
+        fullname, _, _ = v.getNames(name)
+        attr = self._attrs.get(fullname)
+        if attr is not None:
+            return attr
+
+        try:
+            # this works only if the devname is present in the attr full name
+            # (not all schemes are constructed in this way)
+            devname = v.getUriGroups(fullname)['devname']
+            dev = self.getDevice(devname)
+        except:
+            self.debug('Cannot get attribute parent from name "%s"', fullname)
+            dev = None
+
+        cls = self.elementTypesMap[TaurusElementType.Attribute]
+        attr = cls(name=fullname, parent=dev)
+        self._attrs[fullname] = attr
+        return attr
+
+    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
+    # Methods that must be implemented by the specific Factory
+    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
 
     def getAuthorityNameValidator(self):
         raise NotImplementedError("getAuthorityNameValidator cannot be called"
@@ -252,7 +312,7 @@ class TaurusFactory(object):
         return self._polling_period
 
     def isPollingEnabled(self):
-        """Tells if the local tango polling is enabled
+        """Tells if the Taurus polling is enabled
 
            :return: (bool) whether or not the polling is enabled
         """
@@ -282,8 +342,9 @@ class TaurusFactory(object):
            :param period: (float) polling period (in seconds)
            :param unsubscribe_evts: (bool) whether or not to unsubscribe from events
         """
-        raise NotImplementedError("addAttributeToPolling cannot be called"
-                                  " for abstract TaurusFactory")
+        tmr = self.polling_timers.get(period, TaurusPollingTimer(period))
+        self.polling_timers[period] = tmr
+        tmr.addAttribute(attribute, self.isPollingEnabled())
 
     def removeAttributeFromPolling(self, attribute):
         """Deactivate the polling (client side) for the given attribute. If the
@@ -291,8 +352,15 @@ class TaurusFactory(object):
 
            :param attribute: (str) attribute name.
         """
-        raise NotImplementedError("removeAttributeFromPolling cannot be"
-                                  " called for abstract TaurusFactory")
+        p = None
+        for period, timer in self.polling_timers.iteritems():
+            if timer.containsAttribute(attribute):
+                timer.removeAttribute(attribute)
+                if timer.getAttributeCount() == 0:
+                    p = period
+                break
+        if p:
+            del self.polling_timers[period]
 
     def __str__(self):
         return '{0}()'.format(self.__class__.__name__)
diff --git a/lib/taurus/core/taurushelper.py b/lib/taurus/core/taurushelper.py
index 3c234e6..f2a8587 100644
--- a/lib/taurus/core/taurushelper.py
+++ b/lib/taurus/core/taurushelper.py
@@ -184,19 +184,19 @@ def __get_qtcontrols_version_number():
     return __translate_version_str2int(qtcontrols_str)
 
 
-def __get_spyderlib_version():
+def __get_spyder_version():
     try:
-        import spyderlib
-        return spyderlib.__version__
+        import spyder
+        return spyder.__version__
     except:
-        pass
+            pass
 
 
-def __get_spyderlib_version_number():
-    spyderlibver_str = __get_spyderlib_version()
-    if spyderlibver_str is None:
+def __get_spyder_version_number():
+    spyderver_str = __get_spyder_version()
+    if spyderver_str is None:
         return None
-    return __translate_version_str2int(spyderlibver_str)
+    return __translate_version_str2int(spyderver_str)
 
 
 def __w(msg):
@@ -227,6 +227,8 @@ def log_dependencies():
 def _check_dependencies(forlog=False):
     """Checks for the required and optional packages of taurus"""
 
+    # TODO: Checking dependencies should be taken care by setuptools. Remove
+
     if forlog:
         MSG = {'OK': '[OK]', 'ERR': '[ERROR]', 'WARN': '[WARNING]'}
     else:
@@ -256,7 +258,7 @@ def _check_dependencies(forlog=False):
         #    module       minimum  recommended
         "Qub": ("1.0.0", "1.0.0"),
         "qtcontrols": ("1.0.0", "1.0.0"),
-        "spyderlib": ("2.0.0", "2.0.0"),
+        "spyder": ("3.0.0", "3.0.0"),
     }
 
     yield -1, "Checking required dependencies of taurus.core..."
@@ -329,17 +331,17 @@ def _check_dependencies(forlog=False):
     else:
         yield 0, "{msg} {OK} (Found {fnd})".format(msg=m, fnd=currQubStr, **MSG)
 
-    m = "Checking for spyderlib >=%s..." % r["spyderlib"][0]
-    minspyderlib, recspyderlib = map(
-        __translate_version_str2int, r["spyderlib"])
-    currspyderlib, currspyderlibStr = __get_spyderlib_version_number(
-    ), __get_spyderlib_version()
-    if currspyderlib is None:
+    m = "Checking for spyder >=%s..." % r["spyder"][0]
+    minspyder, recspyder = map(
+        __translate_version_str2int, r["spyder"])
+    currspyder, currspyderStr = __get_spyder_version_number(
+    ), __get_spyder_version()
+    if currspyder is None:
         yield 1, "{msg} {WARN} (Not found])".format(msg=m, **MSG)
-    elif currspyderlib < minspyderlib:
-        yield 1, "{msg} {WARN} (Found {fnd}. Recommended >={rec})".format(msg=m, fnd=currspyderlibStr, rec=r['spyderlib'][1], **MSG)
+    elif currspyder < minspyder:
+        yield 1, "{msg} {WARN} (Found {fnd}. Recommended >={rec})".format(msg=m, fnd=currspyderStr, rec=r['spyder'][1], **MSG)
     else:
-        yield 0, "{msg} {OK} (Found {fnd})".format(msg=m, fnd=currspyderlibStr, **MSG)
+        yield 0, "{msg} {OK} (Found {fnd})".format(msg=m, fnd=currspyderStr, **MSG)
 
     m = "Checking for qtcontrols >=%s..." % r["qtcontrols"][0]
     minqtcontrols, recqtcontrols = map(
diff --git a/lib/taurus/core/taurusmodel.py b/lib/taurus/core/taurusmodel.py
index 25fcd2d..0a834d4 100644
--- a/lib/taurus/core/taurusmodel.py
+++ b/lib/taurus/core/taurusmodel.py
@@ -36,10 +36,12 @@ import threading
 from .util.log import Logger
 from .util.event import CallableRef, BoundMethodWeakref
 from .taurusbasetypes import TaurusEventType, MatchLevel
+from .taurushelper import Factory
 
 
 class TaurusModel(Logger):
 
+    _factory = None
     RegularEvent = (TaurusEventType.Change,
                     TaurusEventType.Config, TaurusEventType.Periodic)
 
@@ -85,7 +87,9 @@ class TaurusModel(Logger):
 
     @classmethod
     def factory(cls):
-        raise NotImplementedError("TaurusModel.factory cannot be called")
+        if cls._factory is None:
+            cls._factory = Factory(scheme=cls._scheme)
+        return cls._factory
 
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
     # API for naming
diff --git a/lib/taurus/core/taurusvalidator.py b/lib/taurus/core/taurusvalidator.py
index e4e08bf..8985a07 100644
--- a/lib/taurus/core/taurusvalidator.py
+++ b/lib/taurus/core/taurusvalidator.py
@@ -259,7 +259,7 @@ class TaurusDeviceNameValidator(_TaurusBaseValidator):
     path segment).
     '''
     pattern = r'^(?P<scheme>%(scheme)s):' + \
-              r'((?P<authority>%(authority)s)(?=/))?' + \
+              r'((?P<authority>%(authority)s)($|(?=[/#?])))?' + \
               r'(?P<path>%(path)s)' + \
               r'(\?(?P<query>%(query)s))?' + \
               r'(#(?P<fragment>%(fragment)s))?$'
@@ -285,7 +285,7 @@ class TaurusAttributeNameValidator(_TaurusBaseValidator):
     path segment).
     '''
     pattern = r'^(?P<scheme>%(scheme)s):' + \
-              r'((?P<authority>%(authority)s)(?=/))?' + \
+              r'((?P<authority>%(authority)s)($|(?=[/#?])))?' + \
               r'(?P<path>%(path)s)' + \
               r'(\?(?P<query>%(query)s))?' + \
               r'(#(?P<fragment>%(fragment)s))?$'
diff --git a/lib/taurus/external/argparse/LICENSE.txt b/lib/taurus/external/argparse/LICENSE.txt
new file mode 100644
index 0000000..640bc78
--- /dev/null
+++ b/lib/taurus/external/argparse/LICENSE.txt
@@ -0,0 +1,20 @@
+argparse is (c) 2006-2009 Steven J. Bethard <steven.bethard at gmail.com>.
+
+The argparse module was contributed to Python as of Python 2.7 and thus
+was licensed under the Python license. Same license applies to all files in
+the argparse package project.
+
+For details about the Python License, please see doc/Python-License.txt.
+
+History
+-------
+
+Before (and including) argparse 1.1, the argparse package was licensed under
+Apache License v2.0.
+
+After argparse 1.1, all project files from the argparse project were deleted
+due to license compatibility issues between Apache License 2.0 and GNU GPL v2.
+
+The project repository then had a clean start with some files taken from
+Python 2.7.1, so definitely all files are under Python License now.
+
diff --git a/lib/taurus/external/argparse/argparse_local.py b/lib/taurus/external/argparse/argparse_local.py
new file mode 100644
index 0000000..32d948c
--- /dev/null
+++ b/lib/taurus/external/argparse/argparse_local.py
@@ -0,0 +1,2362 @@
+# Author: Steven J. Bethard <steven.bethard at gmail.com>.
+
+"""Command-line parsing library
+
+This module is an optparse-inspired command-line parsing library that:
+
+    - handles both optional and positional arguments
+    - produces highly informative usage messages
+    - supports parsers that dispatch to sub-parsers
+
+The following is a simple usage example that sums integers from the
+command-line and writes the result to a file::
+
+    parser = argparse.ArgumentParser(
+        description='sum the integers at the command line')
+    parser.add_argument(
+        'integers', metavar='int', nargs='+', type=int,
+        help='an integer to be summed')
+    parser.add_argument(
+        '--log', default=sys.stdout, type=argparse.FileType('w'),
+        help='the file where the sum should be written')
+    args = parser.parse_args()
+    args.log.write('%s' % sum(args.integers))
+    args.log.close()
+
+The module contains the following public classes:
+
+    - ArgumentParser -- The main entry point for command-line parsing. As the
+        example above shows, the add_argument() method is used to populate
+        the parser with actions for optional and positional arguments. Then
+        the parse_args() method is invoked to convert the args at the
+        command-line into an object with attributes.
+
+    - ArgumentError -- The exception raised by ArgumentParser objects when
+        there are errors with the parser's actions. Errors raised while
+        parsing the command-line are caught by ArgumentParser and emitted
+        as command-line messages.
+
+    - FileType -- A factory for defining types of files to be created. As the
+        example above shows, instances of FileType are typically passed as
+        the type= argument of add_argument() calls.
+
+    - Action -- The base class for parser actions. Typically actions are
+        selected by passing strings like 'store_true' or 'append_const' to
+        the action= argument of add_argument(). However, for greater
+        customization of ArgumentParser actions, subclasses of Action may
+        be defined and passed as the action= argument.
+
+    - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter,
+        ArgumentDefaultsHelpFormatter -- Formatter classes which
+        may be passed as the formatter_class= argument to the
+        ArgumentParser constructor. HelpFormatter is the default,
+        RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser
+        not to change the formatting for help text, and
+        ArgumentDefaultsHelpFormatter adds information about argument defaults
+        to the help.
+
+All other classes in this module are considered implementation details.
+(Also note that HelpFormatter and RawDescriptionHelpFormatter are only
+considered public as object names -- the API of the formatter objects is
+still considered an implementation detail.)
+"""
+
+__version__ = '1.2.1'
+__all__ = [
+    'ArgumentParser',
+    'ArgumentError',
+    'ArgumentTypeError',
+    'FileType',
+    'HelpFormatter',
+    'ArgumentDefaultsHelpFormatter',
+    'RawDescriptionHelpFormatter',
+    'RawTextHelpFormatter',
+    'Namespace',
+    'Action',
+    'ONE_OR_MORE',
+    'OPTIONAL',
+    'PARSER',
+    'REMAINDER',
+    'SUPPRESS',
+    'ZERO_OR_MORE',
+]
+
+
+import copy as _copy
+import os as _os
+import re as _re
+import sys as _sys
+import textwrap as _textwrap
+
+from gettext import gettext as _
+
+try:
+    set
+except NameError:
+    # for python < 2.4 compatibility (sets module is there since 2.3):
+    from sets import Set as set
+
+try:
+    basestring
+except NameError:
+    basestring = str
+
+try:
+    sorted
+except NameError:
+    # for python < 2.4 compatibility:
+    def sorted(iterable, reverse=False):
+        result = list(iterable)
+        result.sort()
+        if reverse:
+            result.reverse()
+        return result
+
+
+def _callable(obj):
+    return hasattr(obj, '__call__') or hasattr(obj, '__bases__')
+
+
+SUPPRESS = '==SUPPRESS=='
+
+OPTIONAL = '?'
+ZERO_OR_MORE = '*'
+ONE_OR_MORE = '+'
+PARSER = 'A...'
+REMAINDER = '...'
+_UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args'
+
+# =============================
+# Utility functions and classes
+# =============================
+
+class _AttributeHolder(object):
+    """Abstract base class that provides __repr__.
+
+    The __repr__ method returns a string in the format::
+        ClassName(attr=name, attr=name, ...)
+    The attributes are determined either by a class-level attribute,
+    '_kwarg_names', or by inspecting the instance __dict__.
+    """
+
+    def __repr__(self):
+        type_name = type(self).__name__
+        arg_strings = []
+        for arg in self._get_args():
+            arg_strings.append(repr(arg))
+        for name, value in self._get_kwargs():
+            arg_strings.append('%s=%r' % (name, value))
+        return '%s(%s)' % (type_name, ', '.join(arg_strings))
+
+    def _get_kwargs(self):
+        return sorted(self.__dict__.items())
+
+    def _get_args(self):
+        return []
+
+
+def _ensure_value(namespace, name, value):
+    if getattr(namespace, name, None) is None:
+        setattr(namespace, name, value)
+    return getattr(namespace, name)
+
+
+# ===============
+# Formatting Help
+# ===============
+
+class HelpFormatter(object):
+    """Formatter for generating usage messages and argument help strings.
+
+    Only the name of this class is considered a public API. All the methods
+    provided by the class are considered an implementation detail.
+    """
+
+    def __init__(self,
+                 prog,
+                 indent_increment=2,
+                 max_help_position=24,
+                 width=None):
+
+        # default setting for width
+        if width is None:
+            try:
+                width = int(_os.environ['COLUMNS'])
+            except (KeyError, ValueError):
+                width = 80
+            width -= 2
+
+        self._prog = prog
+        self._indent_increment = indent_increment
+        self._max_help_position = max_help_position
+        self._width = width
+
+        self._current_indent = 0
+        self._level = 0
+        self._action_max_length = 0
+
+        self._root_section = self._Section(self, None)
+        self._current_section = self._root_section
+
+        self._whitespace_matcher = _re.compile(r'\s+')
+        self._long_break_matcher = _re.compile(r'\n\n\n+')
+
+    # ===============================
+    # Section and indentation methods
+    # ===============================
+    def _indent(self):
+        self._current_indent += self._indent_increment
+        self._level += 1
+
+    def _dedent(self):
+        self._current_indent -= self._indent_increment
+        assert self._current_indent >= 0, 'Indent decreased below 0.'
+        self._level -= 1
+
+    class _Section(object):
+
+        def __init__(self, formatter, parent, heading=None):
+            self.formatter = formatter
+            self.parent = parent
+            self.heading = heading
+            self.items = []
+
+        def format_help(self):
+            # format the indented section
+            if self.parent is not None:
+                self.formatter._indent()
+            join = self.formatter._join_parts
+            for func, args in self.items:
+                func(*args)
+            item_help = join([func(*args) for func, args in self.items])
+            if self.parent is not None:
+                self.formatter._dedent()
+
+            # return nothing if the section was empty
+            if not item_help:
+                return ''
+
+            # add the heading if the section was non-empty
+            if self.heading is not SUPPRESS and self.heading is not None:
+                current_indent = self.formatter._current_indent
+                heading = '%*s%s:\n' % (current_indent, '', self.heading)
+            else:
+                heading = ''
+
+            # join the section-initial newline, the heading and the help
+            return join(['\n', heading, item_help, '\n'])
+
+    def _add_item(self, func, args):
+        self._current_section.items.append((func, args))
+
+    # ========================
+    # Message building methods
+    # ========================
+    def start_section(self, heading):
+        self._indent()
+        section = self._Section(self, self._current_section, heading)
+        self._add_item(section.format_help, [])
+        self._current_section = section
+
+    def end_section(self):
+        self._current_section = self._current_section.parent
+        self._dedent()
+
+    def add_text(self, text):
+        if text is not SUPPRESS and text is not None:
+            self._add_item(self._format_text, [text])
+
+    def add_usage(self, usage, actions, groups, prefix=None):
+        if usage is not SUPPRESS:
+            args = usage, actions, groups, prefix
+            self._add_item(self._format_usage, args)
+
+    def add_argument(self, action):
+        if action.help is not SUPPRESS:
+
+            # find all invocations
+            get_invocation = self._format_action_invocation
+            invocations = [get_invocation(action)]
+            for subaction in self._iter_indented_subactions(action):
+                invocations.append(get_invocation(subaction))
+
+            # update the maximum item length
+            invocation_length = max([len(s) for s in invocations])
+            action_length = invocation_length + self._current_indent
+            self._action_max_length = max(self._action_max_length,
+                                          action_length)
+
+            # add the item to the list
+            self._add_item(self._format_action, [action])
+
+    def add_arguments(self, actions):
+        for action in actions:
+            self.add_argument(action)
+
+    # =======================
+    # Help-formatting methods
+    # =======================
+    def format_help(self):
+        help = self._root_section.format_help()
+        if help:
+            help = self._long_break_matcher.sub('\n\n', help)
+            help = help.strip('\n') + '\n'
+        return help
+
+    def _join_parts(self, part_strings):
+        return ''.join([part
+                        for part in part_strings
+                        if part and part is not SUPPRESS])
+
+    def _format_usage(self, usage, actions, groups, prefix):
+        if prefix is None:
+            prefix = _('usage: ')
+
+        # if usage is specified, use that
+        if usage is not None:
+            usage = usage % dict(prog=self._prog)
+
+        # if no optionals or positionals are available, usage is just prog
+        elif usage is None and not actions:
+            usage = '%(prog)s' % dict(prog=self._prog)
+
+        # if optionals and positionals are available, calculate usage
+        elif usage is None:
+            prog = '%(prog)s' % dict(prog=self._prog)
+
+            # split optionals from positionals
+            optionals = []
+            positionals = []
+            for action in actions:
+                if action.option_strings:
+                    optionals.append(action)
+                else:
+                    positionals.append(action)
+
+            # build full usage string
+            format = self._format_actions_usage
+            action_usage = format(optionals + positionals, groups)
+            usage = ' '.join([s for s in [prog, action_usage] if s])
+
+            # wrap the usage parts if it's too long
+            text_width = self._width - self._current_indent
+            if len(prefix) + len(usage) > text_width:
+
+                # break usage into wrappable parts
+                part_regexp = r'\(.*?\)+|\[.*?\]+|\S+'
+                opt_usage = format(optionals, groups)
+                pos_usage = format(positionals, groups)
+                opt_parts = _re.findall(part_regexp, opt_usage)
+                pos_parts = _re.findall(part_regexp, pos_usage)
+                assert ' '.join(opt_parts) == opt_usage
+                assert ' '.join(pos_parts) == pos_usage
+
+                # helper for wrapping lines
+                def get_lines(parts, indent, prefix=None):
+                    lines = []
+                    line = []
+                    if prefix is not None:
+                        line_len = len(prefix) - 1
+                    else:
+                        line_len = len(indent) - 1
+                    for part in parts:
+                        if line_len + 1 + len(part) > text_width:
+                            lines.append(indent + ' '.join(line))
+                            line = []
+                            line_len = len(indent) - 1
+                        line.append(part)
+                        line_len += len(part) + 1
+                    if line:
+                        lines.append(indent + ' '.join(line))
+                    if prefix is not None:
+                        lines[0] = lines[0][len(indent):]
+                    return lines
+
+                # if prog is short, follow it with optionals or positionals
+                if len(prefix) + len(prog) <= 0.75 * text_width:
+                    indent = ' ' * (len(prefix) + len(prog) + 1)
+                    if opt_parts:
+                        lines = get_lines([prog] + opt_parts, indent, prefix)
+                        lines.extend(get_lines(pos_parts, indent))
+                    elif pos_parts:
+                        lines = get_lines([prog] + pos_parts, indent, prefix)
+                    else:
+                        lines = [prog]
+
+                # if prog is long, put it on its own line
+                else:
+                    indent = ' ' * len(prefix)
+                    parts = opt_parts + pos_parts
+                    lines = get_lines(parts, indent)
+                    if len(lines) > 1:
+                        lines = []
+                        lines.extend(get_lines(opt_parts, indent))
+                        lines.extend(get_lines(pos_parts, indent))
+                    lines = [prog] + lines
+
+                # join lines into usage
+                usage = '\n'.join(lines)
+
+        # prefix with 'usage:'
+        return '%s%s\n\n' % (prefix, usage)
+
+    def _format_actions_usage(self, actions, groups):
+        # find group indices and identify actions in groups
+        group_actions = set()
+        inserts = {}
+        for group in groups:
+            try:
+                start = actions.index(group._group_actions[0])
+            except ValueError:
+                continue
+            else:
+                end = start + len(group._group_actions)
+                if actions[start:end] == group._group_actions:
+                    for action in group._group_actions:
+                        group_actions.add(action)
+                    if not group.required:
+                        if start in inserts:
+                            inserts[start] += ' ['
+                        else:
+                            inserts[start] = '['
+                        inserts[end] = ']'
+                    else:
+                        if start in inserts:
+                            inserts[start] += ' ('
+                        else:
+                            inserts[start] = '('
+                        inserts[end] = ')'
+                    for i in range(start + 1, end):
+                        inserts[i] = '|'
+
+        # collect all actions format strings
+        parts = []
+        for i, action in enumerate(actions):
+
+            # suppressed arguments are marked with None
+            # remove | separators for suppressed arguments
+            if action.help is SUPPRESS:
+                parts.append(None)
+                if inserts.get(i) == '|':
+                    inserts.pop(i)
+                elif inserts.get(i + 1) == '|':
+                    inserts.pop(i + 1)
+
+            # produce all arg strings
+            elif not action.option_strings:
+                part = self._format_args(action, action.dest)
+
+                # if it's in a group, strip the outer []
+                if action in group_actions:
+                    if part[0] == '[' and part[-1] == ']':
+                        part = part[1:-1]
+
+                # add the action string to the list
+                parts.append(part)
+
+            # produce the first way to invoke the option in brackets
+            else:
+                option_string = action.option_strings[0]
+
+                # if the Optional doesn't take a value, format is:
+                #    -s or --long
+                if action.nargs == 0:
+                    part = '%s' % option_string
+
+                # if the Optional takes a value, format is:
+                #    -s ARGS or --long ARGS
+                else:
+                    default = action.dest.upper()
+                    args_string = self._format_args(action, default)
+                    part = '%s %s' % (option_string, args_string)
+
+                # make it look optional if it's not required or in a group
+                if not action.required and action not in group_actions:
+                    part = '[%s]' % part
+
+                # add the action string to the list
+                parts.append(part)
+
+        # insert things at the necessary indices
+        for i in sorted(inserts, reverse=True):
+            parts[i:i] = [inserts[i]]
+
+        # join all the action items with spaces
+        text = ' '.join([item for item in parts if item is not None])
+
+        # clean up separators for mutually exclusive groups
+        open = r'[\[(]'
+        close = r'[\])]'
+        text = _re.sub(r'(%s) ' % open, r'\1', text)
+        text = _re.sub(r' (%s)' % close, r'\1', text)
+        text = _re.sub(r'%s *%s' % (open, close), r'', text)
+        text = _re.sub(r'\(([^|]*)\)', r'\1', text)
+        text = text.strip()
+
+        # return the text
+        return text
+
+    def _format_text(self, text):
+        if '%(prog)' in text:
+            text = text % dict(prog=self._prog)
+        text_width = self._width - self._current_indent
+        indent = ' ' * self._current_indent
+        return self._fill_text(text, text_width, indent) + '\n\n'
+
+    def _format_action(self, action):
+        # determine the required width and the entry label
+        help_position = min(self._action_max_length + 2,
+                            self._max_help_position)
+        help_width = self._width - help_position
+        action_width = help_position - self._current_indent - 2
+        action_header = self._format_action_invocation(action)
+
+        # ho nelp; start on same line and add a final newline
+        if not action.help:
+            tup = self._current_indent, '', action_header
+            action_header = '%*s%s\n' % tup
+
+        # short action name; start on the same line and pad two spaces
+        elif len(action_header) <= action_width:
+            tup = self._current_indent, '', action_width, action_header
+            action_header = '%*s%-*s  ' % tup
+            indent_first = 0
+
+        # long action name; start on the next line
+        else:
+            tup = self._current_indent, '', action_header
+            action_header = '%*s%s\n' % tup
+            indent_first = help_position
+
+        # collect the pieces of the action help
+        parts = [action_header]
+
+        # if there was help for the action, add lines of help text
+        if action.help:
+            help_text = self._expand_help(action)
+            help_lines = self._split_lines(help_text, help_width)
+            parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
+            for line in help_lines[1:]:
+                parts.append('%*s%s\n' % (help_position, '', line))
+
+        # or add a newline if the description doesn't end with one
+        elif not action_header.endswith('\n'):
+            parts.append('\n')
+
+        # if there are any sub-actions, add their help as well
+        for subaction in self._iter_indented_subactions(action):
+            parts.append(self._format_action(subaction))
+
+        # return a single string
+        return self._join_parts(parts)
+
+    def _format_action_invocation(self, action):
+        if not action.option_strings:
+            metavar, = self._metavar_formatter(action, action.dest)(1)
+            return metavar
+
+        else:
+            parts = []
+
+            # if the Optional doesn't take a value, format is:
+            #    -s, --long
+            if action.nargs == 0:
+                parts.extend(action.option_strings)
+
+            # if the Optional takes a value, format is:
+            #    -s ARGS, --long ARGS
+            else:
+                default = action.dest.upper()
+                args_string = self._format_args(action, default)
+                for option_string in action.option_strings:
+                    parts.append('%s %s' % (option_string, args_string))
+
+            return ', '.join(parts)
+
+    def _metavar_formatter(self, action, default_metavar):
+        if action.metavar is not None:
+            result = action.metavar
+        elif action.choices is not None:
+            choice_strs = [str(choice) for choice in action.choices]
+            result = '{%s}' % ','.join(choice_strs)
+        else:
+            result = default_metavar
+
+        def format(tuple_size):
+            if isinstance(result, tuple):
+                return result
+            else:
+                return (result, ) * tuple_size
+        return format
+
+    def _format_args(self, action, default_metavar):
+        get_metavar = self._metavar_formatter(action, default_metavar)
+        if action.nargs is None:
+            result = '%s' % get_metavar(1)
+        elif action.nargs == OPTIONAL:
+            result = '[%s]' % get_metavar(1)
+        elif action.nargs == ZERO_OR_MORE:
+            result = '[%s [%s ...]]' % get_metavar(2)
+        elif action.nargs == ONE_OR_MORE:
+            result = '%s [%s ...]' % get_metavar(2)
+        elif action.nargs == REMAINDER:
+            result = '...'
+        elif action.nargs == PARSER:
+            result = '%s ...' % get_metavar(1)
+        else:
+            formats = ['%s' for _ in range(action.nargs)]
+            result = ' '.join(formats) % get_metavar(action.nargs)
+        return result
+
+    def _expand_help(self, action):
+        params = dict(vars(action), prog=self._prog)
+        for name in list(params):
+            if params[name] is SUPPRESS:
+                del params[name]
+        for name in list(params):
+            if hasattr(params[name], '__name__'):
+                params[name] = params[name].__name__
+        if params.get('choices') is not None:
+            choices_str = ', '.join([str(c) for c in params['choices']])
+            params['choices'] = choices_str
+        return self._get_help_string(action) % params
+
+    def _iter_indented_subactions(self, action):
+        try:
+            get_subactions = action._get_subactions
+        except AttributeError:
+            pass
+        else:
+            self._indent()
+            for subaction in get_subactions():
+                yield subaction
+            self._dedent()
+
+    def _split_lines(self, text, width):
+        text = self._whitespace_matcher.sub(' ', text).strip()
+        return _textwrap.wrap(text, width)
+
+    def _fill_text(self, text, width, indent):
+        text = self._whitespace_matcher.sub(' ', text).strip()
+        return _textwrap.fill(text, width, initial_indent=indent,
+                                           subsequent_indent=indent)
+
+    def _get_help_string(self, action):
+        return action.help
+
+
+class RawDescriptionHelpFormatter(HelpFormatter):
+    """Help message formatter which retains any formatting in descriptions.
+
+    Only the name of this class is considered a public API. All the methods
+    provided by the class are considered an implementation detail.
+    """
+
+    def _fill_text(self, text, width, indent):
+        return ''.join([indent + line for line in text.splitlines(True)])
+
+
+class RawTextHelpFormatter(RawDescriptionHelpFormatter):
+    """Help message formatter which retains formatting of all help text.
+
+    Only the name of this class is considered a public API. All the methods
+    provided by the class are considered an implementation detail.
+    """
+
+    def _split_lines(self, text, width):
+        return text.splitlines()
+
+
+class ArgumentDefaultsHelpFormatter(HelpFormatter):
+    """Help message formatter which adds default values to argument help.
+
+    Only the name of this class is considered a public API. All the methods
+    provided by the class are considered an implementation detail.
+    """
+
+    def _get_help_string(self, action):
+        help = action.help
+        if '%(default)' not in action.help:
+            if action.default is not SUPPRESS:
+                defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
+                if action.option_strings or action.nargs in defaulting_nargs:
+                    help += ' (default: %(default)s)'
+        return help
+
+
+# =====================
+# Options and Arguments
+# =====================
+
+def _get_action_name(argument):
+    if argument is None:
+        return None
+    elif argument.option_strings:
+        return  '/'.join(argument.option_strings)
+    elif argument.metavar not in (None, SUPPRESS):
+        return argument.metavar
+    elif argument.dest not in (None, SUPPRESS):
+        return argument.dest
+    else:
+        return None
+
+
+class ArgumentError(Exception):
+    """An error from creating or using an argument (optional or positional).
+
+    The string value of this exception is the message, augmented with
+    information about the argument that caused it.
+    """
+
+    def __init__(self, argument, message):
+        self.argument_name = _get_action_name(argument)
+        self.message = message
+
+    def __str__(self):
+        if self.argument_name is None:
+            format = '%(message)s'
+        else:
+            format = 'argument %(argument_name)s: %(message)s'
+        return format % dict(message=self.message,
+                             argument_name=self.argument_name)
+
+
+class ArgumentTypeError(Exception):
+    """An error from trying to convert a command line string to a type."""
+    pass
+
+
+# ==============
+# Action classes
+# ==============
+
+class Action(_AttributeHolder):
+    """Information about how to convert command line strings to Python objects.
+
+    Action objects are used by an ArgumentParser to represent the information
+    needed to parse a single argument from one or more strings from the
+    command line. The keyword arguments to the Action constructor are also
+    all attributes of Action instances.
+
+    Keyword Arguments:
+
+        - option_strings -- A list of command-line option strings which
+            should be associated with this action.
+
+        - dest -- The name of the attribute to hold the created object(s)
+
+        - nargs -- The number of command-line arguments that should be
+            consumed. By default, one argument will be consumed and a single
+            value will be produced.  Other values include:
+                - N (an integer) consumes N arguments (and produces a list)
+                - '?' consumes zero or one arguments
+                - '*' consumes zero or more arguments (and produces a list)
+                - '+' consumes one or more arguments (and produces a list)
+            Note that the difference between the default and nargs=1 is that
+            with the default, a single value will be produced, while with
+            nargs=1, a list containing a single value will be produced.
+
+        - const -- The value to be produced if the option is specified and the
+            option uses an action that takes no values.
+
+        - default -- The value to be produced if the option is not specified.
+
+        - type -- The type which the command-line arguments should be converted
+            to, should be one of 'string', 'int', 'float', 'complex' or a
+            callable object that accepts a single string argument. If None,
+            'string' is assumed.
+
+        - choices -- A container of values that should be allowed. If not None,
+            after a command-line argument has been converted to the appropriate
+            type, an exception will be raised if it is not a member of this
+            collection.
+
+        - required -- True if the action must always be specified at the
+            command line. This is only meaningful for optional command-line
+            arguments.
+
+        - help -- The help string describing the argument.
+
+        - metavar -- The name to be used for the option's argument with the
+            help string. If None, the 'dest' value will be used as the name.
+    """
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 nargs=None,
+                 const=None,
+                 default=None,
+                 type=None,
+                 choices=None,
+                 required=False,
+                 help=None,
+                 metavar=None):
+        self.option_strings = option_strings
+        self.dest = dest
+        self.nargs = nargs
+        self.const = const
+        self.default = default
+        self.type = type
+        self.choices = choices
+        self.required = required
+        self.help = help
+        self.metavar = metavar
+
+    def _get_kwargs(self):
+        names = [
+            'option_strings',
+            'dest',
+            'nargs',
+            'const',
+            'default',
+            'type',
+            'choices',
+            'help',
+            'metavar',
+        ]
+        return [(name, getattr(self, name)) for name in names]
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        raise NotImplementedError(_('.__call__() not defined'))
+
+
+class _StoreAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 nargs=None,
+                 const=None,
+                 default=None,
+                 type=None,
+                 choices=None,
+                 required=False,
+                 help=None,
+                 metavar=None):
+        if nargs == 0:
+            raise ValueError('nargs for store actions must be > 0; if you '
+                             'have nothing to store, actions such as store '
+                             'true or store const may be more appropriate')
+        if const is not None and nargs != OPTIONAL:
+            raise ValueError('nargs must be %r to supply const' % OPTIONAL)
+        super(_StoreAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=nargs,
+            const=const,
+            default=default,
+            type=type,
+            choices=choices,
+            required=required,
+            help=help,
+            metavar=metavar)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        setattr(namespace, self.dest, values)
+
+
+class _StoreConstAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 const,
+                 default=None,
+                 required=False,
+                 help=None,
+                 metavar=None):
+        super(_StoreConstAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=0,
+            const=const,
+            default=default,
+            required=required,
+            help=help)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        setattr(namespace, self.dest, self.const)
+
+
+class _StoreTrueAction(_StoreConstAction):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 default=False,
+                 required=False,
+                 help=None):
+        super(_StoreTrueAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            const=True,
+            default=default,
+            required=required,
+            help=help)
+
+
+class _StoreFalseAction(_StoreConstAction):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 default=True,
+                 required=False,
+                 help=None):
+        super(_StoreFalseAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            const=False,
+            default=default,
+            required=required,
+            help=help)
+
+
+class _AppendAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 nargs=None,
+                 const=None,
+                 default=None,
+                 type=None,
+                 choices=None,
+                 required=False,
+                 help=None,
+                 metavar=None):
+        if nargs == 0:
+            raise ValueError('nargs for append actions must be > 0; if arg '
+                             'strings are not supplying the value to append, '
+                             'the append const action may be more appropriate')
+        if const is not None and nargs != OPTIONAL:
+            raise ValueError('nargs must be %r to supply const' % OPTIONAL)
+        super(_AppendAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=nargs,
+            const=const,
+            default=default,
+            type=type,
+            choices=choices,
+            required=required,
+            help=help,
+            metavar=metavar)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        items = _copy.copy(_ensure_value(namespace, self.dest, []))
+        items.append(values)
+        setattr(namespace, self.dest, items)
+
+
+class _AppendConstAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 const,
+                 default=None,
+                 required=False,
+                 help=None,
+                 metavar=None):
+        super(_AppendConstAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=0,
+            const=const,
+            default=default,
+            required=required,
+            help=help,
+            metavar=metavar)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        items = _copy.copy(_ensure_value(namespace, self.dest, []))
+        items.append(self.const)
+        setattr(namespace, self.dest, items)
+
+
+class _CountAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 default=None,
+                 required=False,
+                 help=None):
+        super(_CountAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=0,
+            default=default,
+            required=required,
+            help=help)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        new_count = _ensure_value(namespace, self.dest, 0) + 1
+        setattr(namespace, self.dest, new_count)
+
+
+class _HelpAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 dest=SUPPRESS,
+                 default=SUPPRESS,
+                 help=None):
+        super(_HelpAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            default=default,
+            nargs=0,
+            help=help)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        parser.print_help()
+        parser.exit()
+
+
+class _VersionAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 version=None,
+                 dest=SUPPRESS,
+                 default=SUPPRESS,
+                 help="show program's version number and exit"):
+        super(_VersionAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            default=default,
+            nargs=0,
+            help=help)
+        self.version = version
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        version = self.version
+        if version is None:
+            version = parser.version
+        formatter = parser._get_formatter()
+        formatter.add_text(version)
+        parser.exit(message=formatter.format_help())
+
+
+class _SubParsersAction(Action):
+
+    class _ChoicesPseudoAction(Action):
+
+        def __init__(self, name, help):
+            sup = super(_SubParsersAction._ChoicesPseudoAction, self)
+            sup.__init__(option_strings=[], dest=name, help=help)
+
+    def __init__(self,
+                 option_strings,
+                 prog,
+                 parser_class,
+                 dest=SUPPRESS,
+                 help=None,
+                 metavar=None):
+
+        self._prog_prefix = prog
+        self._parser_class = parser_class
+        self._name_parser_map = {}
+        self._choices_actions = []
+
+        super(_SubParsersAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=PARSER,
+            choices=self._name_parser_map,
+            help=help,
+            metavar=metavar)
+
+    def add_parser(self, name, **kwargs):
+        # set prog from the existing prefix
+        if kwargs.get('prog') is None:
+            kwargs['prog'] = '%s %s' % (self._prog_prefix, name)
+
+        # create a pseudo-action to hold the choice help
+        if 'help' in kwargs:
+            help = kwargs.pop('help')
+            choice_action = self._ChoicesPseudoAction(name, help)
+            self._choices_actions.append(choice_action)
+
+        # create the parser and add it to the map
+        parser = self._parser_class(**kwargs)
+        self._name_parser_map[name] = parser
+        return parser
+
+    def _get_subactions(self):
+        return self._choices_actions
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        parser_name = values[0]
+        arg_strings = values[1:]
+
+        # set the parser name if requested
+        if self.dest is not SUPPRESS:
+            setattr(namespace, self.dest, parser_name)
+
+        # select the parser
+        try:
+            parser = self._name_parser_map[parser_name]
+        except KeyError:
+            tup = parser_name, ', '.join(self._name_parser_map)
+            msg = _('unknown parser %r (choices: %s)' % tup)
+            raise ArgumentError(self, msg)
+
+        # parse all the remaining options into the namespace
+        # store any unrecognized options on the object, so that the top
+        # level parser can decide what to do with them
+        namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)
+        if arg_strings:
+            vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
+            getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
+
+
+# ==============
+# Type classes
+# ==============
+
+class FileType(object):
+    """Factory for creating file object types
+
+    Instances of FileType are typically passed as type= arguments to the
+    ArgumentParser add_argument() method.
+
+    Keyword Arguments:
+        - mode -- A string indicating how the file is to be opened. Accepts the
+            same values as the builtin open() function.
+        - bufsize -- The file's desired buffer size. Accepts the same values as
+            the builtin open() function.
+    """
+
+    def __init__(self, mode='r', bufsize=None):
+        self._mode = mode
+        self._bufsize = bufsize
+
+    def __call__(self, string):
+        # the special argument "-" means sys.std{in,out}
+        if string == '-':
+            if 'r' in self._mode:
+                return _sys.stdin
+            elif 'w' in self._mode:
+                return _sys.stdout
+            else:
+                msg = _('argument "-" with mode %r' % self._mode)
+                raise ValueError(msg)
+
+        # all other arguments are used as file names
+        if self._bufsize:
+            return open(string, self._mode, self._bufsize)
+        else:
+            return open(string, self._mode)
+
+    def __repr__(self):
+        args = [self._mode, self._bufsize]
+        args_str = ', '.join([repr(arg) for arg in args if arg is not None])
+        return '%s(%s)' % (type(self).__name__, args_str)
+
+# ===========================
+# Optional and Positional Parsing
+# ===========================
+
+class Namespace(_AttributeHolder):
+    """Simple object for storing attributes.
+
+    Implements equality by attribute names and values, and provides a simple
+    string representation.
+    """
+
+    def __init__(self, **kwargs):
+        for name in kwargs:
+            setattr(self, name, kwargs[name])
+
+    __hash__ = None
+
+    def __eq__(self, other):
+        return vars(self) == vars(other)
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __contains__(self, key):
+        return key in self.__dict__
+
+
+class _ActionsContainer(object):
+
+    def __init__(self,
+                 description,
+                 prefix_chars,
+                 argument_default,
+                 conflict_handler):
+        super(_ActionsContainer, self).__init__()
+
+        self.description = description
+        self.argument_default = argument_default
+        self.prefix_chars = prefix_chars
+        self.conflict_handler = conflict_handler
+
+        # set up registries
+        self._registries = {}
+
+        # register actions
+        self.register('action', None, _StoreAction)
+        self.register('action', 'store', _StoreAction)
+        self.register('action', 'store_const', _StoreConstAction)
+        self.register('action', 'store_true', _StoreTrueAction)
+        self.register('action', 'store_false', _StoreFalseAction)
+        self.register('action', 'append', _AppendAction)
+        self.register('action', 'append_const', _AppendConstAction)
+        self.register('action', 'count', _CountAction)
+        self.register('action', 'help', _HelpAction)
+        self.register('action', 'version', _VersionAction)
+        self.register('action', 'parsers', _SubParsersAction)
+
+        # raise an exception if the conflict handler is invalid
+        self._get_handler()
+
+        # action storage
+        self._actions = []
+        self._option_string_actions = {}
+
+        # groups
+        self._action_groups = []
+        self._mutually_exclusive_groups = []
+
+        # defaults storage
+        self._defaults = {}
+
+        # determines whether an "option" looks like a negative number
+        self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$')
+
+        # whether or not there are any optionals that look like negative
+        # numbers -- uses a list so it can be shared and edited
+        self._has_negative_number_optionals = []
+
+    # ====================
+    # Registration methods
+    # ====================
+    def register(self, registry_name, value, object):
+        registry = self._registries.setdefault(registry_name, {})
+        registry[value] = object
+
+    def _registry_get(self, registry_name, value, default=None):
+        return self._registries[registry_name].get(value, default)
+
+    # ==================================
+    # Namespace default accessor methods
+    # ==================================
+    def set_defaults(self, **kwargs):
+        self._defaults.update(kwargs)
+
+        # if these defaults match any existing arguments, replace
+        # the previous default on the object with the new one
+        for action in self._actions:
+            if action.dest in kwargs:
+                action.default = kwargs[action.dest]
+
+    def get_default(self, dest):
+        for action in self._actions:
+            if action.dest == dest and action.default is not None:
+                return action.default
+        return self._defaults.get(dest, None)
+
+
+    # =======================
+    # Adding argument actions
+    # =======================
+    def add_argument(self, *args, **kwargs):
+        """
+        add_argument(dest, ..., name=value, ...)
+        add_argument(option_string, option_string, ..., name=value, ...)
+        """
+
+        # if no positional args are supplied or only one is supplied and
+        # it doesn't look like an option string, parse a positional
+        # argument
+        chars = self.prefix_chars
+        if not args or len(args) == 1 and args[0][0] not in chars:
+            if args and 'dest' in kwargs:
+                raise ValueError('dest supplied twice for positional argument')
+            kwargs = self._get_positional_kwargs(*args, **kwargs)
+
+        # otherwise, we're adding an optional argument
+        else:
+            kwargs = self._get_optional_kwargs(*args, **kwargs)
+
+        # if no default was supplied, use the parser-level default
+        if 'default' not in kwargs:
+            dest = kwargs['dest']
+            if dest in self._defaults:
+                kwargs['default'] = self._defaults[dest]
+            elif self.argument_default is not None:
+                kwargs['default'] = self.argument_default
+
+        # create the action object, and add it to the parser
+        action_class = self._pop_action_class(kwargs)
+        if not _callable(action_class):
+            raise ValueError('unknown action "%s"' % action_class)
+        action = action_class(**kwargs)
+
+        # raise an error if the action type is not callable
+        type_func = self._registry_get('type', action.type, action.type)
+        if not _callable(type_func):
+            raise ValueError('%r is not callable' % type_func)
+
+        return self._add_action(action)
+
+    def add_argument_group(self, *args, **kwargs):
+        group = _ArgumentGroup(self, *args, **kwargs)
+        self._action_groups.append(group)
+        return group
+
+    def add_mutually_exclusive_group(self, **kwargs):
+        group = _MutuallyExclusiveGroup(self, **kwargs)
+        self._mutually_exclusive_groups.append(group)
+        return group
+
+    def _add_action(self, action):
+        # resolve any conflicts
+        self._check_conflict(action)
+
+        # add to actions list
+        self._actions.append(action)
+        action.container = self
+
+        # index the action by any option strings it has
+        for option_string in action.option_strings:
+            self._option_string_actions[option_string] = action
+
+        # set the flag if any option strings look like negative numbers
+        for option_string in action.option_strings:
+            if self._negative_number_matcher.match(option_string):
+                if not self._has_negative_number_optionals:
+                    self._has_negative_number_optionals.append(True)
+
+        # return the created action
+        return action
+
+    def _remove_action(self, action):
+        self._actions.remove(action)
+
+    def _add_container_actions(self, container):
+        # collect groups by titles
+        title_group_map = {}
+        for group in self._action_groups:
+            if group.title in title_group_map:
+                msg = _('cannot merge actions - two groups are named %r')
+                raise ValueError(msg % (group.title))
+            title_group_map[group.title] = group
+
+        # map each action to its group
+        group_map = {}
+        for group in container._action_groups:
+
+            # if a group with the title exists, use that, otherwise
+            # create a new group matching the container's group
+            if group.title not in title_group_map:
+                title_group_map[group.title] = self.add_argument_group(
+                    title=group.title,
+                    description=group.description,
+                    conflict_handler=group.conflict_handler)
+
+            # map the actions to their new group
+            for action in group._group_actions:
+                group_map[action] = title_group_map[group.title]
+
+        # add container's mutually exclusive groups
+        # NOTE: if add_mutually_exclusive_group ever gains title= and
+        # description= then this code will need to be expanded as above
+        for group in container._mutually_exclusive_groups:
+            mutex_group = self.add_mutually_exclusive_group(
+                required=group.required)
+
+            # map the actions to their new mutex group
+            for action in group._group_actions:
+                group_map[action] = mutex_group
+
+        # add all actions to this container or their group
+        for action in container._actions:
+            group_map.get(action, self)._add_action(action)
+
+    def _get_positional_kwargs(self, dest, **kwargs):
+        # make sure required is not specified
+        if 'required' in kwargs:
+            msg = _("'required' is an invalid argument for positionals")
+            raise TypeError(msg)
+
+        # mark positional arguments as required if at least one is
+        # always required
+        if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
+            kwargs['required'] = True
+        if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
+            kwargs['required'] = True
+
+        # return the keyword arguments with no option strings
+        return dict(kwargs, dest=dest, option_strings=[])
+
+    def _get_optional_kwargs(self, *args, **kwargs):
+        # determine short and long option strings
+        option_strings = []
+        long_option_strings = []
+        for option_string in args:
+            # error on strings that don't start with an appropriate prefix
+            if not option_string[0] in self.prefix_chars:
+                msg = _('invalid option string %r: '
+                        'must start with a character %r')
+                tup = option_string, self.prefix_chars
+                raise ValueError(msg % tup)
+
+            # strings starting with two prefix characters are long options
+            option_strings.append(option_string)
+            if option_string[0] in self.prefix_chars:
+                if len(option_string) > 1:
+                    if option_string[1] in self.prefix_chars:
+                        long_option_strings.append(option_string)
+
+        # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
+        dest = kwargs.pop('dest', None)
+        if dest is None:
+            if long_option_strings:
+                dest_option_string = long_option_strings[0]
+            else:
+                dest_option_string = option_strings[0]
+            dest = dest_option_string.lstrip(self.prefix_chars)
+            if not dest:
+                msg = _('dest= is required for options like %r')
+                raise ValueError(msg % option_string)
+            dest = dest.replace('-', '_')
+
+        # return the updated keyword arguments
+        return dict(kwargs, dest=dest, option_strings=option_strings)
+
+    def _pop_action_class(self, kwargs, default=None):
+        action = kwargs.pop('action', default)
+        return self._registry_get('action', action, action)
+
+    def _get_handler(self):
+        # determine function from conflict handler string
+        handler_func_name = '_handle_conflict_%s' % self.conflict_handler
+        try:
+            return getattr(self, handler_func_name)
+        except AttributeError:
+            msg = _('invalid conflict_resolution value: %r')
+            raise ValueError(msg % self.conflict_handler)
+
+    def _check_conflict(self, action):
+
+        # find all options that conflict with this option
+        confl_optionals = []
+        for option_string in action.option_strings:
+            if option_string in self._option_string_actions:
+                confl_optional = self._option_string_actions[option_string]
+                confl_optionals.append((option_string, confl_optional))
+
+        # resolve any conflicts
+        if confl_optionals:
+            conflict_handler = self._get_handler()
+            conflict_handler(action, confl_optionals)
+
+    def _handle_conflict_error(self, action, conflicting_actions):
+        message = _('conflicting option string(s): %s')
+        conflict_string = ', '.join([option_string
+                                     for option_string, action
+                                     in conflicting_actions])
+        raise ArgumentError(action, message % conflict_string)
+
+    def _handle_conflict_resolve(self, action, conflicting_actions):
+
+        # remove all conflicting options
+        for option_string, action in conflicting_actions:
+
+            # remove the conflicting option
+            action.option_strings.remove(option_string)
+            self._option_string_actions.pop(option_string, None)
+
+            # if the option now has no option string, remove it from the
+            # container holding it
+            if not action.option_strings:
+                action.container._remove_action(action)
+
+
+class _ArgumentGroup(_ActionsContainer):
+
+    def __init__(self, container, title=None, description=None, **kwargs):
+        # add any missing keyword arguments by checking the container
+        update = kwargs.setdefault
+        update('conflict_handler', container.conflict_handler)
+        update('prefix_chars', container.prefix_chars)
+        update('argument_default', container.argument_default)
+        super_init = super(_ArgumentGroup, self).__init__
+        super_init(description=description, **kwargs)
+
+        # group attributes
+        self.title = title
+        self._group_actions = []
+
+        # share most attributes with the container
+        self._registries = container._registries
+        self._actions = container._actions
+        self._option_string_actions = container._option_string_actions
+        self._defaults = container._defaults
+        self._has_negative_number_optionals = \
+            container._has_negative_number_optionals
+
+    def _add_action(self, action):
+        action = super(_ArgumentGroup, self)._add_action(action)
+        self._group_actions.append(action)
+        return action
+
+    def _remove_action(self, action):
+        super(_ArgumentGroup, self)._remove_action(action)
+        self._group_actions.remove(action)
+
+
+class _MutuallyExclusiveGroup(_ArgumentGroup):
+
+    def __init__(self, container, required=False):
+        super(_MutuallyExclusiveGroup, self).__init__(container)
+        self.required = required
+        self._container = container
+
+    def _add_action(self, action):
+        if action.required:
+            msg = _('mutually exclusive arguments must be optional')
+            raise ValueError(msg)
+        action = self._container._add_action(action)
+        self._group_actions.append(action)
+        return action
+
+    def _remove_action(self, action):
+        self._container._remove_action(action)
+        self._group_actions.remove(action)
+
+
+class ArgumentParser(_AttributeHolder, _ActionsContainer):
+    """Object for parsing command line strings into Python objects.
+
+    Keyword Arguments:
+        - prog -- The name of the program (default: sys.argv[0])
+        - usage -- A usage message (default: auto-generated from arguments)
+        - description -- A description of what the program does
+        - epilog -- Text following the argument descriptions
+        - parents -- Parsers whose arguments should be copied into this one
+        - formatter_class -- HelpFormatter class for printing help messages
+        - prefix_chars -- Characters that prefix optional arguments
+        - fromfile_prefix_chars -- Characters that prefix files containing
+            additional arguments
+        - argument_default -- The default value for all arguments
+        - conflict_handler -- String indicating how to handle conflicts
+        - add_help -- Add a -h/-help option
+    """
+
+    def __init__(self,
+                 prog=None,
+                 usage=None,
+                 description=None,
+                 epilog=None,
+                 version=None,
+                 parents=[],
+                 formatter_class=HelpFormatter,
+                 prefix_chars='-',
+                 fromfile_prefix_chars=None,
+                 argument_default=None,
+                 conflict_handler='error',
+                 add_help=True):
+
+        if version is not None:
+            import warnings
+            warnings.warn(
+                """The "version" argument to ArgumentParser is deprecated. """
+                """Please use """
+                """"add_argument(..., action='version', version="N", ...)" """
+                """instead""", DeprecationWarning)
+
+        superinit = super(ArgumentParser, self).__init__
+        superinit(description=description,
+                  prefix_chars=prefix_chars,
+                  argument_default=argument_default,
+                  conflict_handler=conflict_handler)
+
+        # default setting for prog
+        if prog is None:
+            prog = _os.path.basename(_sys.argv[0])
+
+        self.prog = prog
+        self.usage = usage
+        self.epilog = epilog
+        self.version = version
+        self.formatter_class = formatter_class
+        self.fromfile_prefix_chars = fromfile_prefix_chars
+        self.add_help = add_help
+
+        add_group = self.add_argument_group
+        self._positionals = add_group(_('positional arguments'))
+        self._optionals = add_group(_('optional arguments'))
+        self._subparsers = None
+
+        # register types
+        def identity(string):
+            return string
+        self.register('type', None, identity)
+
+        # add help and version arguments if necessary
+        # (using explicit default to override global argument_default)
+        if '-' in prefix_chars:
+            default_prefix = '-'
+        else:
+            default_prefix = prefix_chars[0]
+        if self.add_help:
+            self.add_argument(
+                default_prefix+'h', default_prefix*2+'help',
+                action='help', default=SUPPRESS,
+                help=_('show this help message and exit'))
+        if self.version:
+            self.add_argument(
+                default_prefix+'v', default_prefix*2+'version',
+                action='version', default=SUPPRESS,
+                version=self.version,
+                help=_("show program's version number and exit"))
+
+        # add parent arguments and defaults
+        for parent in parents:
+            self._add_container_actions(parent)
+            try:
+                defaults = parent._defaults
+            except AttributeError:
+                pass
+            else:
+                self._defaults.update(defaults)
+
+    # =======================
+    # Pretty __repr__ methods
+    # =======================
+    def _get_kwargs(self):
+        names = [
+            'prog',
+            'usage',
+            'description',
+            'version',
+            'formatter_class',
+            'conflict_handler',
+            'add_help',
+        ]
+        return [(name, getattr(self, name)) for name in names]
+
+    # ==================================
+    # Optional/Positional adding methods
+    # ==================================
+    def add_subparsers(self, **kwargs):
+        if self._subparsers is not None:
+            self.error(_('cannot have multiple subparser arguments'))
+
+        # add the parser class to the arguments if it's not present
+        kwargs.setdefault('parser_class', type(self))
+
+        if 'title' in kwargs or 'description' in kwargs:
+            title = _(kwargs.pop('title', 'subcommands'))
+            description = _(kwargs.pop('description', None))
+            self._subparsers = self.add_argument_group(title, description)
+        else:
+            self._subparsers = self._positionals
+
+        # prog defaults to the usage message of this parser, skipping
+        # optional arguments and with no "usage:" prefix
+        if kwargs.get('prog') is None:
+            formatter = self._get_formatter()
+            positionals = self._get_positional_actions()
+            groups = self._mutually_exclusive_groups
+            formatter.add_usage(self.usage, positionals, groups, '')
+            kwargs['prog'] = formatter.format_help().strip()
+
+        # create the parsers action and add it to the positionals list
+        parsers_class = self._pop_action_class(kwargs, 'parsers')
+        action = parsers_class(option_strings=[], **kwargs)
+        self._subparsers._add_action(action)
+
+        # return the created parsers action
+        return action
+
+    def _add_action(self, action):
+        if action.option_strings:
+            self._optionals._add_action(action)
+        else:
+            self._positionals._add_action(action)
+        return action
+
+    def _get_optional_actions(self):
+        return [action
+                for action in self._actions
+                if action.option_strings]
+
+    def _get_positional_actions(self):
+        return [action
+                for action in self._actions
+                if not action.option_strings]
+
+    # =====================================
+    # Command line argument parsing methods
+    # =====================================
+    def parse_args(self, args=None, namespace=None):
+        args, argv = self.parse_known_args(args, namespace)
+        if argv:
+            msg = _('unrecognized arguments: %s')
+            self.error(msg % ' '.join(argv))
+        return args
+
+    def parse_known_args(self, args=None, namespace=None):
+        # args default to the system args
+        if args is None:
+            args = _sys.argv[1:]
+
+        # default Namespace built from parser defaults
+        if namespace is None:
+            namespace = Namespace()
+
+        # add any action defaults that aren't present
+        for action in self._actions:
+            if action.dest is not SUPPRESS:
+                if not hasattr(namespace, action.dest):
+                    if action.default is not SUPPRESS:
+                        default = action.default
+                        if isinstance(action.default, basestring):
+                            default = self._get_value(action, default)
+                        setattr(namespace, action.dest, default)
+
+        # add any parser defaults that aren't present
+        for dest in self._defaults:
+            if not hasattr(namespace, dest):
+                setattr(namespace, dest, self._defaults[dest])
+
+        # parse the arguments and exit if there are any errors
+        try:
+            namespace, args = self._parse_known_args(args, namespace)
+            if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
+                args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
+                delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
+            return namespace, args
+        except ArgumentError:
+            err = _sys.exc_info()[1]
+            self.error(str(err))
+
+    def _parse_known_args(self, arg_strings, namespace):
+        # replace arg strings that are file references
+        if self.fromfile_prefix_chars is not None:
+            arg_strings = self._read_args_from_files(arg_strings)
+
+        # map all mutually exclusive arguments to the other arguments
+        # they can't occur with
+        action_conflicts = {}
+        for mutex_group in self._mutually_exclusive_groups:
+            group_actions = mutex_group._group_actions
+            for i, mutex_action in enumerate(mutex_group._group_actions):
+                conflicts = action_conflicts.setdefault(mutex_action, [])
+                conflicts.extend(group_actions[:i])
+                conflicts.extend(group_actions[i + 1:])
+
+        # find all option indices, and determine the arg_string_pattern
+        # which has an 'O' if there is an option at an index,
+        # an 'A' if there is an argument, or a '-' if there is a '--'
+        option_string_indices = {}
+        arg_string_pattern_parts = []
+        arg_strings_iter = iter(arg_strings)
+        for i, arg_string in enumerate(arg_strings_iter):
+
+            # all args after -- are non-options
+            if arg_string == '--':
+                arg_string_pattern_parts.append('-')
+                for arg_string in arg_strings_iter:
+                    arg_string_pattern_parts.append('A')
+
+            # otherwise, add the arg to the arg strings
+            # and note the index if it was an option
+            else:
+                option_tuple = self._parse_optional(arg_string)
+                if option_tuple is None:
+                    pattern = 'A'
+                else:
+                    option_string_indices[i] = option_tuple
+                    pattern = 'O'
+                arg_string_pattern_parts.append(pattern)
+
+        # join the pieces together to form the pattern
+        arg_strings_pattern = ''.join(arg_string_pattern_parts)
+
+        # converts arg strings to the appropriate and then takes the action
+        seen_actions = set()
+        seen_non_default_actions = set()
+
+        def take_action(action, argument_strings, option_string=None):
+            seen_actions.add(action)
+            argument_values = self._get_values(action, argument_strings)
+
+            # error if this argument is not allowed with other previously
+            # seen arguments, assuming that actions that use the default
+            # value don't really count as "present"
+            if argument_values is not action.default:
+                seen_non_default_actions.add(action)
+                for conflict_action in action_conflicts.get(action, []):
+                    if conflict_action in seen_non_default_actions:
+                        msg = _('not allowed with argument %s')
+                        action_name = _get_action_name(conflict_action)
+                        raise ArgumentError(action, msg % action_name)
+
+            # take the action if we didn't receive a SUPPRESS value
+            # (e.g. from a default)
+            if argument_values is not SUPPRESS:
+                action(self, namespace, argument_values, option_string)
+
+        # function to convert arg_strings into an optional action
+        def consume_optional(start_index):
+
+            # get the optional identified at this index
+            option_tuple = option_string_indices[start_index]
+            action, option_string, explicit_arg = option_tuple
+
+            # identify additional optionals in the same arg string
+            # (e.g. -xyz is the same as -x -y -z if no args are required)
+            match_argument = self._match_argument
+            action_tuples = []
+            while True:
+
+                # if we found no optional action, skip it
+                if action is None:
+                    extras.append(arg_strings[start_index])
+                    return start_index + 1
+
+                # if there is an explicit argument, try to match the
+                # optional's string arguments to only this
+                if explicit_arg is not None:
+                    arg_count = match_argument(action, 'A')
+
+                    # if the action is a single-dash option and takes no
+                    # arguments, try to parse more single-dash options out
+                    # of the tail of the option string
+                    chars = self.prefix_chars
+                    if arg_count == 0 and option_string[1] not in chars:
+                        action_tuples.append((action, [], option_string))
+                        char = option_string[0]
+                        option_string = char + explicit_arg[0]
+                        new_explicit_arg = explicit_arg[1:] or None
+                        optionals_map = self._option_string_actions
+                        if option_string in optionals_map:
+                            action = optionals_map[option_string]
+                            explicit_arg = new_explicit_arg
+                        else:
+                            msg = _('ignored explicit argument %r')
+                            raise ArgumentError(action, msg % explicit_arg)
+
+                    # if the action expect exactly one argument, we've
+                    # successfully matched the option; exit the loop
+                    elif arg_count == 1:
+                        stop = start_index + 1
+                        args = [explicit_arg]
+                        action_tuples.append((action, args, option_string))
+                        break
+
+                    # error if a double-dash option did not use the
+                    # explicit argument
+                    else:
+                        msg = _('ignored explicit argument %r')
+                        raise ArgumentError(action, msg % explicit_arg)
+
+                # if there is no explicit argument, try to match the
+                # optional's string arguments with the following strings
+                # if successful, exit the loop
+                else:
+                    start = start_index + 1
+                    selected_patterns = arg_strings_pattern[start:]
+                    arg_count = match_argument(action, selected_patterns)
+                    stop = start + arg_count
+                    args = arg_strings[start:stop]
+                    action_tuples.append((action, args, option_string))
+                    break
+
+            # add the Optional to the list and return the index at which
+            # the Optional's string args stopped
+            assert action_tuples
+            for action, args, option_string in action_tuples:
+                take_action(action, args, option_string)
+            return stop
+
+        # the list of Positionals left to be parsed; this is modified
+        # by consume_positionals()
+        positionals = self._get_positional_actions()
+
+        # function to convert arg_strings into positional actions
+        def consume_positionals(start_index):
+            # match as many Positionals as possible
+            match_partial = self._match_arguments_partial
+            selected_pattern = arg_strings_pattern[start_index:]
+            arg_counts = match_partial(positionals, selected_pattern)
+
+            # slice off the appropriate arg strings for each Positional
+            # and add the Positional and its args to the list
+            for action, arg_count in zip(positionals, arg_counts):
+                args = arg_strings[start_index: start_index + arg_count]
+                start_index += arg_count
+                take_action(action, args)
+
+            # slice off the Positionals that we just parsed and return the
+            # index at which the Positionals' string args stopped
+            positionals[:] = positionals[len(arg_counts):]
+            return start_index
+
+        # consume Positionals and Optionals alternately, until we have
+        # passed the last option string
+        extras = []
+        start_index = 0
+        if option_string_indices:
+            max_option_string_index = max(option_string_indices)
+        else:
+            max_option_string_index = -1
+        while start_index <= max_option_string_index:
+
+            # consume any Positionals preceding the next option
+            next_option_string_index = min([
+                index
+                for index in option_string_indices
+                if index >= start_index])
+            if start_index != next_option_string_index:
+                positionals_end_index = consume_positionals(start_index)
+
+                # only try to parse the next optional if we didn't consume
+                # the option string during the positionals parsing
+                if positionals_end_index > start_index:
+                    start_index = positionals_end_index
+                    continue
+                else:
+                    start_index = positionals_end_index
+
+            # if we consumed all the positionals we could and we're not
+            # at the index of an option string, there were extra arguments
+            if start_index not in option_string_indices:
+                strings = arg_strings[start_index:next_option_string_index]
+                extras.extend(strings)
+                start_index = next_option_string_index
+
+            # consume the next optional and any arguments for it
+            start_index = consume_optional(start_index)
+
+        # consume any positionals following the last Optional
+        stop_index = consume_positionals(start_index)
+
+        # if we didn't consume all the argument strings, there were extras
+        extras.extend(arg_strings[stop_index:])
+
+        # if we didn't use all the Positional objects, there were too few
+        # arg strings supplied.
+        if positionals:
+            self.error(_('too few arguments'))
+
+        # make sure all required actions were present
+        for action in self._actions:
+            if action.required:
+                if action not in seen_actions:
+                    name = _get_action_name(action)
+                    self.error(_('argument %s is required') % name)
+
+        # make sure all required groups had one option present
+        for group in self._mutually_exclusive_groups:
+            if group.required:
+                for action in group._group_actions:
+                    if action in seen_non_default_actions:
+                        break
+
+                # if no actions were used, report the error
+                else:
+                    names = [_get_action_name(action)
+                             for action in group._group_actions
+                             if action.help is not SUPPRESS]
+                    msg = _('one of the arguments %s is required')
+                    self.error(msg % ' '.join(names))
+
+        # return the updated namespace and the extra arguments
+        return namespace, extras
+
+    def _read_args_from_files(self, arg_strings):
+        # expand arguments referencing files
+        new_arg_strings = []
+        for arg_string in arg_strings:
+
+            # for regular arguments, just add them back into the list
+            if arg_string[0] not in self.fromfile_prefix_chars:
+                new_arg_strings.append(arg_string)
+
+            # replace arguments referencing files with the file content
+            else:
+                try:
+                    args_file = open(arg_string[1:])
+                    try:
+                        arg_strings = []
+                        for arg_line in args_file.read().splitlines():
+                            for arg in self.convert_arg_line_to_args(arg_line):
+                                arg_strings.append(arg)
+                        arg_strings = self._read_args_from_files(arg_strings)
+                        new_arg_strings.extend(arg_strings)
+                    finally:
+                        args_file.close()
+                except IOError:
+                    err = _sys.exc_info()[1]
+                    self.error(str(err))
+
+        # return the modified argument list
+        return new_arg_strings
+
+    def convert_arg_line_to_args(self, arg_line):
+        return [arg_line]
+
+    def _match_argument(self, action, arg_strings_pattern):
+        # match the pattern for this action to the arg strings
+        nargs_pattern = self._get_nargs_pattern(action)
+        match = _re.match(nargs_pattern, arg_strings_pattern)
+
+        # raise an exception if we weren't able to find a match
+        if match is None:
+            nargs_errors = {
+                None: _('expected one argument'),
+                OPTIONAL: _('expected at most one argument'),
+                ONE_OR_MORE: _('expected at least one argument'),
+            }
+            default = _('expected %s argument(s)') % action.nargs
+            msg = nargs_errors.get(action.nargs, default)
+            raise ArgumentError(action, msg)
+
+        # return the number of arguments matched
+        return len(match.group(1))
+
+    def _match_arguments_partial(self, actions, arg_strings_pattern):
+        # progressively shorten the actions list by slicing off the
+        # final actions until we find a match
+        result = []
+        for i in range(len(actions), 0, -1):
+            actions_slice = actions[:i]
+            pattern = ''.join([self._get_nargs_pattern(action)
+                               for action in actions_slice])
+            match = _re.match(pattern, arg_strings_pattern)
+            if match is not None:
+                result.extend([len(string) for string in match.groups()])
+                break
+
+        # return the list of arg string counts
+        return result
+
+    def _parse_optional(self, arg_string):
+        # if it's an empty string, it was meant to be a positional
+        if not arg_string:
+            return None
+
+        # if it doesn't start with a prefix, it was meant to be positional
+        if not arg_string[0] in self.prefix_chars:
+            return None
+
+        # if the option string is present in the parser, return the action
+        if arg_string in self._option_string_actions:
+            action = self._option_string_actions[arg_string]
+            return action, arg_string, None
+
+        # if it's just a single character, it was meant to be positional
+        if len(arg_string) == 1:
+            return None
+
+        # if the option string before the "=" is present, return the action
+        if '=' in arg_string:
+            option_string, explicit_arg = arg_string.split('=', 1)
+            if option_string in self._option_string_actions:
+                action = self._option_string_actions[option_string]
+                return action, option_string, explicit_arg
+
+        # search through all possible prefixes of the option string
+        # and all actions in the parser for possible interpretations
+        option_tuples = self._get_option_tuples(arg_string)
+
+        # if multiple actions match, the option string was ambiguous
+        if len(option_tuples) > 1:
+            options = ', '.join([option_string
+                for action, option_string, explicit_arg in option_tuples])
+            tup = arg_string, options
+            self.error(_('ambiguous option: %s could match %s') % tup)
+
+        # if exactly one action matched, this segmentation is good,
+        # so return the parsed action
+        elif len(option_tuples) == 1:
+            option_tuple, = option_tuples
+            return option_tuple
+
+        # if it was not found as an option, but it looks like a negative
+        # number, it was meant to be positional
+        # unless there are negative-number-like options
+        if self._negative_number_matcher.match(arg_string):
+            if not self._has_negative_number_optionals:
+                return None
+
+        # if it contains a space, it was meant to be a positional
+        if ' ' in arg_string:
+            return None
+
+        # it was meant to be an optional but there is no such option
+        # in this parser (though it might be a valid option in a subparser)
+        return None, arg_string, None
+
+    def _get_option_tuples(self, option_string):
+        result = []
+
+        # option strings starting with two prefix characters are only
+        # split at the '='
+        chars = self.prefix_chars
+        if option_string[0] in chars and option_string[1] in chars:
+            if '=' in option_string:
+                option_prefix, explicit_arg = option_string.split('=', 1)
+            else:
+                option_prefix = option_string
+                explicit_arg = None
+            for option_string in self._option_string_actions:
+                if option_string.startswith(option_prefix):
+                    action = self._option_string_actions[option_string]
+                    tup = action, option_string, explicit_arg
+                    result.append(tup)
+
+        # single character options can be concatenated with their arguments
+        # but multiple character options always have to have their argument
+        # separate
+        elif option_string[0] in chars and option_string[1] not in chars:
+            option_prefix = option_string
+            explicit_arg = None
+            short_option_prefix = option_string[:2]
+            short_explicit_arg = option_string[2:]
+
+            for option_string in self._option_string_actions:
+                if option_string == short_option_prefix:
+                    action = self._option_string_actions[option_string]
+                    tup = action, option_string, short_explicit_arg
+                    result.append(tup)
+                elif option_string.startswith(option_prefix):
+                    action = self._option_string_actions[option_string]
+                    tup = action, option_string, explicit_arg
+                    result.append(tup)
+
+        # shouldn't ever get here
+        else:
+            self.error(_('unexpected option string: %s') % option_string)
+
+        # return the collected option tuples
+        return result
+
+    def _get_nargs_pattern(self, action):
+        # in all examples below, we have to allow for '--' args
+        # which are represented as '-' in the pattern
+        nargs = action.nargs
+
+        # the default (None) is assumed to be a single argument
+        if nargs is None:
+            nargs_pattern = '(-*A-*)'
+
+        # allow zero or one arguments
+        elif nargs == OPTIONAL:
+            nargs_pattern = '(-*A?-*)'
+
+        # allow zero or more arguments
+        elif nargs == ZERO_OR_MORE:
+            nargs_pattern = '(-*[A-]*)'
+
+        # allow one or more arguments
+        elif nargs == ONE_OR_MORE:
+            nargs_pattern = '(-*A[A-]*)'
+
+        # allow any number of options or arguments
+        elif nargs == REMAINDER:
+            nargs_pattern = '([-AO]*)'
+
+        # allow one argument followed by any number of options or arguments
+        elif nargs == PARSER:
+            nargs_pattern = '(-*A[-AO]*)'
+
+        # all others should be integers
+        else:
+            nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs)
+
+        # if this is an optional action, -- is not allowed
+        if action.option_strings:
+            nargs_pattern = nargs_pattern.replace('-*', '')
+            nargs_pattern = nargs_pattern.replace('-', '')
+
+        # return the pattern
+        return nargs_pattern
+
+    # ========================
+    # Value conversion methods
+    # ========================
+    def _get_values(self, action, arg_strings):
+        # for everything but PARSER args, strip out '--'
+        if action.nargs not in [PARSER, REMAINDER]:
+            arg_strings = [s for s in arg_strings if s != '--']
+
+        # optional argument produces a default when not present
+        if not arg_strings and action.nargs == OPTIONAL:
+            if action.option_strings:
+                value = action.const
+            else:
+                value = action.default
+            if isinstance(value, basestring):
+                value = self._get_value(action, value)
+                self._check_value(action, value)
+
+        # when nargs='*' on a positional, if there were no command-line
+        # args, use the default if it is anything other than None
+        elif (not arg_strings and action.nargs == ZERO_OR_MORE and
+              not action.option_strings):
+            if action.default is not None:
+                value = action.default
+            else:
+                value = arg_strings
+            self._check_value(action, value)
+
+        # single argument or optional argument produces a single value
+        elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
+            arg_string, = arg_strings
+            value = self._get_value(action, arg_string)
+            self._check_value(action, value)
+
+        # REMAINDER arguments convert all values, checking none
+        elif action.nargs == REMAINDER:
+            value = [self._get_value(action, v) for v in arg_strings]
+
+        # PARSER arguments convert all values, but check only the first
+        elif action.nargs == PARSER:
+            value = [self._get_value(action, v) for v in arg_strings]
+            self._check_value(action, value[0])
+
+        # all other types of nargs produce a list
+        else:
+            value = [self._get_value(action, v) for v in arg_strings]
+            for v in value:
+                self._check_value(action, v)
+
+        # return the converted value
+        return value
+
+    def _get_value(self, action, arg_string):
+        type_func = self._registry_get('type', action.type, action.type)
+        if not _callable(type_func):
+            msg = _('%r is not callable')
+            raise ArgumentError(action, msg % type_func)
+
+        # convert the value to the appropriate type
+        try:
+            result = type_func(arg_string)
+
+        # ArgumentTypeErrors indicate errors
+        except ArgumentTypeError:
+            name = getattr(action.type, '__name__', repr(action.type))
+            msg = str(_sys.exc_info()[1])
+            raise ArgumentError(action, msg)
+
+        # TypeErrors or ValueErrors also indicate errors
+        except (TypeError, ValueError):
+            name = getattr(action.type, '__name__', repr(action.type))
+            msg = _('invalid %s value: %r')
+            raise ArgumentError(action, msg % (name, arg_string))
+
+        # return the converted value
+        return result
+
+    def _check_value(self, action, value):
+        # converted value must be one of the choices (if specified)
+        if action.choices is not None and value not in action.choices:
+            tup = value, ', '.join(map(repr, action.choices))
+            msg = _('invalid choice: %r (choose from %s)') % tup
+            raise ArgumentError(action, msg)
+
+    # =======================
+    # Help-formatting methods
+    # =======================
+    def format_usage(self):
+        formatter = self._get_formatter()
+        formatter.add_usage(self.usage, self._actions,
+                            self._mutually_exclusive_groups)
+        return formatter.format_help()
+
+    def format_help(self):
+        formatter = self._get_formatter()
+
+        # usage
+        formatter.add_usage(self.usage, self._actions,
+                            self._mutually_exclusive_groups)
+
+        # description
+        formatter.add_text(self.description)
+
+        # positionals, optionals and user-defined groups
+        for action_group in self._action_groups:
+            formatter.start_section(action_group.title)
+            formatter.add_text(action_group.description)
+            formatter.add_arguments(action_group._group_actions)
+            formatter.end_section()
+
+        # epilog
+        formatter.add_text(self.epilog)
+
+        # determine help from format above
+        return formatter.format_help()
+
+    def format_version(self):
+        import warnings
+        warnings.warn(
+            'The format_version method is deprecated -- the "version" '
+            'argument to ArgumentParser is no longer supported.',
+            DeprecationWarning)
+        formatter = self._get_formatter()
+        formatter.add_text(self.version)
+        return formatter.format_help()
+
+    def _get_formatter(self):
+        return self.formatter_class(prog=self.prog)
+
+    # =====================
+    # Help-printing methods
+    # =====================
+    def print_usage(self, file=None):
+        if file is None:
+            file = _sys.stdout
+        self._print_message(self.format_usage(), file)
+
+    def print_help(self, file=None):
+        if file is None:
+            file = _sys.stdout
+        self._print_message(self.format_help(), file)
+
+    def print_version(self, file=None):
+        import warnings
+        warnings.warn(
+            'The print_version method is deprecated -- the "version" '
+            'argument to ArgumentParser is no longer supported.',
+            DeprecationWarning)
+        self._print_message(self.format_version(), file)
+
+    def _print_message(self, message, file=None):
+        if message:
+            if file is None:
+                file = _sys.stderr
+            file.write(message)
+
+    # ===============
+    # Exiting methods
+    # ===============
+    def exit(self, status=0, message=None):
+        if message:
+            self._print_message(message, _sys.stderr)
+        _sys.exit(status)
+
+    def error(self, message):
+        """error(message: string)
+
+        Prints a usage message incorporating the message to stderr and
+        exits.
+
+        If you override this in a subclass, it should not return -- it
+        should either exit or raise an exception.
+        """
+        self.print_usage(_sys.stderr)
+        self.exit(2, _('%s: error: %s\n') % (self.prog, message))
diff --git a/lib/taurus/external/enum/enum/__init__.py b/lib/taurus/external/enum/enum/__init__.py
new file mode 100644
index 0000000..cbc3fba
--- /dev/null
+++ b/lib/taurus/external/enum/enum/__init__.py
@@ -0,0 +1,809 @@
+# Copyright (c) 2013, Ethan Furman.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#    Redistributions of source code must retain the above
+#    copyright notice, this list of conditions and the
+#    following disclaimer.
+#
+#    Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials
+#    provided with the distribution.
+#
+#    Neither the name Ethan Furman nor the names of any
+#    contributors may be used to endorse or promote products
+#    derived from this software without specific prior written
+#    permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+"""Python Enumerations"""
+
+import sys as _sys
+
+__all__ = ['Enum', 'IntEnum', 'unique']
+
+pyver = float('%s.%s' % _sys.version_info[:2])
+
+try:
+    any
+except NameError:
+    def any(iterable):
+        for element in iterable:
+            if element:
+                return True
+        return False
+
+try:
+    from collections import OrderedDict
+except ImportError:
+    OrderedDict = None
+
+try:
+    basestring
+except NameError:
+    # In Python 2 basestring is the ancestor of both str and unicode
+    # in Python 3 it's just str, but was missing in 3.1
+    basestring = str
+
+class _RouteClassAttributeToGetattr(object):
+    """Route attribute access on a class to __getattr__.
+
+    This is a descriptor, used to define attributes that act differently when
+    accessed through an instance and through a class.  Instance access remains
+    normal, but access to an attribute through a class will be routed to the
+    class's __getattr__ method; this is done by raising AttributeError.
+
+    """
+    def __init__(self, fget=None):
+        self.fget = fget
+
+    def __get__(self, instance, ownerclass=None):
+        if instance is None:
+            raise AttributeError()
+        return self.fget(instance)
+
+    def __set__(self, instance, value):
+        raise AttributeError("can't set attribute")
+
+    def __delete__(self, instance):
+        raise AttributeError("can't delete attribute")
+
+
+def _is_descriptor(obj):
+    """Returns True if obj is a descriptor, False otherwise."""
+    return (
+            hasattr(obj, '__get__') or
+            hasattr(obj, '__set__') or
+            hasattr(obj, '__delete__'))
+
+
+def _is_dunder(name):
+    """Returns True if a __dunder__ name, False otherwise."""
+    return (name[:2] == name[-2:] == '__' and
+            name[2:3] != '_' and
+            name[-3:-2] != '_' and
+            len(name) > 4)
+
+
+def _is_sunder(name):
+    """Returns True if a _sunder_ name, False otherwise."""
+    return (name[0] == name[-1] == '_' and
+            name[1:2] != '_' and
+            name[-2:-1] != '_' and
+            len(name) > 2)
+
+
+def _make_class_unpicklable(cls):
+    """Make the given class un-picklable."""
+    def _break_on_call_reduce(self, protocol=None):
+        raise TypeError('%r cannot be pickled' % self)
+    cls.__reduce_ex__ = _break_on_call_reduce
+    #cls.__reduce__ = _break_on_call_reduce
+    cls.__module__ = '<unknown>'
+
+
+class _EnumDict(dict):
+    """Track enum member order and ensure member names are not reused.
+
+    EnumMeta will use the names found in self._member_names as the
+    enumeration member names.
+
+    """
+    def __init__(self):
+        super(_EnumDict, self).__init__()
+        self._member_names = []
+
+    def __setitem__(self, key, value):
+        """Changes anything not dundered or not a descriptor.
+
+        If a descriptor is added with the same name as an enum member, the name
+        is removed from _member_names (this may leave a hole in the numerical
+        sequence of values).
+
+        If an enum member name is used twice, an error is raised; duplicate
+        values are not checked for.
+
+        Single underscore (sunder) names are reserved.
+
+        Note:   in 3.x __order__ is simply discarded as a not necessary piece
+                leftover from 2.x
+
+        """
+        if pyver >= 3.0 and key == '__order__':
+            return
+        if _is_sunder(key):
+            raise ValueError('_names_ are reserved for future Enum use')
+        elif _is_dunder(key):
+            pass
+        elif key in self._member_names:
+            # descriptor overwriting an enum?
+            raise TypeError('Attempted to reuse key: %r' % key)
+        elif not _is_descriptor(value):
+            if key in self:
+                # enum overwriting a descriptor?
+                raise TypeError('Key already defined as: %r' % self[key])
+            self._member_names.append(key)
+        super(_EnumDict, self).__setitem__(key, value)
+
+
+# Dummy value for Enum as EnumMeta explicity checks for it, but of course until
+# EnumMeta finishes running the first time the Enum class doesn't exist.  This
+# is also why there are checks in EnumMeta like `if Enum is not None`
+Enum = None
+
+
+class EnumMeta(type):
+    """Metaclass for Enum"""
+    @classmethod
+    def __prepare__(metacls, cls, bases):
+        return _EnumDict()
+
+    def __new__(metacls, cls, bases, classdict):
+        # an Enum class is final once enumeration items have been defined; it
+        # cannot be mixed with other types (int, float, etc.) if it has an
+        # inherited __new__ unless a new __new__ is defined (or the resulting
+        # class will fail).
+        if type(classdict) is dict:
+            original_dict = classdict
+            classdict = _EnumDict()
+            for k, v in original_dict.items():
+                classdict[k] = v
+
+        member_type, first_enum = metacls._get_mixins_(bases)
+        #if member_type is object:
+        #    use_args = False
+        #else:
+        #    use_args = True
+        __new__, save_new, use_args = metacls._find_new_(classdict, member_type,
+                                                        first_enum)
+        # save enum items into separate mapping so they don't get baked into
+        # the new class
+        members = dict((k, classdict[k]) for k in classdict._member_names)
+        for name in classdict._member_names:
+            del classdict[name]
+
+        # py2 support for definition order
+        __order__ = classdict.get('__order__')
+        if __order__ is None:
+            __order__ = classdict._member_names
+            if pyver < 3.0:
+                order_specified = False
+            else:
+                order_specified = True
+        else:
+            del classdict['__order__']
+            order_specified = True
+            if pyver < 3.0:
+                __order__ = __order__.replace(',', ' ').split()
+                aliases = [name for name in members if name not in __order__]
+                __order__ += aliases
+
+        # check for illegal enum names (any others?)
+        invalid_names = set(members) & set(['mro'])
+        if invalid_names:
+            raise ValueError('Invalid enum member name(s): %s' % (
+                ', '.join(invalid_names), ))
+
+        # create our new Enum type
+        enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases, classdict)
+        enum_class._member_names_ = []               # names in random order
+        if order_specified and OrderedDict is not None:
+            enum_class._member_map_ = OrderedDict()
+        else:
+            enum_class._member_map_ = {}             # name->value map
+        enum_class._member_type_ = member_type
+
+        # Reverse value->name map for hashable values.
+        enum_class._value2member_map_ = {}
+
+        # instantiate them, checking for duplicates as we go
+        # we instantiate first instead of checking for duplicates first in case
+        # a custom __new__ is doing something funky with the values -- such as
+        # auto-numbering ;)
+        if __new__ is None:
+            __new__ = enum_class.__new__
+        for member_name in __order__:
+            value = members[member_name]
+            if not isinstance(value, tuple):
+                args = (value, )
+            else:
+                args = value
+            if member_type is tuple:   # special case for tuple enums
+                args = (args, )     # wrap it one more time
+            if not use_args or not args:
+                enum_member = __new__(enum_class)
+                if not hasattr(enum_member, '_value_'):
+                    enum_member._value_ = value
+            else:
+                enum_member = __new__(enum_class, *args)
+                if not hasattr(enum_member, '_value_'):
+                    enum_member._value_ = member_type(*args)
+            value = enum_member._value_
+            enum_member._name_ = member_name
+            enum_member.__objclass__ = enum_class
+            enum_member.__init__(*args)
+            # If another member with the same value was already defined, the
+            # new member becomes an alias to the existing one.
+            for name, canonical_member in enum_class._member_map_.items():
+                if canonical_member.value == enum_member._value_:
+                    enum_member = canonical_member
+                    break
+            else:
+                # Aliases don't appear in member names (only in __members__).
+                enum_class._member_names_.append(member_name)
+            enum_class._member_map_[member_name] = enum_member
+            try:
+                # This may fail if value is not hashable. We can't add the value
+                # to the map, and by-value lookups for this value will be
+                # linear.
+                enum_class._value2member_map_[value] = enum_member
+            except TypeError:
+                pass
+
+        # in Python2.x we cannot know definition order, so go with value order
+        # unless __order__ was specified in the class definition
+        if not order_specified:
+            enum_class._member_names_ = [
+                e[0] for e in sorted(
+                [(name, enum_class._member_map_[name]) for name in enum_class._member_names_],
+                 key=lambda t: t[1]._value_
+                        )]
+
+        # double check that repr and friends are not the mixin's or various
+        # things break (such as pickle)
+        if Enum is not None:
+            setattr(enum_class, '__getnewargs__', Enum.__getnewargs__)
+            setattr(enum_class, '__reduce_ex__', Enum.__reduce_ex__)
+        for name in ('__repr__', '__str__', '__format__', ):
+            class_method = getattr(enum_class, name)
+            obj_method = getattr(member_type, name, None)
+            enum_method = getattr(first_enum, name, None)
+            if obj_method is not None and obj_method is class_method:
+                setattr(enum_class, name, enum_method)
+
+        # check for a __getnewargs__, and if not present sabotage
+        # pickling, since it won't work anyway
+        if member_type is not object:
+            methods = ('__getnewargs_ex__', '__getnewargs__',
+                    '__reduce_ex__', '__reduce__')
+            if not any(map(member_type.__dict__.get, methods)):
+                _make_class_unpicklable(enum_class)
+
+        # method resolution and int's are not playing nice
+        # Python's less than 2.6 use __cmp__
+
+        if pyver < 2.6:
+
+            if issubclass(enum_class, int):
+                setattr(enum_class, '__cmp__', getattr(int, '__cmp__'))
+
+        elif pyver < 3.0:
+
+            if issubclass(enum_class, int):
+                for method in (
+                        '__le__',
+                        '__lt__',
+                        '__gt__',
+                        '__ge__',
+                        '__eq__',
+                        '__ne__',
+                        '__hash__',
+                        ):
+                    setattr(enum_class, method, getattr(int, method))
+
+        # replace any other __new__ with our own (as long as Enum is not None,
+        # anyway) -- again, this is to support pickle
+        if Enum is not None:
+            # if the user defined their own __new__, save it before it gets
+            # clobbered in case they subclass later
+            if save_new:
+                setattr(enum_class, '__member_new__', enum_class.__dict__['__new__'])
+            setattr(enum_class, '__new__', Enum.__dict__['__new__'])
+        return enum_class
+
+    def __call__(cls, value, names=None, module=None, type=None):
+        """Either returns an existing member, or creates a new enum class.
+
+        This method is used both when an enum class is given a value to match
+        to an enumeration member (i.e. Color(3)) and for the functional API
+        (i.e. Color = Enum('Color', names='red green blue')).
+
+        When used for the functional API: `module`, if set, will be stored in
+        the new class' __module__ attribute; `type`, if set, will be mixed in
+        as the first base class.
+
+        Note: if `module` is not set this routine will attempt to discover the
+        calling module by walking the frame stack; if this is unsuccessful
+        the resulting class will not be pickleable.
+
+        """
+        if names is None:  # simple value lookup
+            return cls.__new__(cls, value)
+        # otherwise, functional API: we're creating a new Enum type
+        return cls._create_(value, names, module=module, type=type)
+
+    def __contains__(cls, member):
+        return isinstance(member, cls) and member.name in cls._member_map_
+
+    def __delattr__(cls, attr):
+        # nicer error message when someone tries to delete an attribute
+        # (see issue19025).
+        if attr in cls._member_map_:
+            raise AttributeError(
+                    "%s: cannot delete Enum member." % cls.__name__)
+        super(EnumMeta, cls).__delattr__(attr)
+
+    def __dir__(self):
+        return (['__class__', '__doc__', '__members__', '__module__'] +
+                self._member_names_)
+
+    @property
+    def __members__(cls):
+        """Returns a mapping of member name->value.
+
+        This mapping lists all enum members, including aliases. Note that this
+        is a copy of the internal mapping.
+
+        """
+        return cls._member_map_.copy()
+
+    def __getattr__(cls, name):
+        """Return the enum member matching `name`
+
+        We use __getattr__ instead of descriptors or inserting into the enum
+        class' __dict__ in order to support `name` and `value` being both
+        properties for enum members (which live in the class' __dict__) and
+        enum members themselves.
+
+        """
+        if _is_dunder(name):
+            raise AttributeError(name)
+        try:
+            return cls._member_map_[name]
+        except KeyError:
+            raise AttributeError(name)
+
+    def __getitem__(cls, name):
+        return cls._member_map_[name]
+
+    def __iter__(cls):
+        return (cls._member_map_[name] for name in cls._member_names_)
+
+    def __reversed__(cls):
+        return (cls._member_map_[name] for name in reversed(cls._member_names_))
+
+    def __len__(cls):
+        return len(cls._member_names_)
+
+    def __repr__(cls):
+        return "<enum %r>" % cls.__name__
+
+    def __setattr__(cls, name, value):
+        """Block attempts to reassign Enum members.
+
+        A simple assignment to the class namespace only changes one of the
+        several possible ways to get an Enum member from the Enum class,
+        resulting in an inconsistent Enumeration.
+
+        """
+        member_map = cls.__dict__.get('_member_map_', {})
+        if name in member_map:
+            raise AttributeError('Cannot reassign members.')
+        super(EnumMeta, cls).__setattr__(name, value)
+
+    def _create_(cls, class_name, names=None, module=None, type=None):
+        """Convenience method to create a new Enum class.
+
+        `names` can be:
+
+        * A string containing member names, separated either with spaces or
+          commas.  Values are auto-numbered from 1.
+        * An iterable of member names.  Values are auto-numbered from 1.
+        * An iterable of (member name, value) pairs.
+        * A mapping of member name -> value.
+
+        """
+        metacls = cls.__class__
+        if type is None:
+            bases = (cls, )
+        else:
+            bases = (type, cls)
+        classdict = metacls.__prepare__(class_name, bases)
+        __order__ = []
+
+        # special processing needed for names?
+        if isinstance(names, basestring):
+            names = names.replace(',', ' ').split()
+        if isinstance(names, (tuple, list)) and isinstance(names[0], basestring):
+            names = [(e, i+1) for (i, e) in enumerate(names)]
+
+        # Here, names is either an iterable of (name, value) or a mapping.
+        for item in names:
+            if isinstance(item, basestring):
+                member_name, member_value = item, names[item]
+            else:
+                member_name, member_value = item
+            classdict[member_name] = member_value
+            __order__.append(member_name)
+        # only set __order__ in classdict if name/value was not from a mapping
+        if not isinstance(item, basestring):
+            classdict['__order__'] = ' '.join(__order__)
+        enum_class = metacls.__new__(metacls, class_name, bases, classdict)
+
+        # TODO: replace the frame hack if a blessed way to know the calling
+        # module is ever developed
+        if module is None:
+            try:
+                module = _sys._getframe(2).f_globals['__name__']
+            except (AttributeError, ValueError):
+                pass
+        if module is None:
+            _make_class_unpicklable(enum_class)
+        else:
+            enum_class.__module__ = module
+
+        return enum_class
+
+    @staticmethod
+    def _get_mixins_(bases):
+        """Returns the type for creating enum members, and the first inherited
+        enum class.
+
+        bases: the tuple of bases that was given to __new__
+
+        """
+        if not bases or Enum is None:
+            return object, Enum
+
+
+        # double check that we are not subclassing a class with existing
+        # enumeration members; while we're at it, see if any other data
+        # type has been mixed in so we can use the correct __new__
+        member_type = first_enum = None
+        for base in bases:
+            if  (base is not Enum and
+                    issubclass(base, Enum) and
+                    base._member_names_):
+                raise TypeError("Cannot extend enumerations")
+        # base is now the last base in bases
+        if not issubclass(base, Enum):
+            raise TypeError("new enumerations must be created as "
+                    "`ClassName([mixin_type,] enum_type)`")
+
+        # get correct mix-in type (either mix-in type of Enum subclass, or
+        # first base if last base is Enum)
+        if not issubclass(bases[0], Enum):
+            member_type = bases[0]     # first data type
+            first_enum = bases[-1]  # enum type
+        else:
+            for base in bases[0].__mro__:
+                # most common: (IntEnum, int, Enum, object)
+                # possible:    (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
+                #               <class 'int'>, <Enum 'Enum'>,
+                #               <class 'object'>)
+                if issubclass(base, Enum):
+                    if first_enum is None:
+                        first_enum = base
+                else:
+                    if member_type is None:
+                        member_type = base
+
+        return member_type, first_enum
+
+    if pyver < 3.0:
+        @staticmethod
+        def _find_new_(classdict, member_type, first_enum):
+            """Returns the __new__ to be used for creating the enum members.
+
+            classdict: the class dictionary given to __new__
+            member_type: the data type whose __new__ will be used by default
+            first_enum: enumeration to check for an overriding __new__
+
+            """
+            # now find the correct __new__, checking to see of one was defined
+            # by the user; also check earlier enum classes in case a __new__ was
+            # saved as __member_new__
+            __new__ = classdict.get('__new__', None)
+            if __new__:
+                return None, True, True      # __new__, save_new, use_args
+
+            N__new__ = getattr(None, '__new__')
+            O__new__ = getattr(object, '__new__')
+            if Enum is None:
+                E__new__ = N__new__
+            else:
+                E__new__ = Enum.__dict__['__new__']
+            # check all possibles for __member_new__ before falling back to
+            # __new__
+            for method in ('__member_new__', '__new__'):
+                for possible in (member_type, first_enum):
+                    try:
+                        target = possible.__dict__[method]
+                    except (AttributeError, KeyError):
+                        target = getattr(possible, method, None)
+                    if target not in [
+                            None,
+                            N__new__,
+                            O__new__,
+                            E__new__,
+                            ]:
+                        if method == '__member_new__':
+                            classdict['__new__'] = target
+                            return None, False, True
+                        if isinstance(target, staticmethod):
+                            target = target.__get__(member_type)
+                        __new__ = target
+                        break
+                if __new__ is not None:
+                    break
+            else:
+                __new__ = object.__new__
+
+            # if a non-object.__new__ is used then whatever value/tuple was
+            # assigned to the enum member name will be passed to __new__ and to the
+            # new enum member's __init__
+            if __new__ is object.__new__:
+                use_args = False
+            else:
+                use_args = True
+
+            return __new__, False, use_args
+    else:
+        @staticmethod
+        def _find_new_(classdict, member_type, first_enum):
+            """Returns the __new__ to be used for creating the enum members.
+
+            classdict: the class dictionary given to __new__
+            member_type: the data type whose __new__ will be used by default
+            first_enum: enumeration to check for an overriding __new__
+
+            """
+            # now find the correct __new__, checking to see of one was defined
+            # by the user; also check earlier enum classes in case a __new__ was
+            # saved as __member_new__
+            __new__ = classdict.get('__new__', None)
+
+            # should __new__ be saved as __member_new__ later?
+            save_new = __new__ is not None
+
+            if __new__ is None:
+                # check all possibles for __member_new__ before falling back to
+                # __new__
+                for method in ('__member_new__', '__new__'):
+                    for possible in (member_type, first_enum):
+                        target = getattr(possible, method, None)
+                        if target not in (
+                                None,
+                                None.__new__,
+                                object.__new__,
+                                Enum.__new__,
+                                ):
+                            __new__ = target
+                            break
+                    if __new__ is not None:
+                        break
+                else:
+                    __new__ = object.__new__
+
+            # if a non-object.__new__ is used then whatever value/tuple was
+            # assigned to the enum member name will be passed to __new__ and to the
+            # new enum member's __init__
+            if __new__ is object.__new__:
+                use_args = False
+            else:
+                use_args = True
+
+            return __new__, save_new, use_args
+
+
+########################################################
+# In order to support Python 2 and 3 with a single
+# codebase we have to create the Enum methods separately
+# and then use the `type(name, bases, dict)` method to
+# create the class.
+########################################################
+temp_enum_dict = {}
+temp_enum_dict['__doc__'] = "Generic enumeration.\n\n    Derive from this class to define new enumerations.\n\n"
+
+def __new__(cls, value):
+    # all enum instances are actually created during class construction
+    # without calling this method; this method is called by the metaclass'
+    # __call__ (i.e. Color(3) ), and by pickle
+    if type(value) is cls:
+        # For lookups like Color(Color.red)
+        value = value.value
+        #return value
+    # by-value search for a matching enum member
+    # see if it's in the reverse mapping (for hashable values)
+    try:
+        if value in cls._value2member_map_:
+            return cls._value2member_map_[value]
+    except TypeError:
+        # not there, now do long search -- O(n) behavior
+        for member in cls._member_map_.values():
+            if member.value == value:
+                return member
+    raise ValueError("%s is not a valid %s" % (value, cls.__name__))
+temp_enum_dict['__new__'] = __new__
+del __new__
+
+def __repr__(self):
+    return "<%s.%s: %r>" % (
+            self.__class__.__name__, self._name_, self._value_)
+temp_enum_dict['__repr__'] = __repr__
+del __repr__
+
+def __str__(self):
+    return "%s.%s" % (self.__class__.__name__, self._name_)
+temp_enum_dict['__str__'] = __str__
+del __str__
+
+def __dir__(self):
+    added_behavior = [m for m in self.__class__.__dict__ if m[0] != '_']
+    return (['__class__', '__doc__', '__module__', 'name', 'value'] + added_behavior)
+temp_enum_dict['__dir__'] = __dir__
+del __dir__
+
+def __format__(self, format_spec):
+    # mixed-in Enums should use the mixed-in type's __format__, otherwise
+    # we can get strange results with the Enum name showing up instead of
+    # the value
+
+    # pure Enum branch
+    if self._member_type_ is object:
+        cls = str
+        val = str(self)
+    # mix-in branch
+    else:
+        cls = self._member_type_
+        val = self.value
+    return cls.__format__(val, format_spec)
+temp_enum_dict['__format__'] = __format__
+del __format__
+
+
+####################################
+# Python's less than 2.6 use __cmp__
+
+if pyver < 2.6:
+
+    def __cmp__(self, other):
+        if type(other) is self.__class__:
+            if self is other:
+                return 0
+            return -1
+        return NotImplemented
+        raise TypeError("unorderable types: %s() and %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__cmp__'] = __cmp__
+    del __cmp__
+
+else:
+
+    def __le__(self, other):
+        raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__le__'] = __le__
+    del __le__
+
+    def __lt__(self, other):
+        raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__lt__'] = __lt__
+    del __lt__
+
+    def __ge__(self, other):
+        raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__ge__'] = __ge__
+    del __ge__
+
+    def __gt__(self, other):
+        raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__gt__'] = __gt__
+    del __gt__
+
+
+def __eq__(self, other):
+    if type(other) is self.__class__:
+        return self is other
+    return NotImplemented
+temp_enum_dict['__eq__'] = __eq__
+del __eq__
+
+def __ne__(self, other):
+    if type(other) is self.__class__:
+        return self is not other
+    return NotImplemented
+temp_enum_dict['__ne__'] = __ne__
+del __ne__
+
+def __getnewargs__(self):
+    return (self._value_, )
+temp_enum_dict['__getnewargs__'] = __getnewargs__
+del __getnewargs__
+
+def __hash__(self):
+    return hash(self._name_)
+temp_enum_dict['__hash__'] = __hash__
+del __hash__
+
+def __reduce_ex__(self, proto):
+    return self.__class__, self.__getnewargs__()
+temp_enum_dict['__reduce_ex__'] = __reduce_ex__
+del __reduce_ex__
+
+# _RouteClassAttributeToGetattr is used to provide access to the `name`
+# and `value` properties of enum members while keeping some measure of
+# protection from modification, while still allowing for an enumeration
+# to have members named `name` and `value`.  This works because enumeration
+# members are not set directly on the enum class -- __getattr__ is
+# used to look them up.
+
+ at _RouteClassAttributeToGetattr
+def name(self):
+    return self._name_
+temp_enum_dict['name'] = name
+del name
+
+ at _RouteClassAttributeToGetattr
+def value(self):
+    return self._value_
+temp_enum_dict['value'] = value
+del value
+
+Enum = EnumMeta('Enum', (object, ), temp_enum_dict)
+del temp_enum_dict
+
+# Enum has now been created
+###########################
+
+class IntEnum(int, Enum):
+    """Enum where members are also (and must be) ints"""
+
+
+def unique(enumeration):
+    """Class decorator that ensures only unique members exist in an enumeration."""
+    duplicates = []
+    for name, member in enumeration.__members__.items():
+        if name != member.name:
+            duplicates.append((name, member.name))
+    if duplicates:
+        duplicate_names = ', '.join(
+                ["%s -> %s" % (alias, name) for (alias, name) in duplicates]
+                )
+        raise ValueError('duplicate names found in %r: %s' %
+                (enumeration, duplicate_names)
+                )
+    return enumeration
diff --git a/lib/taurus/external/ordereddict/__init__.py b/lib/taurus/external/ordereddict/__init__.py
index caf0c37..944639d 100644
--- a/lib/taurus/external/ordereddict/__init__.py
+++ b/lib/taurus/external/ordereddict/__init__.py
@@ -25,6 +25,12 @@
 
 from __future__ import absolute_import
 
+from taurus.core.util.log import deprecated
+
+
+deprecated(dep='taurus.external.ordereddict', rel='4.0', alt='ordereddict')
+
+
 try:
     # ordereddict from python 2.7 or from ordereddict installed package?
     from ordereddict import *
diff --git a/lib/taurus/external/ordereddict/ordereddict.py b/lib/taurus/external/ordereddict/ordereddict.py
new file mode 100644
index 0000000..f09d261
--- /dev/null
+++ b/lib/taurus/external/ordereddict/ordereddict.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+
+##############################################################################
+##
+## This file is part of Taurus
+##
+## http://taurus-scada.org
+##
+## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
+##
+## Taurus is free software: you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Taurus 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 Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public License
+## along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
+##
+##############################################################################
+
+# Copyright (c) 2009 Raymond Hettinger
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+#     The above copyright notice and this permission notice shall be
+#     included in all copies or substantial portions of the Software.
+#
+#     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+#     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+#     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+#     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+#     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+#     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+#     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+#     OTHER DEALINGS IN THE SOFTWARE.
+
+from UserDict import DictMixin
+
+
+class OrderedDict(dict, DictMixin):
+
+    def __init__(self, *args, **kwds):
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__end
+        except AttributeError:
+            self.clear()
+        self.update(*args, **kwds)
+
+    def clear(self):
+        self.__end = end = []
+        end += [None, end, end]  # sentinel node for doubly linked list
+        self.__map = {}  # key --> [key, prev, next]
+        dict.clear(self)
+
+    def __setitem__(self, key, value):
+        if key not in self:
+            end = self.__end
+            curr = end[1]
+            curr[2] = end[1] = self.__map[key] = [key, curr, end]
+        dict.__setitem__(self, key, value)
+
+    def __delitem__(self, key):
+        dict.__delitem__(self, key)
+        key, prev_, next_ = self.__map.pop(key)
+        prev_[2] = next_
+        next_[1] = prev_
+
+    def __iter__(self):
+        end = self.__end
+        curr = end[2]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[2]
+
+    def __reversed__(self):
+        end = self.__end
+        curr = end[1]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[1]
+
+    def popitem(self, last=True):
+        if not self:
+            raise KeyError('dictionary is empty')
+        if last:
+            key = reversed(self).next()
+        else:
+            key = iter(self).next()
+        value = self.pop(key)
+        return key, value
+
+    def __reduce__(self):
+        items = [[k, self[k]] for k in self]
+        tmp = self.__map, self.__end
+        del self.__map, self.__end
+        inst_dict = vars(self).copy()
+        self.__map, self.__end = tmp
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def keys(self):
+        return list(self)
+
+    setdefault = DictMixin.setdefault
+    update = DictMixin.update
+    pop = DictMixin.pop
+    values = DictMixin.values
+    items = DictMixin.items
+    iterkeys = DictMixin.iterkeys
+    itervalues = DictMixin.itervalues
+    iteritems = DictMixin.iteritems
+
+    def __repr__(self):
+        if not self:
+            return '%s()' % (self.__class__.__name__,)
+        return '%s(%r)' % (self.__class__.__name__, self.items())
+
+    def copy(self):
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        if isinstance(other, OrderedDict):
+            if len(self) != len(other):
+                return False
+            for p, q in  zip(self.items(), other.items()):
+                if p != q:
+                    return False
+            return True
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other
diff --git a/lib/taurus/external/pint/pint_local/AUTHORS b/lib/taurus/external/pint/pint_local/AUTHORS
new file mode 100644
index 0000000..6c7f068
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/AUTHORS
@@ -0,0 +1,41 @@
+Pint is written and maintained by Hernan E. Grecco <hernan.grecco at gmail.com>.
+
+Other contributors, listed alphabetically, are:
+
+* Aaron Coleman
+* Alexander Böhn <fish2000 at gmail.com>
+* Ana Krivokapic <akrivokapic1 at gmail.com>
+* Andrea Zonca <code at andreazonca.com>
+* Brend Wanders <b.wanders at utwente.nl>
+* choloepus
+* coutinho <coutinho at esrf.fr>
+* Daniel Sokolowski <daniel.sokolowski at danols.com>
+* Dave Brooks <dave at bcs.co.nz>
+* David Linke
+* Ed Schofield <ed at pythoncharmers.com>
+* Eduard Bopp <eduard.bopp at aepsil0n.de>
+* Eli <elifab at gmail.com>
+* Felix Hummel <felix at felixhummel.de>
+* Giel van Schijndel <me at mortis.eu>
+* James Rowe <jnrowe at gmail.com>
+* Jim Turner <jturner314 at gmail.com>
+* Joel B. Mohler <joel at kiwistrawberry.us>
+* John David Reaver <jdreaver at adlerhorst.com>
+* Jonas Olson <jolson at kth.se>
+* Kaido Kert <kaidokert at gmail.com>
+* Kenneth D. Mankoff <mankoff at gmail.com>
+* Kevin Davies <kdavies4 at gmail.com>
+* Luke Campbell <luke.s.campbell at gmail.com>
+* Matthieu Dartiailh <marul at laposte.net>
+* Nate Bogdanowicz <natezb at gmail.com>
+* Peter Grayson <jpgrayson at gmail.com>
+* Richard Barnes <rbarnes at umn.edu>
+* Ryan Dwyer <ryanpdwyer at gmail.com>
+* Ryan Kingsbury <RyanSKingsbury at alumni.unc.edu>
+* Sundar Raman <cybertoast at gmail.com>
+* Tiago Coutinho <coutinho at esrf.fr>
+* Thomas Kluyver <takowl at gmail.com>
+* Tom Ritchford <tom at swirly.com>
+* Virgil Dupras <virgil.dupras at savoirfairelinux.com>
+
+(If you think that your name belongs here, please let the maintainer know)
diff --git a/lib/taurus/external/pint/pint_local/LICENSE b/lib/taurus/external/pint/pint_local/LICENSE
new file mode 100644
index 0000000..49aec52
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/LICENSE
@@ -0,0 +1,33 @@
+Copyright (c) 2012 by Hernan E. Grecco and contributors.  See AUTHORS
+for more details.
+
+Some rights reserved.
+
+Redistribution and use in source and binary forms of the software as well
+as documentation, with or without modification, are permitted provided
+that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the following
+  disclaimer in the documentation and/or other materials provided
+  with the distribution.
+
+* The names of the contributors may not be used to endorse or
+  promote products derived from this software without specific
+  prior written permission.
+
+THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
+NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
diff --git a/lib/taurus/external/pint/pint_local/__init__.py b/lib/taurus/external/pint/pint_local/__init__.py
new file mode 100644
index 0000000..ba4e847
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/__init__.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+"""
+    pint
+    ~~~~
+
+    Pint is Python module/package to define, operate and manipulate
+    **physical quantities**: the product of a numerical value and a
+    unit of measurement. It allows arithmetic operations between them
+    and conversions from and to different units.
+
+    :copyright: (c) 2012 by Hernan E. Grecco.
+    :license: BSD, see LICENSE for more details.
+"""
+from __future__ import with_statement
+import os
+import subprocess
+import pkg_resources
+from .formatting import formatter
+from .unit import (UnitRegistry, LazyRegistry)
+from .errors import (DimensionalityError, OffsetUnitCalculusError,
+                   UndefinedUnitError)
+from .util import pi_theorem, logger
+
+from .context import Context
+
+
+try:                # pragma: no cover
+    __version__ = pkg_resources.get_distribution('pint').version
+except:             # pragma: no cover
+    # we seem to have a local copy not installed without setuptools
+    # so the reported version will be unknown
+    __version__ = "unknown"
+
+
+#: A Registry with the default units and constants.
+_DEFAULT_REGISTRY = LazyRegistry()
+
+#: Registry used for unpickling operations.
+_APP_REGISTRY = _DEFAULT_REGISTRY
+
+
+def _build_quantity(value, units):
+    """Build Quantity using the Application registry.
+    Used only for unpickling operations.
+    """
+    global _APP_REGISTRY
+    return _APP_REGISTRY.Quantity(value, units)
+
+
+def set_application_registry(registry):
+    """Set the application registry which is used for unpickling operations.
+
+    :param registry: a UnitRegistry instance.
+    """
+    assert isinstance(registry, UnitRegistry)
+    global _APP_REGISTRY
+    logger.debug('Changing app registry from %r to %r.', _APP_REGISTRY, registry)
+    _APP_REGISTRY = registry
+
+
+def _run_pyroma(data):   # pragma: no cover
+    """Run pyroma (used to perform checks before releasing a new version).
+    """
+    import sys
+    from zest.releaser.utils import ask
+    if not ask("Run pyroma on the package before uploading?"):
+        return
+    try:
+        from pyroma import run
+        result = run(data['tagdir'])
+        if result != 10:
+            if not ask("Continue?"):
+                sys.exit(1)
+    except ImportError:
+        if not ask("pyroma not available. Continue?"):
+            sys.exit(1)
+
+
+def _check_travis(data):   # pragma: no cover
+    """Check if Travis reports that everything is ok.
+    (used to perform checks before releasing a new version).
+    """
+    import json
+    import sys
+
+    from zest.releaser.utils import system, ask
+    if not ask('Check with Travis before releasing?'):
+        return
+
+    try:
+        # Python 3
+        from urllib.request import urlopen
+        def get(url):
+            return urlopen(url).read().decode('utf-8')
+
+    except ImportError:
+        # Python 2
+        from urllib2 import urlopen
+        def get(url):
+            return urlopen(url).read()
+
+    url = 'https://api.github.com/repos/%s/%s/status/%s'
+
+    username = 'hgrecco'
+    repo = 'pint'
+    commit = system('git rev-parse HEAD')
+
+    try:
+        result = json.loads(get(url % (username, repo, commit)))['state']
+        print('Travis says: %s' % result)
+        if result != 'success':
+            if not ask('Do you want to continue anyway?', default=False):
+                sys.exit(1)
+    except Exception:
+        print('Could not determine the commit state with Travis.')
+        if ask('Do you want to continue anyway?', default=False):
+            sys.exit(1)
+
+
+def test():
+    """Run all tests.
+
+    :return: a :class:`unittest.TestResult` object
+    """
+    from .testsuite import run
+    return run()
diff --git a/lib/taurus/external/pint/pint_local/compat/__init__.py b/lib/taurus/external/pint/pint_local/compat/__init__.py
new file mode 100644
index 0000000..7442967
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/compat/__init__.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.compat
+    ~~~~~~~~~~~
+
+    Compatibility layer.
+
+    :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import division, unicode_literals, print_function, absolute_import
+
+import sys
+
+from io import BytesIO
+from numbers import Number
+from decimal import Decimal
+
+from . import tokenize
+
+ENCODING_TOKEN = tokenize.ENCODING
+
+PYTHON3 = sys.version >= '3'
+
+def tokenizer(input_string):
+    for tokinfo in tokenize.tokenize(BytesIO(input_string.encode('utf-8')).readline):
+        if tokinfo.type == ENCODING_TOKEN:
+            continue
+        yield tokinfo
+
+
+if PYTHON3:
+    string_types = str
+
+    def u(x):
+        return x
+
+    maketrans = str.maketrans
+
+    long_type = int
+else:
+    string_types = basestring
+
+    import codecs
+
+    def u(x):
+        return codecs.unicode_escape_decode(x)[0]
+
+    maketrans = lambda f, t: dict((ord(a), b) for a, b in zip(f, t))
+
+    long_type = long
+
+if sys.version_info < (2, 7):
+    try:
+        import unittest2 as unittest
+    except ImportError:
+        raise Exception("Testing Pint in Python 2.6 requires package 'unittest2'")
+else:
+    import unittest
+
+
+try:
+    from collections import Chainmap
+except ImportError:
+    from .chainmap import ChainMap
+
+try:
+    from functools import lru_cache
+except ImportError:
+    from .lrucache import lru_cache
+
+try:
+    from logging import NullHandler
+except ImportError:
+    from .nullhandler import NullHandler
+
+try:
+    from itertools import zip_longest
+except ImportError:
+    from itertools import izip_longest as zip_longest
+
+try:
+    import numpy as np
+    from numpy import ndarray
+
+    HAS_NUMPY = True
+    NUMPY_VER = np.__version__
+    NUMERIC_TYPES = (Number, Decimal, ndarray, np.number)
+
+    def _to_magnitude(value, force_ndarray=False):
+        if isinstance(value, (dict, bool)) or value is None:
+            raise TypeError('Invalid magnitude for Quantity: {0!r}'.format(value))
+        elif isinstance(value, string_types) and value == '':
+            raise ValueError('Quantity magnitude cannot be an empty string.')
+        elif isinstance(value, (list, tuple)):
+            return np.asarray(value)
+        if force_ndarray:
+            return np.asarray(value)
+        return value
+
+except ImportError:
+
+    np = None
+
+    class ndarray(object):
+        pass
+
+    HAS_NUMPY = False
+    NUMPY_VER = '0'
+    NUMERIC_TYPES = (Number, Decimal)
+
+    def _to_magnitude(value, force_ndarray=False):
+        if isinstance(value, (dict, bool)) or value is None:
+            raise TypeError('Invalid magnitude for Quantity: {0!r}'.format(value))
+        elif isinstance(value, string_types) and value == '':
+            raise ValueError('Quantity magnitude cannot be an empty string.')
+        elif isinstance(value, (list, tuple)):
+            raise TypeError('lists and tuples are valid magnitudes for '
+                             'Quantity only when NumPy is present.')
+        return value
+
+try:
+    from uncertainties import ufloat
+    HAS_UNCERTAINTIES = True
+except ImportError:
+    ufloat = None
+    HAS_UNCERTAINTIES = False
+
diff --git a/lib/taurus/external/pint/pint_local/compat/chainmap.py b/lib/taurus/external/pint/pint_local/compat/chainmap.py
new file mode 100644
index 0000000..f4c9a4e
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/compat/chainmap.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.compat.chainmap
+    ~~~~~~~~~~~~~~~~~~~~
+
+    Taken from the Python 3.3 source code.
+
+    :copyright: 2013, PSF
+    :license: PSF License
+"""
+
+from __future__ import division, unicode_literals, print_function, absolute_import
+
+import sys
+
+from collections import MutableMapping
+if sys.version_info < (3, 0):
+    from thread import get_ident
+elif sys.version_info < (3, 3):
+    from _thread import get_ident
+else:
+    from threading import get_ident
+
+
+def _recursive_repr(fillvalue='...'):
+    'Decorator to make a repr function return fillvalue for a recursive call'
+
+    def decorating_function(user_function):
+        repr_running = set()
+
+        def wrapper(self):
+            key = id(self), get_ident()
+            if key in repr_running:
+                return fillvalue
+            repr_running.add(key)
+            try:
+                result = user_function(self)
+            finally:
+                repr_running.discard(key)
+            return result
+
+        # Can't use functools.wraps() here because of bootstrap issues
+        wrapper.__module__ = getattr(user_function, '__module__')
+        wrapper.__doc__ = getattr(user_function, '__doc__')
+        wrapper.__name__ = getattr(user_function, '__name__')
+        wrapper.__annotations__ = getattr(user_function, '__annotations__', {})
+        return wrapper
+
+    return decorating_function
+
+
+class ChainMap(MutableMapping):
+    ''' A ChainMap groups multiple dicts (or other mappings) together
+    to create a single, updateable view.
+
+    The underlying mappings are stored in a list.  That list is public and can
+    accessed or updated using the *maps* attribute.  There is no other state.
+
+    Lookups search the underlying mappings successively until a key is found.
+    In contrast, writes, updates, and deletions only operate on the first
+    mapping.
+
+    '''
+
+    def __init__(self, *maps):
+        '''Initialize a ChainMap by setting *maps* to the given mappings.
+        If no mappings are provided, a single empty dictionary is used.
+
+        '''
+        self.maps = list(maps) or [{}]          # always at least one map
+
+    def __missing__(self, key):
+        raise KeyError(key)
+
+    def __getitem__(self, key):
+        for mapping in self.maps:
+            try:
+                return mapping[key]             # can't use 'key in mapping' with defaultdict
+            except KeyError:
+                pass
+        return self.__missing__(key)            # support subclasses that define __missing__
+
+    def get(self, key, default=None):
+        return self[key] if key in self else default
+
+    def __len__(self):
+        return len(set().union(*self.maps))     # reuses stored hash values if possible
+
+    def __iter__(self):
+        return iter(set().union(*self.maps))
+
+    def __contains__(self, key):
+        return any(key in m for m in self.maps)
+
+    def __bool__(self):
+        return any(self.maps)
+
+    @_recursive_repr()
+    def __repr__(self):
+        return '{0.__class__.__name__}({1})'.format(
+            self, ', '.join(map(repr, self.maps)))
+
+    @classmethod
+    def fromkeys(cls, iterable, *args):
+        'Create a ChainMap with a single dict created from the iterable.'
+        return cls(dict.fromkeys(iterable, *args))
+
+    def copy(self):
+        'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
+        return self.__class__(self.maps[0].copy(), *self.maps[1:])
+
+    __copy__ = copy
+
+    def new_child(self, m=None):                # like Django's _Context.push()
+        '''
+        New ChainMap with a new map followed by all previous maps. If no
+        map is provided, an empty dict is used.
+        '''
+        if m is None:
+            m = {}
+        return self.__class__(m, *self.maps)
+
+    @property
+    def parents(self):                          # like Django's _Context.pop()
+        'New ChainMap from maps[1:].'
+        return self.__class__(*self.maps[1:])
+
+    def __setitem__(self, key, value):
+        self.maps[0][key] = value
+
+    def __delitem__(self, key):
+        try:
+            del self.maps[0][key]
+        except KeyError:
+            raise KeyError('Key not found in the first mapping: {!r}'.format(key))
+
+    def popitem(self):
+        'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
+        try:
+            return self.maps[0].popitem()
+        except KeyError:
+            raise KeyError('No keys found in the first mapping.')
+
+    def pop(self, key, *args):
+        'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
+        try:
+            return self.maps[0].pop(key, *args)
+        except KeyError:
+            raise KeyError('Key not found in the first mapping: {!r}'.format(key))
+
+    def clear(self):
+        'Clear maps[0], leaving maps[1:] intact.'
+        self.maps[0].clear()
diff --git a/lib/taurus/external/pint/pint_local/compat/lrucache.py b/lib/taurus/external/pint/pint_local/compat/lrucache.py
new file mode 100644
index 0000000..868b598
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/compat/lrucache.py
@@ -0,0 +1,177 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.compat.lrucache
+    ~~~~~~~~~~~~~~~~~~~~
+
+    LRU (least recently used) cache backport.
+
+    From https://code.activestate.com/recipes/578078-py26-and-py30-backport-of-python-33s-lru-cache/
+
+    :copyright: 2004, Raymond Hettinger,
+    :license: MIT License
+"""
+
+from collections import namedtuple
+from functools import update_wrapper
+from threading import RLock
+
+_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])
+
+class _HashedSeq(list):
+    __slots__ = 'hashvalue'
+
+    def __init__(self, tup, hash=hash):
+        self[:] = tup
+        self.hashvalue = hash(tup)
+
+    def __hash__(self):
+        return self.hashvalue
+
+def _make_key(args, kwds, typed,
+             kwd_mark = (object(),),
+             fasttypes = set((int, str, frozenset, type(None))),
+             sorted=sorted, tuple=tuple, type=type, len=len):
+    'Make a cache key from optionally typed positional and keyword arguments'
+    key = args
+    if kwds:
+        sorted_items = sorted(kwds.items())
+        key += kwd_mark
+        for item in sorted_items:
+            key += item
+    if typed:
+        key += tuple(type(v) for v in args)
+        if kwds:
+            key += tuple(type(v) for k, v in sorted_items)
+    elif len(key) == 1 and type(key[0]) in fasttypes:
+        return key[0]
+    return _HashedSeq(key)
+
+def lru_cache(maxsize=100, typed=False):
+    """Least-recently-used cache decorator.
+
+    If *maxsize* is set to None, the LRU features are disabled and the cache
+    can grow without bound.
+
+    If *typed* is True, arguments of different types will be cached separately.
+    For example, f(3.0) and f(3) will be treated as distinct calls with
+    distinct results.
+
+    Arguments to the cached function must be hashable.
+
+    View the cache statistics named tuple (hits, misses, maxsize, currsize) with
+    f.cache_info().  Clear the cache and statistics with f.cache_clear().
+    Access the underlying function with f.__wrapped__.
+
+    See:  http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
+
+    """
+
+    # Users should only access the lru_cache through its public API:
+    #       cache_info, cache_clear, and f.__wrapped__
+    # The internals of the lru_cache are encapsulated for thread safety and
+    # to allow the implementation to change (including a possible C version).
+
+    def decorating_function(user_function):
+
+        cache = dict()
+        stats = [0, 0]                  # make statistics updateable non-locally
+        HITS, MISSES = 0, 1             # names for the stats fields
+        make_key = _make_key
+        cache_get = cache.get           # bound method to lookup key or return None
+        _len = len                      # localize the global len() function
+        lock = RLock()                  # because linkedlist updates aren't threadsafe
+        root = []                       # root of the circular doubly linked list
+        root[:] = [root, root, None, None]      # initialize by pointing to self
+        nonlocal_root = [root]                  # make updateable non-locally
+        PREV, NEXT, KEY, RESULT = 0, 1, 2, 3    # names for the link fields
+
+        if maxsize == 0:
+
+            def wrapper(*args, **kwds):
+                # no caching, just do a statistics update after a successful call
+                result = user_function(*args, **kwds)
+                stats[MISSES] += 1
+                return result
+
+        elif maxsize is None:
+
+            def wrapper(*args, **kwds):
+                # simple caching without ordering or size limit
+                key = make_key(args, kwds, typed)
+                result = cache_get(key, root)   # root used here as a unique not-found sentinel
+                if result is not root:
+                    stats[HITS] += 1
+                    return result
+                result = user_function(*args, **kwds)
+                cache[key] = result
+                stats[MISSES] += 1
+                return result
+
+        else:
+
+            def wrapper(*args, **kwds):
+                # size limited caching that tracks accesses by recency
+                key = make_key(args, kwds, typed) if kwds or typed else args
+                with lock:
+                    link = cache_get(key)
+                    if link is not None:
+                        # record recent use of the key by moving it to the front of the list
+                        root, = nonlocal_root
+                        link_prev, link_next, key, result = link
+                        link_prev[NEXT] = link_next
+                        link_next[PREV] = link_prev
+                        last = root[PREV]
+                        last[NEXT] = root[PREV] = link
+                        link[PREV] = last
+                        link[NEXT] = root
+                        stats[HITS] += 1
+                        return result
+                result = user_function(*args, **kwds)
+                with lock:
+                    root, = nonlocal_root
+                    if key in cache:
+                        # getting here means that this same key was added to the
+                        # cache while the lock was released.  since the link
+                        # update is already done, we need only return the
+                        # computed result and update the count of misses.
+                        pass
+                    elif _len(cache) >= maxsize:
+                        # use the old root to store the new key and result
+                        oldroot = root
+                        oldroot[KEY] = key
+                        oldroot[RESULT] = result
+                        # empty the oldest link and make it the new root
+                        root = nonlocal_root[0] = oldroot[NEXT]
+                        oldkey = root[KEY]
+                        oldvalue = root[RESULT]
+                        root[KEY] = root[RESULT] = None
+                        # now update the cache dictionary for the new links
+                        del cache[oldkey]
+                        cache[key] = oldroot
+                    else:
+                        # put result in a new link at the front of the list
+                        last = root[PREV]
+                        link = [last, root, key, result]
+                        last[NEXT] = root[PREV] = cache[key] = link
+                    stats[MISSES] += 1
+                return result
+
+        def cache_info():
+            """Report cache statistics"""
+            with lock:
+                return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache))
+
+        def cache_clear():
+            """Clear the cache and cache statistics"""
+            with lock:
+                cache.clear()
+                root = nonlocal_root[0]
+                root[:] = [root, root, None, None]
+                stats[:] = [0, 0]
+
+        wrapper.__wrapped__ = user_function
+        wrapper.cache_info = cache_info
+        wrapper.cache_clear = cache_clear
+        return update_wrapper(wrapper, user_function)
+
+    return decorating_function
diff --git a/lib/taurus/external/pint/pint_local/compat/nullhandler.py b/lib/taurus/external/pint/pint_local/compat/nullhandler.py
new file mode 100644
index 0000000..288cbb3
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/compat/nullhandler.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.compat.nullhandler
+    ~~~~~~~~~~~~~~~~~~~~~~~
+
+    Taken from the Python 2.7 source code.
+
+    :copyright: 2013, PSF
+    :license: PSF License
+"""
+
+
+import logging
+
+class NullHandler(logging.Handler):
+    """
+    This handler does nothing. It's intended to be used to avoid the
+    "No handlers could be found for logger XXX" one-off warning. This is
+    important for library code, which may contain code to log events. If a user
+    of the library does not configure logging, the one-off warning might be
+    produced; to avoid this, the library developer simply needs to instantiate
+    a NullHandler and add it to the top-level logger of the library module or
+    package.
+    """
+    def handle(self, record):
+        pass
+
+    def emit(self, record):
+        pass
+
+    def createLock(self):
+        self.lock = None
diff --git a/lib/taurus/external/pint/pint_local/compat/tokenize.py b/lib/taurus/external/pint/pint_local/compat/tokenize.py
new file mode 100644
index 0000000..3166224
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/compat/tokenize.py
@@ -0,0 +1,640 @@
+"""Tokenization help for Python programs.
+
+tokenize(readline) is a generator that breaks a stream of bytes into
+Python tokens.  It decodes the bytes according to PEP-0263 for
+determining source file encoding.
+
+It accepts a readline-like method which is called repeatedly to get the
+next line of input (or b"" for EOF).  It generates 5-tuples with these
+members:
+
+    the token type (see token.py)
+    the token (a string)
+    the starting (row, column) indices of the token (a 2-tuple of ints)
+    the ending (row, column) indices of the token (a 2-tuple of ints)
+    the original line (string)
+
+It is designed to match the working of the Python tokenizer exactly, except
+that it produces COMMENT tokens for comments and gives type OP for all
+operators.  Additionally, all token lists start with an ENCODING token
+which tells you which encoding was used to decode the bytes stream.
+"""
+
+__author__ = 'Ka-Ping Yee <ping at lfw.org>'
+__credits__ = ('GvR, ESR, Tim Peters, Thomas Wouters, Fred Drake, '
+               'Skip Montanaro, Raymond Hettinger, Trent Nelson, '
+               'Michael Foord')
+
+from codecs import lookup, BOM_UTF8
+import collections
+import io
+from io import TextIOWrapper
+from itertools import chain
+import re
+import sys
+from token import *
+
+try:
+    reASCII = re.ASCII
+except:
+    reASCII = 0
+
+
+try:
+    unicode
+    _name_re = re.compile(r"\w*$", re.UNICODE)
+    def isidentifier(s):
+        if s[0] in '0123456789':
+            return False
+        return bool(_name_re.match(s))
+except NameError:
+    def isidentifier(s):
+        return s.isidentifier()
+
+
+cookie_re = re.compile(r'^[ \t\f]*#.*coding[:=][ \t]*([-\w.]+)', reASCII)
+blank_re = re.compile(br'^[ \t\f]*(?:[#\r\n]|$)', reASCII)
+
+COMMENT = N_TOKENS
+tok_name[COMMENT] = 'COMMENT'
+NL = N_TOKENS + 1
+tok_name[NL] = 'NL'
+ENCODING = N_TOKENS + 2
+tok_name[ENCODING] = 'ENCODING'
+N_TOKENS += 3
+EXACT_TOKEN_TYPES = {
+    '(':   LPAR,
+    ')':   RPAR,
+    '[':   LSQB,
+    ']':   RSQB,
+    ':':   COLON,
+    ',':   COMMA,
+    ';':   SEMI,
+    '+':   PLUS,
+    '-':   MINUS,
+    '*':   STAR,
+    '/':   SLASH,
+    '|':   VBAR,
+    '&':   AMPER,
+    '<':   LESS,
+    '>':   GREATER,
+    '=':   EQUAL,
+    '.':   DOT,
+    '%':   PERCENT,
+    '{':   LBRACE,
+    '}':   RBRACE,
+    '==':  EQEQUAL,
+    '!=':  NOTEQUAL,
+    '<=':  LESSEQUAL,
+    '>=':  GREATEREQUAL,
+    '~':   TILDE,
+    '^':   CIRCUMFLEX,
+    '<<':  LEFTSHIFT,
+    '>>':  RIGHTSHIFT,
+    '**':  DOUBLESTAR,
+    '+=':  PLUSEQUAL,
+    '-=':  MINEQUAL,
+    '*=':  STAREQUAL,
+    '/=':  SLASHEQUAL,
+    '%=':  PERCENTEQUAL,
+    '&=':  AMPEREQUAL,
+    '|=':  VBAREQUAL,
+    '^=': CIRCUMFLEXEQUAL,
+    '<<=': LEFTSHIFTEQUAL,
+    '>>=': RIGHTSHIFTEQUAL,
+    '**=': DOUBLESTAREQUAL,
+    '//':  DOUBLESLASH,
+    '//=': DOUBLESLASHEQUAL,
+    '@':   AT
+}
+
+class TokenInfo(collections.namedtuple('TokenInfo', 'type string start end line')):
+    def __repr__(self):
+        annotated_type = '%d (%s)' % (self.type, tok_name[self.type])
+        return ('TokenInfo(type=%s, string=%r, start=%r, end=%r, line=%r)' %
+                self._replace(type=annotated_type))
+
+    @property
+    def exact_type(self):
+        if self.type == OP and self.string in EXACT_TOKEN_TYPES:
+            return EXACT_TOKEN_TYPES[self.string]
+        else:
+            return self.type
+
+def group(*choices): return '(' + '|'.join(choices) + ')'
+def any(*choices): return group(*choices) + '*'
+def maybe(*choices): return group(*choices) + '?'
+
+# Note: we use unicode matching for names ("\w") but ascii matching for
+# number literals.
+Whitespace = r'[ \f\t]*'
+Comment = r'#[^\r\n]*'
+Ignore = Whitespace + any(r'\\\r?\n' + Whitespace) + maybe(Comment)
+Name = r'\w+'
+
+Hexnumber = r'0[xX][0-9a-fA-F]+'
+Binnumber = r'0[bB][01]+'
+Octnumber = r'0[oO][0-7]+'
+Decnumber = r'(?:0+|[1-9][0-9]*)'
+Intnumber = group(Hexnumber, Binnumber, Octnumber, Decnumber)
+Exponent = r'[eE][-+]?[0-9]+'
+Pointfloat = group(r'[0-9]+\.[0-9]*', r'\.[0-9]+') + maybe(Exponent)
+Expfloat = r'[0-9]+' + Exponent
+Floatnumber = group(Pointfloat, Expfloat)
+Imagnumber = group(r'[0-9]+[jJ]', Floatnumber + r'[jJ]')
+Number = group(Imagnumber, Floatnumber, Intnumber)
+
+StringPrefix = r'(?:[bB][rR]?|[rR][bB]?|[uU])?'
+
+# Tail end of ' string.
+Single = r"[^'\\]*(?:\\.[^'\\]*)*'"
+# Tail end of " string.
+Double = r'[^"\\]*(?:\\.[^"\\]*)*"'
+# Tail end of ''' string.
+Single3 = r"[^'\\]*(?:(?:\\.|'(?!''))[^'\\]*)*'''"
+# Tail end of """ string.
+Double3 = r'[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""'
+Triple = group(StringPrefix + "'''", StringPrefix + '"""')
+# Single-line ' or " string.
+String = group(StringPrefix + r"'[^\n'\\]*(?:\\.[^\n'\\]*)*'",
+               StringPrefix + r'"[^\n"\\]*(?:\\.[^\n"\\]*)*"')
+
+# Because of leftmost-then-longest match semantics, be sure to put the
+# longest operators first (e.g., if = came before ==, == would get
+# recognized as two instances of =).
+Operator = group(r"\*\*=?", r">>=?", r"<<=?", r"!=",
+                 r"//=?", r"->",
+                 r"[+\-*/%&|^=<>]=?",
+                 r"~")
+
+Bracket = '[][(){}]'
+Special = group(r'\r?\n', r'\.\.\.', r'[:;.,@]')
+Funny = group(Operator, Bracket, Special)
+
+PlainToken = group(Number, Funny, String, Name)
+Token = Ignore + PlainToken
+
+# First (or only) line of ' or " string.
+ContStr = group(StringPrefix + r"'[^\n'\\]*(?:\\.[^\n'\\]*)*" +
+                group("'", r'\\\r?\n'),
+                StringPrefix + r'"[^\n"\\]*(?:\\.[^\n"\\]*)*' +
+                group('"', r'\\\r?\n'))
+PseudoExtras = group(r'\\\r?\n|\Z', Comment, Triple)
+PseudoToken = Whitespace + group(PseudoExtras, Number, Funny, ContStr, Name)
+
+def _compile(expr):
+    return re.compile(expr, re.UNICODE)
+
+endpats = {"'": Single, '"': Double,
+           "'''": Single3, '"""': Double3,
+           "r'''": Single3, 'r"""': Double3,
+           "b'''": Single3, 'b"""': Double3,
+           "R'''": Single3, 'R"""': Double3,
+           "B'''": Single3, 'B"""': Double3,
+           "br'''": Single3, 'br"""': Double3,
+           "bR'''": Single3, 'bR"""': Double3,
+           "Br'''": Single3, 'Br"""': Double3,
+           "BR'''": Single3, 'BR"""': Double3,
+           "rb'''": Single3, 'rb"""': Double3,
+           "Rb'''": Single3, 'Rb"""': Double3,
+           "rB'''": Single3, 'rB"""': Double3,
+           "RB'''": Single3, 'RB"""': Double3,
+           "u'''": Single3, 'u"""': Double3,
+           "R'''": Single3, 'R"""': Double3,
+           "U'''": Single3, 'U"""': Double3,
+           'r': None, 'R': None, 'b': None, 'B': None,
+           'u': None, 'U': None}
+
+triple_quoted = {}
+for t in ("'''", '"""',
+          "r'''", 'r"""', "R'''", 'R"""',
+          "b'''", 'b"""', "B'''", 'B"""',
+          "br'''", 'br"""', "Br'''", 'Br"""',
+          "bR'''", 'bR"""', "BR'''", 'BR"""',
+          "rb'''", 'rb"""', "rB'''", 'rB"""',
+          "Rb'''", 'Rb"""', "RB'''", 'RB"""',
+          "u'''", 'u"""', "U'''", 'U"""',
+          ):
+    triple_quoted[t] = t
+single_quoted = {}
+for t in ("'", '"',
+          "r'", 'r"', "R'", 'R"',
+          "b'", 'b"', "B'", 'B"',
+          "br'", 'br"', "Br'", 'Br"',
+          "bR'", 'bR"', "BR'", 'BR"' ,
+          "rb'", 'rb"', "rB'", 'rB"',
+          "Rb'", 'Rb"', "RB'", 'RB"' ,
+          "u'", 'u"', "U'", 'U"',
+          ):
+    single_quoted[t] = t
+
+tabsize = 8
+
+class TokenError(Exception): pass
+
+class StopTokenizing(Exception): pass
+
+
+class Untokenizer:
+
+    def __init__(self):
+        self.tokens = []
+        self.prev_row = 1
+        self.prev_col = 0
+        self.encoding = None
+
+    def add_whitespace(self, start):
+        row, col = start
+        if row < self.prev_row or row == self.prev_row and col < self.prev_col:
+            raise ValueError("start ({},{}) precedes previous end ({},{})"
+                             .format(row, col, self.prev_row, self.prev_col))
+        row_offset = row - self.prev_row
+        if row_offset:
+            self.tokens.append("\\\n" * row_offset)
+            self.prev_col = 0
+        col_offset = col - self.prev_col
+        if col_offset:
+            self.tokens.append(" " * col_offset)
+
+    def untokenize(self, iterable):
+        it = iter(iterable)
+        for t in it:
+            if len(t) == 2:
+                self.compat(t, it)
+                break
+            tok_type, token, start, end, line = t
+            if tok_type == ENCODING:
+                self.encoding = token
+                continue
+            if tok_type == ENDMARKER:
+                break
+            self.add_whitespace(start)
+            self.tokens.append(token)
+            self.prev_row, self.prev_col = end
+            if tok_type in (NEWLINE, NL):
+                self.prev_row += 1
+                self.prev_col = 0
+        return "".join(self.tokens)
+
+    def compat(self, token, iterable):
+        indents = []
+        toks_append = self.tokens.append
+        startline = token[0] in (NEWLINE, NL)
+        prevstring = False
+
+        for tok in chain([token], iterable):
+            toknum, tokval = tok[:2]
+            if toknum == ENCODING:
+                self.encoding = tokval
+                continue
+
+            if toknum in (NAME, NUMBER):
+                tokval += ' '
+
+            # Insert a space between two consecutive strings
+            if toknum == STRING:
+                if prevstring:
+                    tokval = ' ' + tokval
+                prevstring = True
+            else:
+                prevstring = False
+
+            if toknum == INDENT:
+                indents.append(tokval)
+                continue
+            elif toknum == DEDENT:
+                indents.pop()
+                continue
+            elif toknum in (NEWLINE, NL):
+                startline = True
+            elif startline and indents:
+                toks_append(indents[-1])
+                startline = False
+            toks_append(tokval)
+
+
+def untokenize(iterable):
+    """Transform tokens back into Python source code.
+    It returns a bytes object, encoded using the ENCODING
+    token, which is the first token sequence output by tokenize.
+
+    Each element returned by the iterable must be a token sequence
+    with at least two elements, a token number and token value.  If
+    only two tokens are passed, the resulting output is poor.
+
+    Round-trip invariant for full input:
+        Untokenized source will match input source exactly
+
+    Round-trip invariant for limited intput:
+        # Output bytes will tokenize the back to the input
+        t1 = [tok[:2] for tok in tokenize(f.readline)]
+        newcode = untokenize(t1)
+        readline = BytesIO(newcode).readline
+        t2 = [tok[:2] for tok in tokenize(readline)]
+        assert t1 == t2
+    """
+    ut = Untokenizer()
+    out = ut.untokenize(iterable)
+    if ut.encoding is not None:
+        out = out.encode(ut.encoding)
+    return out
+
+
+def _get_normal_name(orig_enc):
+    """Imitates get_normal_name in tokenizer.c."""
+    # Only care about the first 12 characters.
+    enc = orig_enc[:12].lower().replace("_", "-")
+    if enc == "utf-8" or enc.startswith("utf-8-"):
+        return "utf-8"
+    if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or \
+       enc.startswith(("latin-1-", "iso-8859-1-", "iso-latin-1-")):
+        return "iso-8859-1"
+    return orig_enc
+
+def detect_encoding(readline):
+    """
+    The detect_encoding() function is used to detect the encoding that should
+    be used to decode a Python source file.  It requires one argument, readline,
+    in the same way as the tokenize() generator.
+
+    It will call readline a maximum of twice, and return the encoding used
+    (as a string) and a list of any lines (left as bytes) it has read in.
+
+    It detects the encoding from the presence of a utf-8 bom or an encoding
+    cookie as specified in pep-0263.  If both a bom and a cookie are present,
+    but disagree, a SyntaxError will be raised.  If the encoding cookie is an
+    invalid charset, raise a SyntaxError.  Note that if a utf-8 bom is found,
+    'utf-8-sig' is returned.
+
+    If no encoding is specified, then the default of 'utf-8' will be returned.
+    """
+    try:
+        filename = readline.__self__.name
+    except AttributeError:
+        filename = None
+    bom_found = False
+    encoding = None
+    default = 'utf-8'
+    def read_or_stop():
+        try:
+            return readline()
+        except StopIteration:
+            return b''
+
+    def find_cookie(line):
+        try:
+            # Decode as UTF-8. Either the line is an encoding declaration,
+            # in which case it should be pure ASCII, or it must be UTF-8
+            # per default encoding.
+            line_string = line.decode('utf-8')
+        except UnicodeDecodeError:
+            msg = "invalid or missing encoding declaration"
+            if filename is not None:
+                msg = '{} for {!r}'.format(msg, filename)
+            raise SyntaxError(msg)
+
+        match = cookie_re.match(line_string)
+        if not match:
+            return None
+        encoding = _get_normal_name(match.group(1))
+        try:
+            codec = lookup(encoding)
+        except LookupError:
+            # This behaviour mimics the Python interpreter
+            if filename is None:
+                msg = "unknown encoding: " + encoding
+            else:
+                msg = "unknown encoding for {!r}: {}".format(filename,
+                        encoding)
+            raise SyntaxError(msg)
+
+        if bom_found:
+            if encoding != 'utf-8':
+                # This behaviour mimics the Python interpreter
+                if filename is None:
+                    msg = 'encoding problem: utf-8'
+                else:
+                    msg = 'encoding problem for {!r}: utf-8'.format(filename)
+                raise SyntaxError(msg)
+            encoding += '-sig'
+        return encoding
+
+    first = read_or_stop()
+    if first.startswith(BOM_UTF8):
+        bom_found = True
+        first = first[3:]
+        default = 'utf-8-sig'
+    if not first:
+        return default, []
+
+    encoding = find_cookie(first)
+    if encoding:
+        return encoding, [first]
+    if not blank_re.match(first):
+        return default, [first]
+
+    second = read_or_stop()
+    if not second:
+        return default, [first]
+
+    encoding = find_cookie(second)
+    if encoding:
+        return encoding, [first, second]
+
+    return default, [first, second]
+
+
+def open(filename):
+    """Open a file in read only mode using the encoding detected by
+    detect_encoding().
+    """
+    buffer = io.open(filename, 'rb')
+    encoding, lines = detect_encoding(buffer.readline)
+    buffer.seek(0)
+    text = TextIOWrapper(buffer, encoding, line_buffering=True)
+    text.mode = 'r'
+    return text
+
+
+def tokenize(readline):
+    """
+    The tokenize() generator requires one argment, readline, which
+    must be a callable object which provides the same interface as the
+    readline() method of built-in file objects.  Each call to the function
+    should return one line of input as bytes.  Alternately, readline
+    can be a callable function terminating with StopIteration:
+        readline = open(myfile, 'rb').__next__  # Example of alternate readline
+
+    The generator produces 5-tuples with these members: the token type; the
+    token string; a 2-tuple (srow, scol) of ints specifying the row and
+    column where the token begins in the source; a 2-tuple (erow, ecol) of
+    ints specifying the row and column where the token ends in the source;
+    and the line on which the token was found.  The line passed is the
+    logical line; continuation lines are included.
+
+    The first token sequence will always be an ENCODING token
+    which tells you which encoding was used to decode the bytes stream.
+    """
+    # This import is here to avoid problems when the itertools module is not
+    # built yet and tokenize is imported.
+    from itertools import chain, repeat
+    encoding, consumed = detect_encoding(readline)
+    rl_gen = iter(readline, b"")
+    empty = repeat(b"")
+
+    try:
+        return _tokenize(chain(consumed, rl_gen, empty).__next__, encoding)
+    except AttributeError:
+        return _tokenize(chain(consumed, rl_gen, empty).next, encoding)
+
+
+def _tokenize(readline, encoding):
+    lnum = parenlev = continued = 0
+    numchars = '0123456789'
+    contstr, needcont = '', 0
+    contline = None
+    indents = [0]
+
+    if encoding is not None:
+        if encoding == "utf-8-sig":
+            # BOM will already have been stripped.
+            encoding = "utf-8"
+        yield TokenInfo(ENCODING, encoding, (0, 0), (0, 0), '')
+    while True:             # loop over lines in stream
+        try:
+            line = readline()
+        except StopIteration:
+            line = b''
+
+        if encoding is not None:
+            line = line.decode(encoding)
+        lnum += 1
+        pos, max = 0, len(line)
+
+        if contstr:                            # continued string
+            if not line:
+                raise TokenError("EOF in multi-line string", strstart)
+            endmatch = endprog.match(line)
+            if endmatch:
+                pos = end = endmatch.end(0)
+                yield TokenInfo(STRING, contstr + line[:end],
+                       strstart, (lnum, end), contline + line)
+                contstr, needcont = '', 0
+                contline = None
+            elif needcont and line[-2:] != '\\\n' and line[-3:] != '\\\r\n':
+                yield TokenInfo(ERRORTOKEN, contstr + line,
+                           strstart, (lnum, len(line)), contline)
+                contstr = ''
+                contline = None
+                continue
+            else:
+                contstr = contstr + line
+                contline = contline + line
+                continue
+
+        elif parenlev == 0 and not continued:  # new statement
+            if not line: break
+            column = 0
+            while pos < max:                   # measure leading whitespace
+                if line[pos] == ' ':
+                    column += 1
+                elif line[pos] == '\t':
+                    column = (column//tabsize + 1)*tabsize
+                elif line[pos] == '\f':
+                    column = 0
+                else:
+                    break
+                pos += 1
+            if pos == max:
+                break
+
+            if line[pos] in '#\r\n':           # skip comments or blank lines
+                if line[pos] == '#':
+                    comment_token = line[pos:].rstrip('\r\n')
+                    nl_pos = pos + len(comment_token)
+                    yield TokenInfo(COMMENT, comment_token,
+                           (lnum, pos), (lnum, pos + len(comment_token)), line)
+                    yield TokenInfo(NL, line[nl_pos:],
+                           (lnum, nl_pos), (lnum, len(line)), line)
+                else:
+                    yield TokenInfo((NL, COMMENT)[line[pos] == '#'], line[pos:],
+                           (lnum, pos), (lnum, len(line)), line)
+                continue
+
+            if column > indents[-1]:           # count indents or dedents
+                indents.append(column)
+                yield TokenInfo(INDENT, line[:pos], (lnum, 0), (lnum, pos), line)
+            while column < indents[-1]:
+                if column not in indents:
+                    raise IndentationError(
+                        "unindent does not match any outer indentation level",
+                        ("<tokenize>", lnum, pos, line))
+                indents = indents[:-1]
+                yield TokenInfo(DEDENT, '', (lnum, pos), (lnum, pos), line)
+
+        else:                                  # continued statement
+            if not line:
+                raise TokenError("EOF in multi-line statement", (lnum, 0))
+            continued = 0
+
+        while pos < max:
+            pseudomatch = _compile(PseudoToken).match(line, pos)
+            if pseudomatch:                                # scan for tokens
+                start, end = pseudomatch.span(1)
+                spos, epos, pos = (lnum, start), (lnum, end), end
+                if start == end:
+                    continue
+                token, initial = line[start:end], line[start]
+
+                if (initial in numchars or                  # ordinary number
+                    (initial == '.' and token != '.' and token != '...')):
+                    yield TokenInfo(NUMBER, token, spos, epos, line)
+                elif initial in '\r\n':
+                    yield TokenInfo(NL if parenlev > 0 else NEWLINE,
+                           token, spos, epos, line)
+                elif initial == '#':
+                    assert not token.endswith("\n")
+                    yield TokenInfo(COMMENT, token, spos, epos, line)
+                elif token in triple_quoted:
+                    endprog = _compile(endpats[token])
+                    endmatch = endprog.match(line, pos)
+                    if endmatch:                           # all on one line
+                        pos = endmatch.end(0)
+                        token = line[start:pos]
+                        yield TokenInfo(STRING, token, spos, (lnum, pos), line)
+                    else:
+                        strstart = (lnum, start)           # multiple lines
+                        contstr = line[start:]
+                        contline = line
+                        break
+                elif initial in single_quoted or \
+                    token[:2] in single_quoted or \
+                    token[:3] in single_quoted:
+                    if token[-1] == '\n':                  # continued string
+                        strstart = (lnum, start)
+                        endprog = _compile(endpats[initial] or
+                                           endpats[token[1]] or
+                                           endpats[token[2]])
+                        contstr, needcont = line[start:], 1
+                        contline = line
+                        break
+                    else:                                  # ordinary string
+                        yield TokenInfo(STRING, token, spos, epos, line)
+                elif isidentifier(initial):               # ordinary name
+                    yield TokenInfo(NAME, token, spos, epos, line)
+                elif initial == '\\':                      # continued stmt
+                    continued = 1
+                else:
+                    if initial in '([{':
+                        parenlev += 1
+                    elif initial in ')]}':
+                        parenlev -= 1
+                    yield TokenInfo(OP, token, spos, epos, line)
+            else:
+                yield TokenInfo(ERRORTOKEN, line[pos],
+                           (lnum, pos), (lnum, pos+1), line)
+                pos += 1
+
+    for indent in indents[1:]:                 # pop remaining indent levels
+        yield TokenInfo(DEDENT, '', (lnum, 0), (lnum, 0), '')
+    yield TokenInfo(ENDMARKER, '', (lnum, 0), (lnum, 0), '')
diff --git a/lib/taurus/external/pint/pint_local/constants_en.txt b/lib/taurus/external/pint/pint_local/constants_en.txt
new file mode 100644
index 0000000..2b67545
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/constants_en.txt
@@ -0,0 +1,51 @@
+# Default Pint constants definition file
+# Based on the International System of Units
+# Language: english
+# Source: http://physics.nist.gov/cuu/Constants/Table/allascii.txt
+# :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+
+speed_of_light = 299792458 * meter / second = c
+standard_gravity = 9.806650 * meter / second ** 2 = g_0 = g_n = gravity
+vacuum_permeability = 4 * pi * 1e-7 * newton / ampere ** 2 = mu_0 = magnetic_constant
+vacuum_permittivity = 1 / (mu_0 * c **2 ) = epsilon_0 = electric_constant
+Z_0 = mu_0 * c = impedance_of_free_space = characteristic_impedance_of_vacuum
+
+# 0.000 000 29 e-34
+planck_constant = 6.62606957e-34 J s = h
+hbar = planck_constant / (2 * pi) = ħ
+
+# 0.000 80 e-11
+newtonian_constant_of_gravitation = 6.67384e-11 m^3 kg^-1 s^-2
+
+# 0.000 000 035 e-19
+# elementary_charge = 1.602176565e-19 C = e
+
+# 0.000 0075
+molar_gas_constant = 8.3144621 J mol^-1 K^-1 = R
+
+# 0.000 000 0024 e-3
+fine_structure_constant = 7.2973525698e-3
+
+# 0.000 000 27 e23
+avogadro_number = 6.02214129e23 mol^-1 =N_A
+
+# 0.000 0013 e-23
+boltzmann_constant = 1.3806488e-23 J K^-1 = k
+
+# 0.000 021 e-8
+stefan_boltzmann_constant = 5.670373e-8 W m^-2 K^-4 = σ
+
+# 0.000 0053 e10
+wien_frequency_displacement_law_constant = 5.8789254e10 Hz K^-1
+
+# 0.000 055
+rydberg_constant = 10973731.568539 m^-1
+
+# 0.000 000 40 e-31
+electron_mass = 9.10938291e-31 kg = m_e
+
+# 0.000 000 074 e-27
+neutron_mass = 1.674927351e-27 kg = m_n
+
+# 0.000 000 074 e-27
+proton_mass = 1.672621777e-27 kg = m_p
diff --git a/lib/taurus/external/pint/pint_local/context.py b/lib/taurus/external/pint/pint_local/context.py
new file mode 100644
index 0000000..7635ca4
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/context.py
@@ -0,0 +1,239 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.context
+    ~~~~~~~~~~~~
+
+    Functions and classes related to context definitions and application.
+
+    :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import division, unicode_literals, print_function, absolute_import
+
+
+import re
+from collections import defaultdict
+import weakref
+
+from .compat import ChainMap
+from .util import (ParserHelper, UnitsContainer, string_types,
+                   to_units_container)
+
+#: Regex to match the header parts of a context.
+_header_re = re.compile('@context\s*(?P<defaults>\(.*\))?\s+(?P<name>\w+)\s*(=(?P<aliases>.*))*')
+
+#: Regex to match variable names in an equation.
+_varname_re = re.compile('[A-Za-z_][A-Za-z0-9_]*')
+
+
+def _expression_to_function(eq):
+    def func(ureg, value, **kwargs):
+        return ureg.parse_expression(eq, value=value, **kwargs)
+    return func
+
+
+class Context(object):
+    """A specialized container that defines transformation functions from one
+    dimension to another. Each Dimension are specified using a UnitsContainer.
+    Simple transformation are given with a function taking a single parameter.
+
+        >>> timedim = UnitsContainer({'[time]': 1})
+        >>> spacedim = UnitsContainer({'[length]': 1})
+        >>> def f(time):
+        ...     'Time to length converter'
+        ...     return 3. * time
+        >>> c = Context()
+        >>> c.add_transformation(timedim, spacedim, f)
+        >>> c.transform(timedim, spacedim, 2)
+        6
+
+    Conversion functions may take optional keyword arguments and the context
+    can have default values for these arguments.
+
+        >>> def f(time, n):
+        ...     'Time to length converter, n is the index of refraction of the material'
+        ...     return 3. * time / n
+        >>> c = Context(n=3)
+        >>> c.add_transformation(timedim, spacedim, f)
+        >>> c.transform(timedim, spacedim, 2)
+        2
+
+    """
+
+    def __init__(self, name, aliases=(), defaults=None):
+
+        self.name = name
+        self.aliases = aliases
+
+        #: Maps (src, dst) -> transformation function
+        self.funcs = {}
+
+        #: Maps defaults variable names to values
+        self.defaults = defaults or {}
+
+        #: Maps (src, dst) -> self
+        #: Used as a convenience dictionary to be composed by ContextChain
+        self.relation_to_context = weakref.WeakValueDictionary()
+
+    @classmethod
+    def from_context(cls, context, **defaults):
+        """Creates a new context that shares the funcs dictionary with the
+        original context. The default values are copied from the original
+        context and updated with the new defaults.
+
+        If defaults is empty, return the same context.
+        """
+        if defaults:
+            newdef = dict(context.defaults, **defaults)
+            c = cls(context.name, context.aliases, newdef)
+            c.funcs = context.funcs
+            for edge in context.funcs.keys():
+                c.relation_to_context[edge] = c
+            return c
+        return context
+
+    @classmethod
+    def from_lines(cls, lines, to_base_func=None):
+        header, lines = lines[0], lines[1:]
+
+        r = _header_re.search(header)
+        name = r.groupdict()['name'].strip()
+        aliases = r.groupdict()['aliases']
+        if aliases:
+            aliases = tuple(a.strip() for a in r.groupdict()['aliases'].split('='))
+        else:
+            aliases = ()
+        defaults = r.groupdict()['defaults']
+
+        if defaults:
+            def to_num(val):
+                val = complex(val)
+                if not val.imag:
+                    return val.real
+                return val
+
+            try:
+                _txt = defaults
+                defaults = (part.split('=') for part in defaults.strip('()').split(','))
+                defaults = dict((str(k).strip(), to_num(v))
+                                for k, v in defaults)
+            except (ValueError, TypeError):
+                raise ValueError('Could not parse Context definition defaults: %s', _txt)
+
+            ctx = cls(name, aliases, defaults)
+        else:
+            ctx = cls(name, aliases)
+
+        names = set()
+        for line in lines:
+            line = line.strip()
+            if not line or line.startswith('#'):
+                continue
+
+            rel, eq = line.split(':')
+            names.update(_varname_re.findall(eq))
+
+            func = _expression_to_function(eq)
+
+            if '<->' in rel:
+                src, dst = (ParserHelper.from_string(s)
+                            for s in rel.split('<->'))
+                if to_base_func:
+                    src = to_base_func(src)
+                    dst = to_base_func(dst)
+                ctx.add_transformation(src, dst, func)
+                ctx.add_transformation(dst, src, func)
+            elif '->' in rel:
+                src, dst = (ParserHelper.from_string(s)
+                            for s in rel.split('->'))
+                if to_base_func:
+                    src = to_base_func(src)
+                    dst = to_base_func(dst)
+                ctx.add_transformation(src, dst, func)
+            else:
+                raise ValueError('Relationships must be specified with <-> or ->.')
+
+        if defaults:
+            missing_pars = set(defaults.keys()).difference(set(names))
+            if missing_pars:
+                raise ValueError('Context parameters {0} not found in any equation.'.format(missing_pars))
+
+        return ctx
+
+    def add_transformation(self, src, dst, func):
+        """Add a transformation function to the context.
+        """
+        _key = self.__keytransform__(src, dst)
+        self.funcs[_key] = func
+        self.relation_to_context[_key] = self
+
+    def remove_transformation(self, src, dst):
+        """Add a transformation function to the context.
+        """
+        _key = self.__keytransform__(src, dst)
+        del self.funcs[_key]
+        del self.relation_to_context[_key]
+
+    @staticmethod
+    def __keytransform__(src, dst):
+        return to_units_container(src), to_units_container(dst)
+
+    def transform(self, src, dst, registry, value):
+        """Transform a value.
+        """
+        _key = self.__keytransform__(src, dst)
+        return self.funcs[_key](registry, value, **self.defaults)
+
+
+class ContextChain(ChainMap):
+    """A specialized ChainMap for contexts that simplifies finding rules
+    to transform from one dimension to another.
+    """
+
+    def __init__(self, *args, **kwargs):
+        super(ContextChain, self).__init__(*args, **kwargs)
+        self._graph = None
+        self._contexts = []
+
+    def insert_contexts(self, *contexts):
+        """Insert one or more contexts in reversed order the chained map.
+        (A rule in last context will take precedence)
+
+        To facilitate the identification of the context with the matching rule,
+        the *relation_to_context* dictionary of the context is used.
+        """
+        self._contexts.insert(0, contexts)
+        self.maps = [ctx.relation_to_context for ctx in reversed(contexts)] + self.maps
+        self._graph = None
+
+    def remove_contexts(self, n):
+        """Remove the last n inserted contexts from the chain.
+        """
+        self._contexts = self._contexts[n:]
+        self.maps = self.maps[n:]
+        self._graph = None
+
+    @property
+    def defaults(self):
+        if self:
+            return list(self.maps[0].values())[0].defaults
+        return {}
+
+    @property
+    def graph(self):
+        """The graph relating
+        """
+        if self._graph is None:
+            self._graph = defaultdict(set)
+            for fr_, to_ in self:
+                self._graph[fr_].add(to_)
+        return self._graph
+
+    def transform(self, src, dst, registry, value):
+        """Transform the value, finding the rule in the chained context.
+        (A rule in last context will take precedence)
+
+        :raises: KeyError if the rule is not found.
+        """
+        return self[(src, dst)].transform(src, dst, registry, value)
diff --git a/lib/taurus/external/pint/pint_local/converters.py b/lib/taurus/external/pint/pint_local/converters.py
new file mode 100644
index 0000000..0768537
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/converters.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.converters
+    ~~~~~~~~~
+
+    Functions and classes related to unit conversions.
+
+    :copyright: 2014 by Pint Authors, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+from __future__ import (division, unicode_literals, print_function,
+                        absolute_import)
+
+
+class Converter(object):
+    """Base class for value converters.
+    """
+
+    is_multiplicative = True
+
+    def to_reference(self, value, inplace=False):
+        return value
+
+    def from_reference(self, value, inplace=False):
+        return value
+
+
+class ScaleConverter(Converter):
+    """A linear transformation
+    """
+
+    is_multiplicative = True
+
+    def __init__(self, scale):
+        self.scale = scale
+
+    def to_reference(self, value, inplace=False):
+        if inplace:
+            value *= self.scale
+        else:
+            value = value * self.scale
+
+        return value
+
+    def from_reference(self, value, inplace=False):
+        if inplace:
+            value /= self.scale
+        else:
+            value = value / self.scale
+
+        return value
+
+
+class OffsetConverter(Converter):
+    """An affine transformation
+    """
+
+    def __init__(self, scale, offset):
+        self.scale = scale
+        self.offset = offset
+
+    @property
+    def is_multiplicative(self):
+        return self.offset == 0
+
+    def to_reference(self, value, inplace=False):
+        if inplace:
+            value *= self.scale
+            value += self.offset
+        else:
+            value = value * self.scale + self.offset
+
+        return value
+
+    def from_reference(self, value, inplace=False):
+        if inplace:
+            value -= self.offset
+            value /= self.scale
+        else:
+            value = (value - self.offset) / self.scale
+
+        return value
diff --git a/lib/taurus/external/pint/pint_local/default_en.txt b/lib/taurus/external/pint/pint_local/default_en.txt
new file mode 100644
index 0000000..a1145c7
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/default_en.txt
@@ -0,0 +1,377 @@
+# Default Pint units definition file
+# Based on the International System of Units
+# Language: english
+# :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+
+# decimal prefixes
+yocto- = 1e-24 = y-
+zepto- = 1e-21 = z-
+atto- =  1e-18 = a-
+femto- = 1e-15 = f-
+pico- =  1e-12 = p-
+nano- =  1e-9  = n-
+micro- = 1e-6  = u- = µ-
+milli- = 1e-3  = m-
+centi- = 1e-2  = c-
+deci- =  1e-1  = d-
+deca- =  1e+1  = da-
+hecto- = 1e2   = h-
+kilo- =  1e3   = k-
+mega- =  1e6   = M-
+giga- =  1e9   = G-
+tera- =  1e12  = T-
+peta- =  1e15  = P-
+exa- =   1e18  = E-
+zetta- = 1e21  = Z-
+yotta- = 1e24  = Y-
+
+# binary_prefixes
+kibi- = 2**10 = Ki-
+mebi- = 2**20 = Mi-
+gibi- = 2**30 = Gi-
+tebi- = 2**40 = Ti-
+pebi- = 2**50 = Pi-
+exbi- = 2**60 = Ei-
+zebi- = 2**70 = Zi-
+yobi- = 2**80 = Yi-
+
+# reference
+meter = [length] = m = metre
+second = [time] = s = sec
+ampere = [current] = A = amp
+candela = [luminosity] = cd = candle
+gram = [mass] = g
+mole = [substance] = mol
+kelvin = [temperature]; offset: 0 = K = degK
+radian = [] = rad
+bit = []
+count = []
+
+ at import constants_en.txt
+
+# acceleration
+[acceleration] = [length] / [time] ** 2
+
+# Angle
+turn = 2 * pi * radian = revolution = cycle = circle
+degree = pi / 180 * radian = deg = arcdeg = arcdegree = angular_degree
+arcminute = arcdeg / 60 = arcmin = arc_minute = angular_minute
+arcsecond = arcmin / 60 =  arcsec = arc_second = angular_second
+steradian = radian ** 2 = sr
+
+# Area
+[area] = [length] ** 2
+are = 100 * m**2
+barn = 1e-28 * m ** 2 = b
+cmil = 5.067075e-10 * m ** 2 = circular_mils
+darcy = 9.869233e-13 * m ** 2
+acre = 4046.8564224 * m ** 2 = international_acre
+hectare = 100 * are = ha
+US_survey_acre = 160 * rod ** 2
+
+# EM
+esu = 1 * erg**0.5 * centimeter**0.5 = statcoulombs = statC = franklin = Fr
+esu_per_second = 1 * esu / second = statampere
+ampere_turn = 1 * A
+gilbert = 10 / (4 * pi ) * ampere_turn
+coulomb = ampere * second = C
+volt = joule / coulomb = V
+farad = coulomb / volt = F
+ohm = volt / ampere = Ω
+siemens = ampere / volt = S = mho
+weber = volt * second = Wb
+tesla = weber / meter ** 2 = T
+henry = weber / ampere = H
+elementary_charge = 1.602176487e-19 * coulomb = e
+chemical_faraday = 9.64957e4 * coulomb
+physical_faraday = 9.65219e4 * coulomb
+faraday =  96485.3399 * coulomb = C12_faraday
+gamma = 1e-9 * tesla
+gauss = 1e-4 * tesla
+maxwell = 1e-8 * weber = mx
+oersted = 1000 / (4 * pi) * A / m = Oe
+statfarad = 1.112650e-12 * farad = statF = stF
+stathenry = 8.987554e11 * henry = statH = stH
+statmho = 1.112650e-12 * siemens = statS = stS
+statohm = 8.987554e11 * ohm
+statvolt = 2.997925e2 * volt = statV = stV
+unit_pole = 1.256637e-7 * weber
+
+# Energy
+[energy] = [force] * [length]
+joule = newton * meter = J
+erg = dyne * centimeter
+btu = 1.05505585262e3 * joule = Btu = BTU = british_thermal_unit
+electron_volt = 1.60217653e-19 * J = eV
+quadrillion_btu = 10**15 * btu = quad
+thm = 100000 * BTU = therm = EC_therm
+cal = 4.184 * joule = calorie = thermochemical_calorie
+international_steam_table_calorie = 4.1868 * joule
+ton_TNT = 4.184e9 * joule = tTNT
+US_therm = 1.054804e8 * joule
+watt_hour = watt * hour = Wh = watthour
+hartree = 4.35974394e-18 * joule = E_h = hartree_energy
+
+# Force
+[force] = [mass] * [acceleration]
+newton = kilogram * meter / second ** 2 = N
+dyne = gram * centimeter / second ** 2 = dyn
+force_kilogram = g_0 * kilogram = kgf = kilogram_force = pond
+force_gram = g_0 * gram = gf = gram_force
+force_ounce = g_0 * ounce = ozf = ounce_force
+force_pound = g_0 * lb = lbf = pound_force
+force_ton = 2000 * force_pound = ton_force
+poundal = lb * feet / second ** 2 = pdl
+kip = 1000*lbf
+
+# Frequency
+[frequency] = 1 / [time]
+hertz = 1 / second = Hz = rps
+revolutions_per_minute = revolution / minute = rpm
+counts_per_second = count / second = cps
+
+# Heat
+#RSI = degK * meter ** 2 / watt
+#clo = 0.155 * RSI = clos
+#R_value = foot ** 2 * degF * hour / btu
+
+# Information
+byte = 8 * bit = Bo = octet
+baud = bit / second = Bd = bps
+
+# Irradiance
+peak_sun_hour = 1000 * watt_hour / meter**2 = PSH
+langley = thermochemical_calorie / centimeter**2 = Langley
+
+# Length
+angstrom = 1e-10 * meter = Å = ångström = Å
+inch = 2.54 * centimeter = in = international_inch = inches = international_inches
+foot = 12 * inch = ft = international_foot = feet = international_feet
+mile = 5280 * foot = mi = international_mile
+yard = 3 * feet = yd = international_yard
+mil = inch / 1000 = thou
+parsec = 3.08568025e16 * meter = pc
+light_year = speed_of_light * julian_year = ly = lightyear
+astronomical_unit = 149597870691 * meter = au
+nautical_mile = 1.852e3 * meter = nmi
+printers_point = 127 * millimeter / 360 = point
+printers_pica = 12 * printers_point = pica
+US_survey_foot = 1200 * meter / 3937
+US_survey_yard =  3 * US_survey_foot
+US_survey_mile = 5280 * US_survey_foot = US_statute_mile
+rod = 16.5 * US_survey_foot = pole = perch
+furlong = 660 * US_survey_foot
+fathom = 6 * US_survey_foot
+chain = 66 * US_survey_foot
+barleycorn = inch / 3
+arpentlin = 191.835 * feet
+kayser = 1 / centimeter = wavenumber
+
+# Mass
+dram = oz / 16 = dr = avoirdupois_dram
+ounce = 28.349523125 * gram = oz = avoirdupois_ounce
+pound = 0.45359237 * kilogram = lb = avoirdupois_pound
+stone = 14 * lb = st
+carat = 200 * milligram
+grain = 64.79891 * milligram = gr
+long_hundredweight = 112 * lb
+short_hundredweight = 100 * lb
+metric_ton = 1000 * kilogram = t = tonne
+pennyweight = 24 * gram = dwt
+slug = 14.59390 * kilogram
+troy_ounce = 480 * grain = toz = apounce = apothecary_ounce
+troy_pound = 12 * toz = tlb = appound = apothecary_pound
+drachm = 60 * grain = apdram = apothecary_dram
+atomic_mass_unit = 1.660538782e-27 * kilogram =  u = amu = dalton = Da
+scruple = 20 * grain
+bag = 94 * lb
+ton = 2000 * lb = short_ton
+
+# Textile
+denier =  gram / (9000 * meter)
+tex = gram/ (1000 * meter)
+dtex = decitex
+
+# Photometry
+lumen = candela * steradian = lm
+lux = lumen / meter ** 2 = lx
+
+# Power
+[power] = [energy] / [time]
+watt = joule / second = W = volt_ampere = VA
+horsepower = 33000 * ft * lbf / min = hp = UK_horsepower = British_horsepower
+boiler_horsepower = 33475 * btu / hour
+metric_horsepower =  75 * force_kilogram * meter / second
+electric_horsepower = 746 * watt
+hydraulic_horsepower = 550 * feet * lbf / second
+refrigeration_ton = 12000 * btu / hour = ton_of_refrigeration
+
+# Pressure
+[pressure] = [force] / [area]
+Hg = gravity * 13.59510 * gram / centimeter ** 3 = mercury = conventional_mercury
+mercury_60F = gravity * 13.5568 * gram / centimeter ** 3
+H2O = gravity * 1000 * kilogram / meter ** 3 = h2o = water = conventional_water
+water_4C = gravity * 999.972 * kilogram / meter ** 3 = water_39F
+water_60F = gravity * 999.001 * kilogram / m ** 3
+pascal = newton / meter ** 2 = Pa
+bar = 100000 * pascal
+atmosphere = 101325 * pascal = atm = standard_atmosphere
+technical_atmosphere = kilogram * gravity / centimeter ** 2 = at
+torr = atm / 760
+psi = pound * gravity / inch ** 2 = pound_force_per_square_inch
+ksi = kip / inch ** 2 = kip_per_square_inch
+barye = 0.1 * newton / meter ** 2 = barie = barad = barrie = baryd = Ba
+mmHg = millimeter * Hg = mm_Hg = millimeter_Hg = millimeter_Hg_0C
+cmHg = centimeter * Hg = cm_Hg = centimeter_Hg
+inHg = inch * Hg = in_Hg = inch_Hg = inch_Hg_32F
+inch_Hg_60F = inch * mercury_60F
+inch_H2O_39F = inch * water_39F
+inch_H2O_60F = inch * water_60F
+footH2O = ft * water
+cmH2O = centimeter * water
+foot_H2O = ft * water = ftH2O
+standard_liter_per_minute = 1.68875 * Pa * m ** 3 / s = slpm = slm
+
+# Radiation
+Bq = Hz = becquerel
+curie = 3.7e10 * Bq = Ci
+rutherford = 1e6*Bq = rd = Rd
+Gy = joule / kilogram = gray = Sv = sievert
+rem = 1e-2 * sievert
+rads = 1e-2 * gray
+roentgen = 2.58e-4 * coulomb / kilogram
+
+# Temperature
+degC = kelvin; offset: 273.15 = celsius
+degR = 5 / 9 * kelvin; offset: 0 = rankine
+degF = 5 / 9 * kelvin; offset: 255.372222 = fahrenheit
+
+# Time
+minute = 60 * second = min
+hour = 60 * minute = hr
+day = 24 * hour
+week = 7 * day
+fortnight = 2 * week
+year = 31556925.9747 * second
+month = year/12
+shake = 1e-8 * second
+sidereal_day = day / 1.00273790935079524
+sidereal_hour = sidereal_day/24
+sidereal_minute=sidereal_hour/60
+sidereal_second =sidereal_minute/60
+sidereal_year = 366.25636042 * sidereal_day
+sidereal_month = 27.321661 * sidereal_day
+tropical_month = 27.321661 * day
+synodic_month = 29.530589 * day = lunar_month
+common_year = 365 * day
+leap_year = 366 * day
+julian_year = 365.25 * day
+gregorian_year = 365.2425 * day
+millenium = 1000 * year = millenia = milenia = milenium
+eon = 1e9 * year
+work_year = 2056 * hour
+work_month = work_year/12
+
+# Velocity
+[speed] = [length] / [time]
+knot = nautical_mile / hour = kt = knot_international = international_knot = nautical_miles_per_hour
+mph = mile / hour = MPH
+kph = kilometer / hour = KPH
+
+# Viscosity
+[viscosity] = [pressure] * [time]
+poise = 1e-1 * Pa * second = P
+stokes = 1e-4 * meter ** 2 / second = St
+rhe = 10 / (Pa * s)
+
+# Volume
+[volume] = [length] ** 3
+liter = 1e-3 * m ** 3 = l = L = litre
+cc = centimeter ** 3 = cubic_centimeter
+stere = meter ** 3
+gross_register_ton = 100 * foot ** 3 = register_ton = GRT
+acre_foot = acre * foot = acre_feet
+board_foot = foot ** 2 * inch = FBM
+bushel = 2150.42 * inch ** 3 = bu = US_bushel
+dry_gallon = bushel / 8 = US_dry_gallon
+dry_quart = dry_gallon / 4 = US_dry_quart
+dry_pint = dry_quart / 2 = US_dry_pint
+gallon = 231 * inch ** 3 = liquid_gallon = US_liquid_gallon
+quart = gallon / 4 = liquid_quart = US_liquid_quart
+pint = quart / 2 = pt = liquid_pint = US_liquid_pint
+cup = pint / 2 = liquid_cup = US_liquid_cup
+gill = cup / 2 = liquid_gill = US_liquid_gill
+fluid_ounce = gill / 4 = floz = US_fluid_ounce = US_liquid_ounce
+imperial_bushel = 36.36872 * liter = UK_bushel
+imperial_gallon = imperial_bushel / 8 = UK_gallon
+imperial_quart = imperial_gallon / 4 = UK_quart
+imperial_pint = imperial_quart / 2 = UK_pint
+imperial_cup = imperial_pint / 2 = UK_cup
+imperial_gill = imperial_cup / 2 = UK_gill
+imperial_floz = imperial_gill / 5 = UK_fluid_ounce = imperial_fluid_ounce
+barrel = 42 * gallon = bbl
+tablespoon = floz / 2 = tbsp = Tbsp = Tblsp = tblsp = tbs = Tbl
+teaspoon = tablespoon / 3 = tsp
+peck = bushel / 4 = pk
+fluid_dram = floz / 8 = fldr = fluidram
+firkin = barrel / 4
+
+
+ at context(n=1) spectroscopy = sp
+    # n index of refraction of the medium.
+    [length] <-> [frequency]: speed_of_light / n / value
+    [frequency] -> [energy]: planck_constant * value
+    [energy] -> [frequency]: value / planck_constant
+ at end
+
+ at context boltzmann
+    [temperature] -> [energy]: boltzmann_constant * value
+    [energy] -> [temperature]: value / boltzmann_constant
+ at end
+
+ at context(mw=0,volume=0,solvent_mass=0) chemistry = chem
+    # mw is the molecular weight of the species
+    # volume is the volume of the solution
+    # solvent_mass is the mass of solvent in the solution
+
+    # moles -> mass require the molecular weight
+    [substance] -> [mass]: value * mw
+    [mass] -> [substance]: value / mw
+
+    # moles/volume -> mass/volume and moles/mass -> mass / mass
+    # require the  molecular weight
+    [substance] / [volume] -> [mass] / [volume]: value * mw
+    [mass] / [volume] -> [substance] / [volume]: value / mw
+    [substance] / [mass] -> [mass] / [mass]: value * mw
+    [mass] / [mass] -> [substance] / [mass]: value / mw
+
+    # moles/volume -> moles requires the solution volume
+    [substance] / [volume] -> [substance]: value * volume
+    [substance] -> [substance] / [volume]: value / volume
+
+    # moles/mass -> moles requires the solvent (usually water) mass
+    [substance] / [mass] -> [substance]: value * solvent_mass
+    [substance] -> [substance] / [mass]: value / solvent_mass
+
+    # moles/mass -> moles/volume require the solvent mass and the volume
+    [substance] / [mass] -> [substance]/[volume]: value * solvent_mass / volume
+    [substance] / [volume] -> [substance] / [mass]: value / solvent_mass * volume
+
+ at end
+
+ at system mks
+    meter
+    kilogram
+    second
+ at end
+
+ at system cgs
+    centimeter
+    gram
+    second
+ at end
+
+ at system imperial
+    yard
+    pound
+ at end
diff --git a/lib/taurus/external/pint/pint_local/definitions.py b/lib/taurus/external/pint/pint_local/definitions.py
new file mode 100644
index 0000000..a25fd73
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/definitions.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.definitions
+    ~~~~~~~~~
+
+    Functions and classes related to unit definitions.
+
+    :copyright: 2014 by Pint Authors, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import (division, unicode_literals, print_function,
+                        absolute_import)
+
+from .converters import ScaleConverter, OffsetConverter
+from .util import UnitsContainer, _is_dim, ParserHelper
+from .compat import string_types
+
+
+class Definition(object):
+    """Base class for definitions.
+
+    :param name: name.
+    :param symbol: a short name or symbol for the definition
+    :param aliases: iterable of other names.
+    :param converter: an instance of Converter.
+    """
+
+    def __init__(self, name, symbol, aliases, converter):
+        self._name = name
+        self._symbol = symbol
+        self._aliases = aliases
+        self._converter = converter
+
+    @property
+    def is_multiplicative(self):
+        return self._converter.is_multiplicative
+
+    @classmethod
+    def from_string(cls, definition):
+        """Parse a definition
+        """
+        name, definition = definition.split('=', 1)
+        name = name.strip()
+
+        result = [res.strip() for res in definition.split('=')]
+        value, aliases = result[0], tuple(result[1:])
+        symbol, aliases = (aliases[0], aliases[1:]) if aliases else (None,
+                                                                     aliases)
+
+        if name.startswith('['):
+            return DimensionDefinition(name, symbol, aliases, value)
+        elif name.endswith('-'):
+            name = name.rstrip('-')
+            return PrefixDefinition(name, symbol, aliases, value)
+        else:
+            return UnitDefinition(name, symbol, aliases, value)
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def symbol(self):
+        return self._symbol or self._name
+
+    @property
+    def has_symbol(self):
+        return bool(self._symbol)
+
+    @property
+    def aliases(self):
+        return self._aliases
+
+    @property
+    def converter(self):
+        return self._converter
+
+    def __str__(self):
+        return self.name
+
+
+class PrefixDefinition(Definition):
+    """Definition of a prefix.
+    """
+
+    def __init__(self, name, symbol, aliases, converter):
+        if isinstance(converter, string_types):
+            converter = ScaleConverter(eval(converter))
+        aliases = tuple(alias.strip('-') for alias in aliases)
+        if symbol:
+            symbol = symbol.strip('-')
+        super(PrefixDefinition, self).__init__(name, symbol, aliases,
+                                               converter)
+
+
+class UnitDefinition(Definition):
+    """Definition of a unit.
+
+    :param reference: Units container with reference units.
+    :param is_base: indicates if it is a base unit.
+    """
+
+    def __init__(self, name, symbol, aliases, converter,
+                 reference=None, is_base=False):
+        self.reference = reference
+        self.is_base = is_base
+        if isinstance(converter, string_types):
+            if ';' in converter:
+                [converter, modifiers] = converter.split(';', 2)
+                modifiers = dict((key.strip(), eval(value)) for key, value in
+                                 (part.split(':')
+                                  for part in modifiers.split(';')))
+            else:
+                modifiers = {}
+
+            converter = ParserHelper.from_string(converter)
+            if all(_is_dim(key) for key in converter.keys()):
+                self.is_base = True
+            elif not any(_is_dim(key) for key in converter.keys()):
+                self.is_base = False
+            else:
+                raise ValueError('Cannot mix dimensions and units in the same definition. '
+                                 'Base units must be referenced only to dimensions. '
+                                 'Derived units must be referenced only to units.')
+            self.reference = UnitsContainer(converter)
+            if modifiers.get('offset', 0.) != 0.:
+                converter = OffsetConverter(converter.scale,
+                                            modifiers['offset'])
+            else:
+                converter = ScaleConverter(converter.scale)
+
+        super(UnitDefinition, self).__init__(name, symbol, aliases, converter)
+
+
+class DimensionDefinition(Definition):
+    """Definition of a dimension.
+    """
+
+    def __init__(self, name, symbol, aliases, converter,
+                 reference=None, is_base=False):
+        self.reference = reference
+        self.is_base = is_base
+        if isinstance(converter, string_types):
+            converter = ParserHelper.from_string(converter)
+            if not converter:
+                self.is_base = True
+            elif all(_is_dim(key) for key in converter.keys()):
+                self.is_base = False
+            else:
+                raise ValueError('Base dimensions must be referenced to None. '
+                                 'Derived dimensions must only be referenced '
+                                 'to dimensions.')
+            self.reference = UnitsContainer(converter)
+
+        super(DimensionDefinition, self).__init__(name, symbol, aliases,
+                                                  converter=None)
diff --git a/lib/taurus/external/pint/pint_local/errors.py b/lib/taurus/external/pint/pint_local/errors.py
new file mode 100644
index 0000000..4d86198
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/errors.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.errors
+    ~~~~~~~~~
+
+    Functions and classes related to unit definitions and conversions.
+
+    :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+from __future__ import (division, unicode_literals, print_function,
+                        absolute_import)
+
+from .compat import string_types
+
+
+class DefinitionSyntaxError(ValueError):
+    """Raised when a textual definition has a syntax error.
+    """
+
+    def __init__(self, msg, filename=None, lineno=None):
+        super(ValueError, self).__init__()
+        self.msg = msg
+        self.filename = None
+        self.lineno = None
+
+    def __str__(self):
+        mess = "While opening {0}, in line {1}: "
+        return mess.format(self.filename, self.lineno) + self.msg
+
+
+class RedefinitionError(ValueError):
+    """Raised when a unit or prefix is redefined.
+    """
+
+    def __init__(self, name, definition_type):
+        super(ValueError, self).__init__()
+        self.name = name
+        self.definition_type = definition_type
+        self.filename = None
+        self.lineno = None
+
+    def __str__(self):
+        msg = "cannot redefine '{0}' ({1})".format(self.name,
+                                                   self.definition_type)
+        if self.filename:
+            mess = "While opening {0}, in line {1}: "
+            return mess.format(self.filename, self.lineno) + msg
+        return msg
+
+
+class UndefinedUnitError(ValueError):
+    """Raised when the units are not defined in the unit registry.
+    """
+
+    def __init__(self, unit_names):
+        super(ValueError, self).__init__()
+        self.unit_names = unit_names
+
+    def __str__(self):
+        mess = "'{0}' is not defined in the unit registry"
+        mess_plural = "'{0}' are not defined in the unit registry"
+        if isinstance(self.unit_names, string_types):
+            return mess.format(self.unit_names)
+        elif isinstance(self.unit_names, (list, tuple))\
+                and len(self.unit_names) == 1:
+            return mess.format(self.unit_names[0])
+        elif isinstance(self.unit_names, set) and len(self.unit_names) == 1:
+            uname = list(self.unit_names)[0]
+            return mess.format(uname)
+        else:
+            return mess_plural.format(self.unit_names)
+
+
+class DimensionalityError(ValueError):
+    """Raised when trying to convert between incompatible units.
+    """
+
+    def __init__(self, units1, units2, dim1=None, dim2=None, extra_msg=''):
+        super(DimensionalityError, self).__init__()
+        self.units1 = units1
+        self.units2 = units2
+        self.dim1 = dim1
+        self.dim2 = dim2
+        self.extra_msg = extra_msg
+
+    def __str__(self):
+        if self.dim1 or self.dim2:
+            dim1 = ' ({0})'.format(self.dim1)
+            dim2 = ' ({0})'.format(self.dim2)
+        else:
+            dim1 = ''
+            dim2 = ''
+
+        msg = "Cannot convert from '{0}'{1} to '{2}'{3}" + self.extra_msg
+
+        return msg.format(self.units1, dim1, self.units2, dim2)
+
+
+class OffsetUnitCalculusError(ValueError):
+    """Raised on ambiguous operations with offset units.
+    """
+    def __init__(self, units1, units2='', extra_msg=''):
+        super(ValueError, self).__init__()
+        self.units1 = units1
+        self.units2 = units2
+        self.extra_msg = extra_msg
+
+    def __str__(self):
+        msg = ("Ambiguous operation with offset unit (%s)." %
+               ', '.join(['%s' % u for u in [self.units1, self.units2] if u])
+               + self.extra_msg)
+        return msg.format(self.units1, self.units2)
diff --git a/lib/taurus/external/pint/pint_local/formatting.py b/lib/taurus/external/pint/pint_local/formatting.py
new file mode 100644
index 0000000..f9b43d0
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/formatting.py
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.formatter
+    ~~~~~~~~~~~~~~
+
+    Format units for pint.
+
+    :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import division, unicode_literals, print_function, absolute_import
+
+import re
+
+__JOIN_REG_EXP = re.compile("\{\d*\}")
+
+
+def _join(fmt, iterable):
+    """Join an iterable with the format specified in fmt.
+
+    The format can be specified in two ways:
+    - PEP3101 format with two replacement fields (eg. '{0} * {1}')
+    - The concatenating string (eg. ' * ')
+    """
+    if not iterable:
+        return ''
+    if not __JOIN_REG_EXP.search(fmt):
+        return fmt.join(iterable)
+    miter = iter(iterable)
+    first = next(miter)
+    for val in miter:
+        ret = fmt.format(first, val)
+        first = ret
+    return first
+
+_PRETTY_EXPONENTS = '⁰¹²³⁴⁵⁶⁷⁸⁹'
+
+
+def _pretty_fmt_exponent(num):
+    """Format an number into a pretty printed exponent.
+    """
+    # TODO: Will not work for decimals
+    ret = '{0:n}'.format(num).replace('-', '⁻')
+    for n in range(10):
+        ret = ret.replace(str(n), _PRETTY_EXPONENTS[n])
+    return ret
+
+
+#: _FORMATS maps format specifications to the corresponding argument set to
+#: formatter().
+_FORMATS = {
+    'P': {   # Pretty format.
+        'as_ratio': True,
+        'single_denominator': False,
+        'product_fmt': '·',
+        'division_fmt': '/',
+        'power_fmt': '{0}{1}',
+        'parentheses_fmt': '({0})',
+        'exp_call': _pretty_fmt_exponent,
+        },
+
+    'L': {   # Latex format.
+        'as_ratio': True,
+        'single_denominator': True,
+        'product_fmt': r' \cdot ',
+        'division_fmt': r'\frac[{0}][{1}]',
+        'power_fmt': '{0}^[{1}]',
+        'parentheses_fmt': r'\left({0}\right)',
+        },
+
+    'H': {   # HTML format.
+        'as_ratio': True,
+        'single_denominator': True,
+        'product_fmt': r' ',
+        'division_fmt': r'{0}/{1}',
+        'power_fmt': '{0}<sup>{1}</sup>',
+        'parentheses_fmt': r'({0})',
+        },
+
+    '': {   # Default format.
+        'as_ratio': True,
+        'single_denominator': False,
+        'product_fmt': ' * ',
+        'division_fmt': ' / ',
+        'power_fmt': '{0} ** {1}',
+        'parentheses_fmt': r'({0})',
+        },
+
+    'C': {  # Compact format.
+        'as_ratio': True,
+        'single_denominator': False,
+        'product_fmt': '*',  # TODO: Should this just be ''?
+        'division_fmt': '/',
+        'power_fmt': '{0}**{1}',
+        'parentheses_fmt': r'({0})',
+        },
+    }
+
+
+def formatter(items, as_ratio=True, single_denominator=False,
+              product_fmt=' * ', division_fmt=' / ', power_fmt='{0} ** {1}',
+              parentheses_fmt='({0})', exp_call=lambda x: '{0:n}'.format(x)):
+    """Format a list of (name, exponent) pairs.
+
+    :param items: a list of (name, exponent) pairs.
+    :param as_ratio: True to display as ratio, False as negative powers.
+    :param single_denominator: all with terms with negative exponents are
+                               collected together.
+    :param product_fmt: the format used for multiplication.
+    :param division_fmt: the format used for division.
+    :param power_fmt: the format used for exponentiation.
+    :param parentheses_fmt: the format used for parenthesis.
+
+    :return: the formula as a string.
+    """
+
+    if not items:
+        return ''
+
+    if as_ratio:
+        fun = lambda x: exp_call(abs(x))
+    else:
+        fun = exp_call
+
+    pos_terms, neg_terms = [], []
+
+    for key, value in sorted(items):
+        if value == 1:
+            pos_terms.append(key)
+        elif value > 0:
+            pos_terms.append(power_fmt.format(key, fun(value)))
+        elif value == -1 and as_ratio:
+            neg_terms.append(key)
+        else:
+            neg_terms.append(power_fmt.format(key, fun(value)))
+
+    if not as_ratio:
+        # Show as Product: positive * negative terms ** -1
+        return _join(product_fmt, pos_terms + neg_terms)
+
+    # Show as Ratio: positive terms / negative terms
+    pos_ret = _join(product_fmt, pos_terms) or '1'
+
+    if not neg_terms:
+        return pos_ret
+
+    if single_denominator:
+        neg_ret = _join(product_fmt, neg_terms)
+        if len(neg_terms) > 1:
+            neg_ret = parentheses_fmt.format(neg_ret)
+    else:
+        neg_ret = _join(division_fmt, neg_terms)
+
+    return _join(division_fmt, [pos_ret, neg_ret])
+
+# Extract just the type from the specification mini-langage: see
+# http://docs.python.org/2/library/string.html#format-specification-mini-language
+# We also add uS for uncertainties.
+_BASIC_TYPES = frozenset('bcdeEfFgGnosxX%uS')
+_KNOWN_TYPES = frozenset(list(_FORMATS.keys()) + ['~'])
+
+def _parse_spec(spec):
+    result = ''
+    for ch in reversed(spec):
+        if ch == '~' or ch in _BASIC_TYPES:
+            continue
+        elif ch in _KNOWN_TYPES:
+            if result:
+                raise ValueError("expected ':' after format specifier")
+            else:
+                result = ch
+        elif ch.isalpha():
+            raise ValueError("Unknown conversion specified " + ch)
+        else:
+            break
+    return result
+
+
+def format_unit(unit, spec):
+    if not unit:
+        return 'dimensionless'
+
+    spec = _parse_spec(spec)
+    fmt = {}
+    # Convert keys and values to str to avoid a bug in Python2.6
+    for k, v in _FORMATS[spec].items():
+        fmt[str(k)] = str(v)
+
+    result = formatter(unit.items(), **fmt)
+    if spec == 'L':
+        result = result.replace('[', '{').replace(']', '}')
+    return result
+
+
+def remove_custom_flags(spec):
+    for flag in _KNOWN_TYPES:
+        if flag:
+            spec = spec.replace(flag, '')
+    return spec
diff --git a/lib/taurus/external/pint/pint_local/measurement.py b/lib/taurus/external/pint/pint_local/measurement.py
new file mode 100644
index 0000000..e82591d
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/measurement.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.measurement
+    ~~~~~~~~~~~~~~~~
+
+    :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import division, unicode_literals, print_function, absolute_import
+
+from .compat import ufloat
+from .formatting import _FORMATS
+
+MISSING = object()
+
+
+class _Measurement(object):
+    """Implements a class to describe a quantity with uncertainty.
+
+    :param value: The most likely value of the measurement.
+    :type value: Quantity or Number
+    :param error: The error or uncertainty of the measurement.
+    :type error: Quantity or Number
+    """
+
+    def __new__(cls, value, error, units=MISSING):
+        if units is MISSING:
+            try:
+                value, units = value.magnitude, value.units
+            except AttributeError:
+                try:
+                    value, error, units = value.nominal_value, value.std_dev, error
+                except AttributeError:
+                    units = ''
+        try:
+            error = error.to(units).magnitude
+        except AttributeError:
+            pass
+
+        inst = super(_Measurement, cls).__new__(cls, ufloat(value, error), units)
+
+        if error < 0:
+            raise ValueError('The magnitude of the error cannot be negative'.format(value, error))
+        return inst
+
+    @property
+    def value(self):
+        return self._REGISTRY.Quantity(self.magnitude.nominal_value, self.units)
+
+    @property
+    def error(self):
+        return self._REGISTRY.Quantity(self.magnitude.std_dev, self.units)
+
+    @property
+    def rel(self):
+        return float(abs(self.magnitude.std_dev / self.magnitude.nominal_value))
+
+    def __repr__(self):
+        return "<Measurement({0:.2f}, {1:.2f}, {2})>".format(self.magnitude.nominal_value,
+                                                             self.magnitude.std_dev,
+                                                             self.units)
+
+    def __str__(self):
+        return '{0}'.format(self)
+
+    def __format__(self, spec):
+        if 'L' in spec:
+            newpm = pm = r'  \pm  '
+            pars = _FORMATS['L']['parentheses_fmt']
+        elif 'P' in spec:
+            newpm = pm = '±'
+            pars = _FORMATS['P']['parentheses_fmt']
+        else:
+            newpm = pm = '+/-'
+            pars = _FORMATS['']['parentheses_fmt']
+
+        if 'C' in spec:
+            sp = ''
+            newspec = spec.replace('C', '')
+            pars = _FORMATS['C']['parentheses_fmt']
+        else:
+            sp = ' '
+            newspec = spec
+
+        if 'H' in spec:
+            newpm = '±'
+            newspec = spec.replace('H', '')
+            pars = _FORMATS['H']['parentheses_fmt']
+
+        mag = format(self.magnitude, newspec).replace(pm, sp + newpm + sp)
+
+        if 'L' in newspec and 'S' in newspec:
+            mag = mag.replace('(', r'\left(').replace(')', r'\right)')
+
+        if 'uS' in newspec or 'ue' in newspec or 'u%' in newspec:
+            return mag + ' ' + format(self.units, spec)
+        else:
+            return pars.format(mag) + ' ' + format(self.units, spec)
diff --git a/lib/taurus/external/pint/pint_local/pint_eval.py b/lib/taurus/external/pint/pint_local/pint_eval.py
new file mode 100644
index 0000000..bae61c0
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/pint_eval.py
@@ -0,0 +1,258 @@
+'''
+Created on Mar 4, 2015
+
+ at author: aaron
+'''
+
+from decimal import Decimal
+import math
+import operator
+
+import token as tokenlib
+
+#for controlling order of operations
+_OP_PRIORITY = {
+    '**' : 3,
+    '^' : 3,
+    'unary' : 2,
+    '*' : 1,
+    '' : 1, #operator for implicit ops
+    '/' : 1,
+    '+' : 0,
+    '-' : 0
+}
+
+_BINARY_OPERATOR_MAP = {
+    '**': operator.pow,
+    '*': operator.mul,
+    '': operator.mul, #operator for implicit ops
+    '/': operator.truediv,
+    '+': operator.add,
+    '-': operator.sub
+}
+
+_UNARY_OPERATOR_MAP = {
+    '+': lambda x : x,
+    '-': lambda x : x * -1
+}
+
+
+class EvalTreeNode(object):
+    
+    def __init__(self, left, operator=None, right=None):
+        '''
+        left + operator + right --> binary op
+        left + operator --> unary op
+        left + right --> implicit op
+        left --> single value
+        '''
+        self.left = left
+        self.operator = operator
+        self.right = right
+        
+    def to_string(self):
+        #for debugging purposes
+        if self.right:
+            comps = [self.left.to_string()]
+            if self.operator:
+                comps.append(self.operator[1])
+            comps.append(self.right.to_string())
+        elif self.operator:
+            comps = [self.operator[1], self.left.to_string()]
+        else:
+            return self.left[1]
+        return '(%s)' % ' '.join(comps)
+    
+    def evaluate(self, define_op, bin_op=_BINARY_OPERATOR_MAP, un_op=_UNARY_OPERATOR_MAP):
+        '''
+        define_op is a callable that translates tokens into objects
+        bin_op and un_op provide functions for performing binary and unary operations
+        '''
+        
+        if self.right:
+            #binary or implicit operator
+            op_text = self.operator[1] if self.operator else ''
+            if op_text not in bin_op:
+                raise Exception('missing binary operator "%s"' % op_text)
+            left = self.left.evaluate(define_op, bin_op, un_op)
+            return bin_op[op_text](left, self.right.evaluate(define_op, bin_op, un_op))
+        elif self.operator:
+            #unary operator
+            op_text = self.operator[1]
+            if op_text not in un_op:
+                raise Exception('missing unary operator "%s"' % op_text)
+            return un_op[op_text](self.left.evaluate(define_op, bin_op, un_op))
+        else:
+            #single value
+            return define_op(self.left)
+        
+
+def build_eval_tree(tokens, op_priority=_OP_PRIORITY, index=0, depth=0, prev_op=None, ):
+    '''
+    Params:
+    Index, depth, and prev_op used recursively, so don't touch.
+    Tokens is an iterable of tokens from an expression to be evaluated.
+    
+    Transform the tokens from an expression into a recursive parse tree, following order of operations.
+    Operations can include binary ops (3 + 4), implicit ops (3 kg), or unary ops (-1).
+    
+    General Strategy:
+    1) Get left side of operator
+    2) If no tokens left, return final result
+    3) Get operator
+    4) Use recursion to create tree starting at token on right side of operator (start at step #1)
+    4.1) If recursive call encounters an operator with lower or equal priority to step #2, exit recursion
+    5) Combine left side, operator, and right side into a new left side
+    6) Go back to step #2
+    '''
+    if depth == 0 and prev_op == None:
+        #ensure tokens is list so we can access by index
+        tokens = list(tokens)
+        
+    result = None
+    
+    while True:
+        current_token = tokens[index]
+        token_type = current_token[0]
+        token_text = current_token[1]
+        
+        if token_type == tokenlib.OP:
+            if token_text == ')':
+                if prev_op == None:
+                    raise Exception('unopened parentheses in tokens: %s' % current_token)
+                elif prev_op == '(':
+                    #close parenthetical group
+                    return result, index
+                else:
+                    #parenthetical group ending, but we need to close sub-operations within group
+                    return result, index - 1
+            elif token_text == '(':
+                #gather parenthetical group
+                right, index = build_eval_tree(tokens, op_priority, index+1, 0, token_text)
+                if not tokens[index][1] == ')':
+                    raise Exception('weird exit from parentheses')
+                if result:
+                    #implicit op with a parenthetical group, i.e. "3 (kg ** 2)"
+                    result = EvalTreeNode(left=result, right=right)
+                else:
+                    #get first token
+                    result = right
+            elif token_text in op_priority:
+                if result:
+                    #equal-priority operators are grouped in a left-to-right order, unless they're
+                    #exponentiation, in which case they're grouped right-to-left
+                    #this allows us to get the expected behavior for multiple exponents 
+                    #    (2^3^4)  --> (2^(3^4))
+                    #    (2 * 3 / 4) --> ((2 * 3) / 4)
+                    if op_priority[token_text] <= op_priority.get(prev_op, -1) and token_text not in ['**', '^']:
+                        #previous operator is higher priority, so end previous binary op
+                        return result, index - 1
+                    #get right side of binary op
+                    right, index = build_eval_tree(tokens, op_priority, index+1, depth+1, token_text)
+                    result = EvalTreeNode(left=result, operator=current_token, right=right)
+                else:
+                    #unary operator
+                    right, index = build_eval_tree(tokens, op_priority, index+1, depth+1, 'unary')
+                    result = EvalTreeNode(left=right, operator=current_token)
+        elif token_type == tokenlib.NUMBER or token_type == tokenlib.NAME:
+            if result:
+                #tokens with an implicit operation i.e. "1 kg"
+                if op_priority[''] <= op_priority.get(prev_op, -1):
+                    #previous operator is higher priority than implicit, so end previous binary op
+                    return result, index - 1
+                right, index = build_eval_tree(tokens, op_priority, index, depth+1, '')
+                result = EvalTreeNode(left=result, right=right)
+            else:
+                #get first token
+                result = EvalTreeNode(left=current_token)
+        
+        if tokens[index][0] == tokenlib.ENDMARKER:
+            if prev_op == '(':
+                raise Exception('unclosed parentheses in tokens')
+            if depth > 0 or prev_op:
+                #have to close recursion
+                return result, index
+            else:
+                #recursion all closed, so just return the final result
+                return result
+            
+        if index + 1 >= len(tokens):
+            #should hit ENDMARKER before this ever happens
+            raise Exception('unexpected end to tokens')
+        
+        index += 1
+
+def _test_build_tree(input_text):
+    '''
+    
+    ####
+    >>> _test_build_tree('3') #single number
+    u'3'
+    >>> _test_build_tree('1 + 2') #basic addition
+    u'(1 + 2)'
+    >>> _test_build_tree('2 * 3 + 4') #order of operations
+    u'((2 * 3) + 4)'
+    >>> _test_build_tree('2 * (3 + 4)') #parentheses
+    u'(2 * (3 + 4))'
+    >>> _test_build_tree('1 + 2 * 3 ** (4 + 3 / 5)') #more order of operations
+    u'(1 + (2 * (3 ** (4 + (3 / 5)))))'
+    >>> _test_build_tree('1 * ((3 + 4) * 5)') #nested parentheses at beginning
+    u'(1 * ((3 + 4) * 5))'
+    >>> _test_build_tree('1 * (5 * (3 + 4))') #nested parentheses at end
+    u'(1 * (5 * (3 + 4)))'
+    >>> _test_build_tree('1 * (5 * (3 + 4) / 6)') #nested parentheses in middle
+    u'(1 * ((5 * (3 + 4)) / 6))'
+    >>> _test_build_tree('-1') #unary
+    u'(- 1)'
+    >>> _test_build_tree('3 * -1') #unary
+    u'(3 * (- 1))'
+    >>> _test_build_tree('3 * --1') #double unary
+    u'(3 * (- (- 1)))'
+    >>> _test_build_tree('3 * -(2 + 4)') #parenthetical unary
+    u'(3 * (- (2 + 4)))'
+    >>> _test_build_tree('3 * -((2 + 4))') #parenthetical unary
+    u'(3 * (- (2 + 4)))'
+    >>> _test_build_tree('3 4') #implicit op
+    u'(3 4)'
+    >>> _test_build_tree('3 (2 + 4)') #implicit op, then parentheses
+    u'(3 (2 + 4))'
+    >>> _test_build_tree('(3 ** 4 ) 5') #parentheses, then implicit
+    u'((3 ** 4) 5)'
+    >>> _test_build_tree('3 4 ** 5') #implicit op, then exponentiation
+    u'(3 (4 ** 5))'
+    >>> _test_build_tree('3 4 + 5') #implicit op, then addition
+    u'((3 4) + 5)'
+    >>> _test_build_tree('3 ** 4 5') #power followed by implicit
+    u'((3 ** 4) 5)'
+    >>> _test_build_tree('3 (4 ** 5)') #implicit with parentheses
+    u'(3 (4 ** 5))'
+    >>> _test_build_tree('3e-1') #exponent with e
+    u'3e-1'
+    
+    >>> _test_build_tree('kg ** 1 * s ** 2') #multiple units with exponents
+    u'((kg ** 1) * (s ** 2))'
+    >>> _test_build_tree('kg ** -1 * s ** -2') #multiple units with neg exponents
+    u'((kg ** (- 1)) * (s ** (- 2)))'
+    >>> _test_build_tree('kg^-1 * s^-2') #multiple units with neg exponents
+    u'((kg ^ (- 1)) * (s ^ (- 2)))'
+    >>> _test_build_tree('kg^-1 s^-2') #multiple units with neg exponents, implicit op
+    u'((kg ^ (- 1)) (s ^ (- 2)))'
+    
+    >>> _test_build_tree('2 ^ 3 ^ 2') #nested power
+    u'(2 ^ (3 ^ 2))'
+    
+    >>> _test_build_tree('gram * second / meter ** 2') #nested power
+    u'((gram * second) / (meter ** 2))'
+    >>> _test_build_tree('gram / meter ** 2 / second') #nested power
+    u'((gram / (meter ** 2)) / second)'
+    
+    #units should behave like numbers, so we don't need a bunch of extra tests for them
+    >>> _test_build_tree('3 kg + 5') #implicit op, then addition
+    u'((3 kg) + 5)'
+    '''
+    return build_eval_tree(tokenizer(input_text)).to_string()
+
+if __name__ == "__main__":
+    import doctest, pint
+    from pint.compat import tokenizer
+    doctest.testmod()
diff --git a/lib/taurus/external/pint/pint_local/quantity.py b/lib/taurus/external/pint/pint_local/quantity.py
new file mode 100644
index 0000000..0eba47c
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/quantity.py
@@ -0,0 +1,1337 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.quantity
+    ~~~~~~~~~~~~~
+
+    :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import division, unicode_literals, print_function, absolute_import
+
+import copy
+import math
+import operator
+import functools
+import bisect
+
+from .formatting import remove_custom_flags
+from .errors import (DimensionalityError, OffsetUnitCalculusError,
+                     UndefinedUnitError)
+from .definitions import UnitDefinition
+from .compat import string_types, ndarray, np, _to_magnitude, long_type
+from .util import (logger, UnitsContainer, SharedRegistryObject,
+                   to_units_container, infer_base_unit)
+
+
+def _eq(first, second, check_all):
+    """Comparison of scalars and arrays
+    """
+    out = first == second
+    if check_all and isinstance(out, ndarray):
+        return np.all(out)
+    return out
+
+
+class _Exception(Exception):            # pragma: no cover
+
+    def __init__(self, internal):
+        self.internal = internal
+
+
+class _Quantity(SharedRegistryObject):
+    """Implements a class to describe a physical quantity:
+    the product of a numerical value and a unit of measurement.
+
+    :param value: value of the physical quantity to be created.
+    :type value: str, Quantity or any numeric type.
+    :param units: units of the physical quantity to be created.
+    :type units: UnitsContainer, str or Quantity.
+    """
+
+    #: Default formatting string.
+    default_format = ''
+
+    def __reduce__(self):
+        from . import _build_quantity
+        return _build_quantity, (self.magnitude, self._units)
+
+    def __new__(cls, value, units=None):
+        if units is None:
+            if isinstance(value, string_types):
+                if value == '':
+                    raise ValueError('Expression to parse as Quantity cannot '
+                                     'be an empty string.')
+                inst = cls._REGISTRY.parse_expression(value)
+                return cls.__new__(cls, inst)
+            elif isinstance(value, cls):
+                inst = copy.copy(value)
+            else:
+                inst = object.__new__(cls)
+                inst._magnitude = _to_magnitude(value, inst.force_ndarray)
+                inst._units = UnitsContainer()
+        elif isinstance(units, (UnitsContainer, UnitDefinition)):
+            inst = object.__new__(cls)
+            inst._magnitude = _to_magnitude(value, inst.force_ndarray)
+            inst._units = units
+        elif isinstance(units, string_types):
+            inst = object.__new__(cls)
+            inst._magnitude = _to_magnitude(value, inst.force_ndarray)
+            inst._units = inst._REGISTRY.parse_units(units)._units
+        elif isinstance(units, SharedRegistryObject):
+            if isinstance(units, _Quantity) and units.magnitude != 1:
+                inst = copy.copy(units)
+                logger.warning('Creating new Quantity using a non unity '
+                               'Quantity as units.')
+            else:
+                inst = object.__new__(cls)
+                inst._units = units._units
+            inst._magnitude = _to_magnitude(value, inst.force_ndarray)
+        else:
+            raise TypeError('units must be of type str, Quantity or '
+                            'UnitsContainer; not {0}.'.format(type(units)))
+
+        inst.__used = False
+        inst.__handling = None
+        return inst
+
+    @property
+    def debug_used(self):
+        return self.__used
+
+    def __copy__(self):
+        ret = self.__class__(copy.copy(self._magnitude), self._units)
+        ret.__used = self.__used
+        return ret
+
+    def __deepcopy__(self, memo):
+        ret = self.__class__(copy.deepcopy(self._magnitude, memo),
+                             copy.deepcopy(self._units, memo))
+        ret.__used = self.__used
+        return ret
+
+    def __str__(self):
+        return format(self)
+
+    def __repr__(self):
+        return "<Quantity({0}, '{1}')>".format(self._magnitude, self._units)
+
+    def __format__(self, spec):
+        spec = spec or self.default_format
+        if '#' in spec:
+            spec = spec.replace('#', '')
+            obj = self.to_compact()
+        else:
+            obj = self
+        return '{0} {1}'.format(
+            format(obj.magnitude, remove_custom_flags(spec)),
+            format(obj.units, spec))
+
+    # IPython related code
+    def _repr_html_(self):
+        return self.__format__('H')
+
+    def _repr_latex_(self):
+        return "$" + self.__format__('L') + "$"
+
+    @property
+    def magnitude(self):
+        """Quantity's magnitude. Long form for `m`
+        """
+        return self._magnitude
+
+    @property
+    def m(self):
+        """Quantity's magnitude. Short form for `magnitude`
+        """
+        return self._magnitude
+
+    def m_as(self, unit):
+        """Quantity's magnitude expressed in particular units.
+
+        :param other: destination units.
+        :type other: Quantity, str or dict
+        """
+        return (self / unit).to('').magnitude
+
+    @property
+    def units(self):
+        """Quantity's units. Long form for `u`
+
+        :rtype: UnitContainer
+        """
+        return self._REGISTRY.Unit(self._units)
+
+    @property
+    def u(self):
+        """Quantity's units. Short form for `units`
+
+        :rtype: UnitContainer
+        """
+        return self._REGISTRY.Unit(self._units)
+
+    @property
+    def unitless(self):
+        """Return true if the quantity does not have units.
+        """
+        return not bool(self.to_root_units()._units)
+
+    @property
+    def dimensionless(self):
+        """Return true if the quantity is dimensionless.
+        """
+        tmp = self.to_root_units()
+
+        return not bool(tmp.dimensionality)
+
+    @property
+    def dimensionality(self):
+        """Quantity's dimensionality (e.g. {length: 1, time: -1})
+        """
+        try:
+            return self._dimensionality
+        except AttributeError:
+            self._dimensionality = self._REGISTRY._get_dimensionality(self._units)
+
+        return self._dimensionality
+
+    def compatible_units(self, *contexts):
+        if contexts:
+            with self._REGISTRY.context(*contexts):
+                return self._REGISTRY.get_compatible_units(self._units)
+
+        return self._REGISTRY.get_compatible_units(self._units)
+
+    def _convert_magnitude_not_inplace(self, other, *contexts, **ctx_kwargs):
+        if contexts:
+            with self._REGISTRY.context(*contexts, **ctx_kwargs):
+                return self._REGISTRY.convert(self._magnitude, self._units, other)
+
+        return self._REGISTRY.convert(self._magnitude, self._units, other)
+
+    def _convert_magnitude(self, other, *contexts, **ctx_kwargs):
+        if contexts:
+            with self._REGISTRY.context(*contexts, **ctx_kwargs):
+                return self._REGISTRY.convert(self._magnitude, self._units, other)
+
+        return self._REGISTRY.convert(self._magnitude, self._units, other,
+                                      inplace=isinstance(self._magnitude, ndarray))
+
+    def ito(self, other=None, *contexts, **ctx_kwargs):
+        """Inplace rescale to different units.
+
+        :param other: destination units.
+        :type other: Quantity, str or dict
+        """
+        other = to_units_container(other, self._REGISTRY)
+
+        self._magnitude = self._convert_magnitude(other, *contexts,
+                                                  **ctx_kwargs)
+        self._units = other
+
+        return None
+
+    def to(self, other=None, *contexts, **ctx_kwargs):
+        """Return Quantity rescaled to different units.
+
+        :param other: destination units.
+        :type other: Quantity, str or dict
+        """
+        other = to_units_container(other, self._REGISTRY)
+
+        magnitude = self._convert_magnitude_not_inplace(other, *contexts, **ctx_kwargs)
+
+        return self.__class__(magnitude, other)
+
+    def ito_root_units(self):
+        """Return Quantity rescaled to base units
+        """
+
+        _, other = self._REGISTRY._get_root_units(self._units)
+
+        self._magnitude = self._convert_magnitude(other)
+        self._units = other
+
+        return None
+
+    def to_root_units(self):
+        """Return Quantity rescaled to base units
+        """
+        _, other = self._REGISTRY._get_root_units(self._units)
+
+        magnitude = self._convert_magnitude_not_inplace(other)
+
+        return self.__class__(magnitude, other)
+
+    def ito_base_units(self):
+        """Return Quantity rescaled to base units
+        """
+
+        _, other = self._REGISTRY._get_base_units(self._units)
+
+        self._magnitude = self._convert_magnitude(other)
+        self._units = other
+
+        return None
+
+    def to_base_units(self):
+        """Return Quantity rescaled to base units
+        """
+        _, other = self._REGISTRY._get_base_units(self._units)
+
+        magnitude = self._convert_magnitude_not_inplace(other)
+
+        return self.__class__(magnitude, other)
+
+
+    def to_compact(self, unit=None):
+        """Return Quantity rescaled to compact, human-readable units.
+
+        To get output in terms of a different unit, use the unit parameter.
+
+        >>> import pint
+        >>> ureg = pint.UnitRegistry()
+        >>> (200e-9*ureg.s).to_compact()
+        <Quantity(200.0, 'nanosecond')>
+        >>> (1e-2*ureg('kg m/s^2')).to_compact('N')
+        <Quantity(10.0, 'millinewton')>
+        """
+        if self.unitless:
+            return self
+
+        SI_prefixes = {}
+        for prefix in self._REGISTRY._prefixes.values():
+            try:
+                scale = prefix.converter.scale
+                # Kludgy way to check if this is an SI prefix
+                log10_scale = int(math.log10(scale))
+                if log10_scale == math.log10(scale):
+                    SI_prefixes[log10_scale] = prefix.name
+            except:
+                SI_prefixes[0] = ''
+
+        SI_prefixes = sorted(SI_prefixes.items())
+        SI_powers = [item[0] for item in SI_prefixes]
+        SI_bases = [item[1] for item in SI_prefixes]
+
+        if unit is None:
+            unit = infer_base_unit(self)
+
+        q_base = self.to(unit)
+
+        magnitude = q_base.magnitude
+        # Only changes the prefix on the first unit in the UnitContainer
+        unit_str = list(q_base._units.items())[0][0]
+        unit_power = list(q_base._units.items())[0][1]
+
+        if unit_power > 0:
+            power = int(math.floor(math.log10(magnitude) / unit_power / 3)) * 3
+        else:
+            power = int(math.ceil(math.log10(magnitude) / unit_power / 3)) * 3
+
+        prefix = SI_bases[bisect.bisect_left(SI_powers, power)]
+
+        new_unit_str = prefix+unit_str
+        new_unit_container = q_base._units.rename(unit_str, new_unit_str)
+
+        return self.to(new_unit_container)
+
+    # Mathematical operations
+    def __int__(self):
+        if self.dimensionless:
+            return int(self._convert_magnitude_not_inplace(UnitsContainer()))
+        raise DimensionalityError(self._units, 'dimensionless')
+
+    def __long__(self):
+        if self.dimensionless:
+            return long_type(self._convert_magnitude_not_inplace(UnitsContainer()))
+        raise DimensionalityError(self._units, 'dimensionless')
+
+    def __float__(self):
+        if self.dimensionless:
+            return float(self._convert_magnitude_not_inplace(UnitsContainer()))
+        raise DimensionalityError(self._units, 'dimensionless')
+
+    def __complex__(self):
+        if self.dimensionless:
+            return complex(self._convert_magnitude_not_inplace(UnitsContainer()))
+        raise DimensionalityError(self._units, 'dimensionless')
+
+    def _iadd_sub(self, other, op):
+        """Perform addition or subtraction operation in-place and return the result.
+
+        :param other: object to be added to / subtracted from self
+        :type other: Quantity or any type accepted by :func:`_to_magnitude`
+        :param op: operator function (e.g. operator.add, operator.isub)
+        :type op: function
+        """
+        if not self._check(other):
+            # other not from same Registry or not a Quantity
+            try:
+                other_magnitude = _to_magnitude(other, self.force_ndarray)
+            except TypeError:
+                return NotImplemented
+            if _eq(other, 0, True):
+                # If the other value is 0 (but not Quantity 0)
+                # do the operation without checking units.
+                # We do the calculation instead of just returning the same
+                # value to enforce any shape checking and type casting due to
+                # the operation.
+                self._magnitude = op(self._magnitude, other_magnitude)
+            elif self.dimensionless:
+                self.ito(UnitsContainer())
+                self._magnitude = op(self._magnitude, other_magnitude)
+            else:
+                raise DimensionalityError(self._units, 'dimensionless')
+            return self
+
+        if not self.dimensionality == other.dimensionality:
+            raise DimensionalityError(self._units, other._units,
+                                      self.dimensionality,
+                                      other.dimensionality)
+
+        # Next we define some variables to make if-clauses more readable.
+        self_non_mul_units = self._get_non_multiplicative_units()
+        is_self_multiplicative = len(self_non_mul_units) == 0
+        if len(self_non_mul_units) == 1:
+            self_non_mul_unit = self_non_mul_units[0]
+        other_non_mul_units = other._get_non_multiplicative_units()
+        is_other_multiplicative = len(other_non_mul_units) == 0
+        if len(other_non_mul_units) == 1:
+            other_non_mul_unit = other_non_mul_units[0]
+
+        # Presence of non-multiplicative units gives rise to several cases.
+        if is_self_multiplicative and is_other_multiplicative:
+            if self._units == other._units:
+                self._magnitude = op(self._magnitude, other._magnitude)
+            # If only self has a delta unit, other determines unit of result.
+            elif self._get_delta_units() and not other._get_delta_units():
+                self._magnitude = op(self._convert_magnitude(other._units),
+                                     other._magnitude)
+                self._units = other._units
+            else:
+                self._magnitude = op(self._magnitude,
+                                     other.to(self._units)._magnitude)
+
+        elif (op == operator.isub and len(self_non_mul_units) == 1
+                and self._units[self_non_mul_unit] == 1
+                and not other._has_compatible_delta(self_non_mul_unit)):
+            if self._units == other._units:
+                self._magnitude = op(self._magnitude, other._magnitude)
+            else:
+                self._magnitude = op(self._magnitude,
+                                     other.to(self._units)._magnitude)
+            self._units = self._units.rename(self_non_mul_unit,
+                                             'delta_' + self_non_mul_unit)
+
+        elif (op == operator.isub and len(other_non_mul_units) == 1
+                and other._units[other_non_mul_unit] == 1
+                and not self._has_compatible_delta(other_non_mul_unit)):
+            # we convert to self directly since it is multiplicative
+            self._magnitude = op(self._magnitude,
+                                 other.to(self._units)._magnitude)
+
+        elif (len(self_non_mul_units) == 1
+                # order of the dimension of offset unit == 1 ?
+                and self._units[self_non_mul_unit] == 1
+                and other._has_compatible_delta(self_non_mul_unit)):
+            # Replace offset unit in self by the corresponding delta unit.
+            # This is done to prevent a shift by offset in the to()-call.
+            tu = self._units.rename(self_non_mul_unit,
+                                    'delta_' + self_non_mul_unit)
+            self._magnitude = op(self._magnitude, other.to(tu)._magnitude)
+        elif (len(other_non_mul_units) == 1
+                # order of the dimension of offset unit == 1 ?
+                and other._units[other_non_mul_unit] == 1
+                and self._has_compatible_delta(other_non_mul_unit)):
+            # Replace offset unit in other by the corresponding delta unit.
+            # This is done to prevent a shift by offset in the to()-call.
+            tu = other._units.rename(other_non_mul_unit,
+                                     'delta_' + other_non_mul_unit)
+            self._magnitude = op(self._convert_magnitude(tu), other._magnitude)
+            self._units = other._units
+        else:
+            raise OffsetUnitCalculusError(self._units, other._units)
+
+        return self
+
+    def _add_sub(self, other, op):
+        """Perform addition or subtraction operation and return the result.
+
+        :param other: object to be added to / subtracted from self
+        :type other: Quantity or any type accepted by :func:`_to_magnitude`
+        :param op: operator function (e.g. operator.add, operator.isub)
+        :type op: function
+        """
+        if not self._check(other):
+            # other not from same Registry or not a Quantity
+            if _eq(other, 0, True):
+                # If the other value is 0 (but not Quantity 0)
+                # do the operation without checking units.
+                # We do the calculation instead of just returning the same
+                # value to enforce any shape checking and type casting due to
+                # the operation.
+                units = self._units
+                magnitude = op(self._magnitude,
+                               _to_magnitude(other, self.force_ndarray))
+            elif self.dimensionless:
+                units = UnitsContainer()
+                magnitude = op(self.to(units)._magnitude,
+                               _to_magnitude(other, self.force_ndarray))
+            else:
+                raise DimensionalityError(self._units, 'dimensionless')
+            return self.__class__(magnitude, units)
+
+        if not self.dimensionality == other.dimensionality:
+            raise DimensionalityError(self._units, other._units,
+                                      self.dimensionality,
+                                      other.dimensionality)
+
+        # Next we define some variables to make if-clauses more readable.
+        self_non_mul_units = self._get_non_multiplicative_units()
+        is_self_multiplicative = len(self_non_mul_units) == 0
+        if len(self_non_mul_units) == 1:
+            self_non_mul_unit = self_non_mul_units[0]
+        other_non_mul_units = other._get_non_multiplicative_units()
+        is_other_multiplicative = len(other_non_mul_units) == 0
+        if len(other_non_mul_units) == 1:
+            other_non_mul_unit = other_non_mul_units[0]
+
+        # Presence of non-multiplicative units gives rise to several cases.
+        if is_self_multiplicative and is_other_multiplicative:
+            if self._units == other._units:
+                magnitude = op(self._magnitude, other._magnitude)
+                units = self._units
+            # If only self has a delta unit, other determines unit of result.
+            elif self._get_delta_units() and not other._get_delta_units():
+                magnitude = op(self._convert_magnitude(other._units),
+                               other._magnitude)
+                units = other._units
+            else:
+                units = self._units
+                magnitude = op(self._magnitude,
+                               other.to(self._units).magnitude)
+
+        elif (op == operator.sub and len(self_non_mul_units) == 1
+                and self._units[self_non_mul_unit] == 1
+                and not other._has_compatible_delta(self_non_mul_unit)):
+            if self._units == other._units:
+                magnitude = op(self._magnitude, other._magnitude)
+            else:
+                magnitude = op(self._magnitude,
+                               other.to(self._units)._magnitude)
+            units = self._units.rename(self_non_mul_unit,
+                                      'delta_' + self_non_mul_unit)
+
+        elif (op == operator.sub and len(other_non_mul_units) == 1
+                and other._units[other_non_mul_unit] == 1
+                and not self._has_compatible_delta(other_non_mul_unit)):
+            # we convert to self directly since it is multiplicative
+            magnitude = op(self._magnitude,
+                           other.to(self._units)._magnitude)
+            units = self._units
+
+        elif (len(self_non_mul_units) == 1
+                # order of the dimension of offset unit == 1 ?
+                and self._units[self_non_mul_unit] == 1
+                and other._has_compatible_delta(self_non_mul_unit)):
+            # Replace offset unit in self by the corresponding delta unit.
+            # This is done to prevent a shift by offset in the to()-call.
+            tu = self._units.rename(self_non_mul_unit,
+                                    'delta_' + self_non_mul_unit)
+            magnitude = op(self._magnitude, other.to(tu).magnitude)
+            units = self._units
+        elif (len(other_non_mul_units) == 1
+                # order of the dimension of offset unit == 1 ?
+                and other._units[other_non_mul_unit] == 1
+                and self._has_compatible_delta(other_non_mul_unit)):
+            # Replace offset unit in other by the corresponding delta unit.
+            # This is done to prevent a shift by offset in the to()-call.
+            tu = other._units.rename(other_non_mul_unit,
+                                     'delta_' + other_non_mul_unit)
+            magnitude = op(self._convert_magnitude(tu), other._magnitude)
+            units = other._units
+        else:
+            raise OffsetUnitCalculusError(self._units, other._units)
+
+        return self.__class__(magnitude, units)
+
+    def __iadd__(self, other):
+        if not isinstance(self._magnitude, ndarray):
+            return self._add_sub(other, operator.add)
+        else:
+            return self._iadd_sub(other, operator.iadd)
+
+    def __add__(self, other):
+        return self._add_sub(other, operator.add)
+
+    __radd__ = __add__
+
+    def __isub__(self, other):
+        if not isinstance(self._magnitude, ndarray):
+            return self._add_sub(other, operator.sub)
+        else:
+            return self._iadd_sub(other, operator.isub)
+
+    def __sub__(self, other):
+        return self._add_sub(other, operator.sub)
+
+    def __rsub__(self, other):
+        return -self._add_sub(other, operator.sub)
+
+    def _imul_div(self, other, magnitude_op, units_op=None):
+        """Perform multiplication or division operation in-place and return the
+        result.
+
+        :param other: object to be multiplied/divided with self
+        :type other: Quantity or any type accepted by :func:`_to_magnitude`
+        :param magnitude_op: operator function to perform on the magnitudes
+            (e.g. operator.mul)
+        :type magnitude_op: function
+        :param units_op: operator function to perform on the units; if None,
+            *magnitude_op* is used
+        :type units_op: function or None
+        """
+        if units_op is None:
+            units_op = magnitude_op
+
+        offset_units_self = self._get_non_multiplicative_units()
+        no_offset_units_self = len(offset_units_self)
+
+        if not self._check(other):
+
+            if not self._ok_for_muldiv(no_offset_units_self):
+                raise OffsetUnitCalculusError(self._units,
+                                              getattr(other, 'units', ''))
+            if len(offset_units_self) == 1:
+                if (self._units[offset_units_self[0]] != 1
+                        or magnitude_op not in [operator.mul, operator.imul]):
+                    raise OffsetUnitCalculusError(self._units,
+                                                  getattr(other, 'units', ''))
+            try:
+                other_magnitude = _to_magnitude(other, self.force_ndarray)
+            except TypeError:
+                return NotImplemented
+            self._magnitude = magnitude_op(self._magnitude, other_magnitude)
+            self._units = units_op(self._units, UnitsContainer())
+            return self
+
+        if not isinstance(other, _Quantity):
+            self._magnitude = magnitude_op(self._magnitude, 1)
+            self._units = units_op(self._units, other._units)
+            return self
+
+        if not self._ok_for_muldiv(no_offset_units_self):
+            raise OffsetUnitCalculusError(self._units, other._units)
+        elif no_offset_units_self == 1 and len(self._units) == 1:
+            self.ito_root_units()
+
+        no_offset_units_other = len(other._get_non_multiplicative_units())
+
+        if not other._ok_for_muldiv(no_offset_units_other):
+            raise OffsetUnitCalculusError(self._units, other._units)
+        elif no_offset_units_other == 1 and len(other._units) == 1:
+            other.ito_root_units()
+
+        self._magnitude = magnitude_op(self._magnitude, other._magnitude)
+        self._units = units_op(self._units, other._units)
+
+        return self
+
+    def _mul_div(self, other, magnitude_op, units_op=None):
+        """Perform multiplication or division operation and return the result.
+
+        :param other: object to be multiplied/divided with self
+        :type other: Quantity or any type accepted by :func:`_to_magnitude`
+        :param magnitude_op: operator function to perform on the magnitudes
+            (e.g. operator.mul)
+        :type magnitude_op: function
+        :param units_op: operator function to perform on the units; if None,
+            *magnitude_op* is used
+        :type units_op: function or None
+        """
+        if units_op is None:
+            units_op = magnitude_op
+
+        offset_units_self = self._get_non_multiplicative_units()
+        no_offset_units_self = len(offset_units_self)
+
+        if not self._check(other):
+
+            if not self._ok_for_muldiv(no_offset_units_self):
+                raise OffsetUnitCalculusError(self._units,
+                                              getattr(other, 'units', ''))
+            if len(offset_units_self) == 1:
+                if (self._units[offset_units_self[0]] != 1
+                        or magnitude_op not in [operator.mul, operator.imul]):
+                    raise OffsetUnitCalculusError(self._units,
+                                                  getattr(other, 'units', ''))
+            try:
+                other_magnitude = _to_magnitude(other, self.force_ndarray)
+            except TypeError:
+                return NotImplemented
+
+            magnitude = magnitude_op(self._magnitude, other_magnitude)
+            units = units_op(self._units, UnitsContainer())
+
+            return self.__class__(magnitude, units)
+
+        if not isinstance(other, _Quantity):
+            magnitude = self._magnitude
+            units = units_op(self._units, other._units)
+            return self.__class__(magnitude, units)
+
+        new_self = self
+
+        if not self._ok_for_muldiv(no_offset_units_self):
+            raise OffsetUnitCalculusError(self._units, other._units)
+        elif no_offset_units_self == 1 and len(self._units) == 1:
+            new_self = self.to_root_units()
+
+        no_offset_units_other = len(other._get_non_multiplicative_units())
+
+        if not other._ok_for_muldiv(no_offset_units_other):
+            raise OffsetUnitCalculusError(self._units, other._units)
+        elif no_offset_units_other == 1 and len(other._units) == 1:
+            other = other.to_root_units()
+
+        magnitude = magnitude_op(new_self._magnitude, other._magnitude)
+        units = units_op(new_self._units, other._units)
+
+        return self.__class__(magnitude, units)
+
+    def __imul__(self, other):
+        if not isinstance(self._magnitude, ndarray):
+            return self._mul_div(other, operator.mul)
+        else:
+            return self._imul_div(other, operator.imul)
+
+    def __mul__(self, other):
+        return self._mul_div(other, operator.mul)
+
+    __rmul__ = __mul__
+
+    def __itruediv__(self, other):
+        if not isinstance(self._magnitude, ndarray):
+            return self._mul_div(other, operator.truediv)
+        else:
+            return self._imul_div(other, operator.itruediv)
+
+    def __truediv__(self, other):
+        return self._mul_div(other, operator.truediv)
+
+    def __ifloordiv__(self, other):
+        if not isinstance(self._magnitude, ndarray):
+            return self._mul_div(other, operator.floordiv, units_op=operator.itruediv)
+        else:
+            return self._imul_div(other, operator.ifloordiv, units_op=operator.itruediv)
+
+    def __floordiv__(self, other):
+        return self._mul_div(other, operator.floordiv, units_op=operator.truediv)
+
+    def __rtruediv__(self, other):
+        try:
+            other_magnitude = _to_magnitude(other, self.force_ndarray)
+        except TypeError:
+            return NotImplemented
+
+        no_offset_units_self = len(self._get_non_multiplicative_units())
+        if not self._ok_for_muldiv(no_offset_units_self):
+            raise OffsetUnitCalculusError(self._units, '')
+        elif no_offset_units_self == 1 and len(self._units) == 1:
+            self = self.to_root_units()
+
+        return self.__class__(other_magnitude / self._magnitude, 1 / self._units)
+
+    def __rfloordiv__(self, other):
+        try:
+            other_magnitude = _to_magnitude(other, self.force_ndarray)
+        except TypeError:
+            return NotImplemented
+
+        no_offset_units_self = len(self._get_non_multiplicative_units())
+        if not self._ok_for_muldiv(no_offset_units_self):
+            raise OffsetUnitCalculusError(self._units, '')
+        elif no_offset_units_self == 1 and len(self._units) == 1:
+            self = self.to_root_units()
+
+        return self.__class__(other_magnitude // self._magnitude, 1 / self._units)
+
+    __div__ = __truediv__
+    __rdiv__ = __rtruediv__
+    __idiv__ = __itruediv__
+
+    def __ipow__(self, other):
+        if not isinstance(self._magnitude, ndarray):
+            return self.__pow__(other)
+
+        try:
+            other_magnitude = _to_magnitude(other, self.force_ndarray)
+        except TypeError:
+            return NotImplemented
+        else:
+            if not self._ok_for_muldiv:
+                raise OffsetUnitCalculusError(self._units)
+
+            if isinstance(getattr(other, '_magnitude', other), ndarray):
+                # arrays are refused as exponent, because they would create
+                #  len(array) quanitites of len(set(array)) different units
+                if np.size(other) > 1:
+                    raise DimensionalityError(self._units, 'dimensionless')
+
+            if other == 1:
+                return self
+            elif other == 0:
+                self._units = UnitsContainer()
+            else:
+                if not self._is_multiplicative:
+                    if self._REGISTRY.autoconvert_offset_to_baseunit:
+                        self.ito_base_units()
+                    else:
+                        raise OffsetUnitCalculusError(self._units)
+
+                if getattr(other, 'dimensionless', False):
+                    other = other.to_base_units()
+                    self._units **= other.magnitude
+                elif not getattr(other, 'dimensionless', True):
+                    raise DimensionalityError(self._units, 'dimensionless')
+                else:
+                    self._units **= other
+
+            self._magnitude **= _to_magnitude(other, self.force_ndarray)
+            return self
+
+    def __pow__(self, other):
+        try:
+            other_magnitude = _to_magnitude(other, self.force_ndarray)
+        except TypeError:
+            return NotImplemented
+        else:
+            if not self._ok_for_muldiv:
+                raise OffsetUnitCalculusError(self._units)
+
+            if isinstance(getattr(other, '_magnitude', other), ndarray):
+                # arrays are refused as exponent, because they would create
+                #  len(array) quantities of len(set(array)) different units
+                if np.size(other) > 1:
+                    raise DimensionalityError(self._units, 'dimensionless')
+
+            new_self = self
+            if other == 1:
+                return self
+            elif other == 0:
+                units = UnitsContainer()
+            else:
+                if not self._is_multiplicative:
+                    if self._REGISTRY.autoconvert_offset_to_baseunit:
+                        new_self = self.to_root_units()
+                    else:
+                        raise OffsetUnitCalculusError(self._units)
+
+                if getattr(other, 'dimensionless', False):
+                    units = new_self._units ** other.to_root_units().magnitude
+                elif not getattr(other, 'dimensionless', True):
+                    raise DimensionalityError(self._units, 'dimensionless')
+                else:
+                    units = new_self._units ** other
+
+            magnitude = new_self._magnitude ** _to_magnitude(other, self.force_ndarray)
+            return self.__class__(magnitude, units)
+
+    def __rpow__(self, other):
+        try:
+            other_magnitude = _to_magnitude(other, self.force_ndarray)
+        except TypeError:
+            return NotImplemented
+        else:
+            if not self.dimensionless:
+                raise DimensionalityError(self._units, 'dimensionless')
+            if isinstance(self._magnitude, ndarray):
+                if np.size(self._magnitude) > 1:
+                    raise DimensionalityError(self._units, 'dimensionless')
+            new_self = self.to_root_units()
+            return other**new_self._magnitude
+
+    def __abs__(self):
+        return self.__class__(abs(self._magnitude), self._units)
+
+    def __round__(self, ndigits=0):
+        return self.__class__(round(self._magnitude, ndigits=ndigits), self._units)
+
+    def __pos__(self):
+        return self.__class__(operator.pos(self._magnitude), self._units)
+
+    def __neg__(self):
+        return self.__class__(operator.neg(self._magnitude), self._units)
+
+    def __eq__(self, other):
+        # We compare to the base class of Quantity because
+        # each Quantity class is unique.
+        if not isinstance(other, _Quantity):
+            return (self.dimensionless and
+                    _eq(self._convert_magnitude(UnitsContainer()), other, False))
+
+        if _eq(self._magnitude, 0, True) and _eq(other._magnitude, 0, True):
+            return self.dimensionality == other.dimensionality
+
+        if self._units == other._units:
+            return _eq(self._magnitude, other._magnitude, False)
+
+        try:
+            return _eq(self._convert_magnitude_not_inplace(other._units),
+                       other._magnitude, False)
+        except DimensionalityError:
+            return False
+
+    def __ne__(self, other):
+        out = self.__eq__(other)
+        if isinstance(out, ndarray):
+            return np.logical_not(out)
+        return not out
+
+    def compare(self, other, op):
+        if not isinstance(other, self.__class__):
+            if self.dimensionless:
+                return op(self._convert_magnitude_not_inplace(UnitsContainer()), other)
+            else:
+                raise ValueError('Cannot compare Quantity and {0}'.format(type(other)))
+
+        if self._units == other._units:
+            return op(self._magnitude, other._magnitude)
+        if self.dimensionality != other.dimensionality:
+            raise DimensionalityError(self._units, other._units,
+                                      self.dimensionality, other.dimensionality)
+        return op(self.to_root_units().magnitude,
+                  other.to_root_units().magnitude)
+
+    __lt__ = lambda self, other: self.compare(other, op=operator.lt)
+    __le__ = lambda self, other: self.compare(other, op=operator.le)
+    __ge__ = lambda self, other: self.compare(other, op=operator.ge)
+    __gt__ = lambda self, other: self.compare(other, op=operator.gt)
+
+    def __bool__(self):
+        return bool(self._magnitude)
+
+    __nonzero__ = __bool__
+
+    # NumPy Support
+    __radian = 'radian'
+    __same_units = 'equal greater greater_equal less less_equal not_equal arctan2'.split()
+    #: Dictionary mapping ufunc/attributes names to the units that they
+    #: require (conversion will be tried).
+    __require_units = {'cumprod': '',
+                       'arccos': '', 'arcsin': '', 'arctan': '',
+                       'arccosh': '', 'arcsinh': '', 'arctanh': '',
+                       'exp': '', 'expm1': '', 'exp2': '',
+                       'log': '', 'log10': '', 'log1p': '', 'log2': '',
+                       'sin': __radian, 'cos': __radian, 'tan': __radian,
+                       'sinh': __radian, 'cosh': __radian, 'tanh': __radian,
+                       'radians': 'degree', 'degrees': __radian,
+                       'deg2rad': 'degree', 'rad2deg': __radian,
+                       'logaddexp': '', 'logaddexp2': ''}
+
+    #: Dictionary mapping ufunc/attributes names to the units that they
+    #: will set on output.
+    __set_units = {'cos': '', 'sin': '', 'tan': '',
+                   'cosh': '', 'sinh': '', 'tanh': '',
+                   'arccos': __radian, 'arcsin': __radian,
+                   'arctan': __radian, 'arctan2': __radian,
+                   'arccosh': __radian, 'arcsinh': __radian,
+                   'arctanh': __radian,
+                   'degrees': 'degree', 'radians': __radian,
+                   'expm1': '', 'cumprod': '',
+                   'rad2deg': 'degree', 'deg2rad': __radian}
+
+    #: List of ufunc/attributes names in which units are copied from the
+    #: original.
+    __copy_units = 'compress conj conjugate copy cumsum diagonal flatten ' \
+                   'max mean min ptp ravel repeat reshape round ' \
+                   'squeeze std sum take trace transpose ' \
+                   'ceil floor hypot rint ' \
+                   'add subtract ' \
+                   'copysign nextafter trunc ' \
+                   'frexp ldexp modf modf__1 ' \
+                   'absolute negative remainder fmod mod'.split()
+
+    #: Dictionary mapping ufunc/attributes names to the units that they will
+    #: set on output. The value is interpreted as the power to which the unit
+    #: will be raised.
+    __prod_units = {'var': 2, 'prod': 'size', 'multiply': 'mul',
+                    'true_divide': 'div', 'divide': 'div', 'floor_divide': 'div',
+                    'remainder': 'div',
+                    'sqrt': .5, 'square': 2, 'reciprocal': -1}
+
+    __skip_other_args = 'ldexp multiply ' \
+                        'true_divide divide floor_divide fmod mod ' \
+                        'remainder'.split()
+
+    __handled = tuple(__same_units) + \
+                tuple(__require_units.keys()) + \
+                tuple(__prod_units.keys()) + \
+                tuple(__copy_units) + tuple(__skip_other_args)
+
+    def clip(self, first=None, second=None, out=None, **kwargs):
+        min = kwargs.get('min', first)
+        max = kwargs.get('max', second)
+
+        if min is None and max is None:
+            raise TypeError('clip() takes at least 3 arguments (2 given)')
+
+        if max is None and 'min' not in kwargs:
+            min, max = max, min
+
+        kwargs = {'out': out}
+
+        if min is not None:
+            if isinstance(min, self.__class__):
+                kwargs['min'] = min.to(self).magnitude
+            elif self.dimensionless:
+                kwargs['min'] = min
+            else:
+                raise DimensionalityError('dimensionless', self._units)
+
+        if max is not None:
+            if isinstance(max, self.__class__):
+                kwargs['max'] = max.to(self).magnitude
+            elif self.dimensionless:
+                kwargs['max'] = max
+            else:
+                raise DimensionalityError('dimensionless', self._units)
+
+        return self.__class__(self.magnitude.clip(**kwargs), self._units)
+
+    def fill(self, value):
+        self._units = value._units
+        return self.magnitude.fill(value.magnitude)
+
+    def put(self, indices, values, mode='raise'):
+        if isinstance(values, self.__class__):
+            values = values.to(self).magnitude
+        elif self.dimensionless:
+            values = self.__class__(values, '').to(self)
+        else:
+            raise DimensionalityError('dimensionless', self._units)
+        self.magnitude.put(indices, values, mode)
+
+    @property
+    def real(self):
+        return self.__class__(self._magnitude.real, self._units)
+
+    @property
+    def imag(self):
+        return self.__class__(self._magnitude.imag, self._units)
+
+    @property
+    def T(self):
+        return self.__class__(self._magnitude.T, self._units)
+
+    def searchsorted(self, v, side='left'):
+        if isinstance(v, self.__class__):
+            v = v.to(self).magnitude
+        elif self.dimensionless:
+            v = self.__class__(v, '').to(self)
+        else:
+            raise DimensionalityError('dimensionless', self._units)
+        return self.magnitude.searchsorted(v, side)
+
+    def __ito_if_needed(self, to_units):
+        if self.unitless and to_units == 'radian':
+            return
+
+        self.ito(to_units)
+
+    def __numpy_method_wrap(self, func, *args, **kwargs):
+        """Convenience method to wrap on the fly numpy method taking
+        care of the units.
+        """
+        if func.__name__ in self.__require_units:
+            self.__ito_if_needed(self.__require_units[func.__name__])
+
+        value = func(*args, **kwargs)
+
+        if func.__name__ in self.__copy_units:
+            return self.__class__(value, self._units)
+
+        if func.__name__ in self.__prod_units:
+            tmp = self.__prod_units[func.__name__]
+            if tmp == 'size':
+                return self.__class__(value, self._units ** self._magnitude.size)
+            return self.__class__(value, self._units ** tmp)
+
+        return value
+
+    def __len__(self):
+        return len(self._magnitude)
+
+    def __iter__(self):
+        # Allow exception to propagate in case of non-iterable magnitude
+        it_mag = iter(self.magnitude)
+        return iter((self.__class__(mag, self._units) for mag in it_mag))
+
+    def __getattr__(self, item):
+        # Attributes starting with `__array_` are common attributes of NumPy ndarray.
+        # They are requested by numpy functions.
+        if item.startswith('__array_'):
+            if isinstance(self._magnitude, ndarray):
+                return getattr(self._magnitude, item)
+            else:
+                # If an `__array_` attributes is requested but the magnitude is not an ndarray,
+                # we convert the magnitude to a numpy ndarray.
+                self._magnitude = _to_magnitude(self._magnitude, force_ndarray=True)
+                return getattr(self._magnitude, item)
+        elif item in self.__handled:
+            if not isinstance(self._magnitude, ndarray):
+                self._magnitude = _to_magnitude(self._magnitude, True)
+            attr = getattr(self._magnitude, item)
+            if callable(attr):
+                return functools.partial(self.__numpy_method_wrap, attr)
+            return attr
+        try:
+            return getattr(self._magnitude, item)
+        except AttributeError as ex:
+            raise AttributeError("Neither Quantity object nor its magnitude ({0}) "
+                                 "has attribute '{1}'".format(self._magnitude, item))
+
+    def __getitem__(self, key):
+        try:
+            value = self._magnitude[key]
+            return self.__class__(value, self._units)
+        except TypeError:
+            raise TypeError("Neither Quantity object nor its magnitude ({0})"
+                            "supports indexing".format(self._magnitude))
+
+    def __setitem__(self, key, value):
+        try:
+            if math.isnan(value):
+                self._magnitude[key] = value
+                return
+        except (TypeError, DimensionalityError):
+            pass
+
+        try:
+            if isinstance(value, self.__class__):
+                factor = self.__class__(value.magnitude, value._units / self._units).to_root_units()
+            else:
+                factor = self.__class__(value, self._units ** (-1)).to_root_units()
+
+            if isinstance(factor, self.__class__):
+                if not factor.dimensionless:
+                    raise DimensionalityError(value, self.units,
+                                              extra_msg='. Assign a quantity with the same dimensionality or '
+                                                        'access the magnitude directly as '
+                                                        '`obj.magnitude[%s] = %s`' % (key, value))
+                self._magnitude[key] = factor.magnitude
+            else:
+                self._magnitude[key] = factor
+
+        except TypeError:
+            raise TypeError("Neither Quantity object nor its magnitude ({0})"
+                            "supports indexing".format(self._magnitude))
+
+    def tolist(self):
+        units = self._units
+        return [self.__class__(value, units).tolist() if isinstance(value, list) else self.__class__(value, units)
+                for value in self._magnitude.tolist()]
+
+    __array_priority__ = 17
+
+    def __array_prepare__(self, obj, context=None):
+        # If this uf is handled by Pint, write it down in the handling dictionary.
+
+        # name of the ufunc, argument of the ufunc, domain of the ufunc
+        # In ufuncs with multiple outputs, domain indicates which output
+        # is currently being prepared (eg. see modf).
+        # In ufuncs with a single output, domain is 0
+        uf, objs, huh = context
+
+        if uf.__name__ in self.__handled and huh == 0:
+            # Only one ufunc should be handled at a time.
+            # If a ufunc is already being handled (and this is not another domain),
+            # something is wrong..
+            if self.__handling:
+                raise Exception('Cannot handled nested ufuncs.\n'
+                                'Current: {0}\n'
+                                'New: {1}'.format(context, self.__handling))
+            self.__handling = context
+
+        return obj
+
+    def __array_wrap__(self, obj, context=None):
+        uf, objs, huh = context
+
+        # if this ufunc is not handled by Pint, pass it to the magnitude.
+        if uf.__name__ not in self.__handled:
+            return self.magnitude.__array_wrap__(obj, context)
+
+        try:
+            ufname = uf.__name__ if huh == 0 else '{0}__{1}'.format(uf.__name__, huh)
+
+            # First, we check the units of the input arguments.
+
+            if huh == 0:
+                # Do this only when the wrap is called for the first ouput.
+
+                # Store the destination units
+                dst_units = None
+                # List of magnitudes of Quantities with the right units
+                # to be used as argument of the ufunc
+                mobjs = None
+
+                if uf.__name__ in self.__require_units:
+                    # ufuncs in __require_units
+                    # require specific units
+                    # This is more complex that it should be due to automatic
+                    # conversion between radians/dimensionless
+                    # TODO: maybe could be simplified using Contexts
+                    dst_units = self.__require_units[uf.__name__]
+                    if dst_units == 'radian':
+                        mobjs = []
+                        for other in objs:
+                            unt = getattr(other, '_units', '')
+                            if unt == 'radian':
+                                mobjs.append(getattr(other, 'magnitude', other))
+                            else:
+                                factor, units = self._REGISTRY._get_root_units(unt)
+                                if units and units != UnitsContainer({'radian': 1}):
+                                    raise DimensionalityError(units, dst_units)
+                                mobjs.append(getattr(other, 'magnitude', other) * factor)
+                        mobjs = tuple(mobjs)
+                    else:
+                        dst_units = self._REGISTRY.parse_expression(dst_units)._units
+
+                elif len(objs) > 1 and uf.__name__ not in self.__skip_other_args:
+                    # ufunc with multiple arguments require that all inputs have
+                    # the same arguments unless they are in __skip_other_args
+                    dst_units = objs[0]._units
+
+                # Do the conversion (if needed) and extract the magnitude for each input.
+                if mobjs is None:
+                    if dst_units is not None:
+                        mobjs = tuple(self._REGISTRY.convert(getattr(other, 'magnitude', other),
+                                                             getattr(other, 'units', ''),
+                                                             dst_units)
+                                      for other in objs)
+                    else:
+                        mobjs = tuple(getattr(other, 'magnitude', other)
+                                      for other in objs)
+
+                # call the ufunc
+                out = uf(*mobjs)
+
+                # If there are multiple outputs,
+                # store them in __handling (uf, objs, huh, out0, out1, ...)
+                # and return the first
+                if uf.nout > 1:
+                    self.__handling += out
+                    out = out[0]
+            else:
+                # If this is not the first output,
+                # just grab the result that was previously calculated.
+                out = self.__handling[3 + huh]
+
+            # Second, we set the units of the output value.
+            if ufname in self.__set_units:
+                try:
+                    out = self.__class__(out, self.__set_units[ufname])
+                except:
+                    raise _Exception(ValueError)
+            elif ufname in self.__copy_units:
+                try:
+                    out = self.__class__(out, self._units)
+                except:
+                    raise _Exception(ValueError)
+            elif ufname in self.__prod_units:
+                tmp = self.__prod_units[ufname]
+                if tmp == 'size':
+                    out = self.__class__(out, self._units ** self._magnitude.size)
+                elif tmp == 'div':
+                    units1 = objs[0]._units if isinstance(objs[0], self.__class__) else UnitsContainer()
+                    units2 = objs[1]._units if isinstance(objs[1], self.__class__) else UnitsContainer()
+                    out = self.__class__(out, units1 / units2)
+                elif tmp == 'mul':
+                    units1 = objs[0]._units if isinstance(objs[0], self.__class__) else UnitsContainer()
+                    units2 = objs[1]._units if isinstance(objs[1], self.__class__) else UnitsContainer()
+                    out = self.__class__(out, units1 * units2)
+                else:
+                    out = self.__class__(out, self._units ** tmp)
+
+            return out
+        except (DimensionalityError, UndefinedUnitError) as ex:
+            raise ex
+        except _Exception as ex:
+            raise ex.internal
+        except Exception as ex:
+            print(ex)
+        finally:
+            # If this is the last output argument for the ufunc,
+            # we are done handling this ufunc.
+            if uf.nout == huh + 1:
+                self.__handling = None
+
+        return self.magnitude.__array_wrap__(obj, context)
+
+    # Measurement support
+    def plus_minus(self, error, relative=False):
+        if isinstance(error, self.__class__):
+            if relative:
+                raise ValueError('{} is not a valid relative error.'.format(error))
+            error = error.to(self._units).magnitude
+        else:
+            if relative:
+                error = error * abs(self.magnitude)
+
+        return self._REGISTRY.Measurement(copy.copy(self.magnitude), error, self._units)
+
+    # methods/properties that help for math operations with offset units
+    @property
+    def _is_multiplicative(self):
+        """Check if the Quantity object has only multiplicative units.
+        """
+        return not self._get_non_multiplicative_units()
+
+    def _get_non_multiplicative_units(self):
+        """Return a list of the of non-multiplicative units of the Quantity object
+        """
+        offset_units = [unit for unit in self._units.keys()
+                        if not self._REGISTRY._units[unit].is_multiplicative]
+        return offset_units
+
+    def _get_delta_units(self):
+        """Return list of delta units ot the Quantity object
+        """
+        delta_units = [u for u in self._units.keys() if u.startswith("delta_")]
+        return delta_units
+
+    def _has_compatible_delta(self, unit):
+        """"Check if Quantity object has a delta_unit that is compatible with unit
+        """
+        deltas = self._get_delta_units()
+        if 'delta_' + unit in deltas:
+            return True
+        else:  # Look for delta units with same dimension as the offset unit
+            offset_unit_dim = self._REGISTRY._units[unit].reference
+            for d in deltas:
+                if self._REGISTRY._units[d].reference == offset_unit_dim:
+                    return True
+        return False
+
+    def _ok_for_muldiv(self, no_offset_units=None):
+        """Checks if Quantity object can be multiplied or divided
+
+        :q: quantity object that is checked
+        :no_offset_units: number of offset units in q
+        """
+        is_ok = True
+        if no_offset_units is None:
+            no_offset_units = len(self._get_non_multiplicative_units())
+        if no_offset_units > 1:
+            is_ok = False
+        if no_offset_units == 1:
+            if len(self._units) > 1:
+                is_ok = False
+            if (len(self._units) == 1
+                    and not self._REGISTRY.autoconvert_offset_to_baseunit):
+                is_ok = False
+            if next(iter(self._units.values())) != 1:
+                is_ok = False
+        return is_ok
diff --git a/lib/taurus/external/pint/pint_local/systems.py b/lib/taurus/external/pint/pint_local/systems.py
new file mode 100644
index 0000000..164d089
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/systems.py
@@ -0,0 +1,423 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.systems
+    ~~~~~~~~~~~~
+
+    Functions and classes related to system definitions and conversions.
+
+    :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import division, unicode_literals, print_function, absolute_import
+
+import re
+
+from .unit import Definition, UnitDefinition, DefinitionSyntaxError
+from .util import to_units_container, UnitsContainer
+
+
+class Group(object):
+    """A group is a list of units.
+
+    Units can be added directly or by including other groups.
+
+    Members are computed dynamically, that is if a unit is added to a group X
+    all groups that include X are affected.
+    """
+
+    #: Regex to match the header parts of a context.
+    _header_re = re.compile('@group\s+(?P<name>\w+)\s*(using\s(?P<used_groups>.*))*')
+
+    def __init__(self, name, groups_systems):
+        """
+        :param name: Name of the group
+        :type name: str
+        :param groups_systems: dictionary containing groups and system.
+                               The newly created group will be added after creation.
+        :type groups_systems: dict[str, Group | System]
+        """
+
+        if name in groups_systems:
+            t = 'group' if isinstance(groups_systems['name'], Group) else 'system'
+            raise ValueError('The system name already in use by a %s' % t)
+
+        # The name of the group.
+        #: type: str
+        self.name = name
+
+        #: Names of the units in this group.
+        #: :type: set[str]
+        self._unit_names = set()
+
+        #: Names of the groups in this group.
+        #: :type: set[str]
+        self._used_groups = set()
+
+        #: Names of the groups in which this group is contained.
+        #: :type: set[str]
+        self._used_by = set()
+
+        #: Maps group name to Group.
+        #: :type: dict[str, Group]
+        self._groups_systems = groups_systems
+
+        self._groups_systems[self.name] = self
+
+        if name != 'root':
+            # All groups are added to root group
+            groups_systems['root'].add_groups(name)
+
+        #: A cache of the included units.
+        #: None indicates that the cache has been invalidated.
+        #: :type: frozenset[str] | None
+        self._computed_members = None
+
+    @property
+    def members(self):
+        """Names of the units that are members of the group.
+
+        Calculated to include to all units in all included _used_groups.
+
+        :rtype: frozenset[str]
+        """
+        if self._computed_members is None:
+            self._computed_members = set(self._unit_names)
+
+            for _, group in self.iter_used_groups():
+                self._computed_members |= group.members
+
+            self._computed_members = frozenset(self._computed_members)
+
+        return self._computed_members
+
+    def invalidate_members(self):
+        """Invalidate computed members in this Group and all parent nodes.
+        """
+        self._computed_members = None
+        d = self._groups_systems
+        for name in self._used_by:
+            d[name].invalidate_members()
+
+    def iter_used_groups(self):
+        pending = set(self._used_groups)
+        d = self._groups_systems
+        while pending:
+            name = pending.pop()
+            group = d[name]
+            pending |= group._used_groups
+            yield name, d[name]
+
+    def is_used_group(self, group_name):
+        for name, _ in self.iter_used_groups():
+            if name == group_name:
+                return True
+        return False
+
+    def add_units(self, *unit_names):
+        """Add units to group.
+
+        :type unit_names: str
+        """
+        for unit_name in unit_names:
+            self._unit_names.add(unit_name)
+
+        self.invalidate_members()
+
+    def remove_units(self, *unit_names):
+        """Remove units from group.
+
+        :type unit_names: str
+        """
+        for unit_name in unit_names:
+            self._unit_names.remove(unit_name)
+
+        self.invalidate_members()
+
+    def add_groups(self, *group_names):
+        """Add groups to group.
+
+        :type group_names: str
+        """
+        d = self._groups_systems
+        for group_name in group_names:
+
+            grp = d[group_name]
+
+            if grp.is_used_group(self.name):
+                raise ValueError('Cyclic relationship found between %s and %s' % (self.name, group_name))
+
+            self._used_groups.add(group_name)
+            grp._used_by.add(self.name)
+
+        self.invalidate_members()
+
+    def remove_groups(self, *group_names):
+        """Remove groups from group.
+
+        :type group_names: str
+        """
+        d = self._groups_systems
+        for group_name in group_names:
+            grp = d[group_name]
+
+            self._used_groups.remove(group_name)
+            grp._used_by.remove(self.name)
+
+
+        self.invalidate_members()
+
+    @classmethod
+    def from_lines(cls, lines, define_func, group_dict):
+        """Return a Group object parsing an iterable of lines.
+
+        :param lines: iterable
+        :type lines: list[str]
+        :param define_func: Function to define a unit in the registry.
+        :type define_func: str -> None
+        :param group_dict: Maps group name to Group.
+        :type group_dict: dict[str, Group]
+        """
+        header, lines = lines[0], lines[1:]
+
+        r = cls._header_re.search(header)
+        name = r.groupdict()['name'].strip()
+        groups = r.groupdict()['used_groups']
+        if groups:
+            group_names = tuple(a.strip() for a in groups.split(','))
+        else:
+            group_names = ()
+
+        unit_names = []
+        for line in lines:
+            if '=' in line:
+                # Is a definition
+                definition = Definition.from_string(line)
+                if not isinstance(definition, UnitDefinition):
+                    raise DefinitionSyntaxError('Only UnitDefinition are valid inside _used_groups, '
+                                                'not %s' % type(definition))
+                define_func(definition)
+                unit_names.append(definition.name)
+            else:
+                unit_names.append(line.strip())
+
+        grp = cls(name, group_dict)
+
+        grp.add_units(*unit_names)
+
+        if group_names:
+            grp.add_groups(*group_names)
+
+        return grp
+
+
+class System(object):
+    """A system is a Group plus a set of base units.
+
+    @system <name> [using <group 1>, ..., <group N>]
+        <rule 1>
+        ...
+        <rule N>
+    @end
+
+    """
+
+    #: Regex to match the header parts of a context.
+    _header_re = re.compile('@system\s+(?P<name>\w+)\s*(using\s(?P<used_groups>.*))*')
+
+    def __init__(self, name, groups_systems):
+        """
+        :param name: Name of the group
+        :type name: str
+        :param groups_systems: dictionary containing groups and system.
+                               The newly created group will be added after creation.
+        :type groups_systems: dict[str, Group | System]
+        """
+
+        if name in groups_systems:
+            t = 'group' if isinstance(groups_systems[name], Group) else 'system'
+            raise ValueError('The system name (%s) already in use by a %s' % (name, t))
+
+        #: Name of the system
+        #: :type: str
+        self.name = name
+
+        #: Maps root unit names to a dict indicating the new unit and its exponent.
+        #: :type: dict[str, dict[str, number]]]
+        self.base_units = {}
+
+        #: Derived unit names.
+        #: :type: set(str)
+        self.derived_units = set()
+
+        #: Names of the _used_groups in used by this system.
+        #: :type: set(str)
+        self._used_groups = set()
+
+        #: :type: frozenset | None
+        self._computed_members = None
+
+        #: Maps group name to Group.
+        self._group_systems_dict = groups_systems
+
+        self._group_systems_dict[self.name] = self
+
+    @property
+    def members(self):
+        if self._computed_members is None:
+            self._computed_members = set()
+
+            for group_name in self._used_groups:
+                self._computed_members |= self._group_systems_dict[group_name].members
+
+            self._computed_members = frozenset(self._computed_members)
+
+        return self._computed_members
+
+    def invalidate_members(self):
+        """Invalidate computed members in this Group and all parent nodes.
+        """
+        self._computed_members = None
+
+    def add_groups(self, *group_names):
+        """Add groups to group.
+
+        :type group_names: str
+        """
+        self._used_groups |= set(group_names)
+
+        self.invalidate_members()
+
+    def remove_groups(self, *group_names):
+        """Remove groups from group.
+
+        :type group_names: str
+        """
+        self._used_groups -= set(group_names)
+
+        self.invalidate_members()
+
+    @classmethod
+    def from_lines(cls, lines, get_root_func, group_dict):
+        header, lines = lines[0], lines[1:]
+
+        r = cls._header_re.search(header)
+        name = r.groupdict()['name'].strip()
+        groups = r.groupdict()['used_groups']
+
+        # If the systems has no group, it automatically uses the root group.
+        if groups:
+            group_names = tuple(a.strip() for a in groups.split(','))
+        else:
+            group_names = ('root', )
+
+        base_unit_names = {}
+        derived_unit_names = []
+        for line in lines:
+            line = line.strip()
+
+            # We would identify a
+            #  - old_unit: a root unit part which is going to be removed from the system.
+            #  - new_unit: a non root unit which is going to replace the old_unit.
+
+            if ':' in line:
+                # The syntax is new_unit:old_unit
+
+                new_unit, old_unit = line.split(':')
+                new_unit, old_unit = new_unit.strip(), old_unit.strip()
+
+                # The old unit MUST be a root unit, if not raise an error.
+                if old_unit != str(get_root_func(old_unit)[1]):
+                    raise ValueError('In `%s`, the unit at the right of the `:` must be a root unit.' % line)
+
+                # Here we find new_unit expanded in terms of root_units
+                new_unit_expanded = to_units_container(get_root_func(new_unit)[1])
+
+                # We require that the old unit is present in the new_unit expanded
+                if old_unit not in new_unit_expanded:
+                    raise ValueError('Old unit must be a component of new unit')
+
+                # Here we invert the equation, in other words
+                # we write old units in terms new unit and expansion
+                new_unit_dict = dict((new_unit, -1./value)
+                                     for new_unit, value in new_unit_expanded.items()
+                                     if new_unit != old_unit)
+                new_unit_dict[new_unit] = 1 / new_unit_expanded[old_unit]
+
+                base_unit_names[old_unit] = new_unit_dict
+
+            else:
+                # The syntax is new_unit
+                # old_unit is inferred as the root unit with the same dimensionality.
+
+                new_unit = line
+                old_unit_dict = to_units_container(get_root_func(line)[1])
+
+                if len(old_unit_dict) != 1:
+                    raise ValueError('The new base must be a root dimension if not discarded unit is specified.')
+
+                old_unit, value = dict(old_unit_dict).popitem()
+
+                base_unit_names[old_unit] = {new_unit: 1./value}
+
+        system = cls(name, group_dict)
+
+        system.add_groups(*group_names)
+
+        system.base_units.update(**base_unit_names)
+        system.derived_units |= set(derived_unit_names)
+
+        return system
+
+
+class GSManager(object):
+
+    def __init__(self):
+        #: :type: dict[str, Group | System]
+        self._groups_systems = dict()
+        self._root_group = Group('root', self._groups_systems)
+
+    def add_system_from_lines(self, lines, get_root_func):
+        System.from_lines(lines, get_root_func, self._groups_systems)
+
+    def add_group_from_lines(self, lines, define_func):
+        Group.from_lines(lines, define_func, self._groups_systems)
+
+    def get_group(self, name, create_if_needed=True):
+        """Return a Group.
+
+        :param name: Name of the group.
+        :param create_if_needed: Create a group if not Found. If False, raise an Exception.
+        :return: Group
+        """
+        try:
+            return self._groups_systems[name]
+        except KeyError:
+            if create_if_needed:
+                if name == 'root':
+                    raise ValueError('The name root is reserved.')
+                return Group(name, self._groups_systems)
+            else:
+                raise KeyError('No group %s found.' % name)
+
+    def get_system(self, name, create_if_needed=True):
+        """Return a Group.
+
+        :param name: Name of the system
+        :param create_if_needed: Create a group if not Found. If False, raise an Exception.
+        :return: System
+        """
+
+        try:
+            return self._groups_systems[name]
+        except KeyError:
+            if create_if_needed:
+                return System(name, self._groups_systems)
+            else:
+                raise KeyError('No system found named %s.' % name)
+
+    def __getitem__(self, item):
+        if item in self._groups_systems:
+            return self._groups_systems[item]
+
+        raise KeyError('No group or system found named %s.' % item)
diff --git a/lib/taurus/external/pint/pint_local/unit.py b/lib/taurus/external/pint/pint_local/unit.py
new file mode 100644
index 0000000..bbf9dfb
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/unit.py
@@ -0,0 +1,1375 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.unit
+    ~~~~~~~~~
+
+    Functions and classes related to unit definitions and conversions.
+
+    :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import division, unicode_literals, print_function, absolute_import
+
+import os
+import math
+import itertools
+import functools
+import operator
+import pkg_resources
+from decimal import Decimal
+from contextlib import contextmanager, closing
+from io import open, StringIO
+from collections import defaultdict
+from tokenize import untokenize, NUMBER, STRING, NAME, OP
+from numbers import Number
+
+from .context import Context, ContextChain
+from .util import (logger, pi_theorem, solve_dependencies, ParserHelper,
+                   string_preprocessor, find_connected_nodes,
+                   find_shortest_path, UnitsContainer, _is_dim,
+                   SharedRegistryObject, to_units_container)
+from .compat import tokenizer, string_types, NUMERIC_TYPES, long_type, zip_longest
+from .definitions import (Definition, UnitDefinition, PrefixDefinition,
+                          DimensionDefinition)
+from .converters import ScaleConverter
+from .errors import (DimensionalityError, UndefinedUnitError,
+                     DefinitionSyntaxError, RedefinitionError)
+
+from .pint_eval import build_eval_tree
+from . import systems
+
+
+def _capture_till_end(ifile):
+    context = []
+    for no, line in ifile:
+        line = line.strip()
+        if line.startswith('@end'):
+            break
+        elif line.startswith('@'):
+            raise DefinitionSyntaxError('cannot nest @ directives', lineno=no)
+        context.append(line)
+    return context
+
+
+class _Unit(SharedRegistryObject):
+    """Implements a class to describe a unit supporting math operations.
+
+    :type units: UnitsContainer, str, Unit or Quantity.
+
+    """
+
+    #: Default formatting string.
+    default_format = ''
+
+    def __reduce__(self):
+        from . import _build_unit
+        return _build_unit, (self._units)
+
+    def __new__(cls, units):
+        inst = object.__new__(cls)
+        if isinstance(units, (UnitsContainer, UnitDefinition)):
+            inst._units = units
+        elif isinstance(units, string_types):
+            inst._units = inst._REGISTRY.parse_units(units)._units
+        elif isinstance(units, _Unit):
+            inst._units = units._units
+        else:
+            raise TypeError('units must be of type str, Unit or '
+                            'UnitsContainer; not {0}.'.format(type(units)))
+
+        inst.__used = False
+        inst.__handling = None
+        return inst
+
+    @property
+    def debug_used(self):
+        return self.__used
+
+    def __copy__(self):
+        ret = self.__class__(self._units)
+        ret.__used = self.__used
+        return ret
+
+    def __str__(self):
+        return format(self)
+
+    def __repr__(self):
+        return "<Unit('{0}')>".format(self._units)
+
+    def __format__(self, spec):
+        spec = spec or self.default_format
+
+        if '~' in spec:
+            if self.dimensionless:
+                return ''
+            units = UnitsContainer(dict((self._REGISTRY._get_symbol(key),
+                                         value)
+                                   for key, value in self._units.items()))
+            spec = spec.replace('~', '')
+        else:
+            units = self._units
+
+        return '%s' % (format(units, spec))
+
+    # IPython related code
+    def _repr_html_(self):
+        return self.__format__('H')
+
+    def _repr_latex_(self):
+        return "$" + self.__format__('L') + "$"
+
+    @property
+    def dimensionless(self):
+        """Return true if the Unit is dimensionless.
+
+        """
+        return not bool(self.dimensionality)
+
+    @property
+    def dimensionality(self):
+        """Unit's dimensionality (e.g. {length: 1, time: -1})
+
+        """
+        try:
+            return self._dimensionality
+        except AttributeError:
+            dim = self._REGISTRY._get_dimensionality(self._units)
+            self._dimensionality = dim
+
+        return self._dimensionality
+
+    def compatible_units(self, *contexts):
+        if contexts:
+            with self._REGISTRY.context(*contexts):
+                return self._REGISTRY.get_compatible_units(self)
+
+        return self._REGISTRY.get_compatible_units(self)
+
+    def __mul__(self, other):
+        if self._check(other):
+            if isinstance(other, self.__class__):
+                return self.__class__(self._units*other._units)
+            else:
+                return self._REGISTRY.Quantity(other._magnitude,
+                                               self._units*other._units)
+
+        return self._REGISTRY.Quantity(other, self._units)
+
+    __rmul__ = __mul__
+
+    def __truediv__(self, other):
+        if self._check(other):
+            if isinstance(other, self.__class__):
+                return self.__class__(self._units/other._units)
+            else:
+                return self._REGISTRY.Quantity(1/other._magnitude,
+                                           self._units/other._units)
+
+        return self._REGISTRY.Quantity(1/other, self._units)
+
+    def __rtruediv__(self, other):
+        # As Unit and Quantity both handle truediv with each other rtruediv can
+        # only be called for something different.
+        if isinstance(other, NUMERIC_TYPES):
+            return self._REGISTRY.Quantity(other, 1/self._units)
+        elif isinstance(other, UnitsContainer):
+            return self.__class__(other/self._units)
+        else:
+            return NotImplemented
+
+    __div__ = __truediv__
+    __rdiv__ = __rtruediv__
+
+    def __pow__(self, other):
+        if isinstance(other, NUMERIC_TYPES):
+            return self.__class__(self._units**other)
+
+        else:
+            mess = 'Cannot power Unit by {}'.format(type(other))
+            raise TypeError(mess)
+
+    def __hash__(self):
+        return self._units.__hash__()
+
+    def __eq__(self, other):
+        # We compare to the base class of Unit because each Unit class is
+        # unique.
+        if self._check(other):
+            if isinstance(other, self.__class__):
+                return self._units == other._units
+            else:
+                return other == self._REGISTRY.Quantity(1, self._units)
+
+        elif isinstance(other, NUMERIC_TYPES):
+            return other == self._REGISTRY.Quantity(1, self._units)
+
+        else:
+            return self._units == other
+
+    def compare(self, other, op):
+        self_q = self._REGISTRY.Quantity(1, self)
+
+        if isinstance(other, NUMERIC_TYPES):
+            return self_q.compare(other, op)
+        elif isinstance(other, (_Unit, UnitsContainer, dict)):
+            return self_q.compare(self._REGISTRY.Quantity(1, other), op)
+        else:
+            return NotImplemented
+
+    __lt__ = lambda self, other: self.compare(other, op=operator.lt)
+    __le__ = lambda self, other: self.compare(other, op=operator.le)
+    __ge__ = lambda self, other: self.compare(other, op=operator.ge)
+    __gt__ = lambda self, other: self.compare(other, op=operator.gt)
+
+    def __int__(self):
+        return int(self._REGISTRY.Quantity(1, self._units))
+
+    def __long__(self):
+        return long_type(self._REGISTRY.Quantity(1, self._units))
+
+    def __float__(self):
+        return float(self._REGISTRY.Quantity(1, self._units))
+
+    def __complex__(self):
+        return complex(self._REGISTRY.Quantity(1, self._units))
+
+    __array_priority__ = 17
+
+    def __array_prepare__(self, array, context=None):
+        return 1
+
+    def __array_wrap__(self, array, context=None):
+        uf, objs, huh = context
+
+        if uf.__name__ in ('true_divide', 'divide', 'floor_divide'):
+            return self._REGISTRY.Quantity(array, 1/self._units)
+        elif uf.__name__ in ('multiply',):
+            return self._REGISTRY.Quantity(array, self._units)
+        else:
+            raise ValueError('Unsupproted operation for Unit')
+
+
+class UnitRegistry(object):
+    """The unit registry stores the definitions and relationships between
+    units.
+
+    :param filename: path of the units definition file to load.
+                     Empty to load the default definition file.
+                     None to leave the UnitRegistry empty.
+    :param force_ndarray: convert any input, scalar or not to a numpy.ndarray.
+    :param default_as_delta: In the context of a multiplication of units, interpret
+                             non-multiplicative units as their *delta* counterparts.
+    :autoconvert_offset_to_baseunit: If True converts offset units in quantites are
+                                     converted to their base units in multiplicative
+                                     context. If False no conversion happens.
+    :param on_redefinition: action to take in case a unit is redefined.
+                            'warn', 'raise', 'ignore'
+    :type on_redefintion: str
+    """
+
+    def __init__(self, filename='', force_ndarray=False, default_as_delta=True,
+                 autoconvert_offset_to_baseunit=False,
+                 on_redefinition='warn', system=None):
+        self.Unit = build_unit_class(self)
+        self.Quantity = build_quantity_class(self, force_ndarray)
+        self.Measurement = build_measurement_class(self, force_ndarray)
+
+        #: Action to take in case a unit is redefined. 'warn', 'raise', 'ignore'
+        self._on_redefinition = on_redefinition
+
+        #: Map dimension name (string) to its definition (DimensionDefinition).
+        self._dimensions = {}
+
+        #: :type: systems.GSManager
+        self._gsmanager = systems.GSManager()
+
+        #: Map unit name (string) to its definition (UnitDefinition).
+        #: Might contain prefixed units.
+        self._units = {}
+
+        #: Map unit name in lower case (string) to a set of unit names with the right case.
+        #: Does not contain prefixed units.
+        #: e.g: 'hz' - > set('Hz', )
+        self._units_casei = defaultdict(set)
+
+        #: Map prefix name (string) to its definition (PrefixDefinition).
+        self._prefixes = {'': PrefixDefinition('', '', (), 1)}
+
+        #: Map suffix name (string) to canonical , and unit alias to canonical unit name
+        self._suffixes = {'': None, 's': ''}
+
+        #: Map context name (string) or abbreviation to context.
+        self._contexts = {}
+
+        #: Stores active contexts.
+        self._active_ctx = ContextChain()
+
+        #: Maps dimensionality (UnitsContainer) to Units (str)
+        self._dimensional_equivalents = dict()
+
+        #: Maps dimensionality (UnitsContainer) to Dimensionality (UnitsContainer)
+        self._root_units_cache = dict()
+
+        #: Maps dimensionality (UnitsContainer) to Dimensionality (UnitsContainer)
+        self._base_units_cache = dict()
+
+        #: Maps dimensionality (UnitsContainer) to Units (UnitsContainer)
+        self._dimensionality_cache = dict()
+
+        #: Cache the unit name associated to user input. ('mV' -> 'millivolt')
+        self._parse_unit_cache = dict()
+
+        #: When performing a multiplication of units, interpret
+        #: non-multiplicative units as their *delta* counterparts.
+        self.default_as_delta = default_as_delta
+
+        #: System name to be used by default.
+        self._default_system_name = None
+
+        # Determines if quantities with offset units are converted to their
+        # base units on multiplication and division.
+        self.autoconvert_offset_to_baseunit = autoconvert_offset_to_baseunit
+
+        if filename == '':
+            self.load_definitions('default_en.txt', True)
+        elif filename is not None:
+            self.load_definitions(filename)
+
+        self.define(UnitDefinition('pi', 'π', (), ScaleConverter(math.pi)))
+
+        self._build_cache()
+
+        self.set_default_system(system)
+
+    def __name__(self):
+        return 'UnitRegistry'
+
+    def __getattr__(self, item):
+        return self.Unit(item)
+
+    def __getitem__(self, item):
+        logger.warning('Calling the getitem method from a UnitRegistry is deprecated. '
+                       'use `parse_expression` method or use the registry as a callable.')
+        return self.parse_expression(item)
+
+    def __dir__(self):
+        return list(self._units.keys()) + \
+            ['define', 'load_definitions', 'get_name', 'get_symbol',
+             'get_dimensionality', 'Quantity', 'wraps',
+             'parse_units', 'parse_expression', 'pi_theorem',
+             'convert', 'get_base_units']
+
+    @property
+    def default_format(self):
+        """Default formatting string for quantities.
+        """
+        return self.Quantity.default_format
+
+    @default_format.setter
+    def default_format(self, value):
+        self.Unit.default_format = value
+        self.Quantity.default_format = value
+
+    def get_group(self, name, create_if_needed=True):
+        """Return a Group.
+
+        :param name: Name of the group to be
+        :param create_if_needed: Create a group if not Found. If False, raise an Exception.
+        :return: Group
+        """
+        return self._gsmanager.get_group(name, create_if_needed)
+
+    def get_system(self, name, create_if_needed=True):
+        """Return a Group.
+
+        :param registry:
+        :param name: Name of the group to be
+        :param create_if_needed: Create a group if not Found. If False, raise an Exception.
+        :return: System
+        """
+        return self._gsmanager.get_system(name, create_if_needed)
+
+    def set_default_system(self, name):
+        # Test if exists
+        if name:
+            system = self._gsmanager.get_system(name, False)
+            self._default_system_name = name
+            self._base_units_cache = {}
+
+    def add_context(self, context):
+        """Add a context object to the registry.
+
+        The context will be accessible by its name and aliases.
+
+        Notice that this method will NOT enable the context. Use `enable_contexts`.
+        """
+        if context.name in self._contexts:
+            logger.warning('The name %s was already registered for another context.',
+                           context.name)
+        self._contexts[context.name] = context
+        for alias in context.aliases:
+            if alias in self._contexts:
+                logger.warning('The name %s was already registered for another context',
+                               context.name)
+            self._contexts[alias] = context
+
+    def remove_context(self, name_or_alias):
+        """Remove a context from the registry and return it.
+
+        Notice that this methods will not disable the context. Use `disable_contexts`.
+        """
+        context = self._contexts[name_or_alias]
+
+        del self._contexts[context.name]
+        for alias in context.aliases:
+            del self._contexts[alias]
+
+        return context
+
+    def enable_contexts(self, *names_or_contexts, **kwargs):
+        """Enable contexts provided by name or by object.
+
+        :param names_or_contexts: sequence of the contexts or contexts names/alias
+        :param kwargs: keyword arguments for the context
+        """
+
+        # If present, copy the defaults from the containing contexts
+        if self._active_ctx.defaults:
+            kwargs = dict(self._active_ctx.defaults, **kwargs)
+
+        # For each name, we first find the corresponding context
+        ctxs = tuple((self._contexts[name] if isinstance(name, string_types) else name)
+                     for name in names_or_contexts)
+
+        # Check if the contexts have been checked first, if not we make sure
+        # that dimensions are expressed in terms of base dimensions.
+        for ctx in ctxs:
+            if getattr(ctx, '_checked', False):
+                continue
+            for (src, dst), func in ctx.funcs.items():
+                src_ = self._get_dimensionality(src)
+                dst_ = self._get_dimensionality(dst)
+                if src != src_ or dst != dst_:
+                    ctx.remove_transformation(src, dst)
+                    ctx.add_transformation(src_, dst_, func)
+            ctx._checked = True
+
+        # and create a new one with the new defaults.
+        ctxs = tuple(Context.from_context(ctx, **kwargs)
+                     for ctx in ctxs)
+
+        # Finally we add them to the active context.
+        self._active_ctx.insert_contexts(*ctxs)
+
+    def disable_contexts(self, n=None):
+        """Disable the last n enabled contexts.
+        """
+        if n is None:
+            n = len(self._contexts)
+        self._active_ctx.remove_contexts(n)
+
+    @contextmanager
+    def context(self, *names, **kwargs):
+        """Used as a context manager, this function enables to activate a context
+        which is removed after usage.
+
+        :param names: name of the context.
+        :param kwargs: keyword arguments for the contexts.
+
+        Context are called by their name::
+
+            >>> with ureg.context('one'):
+            ...     pass
+
+        If the context has an argument, you can specify its value as a keyword
+        argument::
+
+            >>> with ureg.context('one', n=1):
+            ...     pass
+
+        Multiple contexts can be entered in single call:
+
+            >>> with ureg.context('one', 'two', n=1):
+            ...     pass
+
+        or nested allowing you to give different values to the same keyword argument::
+
+            >>> with ureg.context('one', n=1):
+            ...     with ureg.context('two', n=2):
+            ...         pass
+
+        A nested context inherits the defaults from the containing context::
+
+            >>> with ureg.context('one', n=1):
+            ...     with ureg.context('two'): # Here n takes the value of the upper context
+            ...         pass
+
+        """
+
+        # Enable the contexts.
+        self.enable_contexts(*names, **kwargs)
+
+        try:
+            # After adding the context and rebuilding the graph, the registry
+            # is ready to use.
+            yield self
+        finally:
+            # Upon leaving the with statement,
+            # the added contexts are removed from the active one.
+            self.disable_contexts(len(names))
+
+    def define(self, definition):
+        """Add unit to the registry.
+        """
+        if isinstance(definition, string_types):
+            definition = Definition.from_string(definition)
+
+        if isinstance(definition, DimensionDefinition):
+            d, di = self._dimensions, None
+        elif isinstance(definition, UnitDefinition):
+            d, di = self._units, self._units_casei
+            if definition.is_base:
+                for dimension in definition.reference.keys():
+                    if dimension in self._dimensions:
+                        if dimension != '[]':
+                            raise DefinitionSyntaxError('only one unit per dimension can be a base unit.')
+                        continue
+
+                    self.define(DimensionDefinition(dimension, '', (), None, is_base=True))
+
+        elif isinstance(definition, PrefixDefinition):
+            d, di = self._prefixes, None
+        else:
+            raise TypeError('{0} is not a valid definition.'.format(definition))
+
+        def _adder(key, value, action=self._on_redefinition, selected_dict=d, casei_dict=di):
+            if key in selected_dict:
+                if action == 'raise':
+                    raise RedefinitionError(key, type(value))
+                elif action == 'warn':
+                    logger.warning("Redefining '%s' (%s)", key, type(value))
+
+            selected_dict[key] = value
+            if casei_dict is not None:
+                casei_dict[key.lower()].add(key)
+
+        _adder(definition.name, definition)
+
+        if definition.has_symbol:
+            _adder(definition.symbol, definition)
+
+        for alias in definition.aliases:
+            if ' ' in alias:
+                logger.warn('Alias cannot contain a space: ' + alias)
+
+            _adder(alias, definition)
+
+        # define additional "delta_" units for units with an offset
+        if getattr(definition.converter, "offset", 0.0) != 0.0:
+            d_name = 'delta_' + definition.name
+            if definition.symbol:
+                d_symbol = 'Δ' + definition.symbol
+            else:
+                d_symbol = None
+            d_aliases = tuple('Δ' + alias for alias in definition.aliases)
+
+            def prep(_name):
+                if _name.startswith('['):
+                    return '[delta_' + _name[1:]
+                return 'delta_' + _name
+
+            d_reference = UnitsContainer(dict((ref, value)
+                                         for ref, value in definition.reference.items()))
+
+            self.define(UnitDefinition(d_name, d_symbol, d_aliases,
+                                       ScaleConverter(definition.converter.scale),
+                                       d_reference, definition.is_base))
+
+    def load_definitions(self, file, is_resource=False):
+        """Add units and prefixes defined in a definition text file.
+        """
+        # Permit both filenames and line-iterables
+        if isinstance(file, string_types):
+            try:
+                if is_resource:
+                    with closing(pkg_resources.resource_stream(__name__, file)) as fp:
+                        rbytes = fp.read()
+                    return self.load_definitions(StringIO(rbytes.decode('utf-8')), is_resource)
+                else:
+                    with open(file, encoding='utf-8') as fp:
+                        return self.load_definitions(fp, is_resource)
+            except (RedefinitionError, DefinitionSyntaxError) as e:
+                if e.filename is None:
+                    e.filename = file
+                raise e
+            except Exception as e:
+                msg = getattr(e, 'message', '') or str(e)
+                raise ValueError('While opening {0}\n{1}'.format(file, msg))
+
+        ifile = enumerate(file, 1)
+        for no, line in ifile:
+            line = line.strip()
+            if not line or line.startswith('#'):
+                continue
+            if line.startswith('@import'):
+                if is_resource:
+                    path = line[7:].strip()
+                else:
+                    try:
+                        path = os.path.dirname(file.name)
+                    except AttributeError:
+                        path = os.getcwd()
+                    path = os.path.join(path, os.path.normpath(line[7:].strip()))
+                self.load_definitions(path, is_resource)
+            elif line.startswith('@context'):
+                context = [line, ] + _capture_till_end(ifile)
+                try:
+                    self.add_context(Context.from_lines(context, self.get_dimensionality))
+                except KeyError as e:
+                    raise DefinitionSyntaxError('unknown dimension {0} in context'.format(str(e)), lineno=no)
+            elif line.startswith('@system'):
+                context = [line, ] + _capture_till_end(ifile)
+                try:
+                    self._gsmanager.add_system_from_lines(context, self.get_root_units)
+                except KeyError as e:
+                    raise DefinitionSyntaxError('unknown dimension {0} in context'.format(str(e)), lineno=no)
+            elif line.startswith('@group'):
+                context = [line, ] + _capture_till_end(ifile)
+                try:
+                    self._gsmanager.add_group_from_lines(context, self.define)
+                except KeyError as e:
+                    raise DefinitionSyntaxError('unknown dimension {0} in context'.format(str(e)), lineno=no)
+            else:
+                try:
+                    self.define(Definition.from_string(line))
+                except (RedefinitionError, DefinitionSyntaxError) as ex:
+                    if ex.lineno is None:
+                        ex.lineno = no
+                    raise ex
+                except Exception as ex:
+                    logger.error("In line {0}, cannot add '{1}' {2}".format(no, line, ex))
+
+    def _build_cache(self):
+        """Build a cache of dimensionality and base units.
+        """
+
+        deps = dict((name, set(definition.reference.keys() if definition.reference else {}))
+                    for name, definition in self._units.items())
+
+        for unit_names in solve_dependencies(deps):
+            for unit_name in unit_names:
+                prefixed = False
+                for p in self._prefixes.keys():
+                    if p and unit_name.startswith(p):
+                        prefixed = True
+                        break
+                if '[' in unit_name:
+                    continue
+                try:
+                    uc = ParserHelper.from_word(unit_name)
+
+                    bu = self._get_root_units(uc)
+                    di = self._get_dimensionality(uc)
+
+                    self._root_units_cache[uc] = bu
+                    self._dimensionality_cache[uc] = di
+
+                    if not prefixed:
+                        if di not in self._dimensional_equivalents:
+                            self._dimensional_equivalents[di] = set()
+
+                        self._dimensional_equivalents[di].add(self._units[unit_name]._name)
+
+                except Exception as e:
+                    logger.warning('Could not resolve {0}: {1!r}'.format(unit_name, e))
+
+    def get_name(self, name_or_alias, case_sensitive=True):
+        """Return the canonical name of a unit.
+        """
+
+        if name_or_alias == 'dimensionless':
+            return ''
+
+        try:
+            return self._units[name_or_alias]._name
+        except KeyError:
+            pass
+
+        candidates = self._dedup_candidates(self.parse_unit_name(name_or_alias, case_sensitive))
+        if not candidates:
+            raise UndefinedUnitError(name_or_alias)
+        elif len(candidates) == 1:
+            prefix, unit_name, _ = candidates[0]
+        else:
+            logger.warning('Parsing {0} yield multiple results. '
+                           'Options are: {1}'.format(name_or_alias, candidates))
+            prefix, unit_name, _ = candidates[0]
+
+        if prefix:
+            name = prefix + unit_name
+            symbol = self.get_symbol(name)
+            prefix_def = self._prefixes[prefix]
+            self._units[name] = UnitDefinition(name, symbol, (), prefix_def.converter,
+                                               UnitsContainer({unit_name: 1}))
+            return prefix + unit_name
+
+        return unit_name
+
+    def get_symbol(self, name_or_alias):
+        """Return the preferred alias for a unit
+        """
+        candidates = self._dedup_candidates(self.parse_unit_name(name_or_alias))
+        if not candidates:
+            raise UndefinedUnitError(name_or_alias)
+        elif len(candidates) == 1:
+            prefix, unit_name, _ = candidates[0]
+        else:
+            logger.warning('Parsing {0} yield multiple results. '
+                           'Options are: {1!r}'.format(name_or_alias, candidates))
+            prefix, unit_name, _ = candidates[0]
+
+        return self._prefixes[prefix].symbol + self._units[unit_name].symbol
+
+    def _get_symbol(self, name):
+        return self._units[name].symbol
+
+    def get_dimensionality(self, input_units):
+        """Convert unit or dict of units or dimensions to a dict of base dimensions
+        dimensions
+
+        :param input_units:
+        :return: dimensionality
+        """
+        input_units = to_units_container(input_units)
+
+        return self._get_dimensionality(input_units)
+
+    def _get_dimensionality(self, input_units):
+        """ Convert a UnitsContainer to base dimensions.
+
+        :param input_units:
+        :return: dimensionality
+        """
+        if not input_units:
+            return UnitsContainer()
+
+        if input_units in self._dimensionality_cache:
+            return self._dimensionality_cache[input_units]
+
+        accumulator = defaultdict(float)
+        self._get_dimensionality_recurse(input_units, 1.0, accumulator)
+
+        if '[]' in accumulator:
+            del accumulator['[]']
+
+        dims = UnitsContainer(dict((k, v) for k, v in accumulator.items()
+                                   if v != 0.0))
+
+        self._dimensionality_cache[input_units] = dims
+
+        return dims
+
+    def _get_dimensionality_recurse(self, ref, exp, accumulator):
+        for key in ref:
+            exp2 = exp*ref[key]
+            if _is_dim(key):
+                reg = self._dimensions[key]
+                if reg.is_base:
+                    accumulator[key] += exp2
+                elif reg.reference is not None:
+                    self._get_dimensionality_recurse(reg.reference, exp2, accumulator)
+            else:
+                reg = self._units[self.get_name(key)]
+                if reg.reference is not None:
+                    self._get_dimensionality_recurse(reg.reference, exp2, accumulator)
+
+    def get_root_units(self, input_units, check_nonmult=True):
+        """Convert unit or dict of units to the root units.
+
+        If any unit is non multiplicative and check_converter is True,
+        then None is returned as the multiplicative factor.
+
+        :param input_units: units
+        :type input_units: UnitsContainer or str
+        :param check_nonmult: if True, None will be returned as the
+                              multiplicative factor if a non-multiplicative
+                              units is found in the final Units.
+        :return: multiplicative factor, base units
+        """
+        input_units = to_units_container(input_units)
+
+        f, units = self._get_root_units(input_units, check_nonmult)
+
+        return f, self.Unit(units)
+
+    def _get_root_units(self, input_units, check_nonmult=True):
+        """Convert unit or dict of units to the root units.
+
+        If any unit is non multiplicative and check_converter is True,
+        then None is returned as the multiplicative factor.
+
+        :param input_units: units
+        :type input_units: UnitsContainer or dict
+        :param check_nonmult: if True, None will be returned as the
+                              multiplicative factor if a non-multiplicative
+                              units is found in the final Units.
+        :return: multiplicative factor, base units
+        """
+        if not input_units:
+            return 1., UnitsContainer()
+
+        # The cache is only done for check_nonmult=True
+        if check_nonmult and input_units in self._root_units_cache:
+            return self._root_units_cache[input_units]
+
+        accumulators = [1., defaultdict(float)]
+        self._get_root_units_recurse(input_units, 1.0, accumulators)
+
+        factor = accumulators[0]
+        units = UnitsContainer(dict((k, v) for k, v in accumulators[1].items()
+                                    if v != 0.))
+
+        # Check if any of the final units is non multiplicative and return None instead.
+        if check_nonmult:
+            for unit in units.keys():
+                if not self._units[unit].converter.is_multiplicative:
+                    return None, units
+
+        if check_nonmult:
+            self._root_units_cache[input_units] = factor, units
+
+        return factor, units
+
+    def get_base_units(self, input_units, check_nonmult=True, system=None):
+        """Convert unit or dict of units to the base units.
+
+        If any unit is non multiplicative and check_converter is True,
+        then None is returned as the multiplicative factor.
+
+        :param input_units: units
+        :type input_units: UnitsContainer or str
+        :param check_nonmult: if True, None will be returned as the
+                              multiplicative factor if a non-multiplicative
+                              units is found in the final Units.
+        :return: multiplicative factor, base units
+        """
+        input_units = to_units_container(input_units)
+
+        f, units = self._get_base_units(input_units, check_nonmult, system)
+
+        return f, self.Unit(units)
+
+    def _get_base_units(self, input_units, check_nonmult=True, system=None):
+        """
+        :param registry:
+        :param input_units:
+        :param check_nonmult:
+        :param system: System
+        :return:
+        """
+
+        # The cache is only done for check_nonmult=True
+        if check_nonmult and input_units in self._base_units_cache:
+            return self._base_units_cache[input_units]
+
+        factor, units = self.get_root_units(input_units, check_nonmult)
+
+        if system is None:
+            system = self._default_system_name
+
+        if not system:
+            return factor, units
+
+        # This will not be necessary after integration with the registry as it has a UnitsContainer intermediate
+        units = to_units_container(units, self)
+
+        destination_units = UnitsContainer()
+
+        bu = self._gsmanager.get_system(system, False).base_units
+
+        for unit, value in units.items():
+            if unit in bu:
+                new_unit = bu[unit]
+                new_unit = to_units_container(new_unit, self)
+                destination_units *= new_unit ** value
+            else:
+                destination_units *= UnitsContainer({unit: value})
+
+        base_factor = self.convert(factor, units, destination_units)
+
+        if check_nonmult:
+            self._base_units_cache[input_units] = base_factor, destination_units
+
+        return base_factor, destination_units
+
+    def _get_root_units_recurse(self, ref, exp, accumulators):
+        for key in ref:
+            exp2 = exp*ref[key]
+            key = self.get_name(key)
+            reg = self._units[key]
+            if reg.is_base:
+                accumulators[1][key] += exp2
+            else:
+                accumulators[0] *= reg._converter.scale ** exp2
+                if reg.reference is not None:
+                    self._get_root_units_recurse(reg.reference, exp2,
+                                                 accumulators)
+
+    def get_compatible_units(self, input_units, group_or_system=None):
+        """
+        """
+        input_units = to_units_container(input_units)
+
+        equiv = self._get_compatible_units(input_units, group_or_system)
+
+        return frozenset(self.Unit(eq) for eq in equiv)
+
+    def _get_compatible_units(self, input_units, group_or_system=None):
+        """
+        """
+        if not input_units:
+            return frozenset()
+
+        src_dim = self._get_dimensionality(input_units)
+
+        ret = self._dimensional_equivalents[src_dim]
+
+        if self._active_ctx:
+            nodes = find_connected_nodes(self._active_ctx.graph, src_dim)
+            ret = set()
+            if nodes:
+                for node in nodes:
+                    ret |= self._dimensional_equivalents[node]
+
+        if group_or_system:
+            members = self._gsmanager[group_or_system].members
+            return frozenset(ret.intersection(members))
+
+        return ret
+
+    def convert(self, value, src, dst, inplace=False):
+        """Convert value from some source to destination units.
+
+        :param value: value
+        :param src: source units.
+        :type src: Quantity or str
+        :param dst: destination units.
+        :type dst: Quantity or str
+
+        :return: converted value
+        """
+        src = to_units_container(src, self)
+
+        dst = to_units_container(dst, self)
+
+        return self._convert(value, src, dst, inplace)
+
+    def _convert(self, value, src, dst, inplace=False):
+        """Convert value from some source to destination units.
+
+        :param value: value
+        :param src: source units.
+        :type src: UnitsContainer
+        :param dst: destination units.
+        :type dst: UnitsContainer
+
+        :return: converted value
+        """
+        if src == dst:
+            return value
+
+        src_dim = self._get_dimensionality(src)
+        dst_dim = self._get_dimensionality(dst)
+
+        # If there is an active context, we look for a path connecting source and
+        # destination dimensionality. If it exists, we transform the source value
+        # by applying sequentially each transformation of the path.
+        if self._active_ctx:
+            path = find_shortest_path(self._active_ctx.graph, src_dim, dst_dim)
+            if path:
+                src = self.Quantity(value, src)
+                for a, b in zip(path[:-1], path[1:]):
+                    src = self._active_ctx.transform(a, b, self, src)
+
+                value, src = src._magnitude, src._units
+
+                src_dim = self._get_dimensionality(src)
+
+        # If the source and destination dimensionality are different,
+        # then the conversion cannot be performed.
+
+        if src_dim != dst_dim:
+            raise DimensionalityError(src, dst, src_dim, dst_dim)
+
+        # Conversion needs to consider if non-multiplicative (AKA offset
+        # units) are involved. Conversion is only possible if src and dst
+        # have at most one offset unit per dimension.
+        src_offset_units = [(u, e) for u, e in src.items()
+                            if not self._units[u].is_multiplicative]
+        dst_offset_units = [(u, e) for u, e in dst.items()
+                            if not self._units[u].is_multiplicative]
+
+        # For offset units we need to check if the conversion is allowed.
+        if src_offset_units or dst_offset_units:
+
+            # Validate that not more than one offset unit is present
+            if len(src_offset_units) > 1 or len(dst_offset_units) > 1:
+                raise DimensionalityError(
+                    src, dst, src_dim, dst_dim,
+                    extra_msg=' - more than one offset unit.')
+
+            # validate that offset unit is not used in multiplicative context
+            if ((len(src_offset_units) == 1 and len(src) > 1)
+                    or (len(dst_offset_units) == 1 and len(dst) > 1)
+                    and not self.autoconvert_offset_to_baseunit):
+                raise DimensionalityError(
+                    src, dst, src_dim, dst_dim,
+                    extra_msg=' - offset unit used in multiplicative context.')
+
+            # Validate that order of offset unit is exactly one.
+            if src_offset_units:
+                if src_offset_units[0][1] != 1:
+                    raise DimensionalityError(
+                        src, dst, src_dim, dst_dim,
+                        extra_msg=' - offset units in higher order.')
+            else:
+                if dst_offset_units[0][1] != 1:
+                    raise DimensionalityError(
+                        src, dst, src_dim, dst_dim,
+                        extra_msg=' - offset units in higher order.')
+
+        # Here we convert only the offset quantities. Any remaining scaled
+        # quantities will be converted later.
+
+        # TODO: Shouldn't this (until factor, units) be inside the If above?
+
+        # clean src from offset units by converting to reference
+        for u, e in src_offset_units:
+            value = self._units[u].converter.to_reference(value, inplace)
+        src = src.remove([u for u, e in src_offset_units])
+
+        # clean dst units from offset units
+        dst = dst.remove([u for u, e in dst_offset_units])
+
+        # Here src and dst have only multiplicative units left. Thus we can
+        # convert with a factor.
+        factor, units = self._get_root_units(src / dst)
+
+        # factor is type float and if our magnitude is type Decimal then
+        # must first convert to Decimal before we can '*' the values
+        if isinstance(value, Decimal):
+            factor = Decimal(str(factor))
+
+        if inplace:
+            value *= factor
+        else:
+            value = value * factor
+
+        # Finally convert to offset units specified in destination
+        for u, e in dst_offset_units:
+            value = self._units[u].converter.from_reference(value, inplace)
+
+        return value
+
+    def pi_theorem(self, quantities):
+        """Builds dimensionless quantities using the Buckingham π theorem
+
+        :param quantities: mapping between variable name and units
+        :type quantities: dict
+        :return: a list of dimensionless quantities expressed as dicts
+        """
+        return pi_theorem(quantities, self)
+
+    def _dedup_candidates(self, candidates):
+        """Given a list of units, remove those with different names but equal value.
+        """
+        candidates = tuple(candidates)
+        if len(candidates) < 2:
+            return candidates
+
+        unique = [candidates[0]]
+        for c in candidates[2:]:
+            for u in unique:
+                if c == u:
+                    break
+            else:
+                unique.append(c)
+
+        return tuple(unique)
+
+    def parse_unit_name(self, unit_name, case_sensitive=True):
+        """Parse a unit to identify prefix, unit name and suffix
+        by walking the list of prefix and suffix.
+        """
+        stw = unit_name.startswith
+        edw = unit_name.endswith
+        for suffix, prefix in itertools.product(self._suffixes, self._prefixes):
+            if stw(prefix) and edw(suffix):
+                name = unit_name[len(prefix):]
+                if suffix:
+                    name = name[:-len(suffix)]
+                    if len(name) == 1:
+                        continue
+                if case_sensitive:
+                    if name in self._units:
+                        yield (self._prefixes[prefix]._name,
+                               self._units[name]._name,
+                               self._suffixes[suffix])
+                else:
+                    for real_name in self._units_casei.get(name.lower(), ()):
+                        yield (self._prefixes[prefix]._name,
+                               self._units[real_name]._name,
+                               self._suffixes[suffix])
+
+    def parse_units(self, input_string, as_delta=None):
+        """Parse a units expression and returns a UnitContainer with
+        the canonical names.
+
+        The expression can only contain products, ratios and powers of units.
+
+        :param as_delta: if the expression has multiple units, the parser will
+                         interpret non multiplicative units as their `delta_` counterparts.
+
+        :raises:
+            :class:`pint.UndefinedUnitError` if a unit is not in the registry
+            :class:`ValueError` if the expression is invalid.
+        """
+        units = self._parse_units(input_string, as_delta)
+        return self.Unit(units)
+
+    def _parse_units(self, input_string, as_delta=None):
+        """
+        """
+        if input_string in self._parse_unit_cache:
+            return self._parse_unit_cache[input_string]
+
+        if as_delta is None:
+            as_delta = self.default_as_delta
+
+        if not input_string:
+            return UnitsContainer()
+
+        units = ParserHelper.from_string(input_string)
+        if units.scale != 1:
+            raise ValueError('Unit expression cannot have a scaling factor.')
+
+        ret = {}
+        many = len(units) > 1
+        for name in units:
+            cname = self.get_name(name)
+            value = units[name]
+            if not cname:
+                continue
+            if as_delta and (many or (not many and value != 1)):
+                definition = self._units[cname]
+                if not definition.is_multiplicative:
+                    cname = 'delta_' + cname
+            ret[cname] = value
+
+        ret = UnitsContainer(ret)
+
+        self._parse_unit_cache[input_string] = ret
+
+        return ret
+    
+    def _eval_token(self, token, case_sensitive=True, **values):
+        token_type = token[0]
+        token_text = token[1]
+        if token_type == NAME:
+            if token_text == 'pi':
+                return self.Quantity(math.pi)
+            elif token_text in values:
+                return self.Quantity(values[token_text])
+            else:
+                return self.Quantity(1, UnitsContainer({self.get_name(token_text, 
+                                                                      case_sensitive=case_sensitive) : 1}))
+        elif token_type == NUMBER:
+            return ParserHelper.eval_token(token)
+        else:
+            raise Exception('unknown token type')
+
+    def parse_expression(self, input_string, case_sensitive=True, **values):
+        """Parse a mathematical expression including units and return a quantity object.
+
+        Numerical constants can be specified as keyword arguments and will take precedence
+        over the names defined in the registry.
+        """
+        
+        if not input_string:
+            return self.Quantity(1)
+
+        input_string = string_preprocessor(input_string)
+        gen = tokenizer(input_string)
+        
+        return build_eval_tree(gen).evaluate(lambda x : self._eval_token(x, case_sensitive=case_sensitive,
+                                                                          **values))
+
+    __call__ = parse_expression
+
+    def wraps(self, ret, args, strict=True):
+        """Wraps a function to become pint-aware.
+
+        Use it when a function requires a numerical value but in some specific
+        units. The wrapper function will take a pint quantity, convert to the units
+        specified in `args` and then call the wrapped function with the resulting
+        magnitude.
+
+        The value returned by the wrapped function will be converted to the units
+        specified in `ret`.
+
+        Use None to skip argument conversion.
+        Set strict to False, to accept also numerical values.
+
+        :param ret: output units.
+        :param args: iterable of input units.
+        :param strict: boolean to indicate that only quantities are accepted.
+        :return: the wrapped function.
+        :raises:
+            :class:`ValueError` if strict and one of the arguments is not a Quantity.
+        """
+
+        Q_ = self.Quantity
+
+        if not isinstance(args, (list, tuple)):
+            args = (args, )
+
+        units = [to_units_container(arg, self) for arg in args]
+
+        if isinstance(ret, (list, tuple)):
+            ret = ret.__class__([to_units_container(arg, self) for arg in ret])
+        elif isinstance(ret, string_types):
+            ret = self.parse_units(ret)
+
+        def decorator(func):
+            assigned = tuple(attr for attr in functools.WRAPPER_ASSIGNMENTS if hasattr(func, attr))
+            updated = tuple(attr for attr in functools.WRAPPER_UPDATES if hasattr(func, attr))
+            @functools.wraps(func, assigned=assigned, updated=updated)
+            def wrapper(*values, **kw):
+                new_args = []
+                for unit, value in zip(units, values):
+                    if unit is None:
+                        new_args.append(value)
+                    elif isinstance(value, Q_):
+                        new_args.append(self._convert(value._magnitude,
+                                                      value._units, unit))
+                    elif not strict:
+                        new_args.append(value)
+                    else:
+                        raise ValueError('A wrapped function using strict=True requires '
+                                         'quantity for all arguments with not None units. '
+                                         '(error found for {0}, {1})'.format(unit, value))
+
+                result = func(*new_args, **kw)
+
+                if isinstance(ret, (list, tuple)):
+                    return ret.__class__(res if unit is None else Q_(res, unit)
+                                         for unit, res in zip(ret, result))
+                elif ret is not None:
+                    return Q_(result, ret)
+
+                return result
+            return wrapper
+        return decorator
+
+    def check(self, *args):
+        """Decorator to for quantity type checking for function inputs.
+
+        Use it to ensure that the decorated function input parameters match
+        the expected type of pint quantity.
+
+        Use None to skip argument checking.
+
+        :param args: iterable of input units.
+        :return: the wrapped function.
+        :raises:
+            :class:`DimensionalityError` if the parameters don't match dimensions
+        """
+        dimensions = [self.get_dimensionality(dim) for dim in args]
+
+        def decorator(func):
+            assigned = tuple(attr for attr in functools.WRAPPER_ASSIGNMENTS if hasattr(func, attr))
+            updated = tuple(attr for attr in functools.WRAPPER_UPDATES if hasattr(func, attr))
+
+            @functools.wraps(func, assigned=assigned, updated=updated)
+            def wrapper(*values, **kwargs):
+                for dim, value in zip_longest(dimensions, values):
+                    if dim and value.dimensionality != dim:
+                        raise DimensionalityError(value, 'a quantity of',
+                                                  value.dimensionality, dim)
+                return func(*values, **kwargs)
+            return wrapper
+        return decorator
+
+
+def build_unit_class(registry):
+
+    class Unit(_Unit):
+        pass
+
+    Unit._REGISTRY = registry
+    return Unit
+
+
+def build_quantity_class(registry, force_ndarray=False):
+    from .quantity import _Quantity
+
+    class Quantity(_Quantity):
+        pass
+
+    Quantity._REGISTRY = registry
+    Quantity.force_ndarray = force_ndarray
+
+    return Quantity
+
+
+def build_measurement_class(registry, force_ndarray=False):
+    from .measurement import _Measurement, ufloat
+
+    if ufloat is None:
+        class Measurement(object):
+
+            def __init__(self, *args):
+                raise RuntimeError("Pint requires the 'uncertainties' package to create a Measurement object.")
+
+    else:
+        class Measurement(_Measurement, registry.Quantity):
+            pass
+
+    Measurement._REGISTRY = registry
+    Measurement.force_ndarray = force_ndarray
+
+    return Measurement
+
+
+class LazyRegistry(object):
+
+    def __init__(self, args=None, kwargs=None):
+        self.__dict__['params'] = args or (), kwargs or {}
+
+    def __init(self):
+        args, kwargs = self.__dict__['params']
+        kwargs['on_redefinition'] = 'raise'
+        self.__class__ = UnitRegistry
+        self.__init__(*args, **kwargs)
+
+    def __getattr__(self, item):
+        if item == '_on_redefinition':
+            return 'raise'
+        self.__init()
+        return getattr(self, item)
+
+    def __setattr__(self, key, value):
+        if key == '__class__':
+            super(LazyRegistry, self).__setattr__(key, value)
+        else:
+            self.__init()
+            setattr(self, key, value)
+
+    def __getitem__(self, item):
+        self.__init()
+        return self[item]
+
+    def __call__(self, *args, **kwargs):
+        self.__init()
+        return self(*args, **kwargs)
diff --git a/lib/taurus/external/pint/pint_local/util.py b/lib/taurus/external/pint/pint_local/util.py
new file mode 100644
index 0000000..7ff28e3
--- /dev/null
+++ b/lib/taurus/external/pint/pint_local/util.py
@@ -0,0 +1,643 @@
+# -*- coding: utf-8 -*-
+"""
+    pint.util
+    ~~~~~~~~~
+
+    Miscellaneous functions for pint.
+
+    :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import division, unicode_literals, print_function, absolute_import
+
+import re
+import operator
+from numbers import Number
+from fractions import Fraction
+from collections import Mapping
+
+import logging
+from token import STRING, NAME, OP, NUMBER
+from tokenize import untokenize
+
+from .compat import string_types, tokenizer, lru_cache, NullHandler, maketrans, NUMERIC_TYPES
+from .formatting import format_unit
+from .pint_eval import build_eval_tree
+
+logger = logging.getLogger(__name__)
+logger.addHandler(NullHandler())
+
+
+def matrix_to_string(matrix, row_headers=None, col_headers=None, fmtfun=lambda x: str(int(x))):
+    """Takes a 2D matrix (as nested list) and returns a string.
+    """
+    ret = []
+    if col_headers:
+        ret.append(('\t' if row_headers else '') + '\t'.join(col_headers))
+    if row_headers:
+        ret += [rh + '\t' + '\t'.join(fmtfun(f) for f in row)
+                for rh, row in zip(row_headers, matrix)]
+    else:
+        ret += ['\t'.join(fmtfun(f) for f in row)
+                for row in matrix]
+
+    return '\n'.join(ret)
+
+
+def transpose(matrix):
+    """Takes a 2D matrix (as nested list) and returns the transposed version.
+    """
+    return [list(val) for val in zip(*matrix)]
+
+
+def column_echelon_form(matrix, ntype=Fraction, transpose_result=False):
+    """Calculates the column echelon form using Gaussian elimination.
+
+    :param matrix: a 2D matrix as nested list.
+    :param ntype: the numerical type to use in the calculation.
+    :param transpose_result: indicates if the returned matrix should be transposed.
+    :return: column echelon form, transformed identity matrix, swapped rows
+    """
+    lead = 0
+
+    M = transpose(matrix)
+
+    _transpose = transpose if transpose_result else lambda x: x
+
+    rows, cols = len(M), len(M[0])
+
+    new_M = []
+    for row in M:
+        r = []
+        for x in row:
+            if isinstance(x, float):
+                x = ntype.from_float(x)
+            else:
+                x = ntype(x)
+            r.append(x)
+        new_M.append(r)
+    M = new_M
+
+#    M = [[ntype(x) for x in row] for row in M]
+    I = [[ntype(1) if n == nc else ntype(0) for nc in range(rows)] for n in range(rows)]
+    swapped = []
+
+    for r in range(rows):
+        if lead >= cols:
+            return _transpose(M), _transpose(I), swapped
+        i = r
+        while M[i][lead] == 0:
+            i += 1
+            if i != rows:
+                continue
+            i = r
+            lead += 1
+            if cols == lead:
+                return _transpose(M), _transpose(I), swapped
+
+        M[i], M[r] = M[r], M[i]
+        I[i], I[r] = I[r], I[i]
+
+        swapped.append(i)
+        lv = M[r][lead]
+        M[r] = [mrx / lv for mrx in M[r]]
+        I[r] = [mrx / lv for mrx in I[r]]
+
+        for i in range(rows):
+            if i == r:
+                continue
+            lv = M[i][lead]
+            M[i] = [iv - lv*rv for rv, iv in zip(M[r], M[i])]
+            I[i] = [iv - lv*rv for rv, iv in zip(I[r], I[i])]
+
+        lead += 1
+
+    return _transpose(M), _transpose(I), swapped
+
+
+def pi_theorem(quantities, registry=None):
+    """Builds dimensionless quantities using the Buckingham π theorem
+
+    :param quantities: mapping between variable name and units
+    :type quantities: dict
+    :return: a list of dimensionless quantities expressed as dicts
+    """
+
+    # Preprocess input and build the dimensionality Matrix
+    quant = []
+    dimensions = set()
+
+    if registry is None:
+        getdim = lambda x: x
+    else:
+        getdim = registry.get_dimensionality
+
+    for name, value in quantities.items():
+        if isinstance(value, string_types):
+            value = ParserHelper.from_string(value)
+        if isinstance(value, dict):
+            dims = getdim(UnitsContainer(value))
+        elif not hasattr(value, 'dimensionality'):
+            dims = getdim(value)
+        else:
+            dims = value.dimensionality
+
+        if not registry and any(not key.startswith('[') for key in dims):
+            logger.warning('A non dimension was found and a registry was not provided. '
+                           'Assuming that it is a dimension name: {0}.'.format(dims))
+
+        quant.append((name, dims))
+        dimensions = dimensions.union(dims.keys())
+
+    dimensions = list(dimensions)
+
+    # Calculate dimensionless  quantities
+    M = [[dimensionality[dimension] for name, dimensionality in quant]
+         for dimension in dimensions]
+
+    M, identity, pivot = column_echelon_form(M, transpose_result=False)
+
+    # Collect results
+    # Make all numbers integers and minimize the number of negative exponents.
+    # Remove zeros
+    results = []
+    for rowm, rowi in zip(M, identity):
+        if any(el != 0 for el in rowm):
+            continue
+        max_den = max(f.denominator for f in rowi)
+        neg = -1 if sum(f < 0 for f in rowi) > sum(f > 0 for f in rowi) else 1
+        results.append(dict((q[0], neg * f.numerator * max_den / f.denominator)
+                            for q, f in zip(quant, rowi) if f.numerator != 0))
+    return results
+
+
+def solve_dependencies(dependencies):
+    """Solve a dependency graph.
+
+    :param dependencies: dependency dictionary. For each key, the value is
+                         an iterable indicating its dependencies.
+    :return: list of sets, each containing keys of independents tasks dependent
+                           only of the previous tasks in the list.
+    """
+    d = dict((key, set(dependencies[key])) for key in dependencies)
+    r = []
+    while d:
+        # values not in keys (items without dep)
+        t = set(i for v in d.values() for i in v) - set(d.keys())
+        # and keys without value (items without dep)
+        t.update(k for k, v in d.items() if not v)
+        # can be done right away
+        r.append(t)
+        # and cleaned up
+        d = dict(((k, v - t) for k, v in d.items() if v))
+    return r
+
+
+def find_shortest_path(graph, start, end, path=None):
+    path = (path or []) + [start]
+    if start == end:
+        return path
+    if not start in graph:
+        return None
+    shortest = None
+    for node in graph[start]:
+        if node not in path:
+            newpath = find_shortest_path(graph, node, end, path)
+            if newpath:
+                if not shortest or len(newpath) < len(shortest):
+                    shortest = newpath
+    return shortest
+
+
+def find_connected_nodes(graph, start, visited=None):
+    if not start in graph:
+        return None
+
+    visited = (visited or set())
+    visited.add(start)
+
+    for node in graph[start]:
+        if node not in visited:
+            find_connected_nodes(graph, node, visited)
+
+    return visited
+
+
+class udict(dict):
+    """ Custom dict implementing __missing__.
+
+    """
+    def __missing__(self, key):
+        return 0.
+
+
+class UnitsContainer(Mapping):
+    """The UnitsContainer stores the product of units and their respective
+    exponent and implements the corresponding operations.
+
+    UnitsContainer is a read-only mapping. All operations (even in place ones)
+    return new instances.
+
+    """
+    __slots__ = ('_d', '_hash')
+
+    def __init__(self, *args, **kwargs):
+        d = udict(*args, **kwargs)
+        self._d = d
+        for key, value in d.items():
+            if not isinstance(key, string_types):
+                raise TypeError('key must be a str, not {0}'.format(type(key)))
+            if not isinstance(value, Number):
+                raise TypeError('value must be a number, not {0}'.format(type(value)))
+            if not isinstance(value, float):
+                d[key] = float(value)
+        self._hash = hash(frozenset(self._d.items()))
+
+    def copy(self):
+        return self.__copy__()
+
+    def add(self, key, value):
+        newval = self._d[key] + value
+        new = self.copy()
+        if newval:
+            new._d[key] = newval
+        else:
+            del new._d[key]
+
+        return new
+
+    def remove(self, keys):
+        """ Create a new UnitsContainer purged from given keys.
+
+        """
+        d = udict(self._d)
+        return UnitsContainer(((key, d[key]) for key in d if key not in keys))
+
+    def rename(self, oldkey, newkey):
+        """ Create a new UnitsContainer in which an entry has been renamed.
+
+        """
+        d = udict(self._d)
+        d[newkey] = d.pop(oldkey)
+        return UnitsContainer(d)
+
+    def __iter__(self):
+        return iter(self._d)
+
+    def __len__(self):
+        return len(self._d)
+
+    def __getitem__(self, key):
+        return self._d[key]
+
+    def __hash__(self):
+        return self._hash
+
+    def __getstate__(self):
+        return {'_d': self._d, '_hash': self._hash}
+
+    def __setstate__(self, state):
+        self._d = state['_d']
+        self._hash = state['_hash']
+
+    def __eq__(self, other):
+        if isinstance(other, UnitsContainer):
+            other = other._d
+        elif isinstance(other, string_types):
+            other = ParserHelper.from_string(other)
+            other = other._d
+
+        return dict.__eq__(self._d, other)
+
+    def __str__(self):
+        return self.__format__('')
+
+    def __repr__(self):
+        tmp = '{%s}' % ', '.join(["'{0}': {1}".format(key, value)
+                                  for key, value in sorted(self._d.items())])
+        return '<UnitsContainer({0})>'.format(tmp)
+
+    def __format__(self, spec):
+        return format_unit(self, spec)
+
+    def __copy__(self):
+        return UnitsContainer(self._d)
+
+    def __mul__(self, other):
+        d = udict(self._d)
+        if not isinstance(other, self.__class__):
+            err = 'Cannot multiply UnitsContainer by {0}'
+            raise TypeError(err.format(type(other)))
+        for key, value in other.items():
+            d[key] += value
+        keys = [key for key, value in d.items() if value == 0]
+        for key in keys:
+            del d[key]
+
+        return UnitsContainer(d)
+
+    __rmul__ = __mul__
+
+    def __pow__(self, other):
+        if not isinstance(other, NUMERIC_TYPES):
+            err = 'Cannot power UnitsContainer by {0}'
+            raise TypeError(err.format(type(other)))
+        d = udict(self._d)
+        for key, value in d.items():
+            d[key] *= other
+        return UnitsContainer(d)
+
+    def __truediv__(self, other):
+        if not isinstance(other, self.__class__):
+            err = 'Cannot divide UnitsContainer by {0}'
+            raise TypeError(err.format(type(other)))
+
+        d = udict(self._d)
+
+        for key, value in other.items():
+            d[key] -= value
+
+        keys = [key for key, value in d.items() if value == 0]
+        for key in keys:
+            del d[key]
+
+        return UnitsContainer(d)
+
+    def __rtruediv__(self, other):
+        if not isinstance(other, self.__class__) and other != 1:
+            err = 'Cannot divide {0} by UnitsContainer'
+            raise TypeError(err.format(type(other)))
+
+        return self**-1
+
+
+class ParserHelper(UnitsContainer):
+    """ The ParserHelper stores in place the product of variables and
+    their respective exponent and implements the corresponding operations.
+
+    ParserHelper is a read-only mapping. All operations (even in place ones)
+    return new instances.
+
+    WARNING : The hash value used does not take into account the scale
+    attribute so be careful if you use it as a dict key and then two unequal
+    object can have the same hash.
+
+    """
+
+    __slots__ = ('scale', )
+
+    def __init__(self, scale=1, *args, **kwargs):
+        super(ParserHelper, self).__init__(*args, **kwargs)
+        self.scale = scale
+
+    @classmethod
+    def from_word(cls, input_word):
+        """Creates a ParserHelper object with a single variable with exponent one.
+
+        Equivalent to: ParserHelper({'word': 1})
+
+        """
+        return cls(1, [(input_word, 1)])
+
+    @classmethod
+    def from_string(cls, input_string):
+        return cls._from_string(input_string)
+    
+    @classmethod
+    def eval_token(cls, token, use_decimal=False):
+        token_type = token[0]
+        token_text = token[1]
+        if token_type == NUMBER:
+            if '.' in token_text or 'e' in token_text:
+                if use_decimal:
+                    return Decimal(token_text)
+                return float(token_text)
+            return int(token_text)
+        elif token_type == NAME:
+            return ParserHelper.from_word(token_text)
+        else:
+            raise Exception('unknown token type')
+    
+    @classmethod
+    @lru_cache()
+    def _from_string(cls, input_string):
+        """Parse linear expression mathematical units and return a quantity object.
+
+        """
+        if not input_string:
+            return cls()
+
+        input_string = string_preprocessor(input_string)
+        if '[' in input_string:
+            input_string = input_string.replace('[', '__obra__').replace(']', '__cbra__')
+            reps = True
+        else:
+            reps = False
+
+        gen = tokenizer(input_string)
+        ret = build_eval_tree(gen).evaluate(cls.eval_token)
+
+        if isinstance(ret, Number):
+            return ParserHelper(ret)
+
+        if not reps:
+            return ret
+
+        return ParserHelper(ret.scale,
+                            dict((key.replace('__obra__', '[').replace('__cbra__', ']'), value)
+                                 for key, value in ret.items()))
+
+    def __copy__(self):
+        # workaround for python2.6 compatibility (cast kwarg keys to str)
+        # see http://bugs.python.org/issue2646
+        kwargs = dict([(str(k),v) for k,v in self.items()])
+        return ParserHelper(scale=self.scale, **kwargs)
+
+    def copy(self):
+        return self.__copy__()
+
+    def __hash__(self):
+        if self.scale != 1.0:
+            mess = 'Only scale 1.0 ParserHelper instance should be considered hashable'
+            raise ValueError(mess)
+        return self._hash
+
+    def __eq__(self, other):
+        if isinstance(other, self.__class__):
+            return self.scale == other.scale and\
+                super(ParserHelper, self).__eq__(other)
+        elif isinstance(other, string_types):
+            return self == ParserHelper.from_string(other)
+        elif isinstance(other, Number):
+            return self.scale == other and not len(self._d)
+        else:
+            return self.scale == 1. and super(ParserHelper, self).__eq__(other)
+
+    def operate(self, items, op=operator.iadd, cleanup=True):
+        d = udict(self._d)
+        for key, value in items:
+            d[key] = op(d[key], value)
+
+        if cleanup:
+            keys = [key for key, value in d.items() if value == 0]
+            for key in keys:
+                del d[key]
+
+        return self.__class__(self.scale, d)
+
+    def __str__(self):
+        tmp = '{%s}' % ', '.join(["'{0}': {1}".format(key, value)
+                                  for key, value in sorted(self._d.items())])
+        return '{0} {1}'.format(self.scale, tmp)
+
+    def __repr__(self):
+        tmp = '{%s}' % ', '.join(["'{0}': {1}".format(key, value)
+                                  for key, value in sorted(self._d.items())])
+        return '<ParserHelper({0}, {1})>'.format(self.scale, tmp)
+
+    def __mul__(self, other):
+        if isinstance(other, string_types):
+            new = self.add(other, 1)
+        elif isinstance(other, Number):
+            new = self.copy()
+            new.scale *= other
+        elif isinstance(other, self.__class__):
+            new = self.operate(other.items())
+            new.scale *= other.scale
+        else:
+            new = self.operate(other.items())
+        return new
+
+    __rmul__ = __mul__
+
+    def __pow__(self, other):
+        d = self._d.copy()
+        for key in self._d:
+            d[key] *= other
+        return self.__class__(self.scale**other, d)
+
+    def __truediv__(self, other):
+        if isinstance(other, string_types):
+            new = self.add(other, -1)
+        elif isinstance(other, Number):
+            new = self.copy()
+            new.scale /= other
+        elif isinstance(other, self.__class__):
+            new = self.operate(other.items(), operator.sub)
+            new.scale /= other.scale
+        else:
+            new = self.operate(other.items(), operator.sub)
+        return new
+
+    __floordiv__ = __truediv__
+
+    def __rtruediv__(self, other):
+        new = self.__pow__(-1)
+        if isinstance(other, string_types):
+            new = new.add(other, 1)
+        elif isinstance(other, Number):
+            new.scale *= other
+        elif isinstance(other, self.__class__):
+            new = self.operate(other.items(), operator.add)
+            new.scale *= other.scale
+        else:
+            new = new.operate(other.items(), operator.add)
+        return new
+
+
+#: List of regex substitution pairs.
+_subs_re = [(r"([\w\.\-\+\*\\\^])\s+", r"\1 "), # merge multiple spaces
+            (r"({0}) squared", r"\1**2"),  # Handle square and cube
+            (r"({0}) cubed", r"\1**3"),
+            (r"cubic ({0})", r"\1**3"),
+            (r"square ({0})", r"\1**2"),
+            (r"sq ({0})", r"\1**2"),
+            (r"\b([0-9]+\.?[0-9]*)(?=[e|E][a-zA-Z]|[a-df-zA-DF-Z])", r"\1*"),  # Handle numberLetter for multiplication
+            (r"([\w\.\-])\s+(?=\w)", r"\1*"),  # Handle space for multiplication
+            ]
+
+#: Compiles the regex and replace {0} by a regex that matches an identifier.
+_subs_re = [(re.compile(a.format(r"[_a-zA-Z][_a-zA-Z0-9]*")), b) for a, b in _subs_re]
+_pretty_table = maketrans('⁰¹²³⁴⁵⁶⁷⁸⁹·⁻', '0123456789*-')
+_pretty_exp_re = re.compile(r"⁻?[⁰¹²³⁴⁵⁶⁷⁸⁹]+(?:\.[⁰¹²³⁴⁵⁶⁷⁸⁹]*)?")
+
+
+def string_preprocessor(input_string):
+
+    input_string = input_string.replace(",", "")
+    input_string = input_string.replace(" per ", "/")
+
+    for a, b in _subs_re:
+        input_string = a.sub(b, input_string)
+
+    # Replace pretty format characters
+    for pretty_exp in _pretty_exp_re.findall(input_string):
+        exp = '**' + pretty_exp.translate(_pretty_table)
+        input_string = input_string.replace(pretty_exp, exp)
+    input_string = input_string.translate(_pretty_table)
+
+    # Handle caret exponentiation
+    input_string = input_string.replace("^", "**")
+    return input_string
+
+
+def _is_dim(name):
+    return name[0] == '[' and name[-1] == ']'
+
+
+class SharedRegistryObject(object):
+    """Base class for object keeping a refrence to the registree.
+
+    Such object are for now _Quantity and _Unit, in a number of places it is
+    that an object from this class has a '_units' attribute.
+
+    """
+
+    def _check(self, other):
+        """Check if the other object use a registry and if so that it is the
+        same registry.
+
+        Return True is both use a registry and they use the same, False is
+        other don't use a registry and raise ValueError if other don't use the
+        same unit registry.
+
+        """
+        if self._REGISTRY is getattr(other, '_REGISTRY', None):
+            return True
+
+        elif isinstance(other, SharedRegistryObject):
+            mess = 'Cannot operate with {0} and {1} of different registries.'
+            raise ValueError(mess.format(self.__class__.__name__,
+                                         other.__class__.__name__))
+        else:
+            return False
+
+def to_units_container(unit_like, registry=None):
+    """ Convert a unit compatible type to a UnitsContainer.
+
+    """
+    mro = type(unit_like).mro()
+    if UnitsContainer in mro:
+        return unit_like
+    elif SharedRegistryObject in mro:
+        return unit_like._units
+    elif string_types in mro:
+        if registry:
+            return registry._parse_units(unit_like)
+        else:
+            return ParserHelper.from_string(unit_like)
+    elif dict in mro:
+        return UnitsContainer(unit_like)
+
+
+def infer_base_unit(q):
+    """Return UnitsContainer of q with all prefixes stripped."""
+    d = {}
+    parse = q._REGISTRY.parse_unit_name
+    for unit_name, power in q._units.items():
+        completely_parsed_unit = list(parse(unit_name))[-1]
+
+        _, base_unit, __ = completely_parsed_unit
+        d[base_unit] = power
+    return UnitsContainer(d)
diff --git a/lib/taurus/qt/qtgui/compact/basicswitcher.py b/lib/taurus/qt/qtgui/compact/basicswitcher.py
index 9a908dc..7cd4d2e 100644
--- a/lib/taurus/qt/qtgui/compact/basicswitcher.py
+++ b/lib/taurus/qt/qtgui/compact/basicswitcher.py
@@ -26,7 +26,7 @@
 """This module provides some basic usable widgets based on TaurusReadWriteSwitcher
 """
 
-__all__ = ["TaurusLabelEditRW", "TaurusLabelEditRW"]
+__all__ = ["TaurusLabelEditRW", "TaurusBoolRW"]
 
 __docformat__ = 'restructuredtext'
 
diff --git a/lib/taurus/qt/qtgui/display/taurusled.py b/lib/taurus/qt/qtgui/display/taurusled.py
index 21eaedd..f491839 100644
--- a/lib/taurus/qt/qtgui/display/taurusled.py
+++ b/lib/taurus/qt/qtgui/display/taurusled.py
@@ -35,7 +35,7 @@ import operator
 
 from taurus.external.qt import Qt
 
-from taurus.core import DataFormat, AttrQuality
+from taurus.core import DataFormat, AttrQuality, DataType
 from taurus.core.tango import DevState
 
 from taurus.qt.qtgui.base import TaurusBaseWidget
@@ -225,7 +225,7 @@ class TaurusLed(QLed, TaurusBaseWidget):
             klass = _TaurusLedController
         elif model.isBoolean():
             klass = _TaurusLedControllerBool
-        elif model.isState():
+        elif model.type == DataType.DevState:
             klass = _TaurusLedControllerState
         return klass
 
diff --git a/lib/taurus/qt/qtgui/display/test/test_tauruslabel.py b/lib/taurus/qt/qtgui/display/test/test_tauruslabel.py
index 12ce0be..d2a78e0 100644
--- a/lib/taurus/qt/qtgui/display/test/test_tauruslabel.py
+++ b/lib/taurus/qt/qtgui/display/test/test_tauruslabel.py
@@ -143,7 +143,7 @@ class TaurusLabelTest2(TangoSchemeTestLauncher, BaseWidgetTestCase,
         self._widget.setModel(model)
         if fgRole is not None:
             self._widget.setFgRole(fgRole)
-        self._app.processEvents()
+        self.processEvents(repetitions=10, sleep=.1)
         got = str(self._widget.text())
         msg = ('wrong text for "%s":\n expected: %s\n got: %s' %
                (model, expected, got))
@@ -156,11 +156,11 @@ class TaurusLabelTest2(TangoSchemeTestLauncher, BaseWidgetTestCase,
     #  expected: (0, 255, 0)
     #  got: (239, 240, 241)  # <-- these values change (unitialized garbage?)
     # ~~~~~~~~~~~~~~~~~~~~~~~
-    @unittest.skip('bgRole tests fail randomly. Skip until fixed.')
+    #@unittest.skip('bgRole tests fail randomly. Skip until fixed.')
     def bgRole(self, model=None, expected=None, bgRole=None, maxdepr=0):
         '''Check that the label text'''
         self._widget.setModel(model)
-        self._app.processEvents()
+        self.processEvents(repetitions=10, sleep=.1)
         if bgRole is not None:
             self._widget.setBgRole(bgRole)
         p = self._widget.palette()
diff --git a/lib/taurus/qt/qtgui/editor/tauruseditor.py b/lib/taurus/qt/qtgui/editor/tauruseditor.py
index 09b5ee6..bc47297 100644
--- a/lib/taurus/qt/qtgui/editor/tauruseditor.py
+++ b/lib/taurus/qt/qtgui/editor/tauruseditor.py
@@ -29,29 +29,16 @@ __all__ = ["TaurusBaseEditor"]
 
 __docformat__ = 'restructuredtext'
 
-import sys
-import os
-
 from taurus.external.qt import Qt
 
-try:
-    import spyderlib
-    v = spyderlib.__version__.split('.', 2)[:2]
-    v = map(int, v)
-    if v < [2, 1]:
-        raise Exception("TaurusEditor needs spyderlib >= 2.1")
-except ImportError:
-    raise Exception("TaurusEditor needs spyderlib >= 2.1")
-
-from spyderlib.utils.qthelpers import create_toolbutton
-from spyderlib.widgets.findreplace import FindReplace
-from spyderlib.widgets.editortools import OutlineExplorerWidget
-from spyderlib.widgets.editor import EditorMainWindow, EditorSplitter
-from taurus.qt.qtgui.util import ActionFactory
-
+from spyder.utils.qthelpers import create_toolbutton
+from spyder.widgets.findreplace import FindReplace
+from spyder.widgets.editortools import OutlineExplorerWidget
+from spyder.widgets.editor import EditorMainWindow, EditorSplitter
+from spyder.py3compat import to_text_string
+from spyder.utils.introspection.manager import IntrospectionManager
 
 class TaurusBaseEditor(Qt.QSplitter):
-
     def __init__(self, parent=None):
         Qt.QSplitter.__init__(self, parent)
 
@@ -60,42 +47,60 @@ class TaurusBaseEditor(Qt.QSplitter):
 
         self.menu_actions, self.io_actions = self.createMenuActions()
 
-        self.toolbar_list = None
-        self.menu_list = None
-
         self.find_widget = FindReplace(self, enable_replace=True)
         self.outlineexplorer = OutlineExplorerWidget(self, show_fullpath=False,
                                                      show_all_files=False)
+        self.outlineexplorer.edit_goto.connect(self.go_to_file)
+        self.editor_splitter = EditorSplitter(self, self, self.menu_actions,
+                                              first=True)
+
         editor_widgets = Qt.QWidget(self)
         editor_layout = Qt.QVBoxLayout()
         editor_layout.setContentsMargins(0, 0, 0, 0)
         editor_widgets.setLayout(editor_layout)
-
-        editor_layout.addWidget(EditorSplitter(self, self, self.menu_actions,
-                                               first=True))
+        editor_layout.addWidget(self.editor_splitter)
         editor_layout.addWidget(self.find_widget)
 
         self.setContentsMargins(0, 0, 0, 0)
         self.addWidget(editor_widgets)
         self.addWidget(self.outlineexplorer)
 
-        self.setStretchFactor(0, 1)
-        self.setStretchFactor(1, 0)
+        self.setStretchFactor(0, 5)
+        self.setStretchFactor(1, 1)
 
+        self.toolbar_list = None
+        self.menu_list = None
         self.setup_window([], [])
 
-    def editorStack(self):
-        return self.editorstacks[0]
+        # Set introspector
+        introspector = IntrospectionManager()
+        editorstack = self.editor_splitter.editorstack
+        editorstack.set_introspector(introspector)
+        introspector.set_editor_widget(editorstack)
 
     def createMenuActions(self):
-        return [], []
+        """Returns a list of menu actions and a list of IO actions.
+        Reimplement in derived classes.
+        This Base (dummy) implementation creates empty menu actions and 
+        a list of 5 dummy actions for the IO actions
+        """
+        dummyaction = Qt.QAction(self)
+        return [], [dummyaction]*4
+
+    def go_to_file(self, fname, lineno, text):
+        editorstack = self.editorstacks[0]
+        editorstack.set_current_filename(to_text_string(fname))
+        editor = editorstack.get_current_editor()
+        editor.go_to_line(lineno, word=text)
 
     def closeEvent(self, event):
         for win in self.editorwindows[:]:
             win.close()
+
         event.accept()
 
     def load(self, filename, goto=None):
+        Qt.QApplication.processEvents()
         editorstack = self.editorStack()
         fileinfo = editorstack.load(filename)
         editorstack.analyze_script()
@@ -104,7 +109,7 @@ class TaurusBaseEditor(Qt.QSplitter):
 
     def reload(self, idx=None, filename=None, goto=None):
         if idx is None:
-            idx = is_file_opened(filename)
+            idx = self.is_file_opened(filename)
         if idx is not None:
             editorstack = self.editorStack()
             editorstack.reload(idx)
@@ -116,12 +121,19 @@ class TaurusBaseEditor(Qt.QSplitter):
     def set_current_filename(self, filename):
         self.editorStack().set_current_filename(filename)
 
+    def is_file_opened(self, filename=None):
+        """Dummy implementation that always returns None. Reimplement 
+        in derived classes to return the index of already-open files 
+        in the editor_stack, or None if the file is not already open.
+        """
+        return None
+
     def register_editorstack(self, editorstack):
         self.editorstacks.append(editorstack)
         if self.isAncestorOf(editorstack):
             # editorstack is a child of the Editor plugin
             editorstack.set_fullpath_sorting_enabled(True)
-            editorstack.set_closable(len(self.editorstacks) > 1)
+            editorstack.set_closable( len(self.editorstacks) > 1 )
             editorstack.set_outlineexplorer(self.outlineexplorer)
             editorstack.set_find_widget(self.find_widget)
             oe_btn = create_toolbutton(self)
@@ -132,7 +144,8 @@ class TaurusBaseEditor(Qt.QSplitter):
         font = Qt.QFont("Monospace")
         font.setPointSize(10)
         editorstack.set_default_font(font, color_scheme='Spyder')
-        editorstack.close_file.connect(self.close_file_in_all_editorstacks)
+
+        editorstack.sig_close_file.connect(self.close_file_in_all_editorstacks)
         editorstack.create_new_window.connect(self.create_new_window)
         editorstack.plugin_load.connect(self.load)
 
@@ -154,7 +167,7 @@ class TaurusBaseEditor(Qt.QSplitter):
         window.resize(self.size())
         window.show()
         self.register_editorwindow(window)
-        window.destroyed.connect(lambda win=window: self.unregister_editorwindow(win))
+        window.destroyed.connect(lambda: self.unregister_editorwindow(window))
 
     def register_editorwindow(self, window):
         self.editorwindows.append(window)
@@ -165,47 +178,32 @@ class TaurusBaseEditor(Qt.QSplitter):
     def get_focus_widget(self):
         pass
 
-    def close_file_in_all_editorstacks(self, index):
-        sender = self.sender()
+    def editorStack(self):
+        return self.editorstacks[0]
+
+    @Qt.Slot(str, int)
+    def close_file_in_all_editorstacks(self, editorstack_id_str, index):
         for editorstack in self.editorstacks:
-            if editorstack is not sender:
+            if str(id(editorstack)) != editorstack_id_str:
                 editorstack.blockSignals(True)
-                editorstack.close_file(index)
+                editorstack.close_file(index, force=True)
                 editorstack.blockSignals(False)
 
-    def register_widget_shortcuts(self, context, widget):
+    def register_widget_shortcuts(self, widget):
         """Fake!"""
         pass
 
     def refresh_save_all_action(self):
         pass
 
-from spyderlib.plugins.editor import Editor
-
-
-class TaurusBaseEditor2(Qt.QMainWindow):
-
-    def __init__(self, parent=None):
-        Qt.QMainWindow.__init__(self, parent)
-
-        self._editor = Editor(self)
-
-        self.setCentralWidget(self._editor)
-
-    def register_shortcut(self, qaction_or_qshortcut, context, name,
-                          default=None):
-        pass
-
-    def show_hide_project_explorer(self):
-        pass
-
 
 def demo():
     test = TaurusBaseEditor()
     test.resize(1000, 800)
-    test.load(__file__)
-    test.load("__init__.py")
     test.show()
+
+    test.load(__file__)
+
     return test
 
 
@@ -230,7 +228,7 @@ def main():
     if len(args) == 0:
         w = demo()
     else:
-        w = TaurusEditor()
+        w = TaurusBaseEditor()
         w.resize(900, 800)
         for name in args:
             w.load(name)
diff --git a/lib/taurus/qt/qtgui/extra_guiqwt/curvesmodel.py b/lib/taurus/qt/qtgui/extra_guiqwt/curvesmodel.py
index a4206ed..c80523d 100644
--- a/lib/taurus/qt/qtgui/extra_guiqwt/curvesmodel.py
+++ b/lib/taurus/qt/qtgui/extra_guiqwt/curvesmodel.py
@@ -435,8 +435,10 @@ class CurveItemConfDlg(Qt.QWidget):
                 group.edit()
                 c.x.processSrc(c.taurusparam.xModel)
                 c.y.processSrc(c.taurusparam.yModel)
-                self.dataChanged.emit(self.model.index(
-                    row, 0), self.model.index(row, self.model.rowCount()))
+                self.dataChanged.emit(
+                        self.model.index(row, 0),
+                        self.model.index(row, self.model.rowCount() - 1)
+                )
 
     def onModelsAdded(self, models):
         nmodels = len(models)
diff --git a/lib/taurus/qt/qtgui/graphic/taurusgraphic.py b/lib/taurus/qt/qtgui/graphic/taurusgraphic.py
index 713116a..2723995 100755
--- a/lib/taurus/qt/qtgui/graphic/taurusgraphic.py
+++ b/lib/taurus/qt/qtgui/graphic/taurusgraphic.py
@@ -28,7 +28,7 @@ taurusgraphic.py:
 
 __all__ = ['SynopticSelectionStyle',
            'parseTangoUri',
-           'QEmitter',
+           'QEmitter',  # TODO: QEmitter should probably be removed (kept priv)
            'TaurusGraphicsUpdateThread',
            'TaurusGraphicsScene',
            'QGraphicsTextBoxing',
@@ -65,7 +65,7 @@ import Queue
 from taurus import Manager
 from taurus.core import AttrQuality, DataType
 from taurus.core.util.containers import CaselessDefaultDict
-from taurus.core.util.log import Logger
+from taurus.core.util.log import Logger, deprecation_decorator
 from taurus.core.taurusdevice import TaurusDevice
 from taurus.core.taurusattribute import TaurusAttribute
 from taurus.core.util.enumeration import Enumeration
@@ -100,7 +100,7 @@ def parseTangoUri(name):
 
 
 class QEmitter(Qt.QObject):
-    updateView = Qt.pyqtSignal('QGraphicsView')
+    updateView = Qt.pyqtSignal(object)
 
 
 class TaurusGraphicsUpdateThread(Qt.QThread):
@@ -151,7 +151,7 @@ class TaurusGraphicsUpdateThread(Qt.QThread):
             for v in p.views():
                 # p.debug("emit('updateView')")
                 # emitter.updateView.emit(v)
-                self.emitter.updateView.emit(v)
+                emitter.updateView.emit(v)
             # This sleep is needed to reduce CPU usage of the application!
             self.sleep(self.period)
             # End of while
@@ -1111,14 +1111,10 @@ class TaurusGraphicsAttributeItem(TaurusGraphicsItem):
         self._unitVisible = True
         self.call__init__(TaurusGraphicsItem, name, parent)
 
+    @deprecation_decorator(alt=".getDisplayValue(fragmentName='rvalue.units')",
+                           rel="4.0.3")
     def getUnit(self):
-        unit = ''
-        modelObj = self.getModelObj()
-        if not modelObj is None:
-            unit = modelObj.getUnit()
-            if not unit or unit == 'No unit':
-                unit = ''
-        return unit
+        return self.getDisplayValue(fragmentName='rvalue.units')
 
     def updateStyle(self):
         v = self.getModelValueObj()
@@ -1139,11 +1135,19 @@ class TaurusGraphicsAttributeItem(TaurusGraphicsItem):
                 self.warning(traceback.format_exc())
 
         if v and self._userFormat:
-            text = self._userFormat % (v.value)
+            # TODO: consider extending this to use newer pyhon formatting syntax
+            if hasattr(v.rvalue, 'magnitude'):
+                text = self._userFormat % v.rvalue.magnitude
+            else:
+                text = self._userFormat % v.rvalue
+            if self._unitVisible:
+                text = "{0} {1.rvalue.units:~s}".format(text, v)
         else:
-            text = self._currText = self.getDisplayValue()
-        if self._unitVisible:
-            text = text + ' ' + self.getUnit()
+            if self._unitVisible:
+                _frName = None
+            else:
+                _frName = 'rvalue.magnitude'
+            text = self._currText = self.getDisplayValue(fragmentName=_frName)
         self._currText = text.decode('unicode-escape')
         self._currHtmlText = None
 
diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py
index aa2c47c..eb2c202 100755
--- a/lib/taurus/qt/qtgui/input/tauruslineedit.py
+++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py
@@ -106,11 +106,24 @@ class TaurusValueLineEdit(Qt.QLineEdit, TaurusBaseWritableWidget):
         TaurusBaseWritableWidget.handleEvent(
             self, evt_src, evt_type, evt_value)
 
+    def isTextValid(self):
+        """
+        Validates current text
+
+        :return: (bool) Returns False if there is a validator and the current
+                 text is not Acceptable. Returns True otherwise.
+        """
+        val = self.validator()
+        if val is None:
+            return True
+        return val.validate(str(self.text()), 0)[0] == val.Acceptable
+
     def updateStyle(self):
         TaurusBaseWritableWidget.updateStyle(self)
 
         value = self.getValue()
-        if value is None:
+
+        if value is None or not self.isTextValid():
             # invalid value
             color, weight = 'gray', 'normal'
         else:
@@ -196,14 +209,14 @@ class TaurusValueLineEdit(Qt.QLineEdit, TaurusBaseWritableWidget):
             model_type = model_obj.type
             model_format = model_obj.data_format
             if model_type in [DataType.Integer, DataType.Float]:
-                if val is None or \
-                        val.validate(str(text), 0)[0] != val.Acceptable:
+                try:
+                    q = Quantity(text)
+                    # allow implicit units (assume wvalue.units implicitly)
+                    if q.dimensionless:
+                        q = Quantity(q.magnitude, val.units)
+                    return q
+                except:
                     return None
-                q = Quantity(text)
-                # allow implicit units (assume wvalue.units implicitly)
-                if q.dimensionless:
-                    q = Quantity(q.magnitude, val.units)
-                return q
             elif model_type == DataType.Boolean:
                 if model_format == DataFormat._0D:
                     return bool(int(eval(text)))
diff --git a/lib/taurus/qt/qtgui/input/taurusspinbox.py b/lib/taurus/qt/qtgui/input/taurusspinbox.py
index d53b6e0..82ee026 100644
--- a/lib/taurus/qt/qtgui/input/taurusspinbox.py
+++ b/lib/taurus/qt/qtgui/input/taurusspinbox.py
@@ -31,9 +31,9 @@ __docformat__ = 'restructuredtext'
 
 from taurus.external.qt import Qt
 
-from taurus.qt.qtgui.base import TaurusBaseWritableWidget
 from tauruslineedit import TaurusValueLineEdit
 from taurus.qt.qtgui.icon import getStandardIcon
+from taurus.external.pint import Quantity
 
 
 class TaurusValueSpinBox(Qt.QAbstractSpinBox):
@@ -51,7 +51,6 @@ class TaurusValueSpinBox(Qt.QAbstractSpinBox):
         lineEdit = TaurusValueLineEdit(designMode=designMode)
         self.setLineEdit(lineEdit)
         self.setAccelerated(True)
-        self.editingFinished.connect(self.writeValue)
 
     def __getattr__(self, name):
         return getattr(self.lineEdit(), name)
@@ -62,13 +61,15 @@ class TaurusValueSpinBox(Qt.QAbstractSpinBox):
     def getValue(self):
         return self.lineEdit().getValue()
 
+    def keyPressEvent(self, evt):
+        return self.lineEdit().keyPressEvent(evt)
+
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
     # Mandatory overload from QAbstractSpinBox
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
 
     def stepBy(self, steps):
-        #validator = self.lineEdit().validator()
-        self.setValue(self.getValue() + self.getSingleStep() * steps)
+        self.setValue(self.getValue() + self._getSingleStepQuantity() * steps)
 
         if self.lineEdit().getAutoApply():
             self.lineEdit().editingFinished.emit()
@@ -85,18 +86,34 @@ class TaurusValueSpinBox(Qt.QAbstractSpinBox):
         if self.getModelObj().getValueObj() is None:
             return ret
 
-        le, curr_val, ss = self.lineEdit(), self.getValue(), self.getSingleStep()
+        curr_val = self.getValue()
 
         if curr_val is None:
             return ret
 
-        if not le._outOfRange(curr_val + ss):
+        ss = self._getSingleStepQuantity()
+
+        if self.validate(str(curr_val + ss), 0)[0] == Qt.QValidator.Acceptable:
             ret |= Qt.QAbstractSpinBox.StepUpEnabled
-        if not le._outOfRange(curr_val - ss):
+        if self.validate(str(curr_val - ss), 0)[0] == Qt.QValidator.Acceptable:
             ret |= Qt.QAbstractSpinBox.StepDownEnabled
         return ret
 
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
+    # Overload from QAbstractSpinBox
+    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
+
+    def validate(self, input, pos):
+        """Overloaded to use the current validator from the TaurusValueLineEdit,
+        instead of the default QAbstractSpinBox validator. If no validator is
+        set in the LineEdit, it falls back to the original behaviour
+        """
+        val = self.lineEdit().validator()
+        if val is None:
+            return Qt.QAbstractSpinBox.validate(self, input, pos)
+        return val.validate(input, pos)
+
+    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
     # Model related methods
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
 
@@ -146,6 +163,16 @@ class TaurusValueSpinBox(Qt.QAbstractSpinBox):
     def resetSingleStep(self):
         self.setSingleStep(1.0)
 
+    def _getSingleStepQuantity(self):
+        """
+        :return: (Quantity) returns a single step with the units of the current
+                 value
+        """
+        value = self.getValue()
+        if value is None:
+            return None
+        return Quantity(self.getSingleStep(), value.units)
+
     @classmethod
     def getQtDesignerPluginInfo(cls):
         return {
@@ -218,3 +245,16 @@ class TaurusValueSpinBoxEx(Qt.QWidget):
 
     def __setattr__(self, name, value):
         setattr(self.spinBox, name, value)
+
+
+if __name__ == "__main__":
+    import sys
+    from taurus.qt.qtgui.application import TaurusApplication
+    app = TaurusApplication()
+
+    w = TaurusValueSpinBox()
+    w.setModel('sys/tg_test/1/double_scalar')
+    w.resize(300, 50)
+    w.show()
+
+    sys.exit(app.exec_())
\ No newline at end of file
diff --git a/lib/taurus/qt/qtgui/input/tauruswheel.py b/lib/taurus/qt/qtgui/input/tauruswheel.py
index a400734..3070b61 100644
--- a/lib/taurus/qt/qtgui/input/tauruswheel.py
+++ b/lib/taurus/qt/qtgui/input/tauruswheel.py
@@ -56,9 +56,10 @@ class TaurusWheelEdit(QWheelEdit, TaurusBaseWritableWidget):
         if evt_type == TaurusEventType.Config and evt_value is not None:
             import re
             # match the format string to "%[width][.precision][f_type]"
-            m = re.match(r'%([0-9]+)?(\.([0-9]+))?([df])', evt_value.format)
+            obj = self.getModelObj()
+            m = re.match(r'%([0-9]+)?(\.([0-9]+))?([df])', obj.format)
             if m is None:
-                raise ValueError("'%s' format unsupported" % evt_value.format)
+                raise ValueError("'%s' format unsupported" % obj.format)
 
             width, _, precision, f_type = m.groups()
 
@@ -82,11 +83,11 @@ class TaurusWheelEdit(QWheelEdit, TaurusBaseWritableWidget):
 
             self.setDigitCount(int_nb=int_nb, dec_nb=dec_nb)
             try:
-                self.setMinValue(float(evt_value.min_value))
+                self.setMinValue(float(obj.min_value))
             except:
                 pass
             try:
-                self.setMaxValue(float(evt_value.max_value))
+                self.setMaxValue(float(obj.max_value))
             except:
                 pass
         TaurusBaseWritableWidget.handleEvent(
diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py
index 9eae372..c124432 100644
--- a/lib/taurus/qt/qtgui/panel/taurusvalue.py
+++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py
@@ -1195,11 +1195,19 @@ class TaurusValue(Qt.QWidget, TaurusBaseWidget):
         if self._labelWidget is None:
             return
         if self.hasPendingOperations():
-            self._labelWidget.setStyleSheet(
-                '%s {border-style: solid ; border-width: 1px; border-color: blue; color: blue; border-radius:4px;}' % self._labelWidget.__class__.__name__)
+            newstyle = '%s {border-style: solid ; border-width: 1px; ' \
+                       'border-color: blue; color: blue; border-radius:4px;}' \
+                       % self._labelWidget.__class__.__name__
+            oldstyle = self._labelWidget.styleSheet()
+            if newstyle != oldstyle:
+                self._labelWidget.setStyleSheet(newstyle)
         else:
-            self._labelWidget.setStyleSheet(
-                '%s {border-style: solid; border-width: 1px; border-color: transparent; color: black;  border-radius:4px;}' % self._labelWidget.__class__.__name__)
+            newstyle = '%s {border-style: solid; border-width: 1px; ' \
+                       'border-color: transparent; color: black;  ' \
+                       'border-radius:4px;}' % self._labelWidget.__class__.__name__
+            oldstyle = self._labelWidget.styleSheet()
+            if newstyle != oldstyle:
+                self._labelWidget.setStyleSheet(newstyle)
 
     def getLabelConfig(self):
         return self._labelConfig
diff --git a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py
index 32e373e..ffddeee 100644
--- a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py
+++ b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py
@@ -54,7 +54,7 @@ class TaurusValueTest(TangoSchemeTestLauncher, BaseWidgetTestCase,
         self._widget.setModel('tango:' + DEV_NAME + '/double_scalar')
         label = 'MIXEDcase'
         w.setLabelConfig(label)
-        self._app.processEvents()  # required
+        self.processEvents(repetitions=10, sleep=.1)
         shownLabel = str(w.labelWidget().text())
         msg = 'Shown label ("%s") differs from set label ("%s")' % (shownLabel,
                                                                     label)
@@ -66,7 +66,7 @@ class TaurusValueTest(TangoSchemeTestLauncher, BaseWidgetTestCase,
         self._widget.setModel(model)
         if fgRole is not None:
             self._widget.setFgRole(fgRole)
-        self._app.processEvents()
+        self.processEvents(repetitions=10, sleep=.1)
         got = (str(self._widget.labelWidget().text()),
                str(self._widget.readWidget().text()),
                str(self._widget.writeWidget().displayText()),
diff --git a/lib/taurus/qt/qtgui/taurusgui/appsettingswizard.py b/lib/taurus/qt/qtgui/taurusgui/appsettingswizard.py
index 25364e2..102d39e 100644
--- a/lib/taurus/qt/qtgui/taurusgui/appsettingswizard.py
+++ b/lib/taurus/qt/qtgui/taurusgui/appsettingswizard.py
@@ -59,7 +59,7 @@ class BooleanWidget(Qt.QWidget):
     It change the value by using getValue and setValue methods
     """
 
-    valueChanged = Qt.pyqtSignal(bool, bool)
+    valueChangedSignal = Qt.pyqtSignal(bool, bool)
 
     def __init__(self, parent=None):
         Qt.QWidget.__init__(self, parent)
@@ -76,7 +76,7 @@ class BooleanWidget(Qt.QWidget):
 
     def valueChanged(self):
         if not (self.trueButton.isChecked() == self._actualValue):
-            self.valueChanged.emit(self._actualValue, not self._actualValue)
+            self.valueChangedSignal.emit(self._actualValue, not self._actualValue)
         self._actualValue = self.trueButton.isChecked()
 
     def setValue(self, value):
@@ -1387,6 +1387,7 @@ class AppSettingsWizard(Qt.QWizard):
     """
     Pages = Enumeration('Pages', ('IntroPage', 'ProjectPage', 'GeneralSettings', 'CustomLogoPage', 'SynopticPage',
                                   'MacroServerInfo', 'InstrumentsPage', 'PanelsPage', 'ExternalAppPage', 'MonitorPage', 'OutroPage'))
+    SARDANA_INSTALLED = False
 
     def __init__(self, parent=None, jdrawCommand='jdraw', configFilePrefix='config'):
         Qt.QWizard.__init__(self, parent)
@@ -1508,10 +1509,10 @@ class AppSettingsWizard(Qt.QWizard):
         try:
             from sardana.taurus.qt.qtgui.extra_macroexecutor.common import \
                 TaurusMacroConfigurationDialog
-            SARDANA_INSTALLED = True
+            self.SARDANA_INSTALLED = True
         except:
-            SARDANA_INSTALLED = False
-        if SARDANA_INSTALLED:
+            self.SARDANA_INSTALLED = False
+        if self.SARDANA_INSTALLED:
             synoptic_page.setNextPageId(self.Pages.MacroServerInfo)
 
             macroserver_page = MacroServerInfoPage()
@@ -1594,7 +1595,7 @@ class AppSettingsWizard(Qt.QWizard):
                         self._projectWarnings.append((short, long))
 
         # macroserver page
-        if SARDANA_INSTALLED and self.__getitem__("macroServerName"):
+        if self.SARDANA_INSTALLED and self.__getitem__("macroServerName"):
             macroServerName = etree.SubElement(root, "MACROSERVER_NAME")
             macroServerName.text = self.__getitem__("macroServerName")
             doorName = etree.SubElement(root, "DOOR_NAME")
diff --git a/lib/taurus/qt/qtgui/taurusgui/macrolistener.py b/lib/taurus/qt/qtgui/taurusgui/macrolistener.py
index 53ca2cd..4a90225 100644
--- a/lib/taurus/qt/qtgui/taurusgui/macrolistener.py
+++ b/lib/taurus/qt/qtgui/taurusgui/macrolistener.py
@@ -126,14 +126,15 @@ class DynamicPlotManager(Qt.QObject, TaurusBaseComponent):
                         QDoor.getExperimentDescription`
                         for more details
         '''
-        if expconf['ActiveMntGrp'] is None:
+        activeMntGrp = expconf['ActiveMntGrp']
+        if activeMntGrp is None:
             return
-        if expconf['ActiveMntGrp'] not in expconf['MntGrpConfigs'].keys():
+        if activeMntGrp not in expconf['MntGrpConfigs']:
             self.warning(
                 "ActiveMntGrp '%s' is not defined" %
-                expconf['ActiveMntGrp'])
+                activeMntGrp)
             return
-        mgconfig = expconf['MntGrpConfigs'][expconf['ActiveMntGrp']]
+        mgconfig = expconf['MntGrpConfigs'][activeMntGrp]
         channels = dict(getChannelConfigs(mgconfig, sort=False))
 
         # classify by type of plot:
@@ -342,16 +343,40 @@ class MacroBroker(DynamicPlotManager):
         door = self.getModelObj()
         if door is not None:  # disconnect it from *all* shared data providing
             SDM = Qt.qApp.SDM
-            SDM.disconnectWriter("macroStatus", door, "macroStatusUpdated")
-            SDM.disconnectWriter("doorOutputChanged", door, "outputUpdated")
-            SDM.disconnectWriter("doorInfoChanged", door, "infoUpdated")
-            SDM.disconnectWriter("doorWarningChanged", door, "warningUpdated")
-            SDM.disconnectWriter("doorErrorChanged", door, "errorUpdated")
-            SDM.disconnectWriter("doorDebugChanged", door, "debugUpdated")
-            SDM.disconnectWriter("doorResultChanged", door, "resultUpdated")
-            SDM.disconnectWriter("expConfChanged", door,
-                                 "experimentConfigurationChanged")
-
+            try:
+                SDM.disconnectWriter("macroStatus", door, "macroStatusUpdated")
+            except:
+                self.info("Could not disconnect macroStatusUpdated")
+            try:
+                SDM.disconnectWriter("doorOutputChanged", door, "outputUpdated")
+            except:
+                self.info("Could not disconnect outputUpdated")
+            try:
+                SDM.disconnectWriter("doorInfoChanged", door, "infoUpdated")
+            except:
+                self.info("Could not disconnect infoUpdated")
+            try:
+                SDM.disconnectWriter("doorWarningChanged", door,
+                                     "warningUpdated")
+            except:
+                self.info("Could not disconnect warningUpdated")
+            try:
+                SDM.disconnectWriter("doorErrorChanged", door, "errorUpdated")
+            except:
+                self.info("Could not disconnect errorUpdated")
+            try:
+                SDM.disconnectWriter("doorDebugChanged", door, "debugUpdated")
+            except:
+                self.info("Could not disconnect debugUpdated")
+            try:
+                SDM.disconnectWriter("doorResultChanged", door, "resultUpdated")
+            except:
+                self.info("Could not disconnect resultUpdated")
+            try:
+                SDM.disconnectWriter("expConfChanged", door,
+                                     "experimentConfigurationChanged")
+            except:
+                self.info("Could not disconnect experimentConfigurationChanged")
         # set the model
         DynamicPlotManager.setModel(self, doorname)
 
diff --git a/lib/taurus/qt/qtgui/taurusgui/taurusgui.py b/lib/taurus/qt/qtgui/taurusgui/taurusgui.py
index 7680107..e43f171 100644
--- a/lib/taurus/qt/qtgui/taurusgui/taurusgui.py
+++ b/lib/taurus/qt/qtgui/taurusgui/taurusgui.py
@@ -905,7 +905,7 @@ class TaurusGui(TaurusMainWindow):
             instrument = elem.instrument
             if instrument:
                 i_name = instrument
-                e_name = elem.full_name
+                e_name = "tango://%s" % elem.full_name
                 instrument_dict[i_name].model.append(e_name)
         # filter out empty panels
         ret = [instrument for instrument in instrument_dict.values()
diff --git a/lib/taurus/qt/qtgui/test/base.py b/lib/taurus/qt/qtgui/test/base.py
index 2fff74c..554c739 100644
--- a/lib/taurus/qt/qtgui/test/base.py
+++ b/lib/taurus/qt/qtgui/test/base.py
@@ -25,6 +25,7 @@
 
 """Utilities for creating generic tests for Taurus widgets"""
 
+import time
 import taurus.core
 from taurus.external import unittest
 from taurus.qt.qtgui.application import TaurusApplication
@@ -82,6 +83,11 @@ class BaseWidgetTestCase(object):
                    (deps, maximum, self._depCounter.pretty()))
         self.assertTrue(deps <= maximum, msg)
 
+    def processEvents(self, repetitions=1, sleep=0):
+         for i in xrange(repetitions):
+            time.sleep(sleep)
+            self._app.processEvents()
+
 
 class GenericWidgetTestCase(BaseWidgetTestCase):
 
diff --git a/lib/taurus/qt/qtgui/util/taurusaction.py b/lib/taurus/qt/qtgui/util/taurusaction.py
index 698dc6d..6df3e6d 100644
--- a/lib/taurus/qt/qtgui/util/taurusaction.py
+++ b/lib/taurus/qt/qtgui/util/taurusaction.py
@@ -43,6 +43,7 @@ import os
 import xml.dom.minidom
 
 from taurus.external.qt import Qt
+from taurus.core.taurushelper import getSchemeFromName
 from taurus.qt.qtcore.configuration import BaseConfigurableClass
 
 
@@ -334,3 +335,7 @@ class ConfigurationMenu(TaurusMenu):
     def __init__(self, parent):
         TaurusMenu.__init__(self, parent)
         self.build(self.menuData)
+
+        # TODO: AttrConfigMenu is tangocentric avoid to use with other schemes
+        if parent and getSchemeFromName(parent.getModelName()) != "tango":
+            self.setEnabled(False)
diff --git a/lib/taurus/qt/qtgui/util/taurusropepatch.py b/lib/taurus/qt/qtgui/util/taurusropepatch.py
index 27dde8f..bf2e676 100644
--- a/lib/taurus/qt/qtgui/util/taurusropepatch.py
+++ b/lib/taurus/qt/qtgui/util/taurusropepatch.py
@@ -23,13 +23,18 @@
 ##
 #############################################################################
 
-"""Rope patch for better performances. Based on spyderlib.rope_patch"""
+"""[DEPRECATED] Rope patch for better performance.
+Based on spyder.rope_patch"""
 
 __all__ = ["apply"]
 
 __docformat__ = 'restructuredtext'
 
 
+from taurus.core.util.log import deprecated
+deprecated(dep='taurusropepatch module', rel='4.0.1')
+
+
 def apply():
     """Monkey patching rope for better performances"""
     import rope
@@ -59,7 +64,8 @@ def apply():
     # Patching BuiltinFunction for the calltip/doc functions to be
     # able to retrieve the function signatures with forced builtins
     from rope.base import builtins, pyobjects
-    from spyderlib.utils.dochelpers import getargs
+    from spyder.utils.dochelpers import getargs
+
 
     class PatchedBuiltinFunction(builtins.BuiltinFunction):
 
diff --git a/setup.cfg b/setup.cfg
index 861a9f5..8bfd5a1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,4 @@
 [egg_info]
 tag_build = 
 tag_date = 0
-tag_svn_revision = 0
 
diff --git a/setup.py b/setup.py
index a69e547..3b5cbf4 100644
--- a/setup.py
+++ b/setup.py
@@ -158,5 +158,5 @@ setup(name='taurus',
       provides=provides,
       requires=requires,
       extras_require=extras_require,
-      test_suite='taurus.test.testsuite.get_suite',
+      test_suite='taurus.test.testsuite.get_taurus_suite',
       )

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/taurus.git



More information about the debian-science-commits mailing list