Skip to content

Commit

Permalink
Merge pull request #145 from enthought/fix/global-adaptation-manager
Browse files Browse the repository at this point in the history
Allow setting and resetting the global adaptation manager.
  • Loading branch information
mdickinson committed Feb 25, 2014
2 parents c4491f8 + 73fdb51 commit eb6a4da
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Enhancements
* Removed obsolete code (#112,#113)
* Increased test coverage (#114)
* Python 3 support (#115)
* Allow setting and resetting the global adaptation manager (#145)

Fixes

Expand Down
6 changes: 0 additions & 6 deletions docs/source/traits_api_reference/traits.adaptation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@
:undoc-members:
:show-inheritance:

.. autofunction:: adapt
.. autofunction:: register_factory
.. autofunction:: register_offer
.. autofunction:: register_provides
.. autofunction:: supports_protocol


:mod:`adaptation_offer` Module
------------------------------
Expand Down
19 changes: 19 additions & 0 deletions docs/source/traits_user_manual/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,25 @@ Gotchas
the classes involved in adaptation are typically subclasses of
:class:`~.HasTraits`, in which case this is not an issue.

2) The methods :func:`~traits.adaptation.adaptation_manager.register_factory`,
:func:`~traits.adaptation.adaptation_manager.adapt`,
etc. use a global adaptation manager, which is accessible through the
function
:func:`~traits.adaptation.adaptation_manager.get_global_adaptation_manager`.
The traits automatic adaptation features also use the global manager.
Having a global adaptation manager can get you into trouble, for the usual
reasons related to having a global state. If you want to have more control
over adaptation, we recommend creating a new
:class:`~traits.adaptation.adaptation_manager.AdaptationManager`
instance, use it directly in your application, and set it as the global
manager using
:func:`~traits.adaptation.adaptation_manager.set_global_adaptation_manager`.
A common issue with the global manager arises in unittesting, where adapters
registered in one test influence the outcome of other tests downstream.
Tests relying on adaptation should make sure to reset the state of the
global adapter using
:func:`~traits.adaptation.adaptation_manager.reset_global_adaptation_manager`.

Recommended readings about adaptation
`````````````````````````````````````

Expand Down
64 changes: 57 additions & 7 deletions traits/adaptation/adaptation_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,20 +361,70 @@ def _by_weight_then_from_protocol_specificity(edge_1, edge_2):
return 0


#: The default global adaptation manager.
# The default global adaptation manager.
# PROVIDED FOR BACKWARD COMPATIBILITY ONLY, IT SHOULD NEVER BE USED DIRECTLY.
# If you must use a global adaptation manager, use the functions
# `get_global_adaptation_manager`, `reset_global_adaptation_manager`,
# `set_global_adaptation_manager`.
adaptation_manager = AdaptationManager()


def set_global_adaptation_manager(new_adaptation_manager):
""" Set the global adaptation manager to the given instance. """
global adaptation_manager
adaptation_manager = new_adaptation_manager


def reset_global_adaptation_manager():
""" Set the global adaptation manager to a new AdaptationManager instance.
"""
global adaptation_manager
adaptation_manager = AdaptationManager()


def get_global_adaptation_manager():
""" Set a reference to the global adaptation manager. """
global adaptation_manager
return adaptation_manager


# Convenience references to methods on the default adaptation manager.
#
# If you add a public method to the adaptation manager protocol then don't
# forget to add a convenience function here!

adapt = adaptation_manager.adapt
register_factory = adaptation_manager.register_factory
register_offer = adaptation_manager.register_offer
register_provides = adaptation_manager.register_provides
supports_protocol = adaptation_manager.supports_protocol
provides_protocol = AdaptationManager.provides_protocol
def adapt(adaptee, to_protocol, default=AdaptationError):
""" Attempt to adapt an object to a given protocol. """
manager = get_global_adaptation_manager()
return manager.adapt(adaptee, to_protocol, default)


def register_factory(factory, from_protocol, to_protocol):
""" Register an adapter factory. """
manager = get_global_adaptation_manager()
return manager.register_factory(factory, from_protocol, to_protocol)


def register_offer(offer):
""" Register an offer to adapt from one protocol to another. """
manager = get_global_adaptation_manager()
return manager.register_offer(offer)


def register_provides(provider_protocol, protocol):
""" Register that a protocol provides another. """
manager = get_global_adaptation_manager()
return manager.register_provides(provider_protocol, protocol)


def supports_protocol(obj, protocol):
""" Does the object support a given protocol? """
manager = get_global_adaptation_manager()
return manager.supports_protocol(obj, protocol)


def provides_protocol(type_, protocol):
""" Does the given type provide (i.e implement) a given protocol? """
return AdaptationManager.provides_protocol(type_, protocol)

#### EOF ######################################################################
5 changes: 3 additions & 2 deletions traits/adaptation/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from .adaptation_error import AdaptationError

from .adaptation_manager import adapt, AdaptationManager, \
provides_protocol, register_factory, register_offer, register_provides, \
supports_protocol
get_global_adaptation_manager, provides_protocol, register_factory, \
register_offer, register_provides, reset_global_adaptation_manager, \
set_global_adaptation_manager, supports_protocol

from .adaptation_offer import AdaptationOffer
4 changes: 2 additions & 2 deletions traits/adaptation/tests/test_adaptation_manager.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""" Test the adapter manager. """
""" Test the adaptation manager. """

import sys

Expand All @@ -9,7 +9,7 @@


class TestAdaptationManagerWithABC(unittest.TestCase):
""" Test the adapter manager. """
""" Test the adaptation manager. """

#: Class attribute pointing at the module containing the example data
examples = traits.adaptation.tests.abc_examples
Expand Down
123 changes: 123 additions & 0 deletions traits/adaptation/tests/test_global_adaptation_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
""" Test the setting/getting/resetting/using the global adaptation manager. """

from traits.adaptation.api import adapt, AdaptationError, AdaptationManager, \
AdaptationOffer, get_global_adaptation_manager, provides_protocol, \
register_factory, register_provides, register_offer, \
reset_global_adaptation_manager, set_global_adaptation_manager, \
supports_protocol
import traits.adaptation.tests.abc_examples
from traits.testing.unittest_tools import unittest


class TestGlobalAdaptationManager(unittest.TestCase):
""" Test the setting/getting/resetting/using the global adaptation manager.
"""

#: Class attribute pointing at the module containing the example data
examples = traits.adaptation.tests.abc_examples

#### 'TestCase' protocol ##################################################

def setUp(self):
""" Prepares the test fixture before each test method is called. """

reset_global_adaptation_manager()

#### Tests ################################################################

def test_reset_adaptation_manager(self):
ex = self.examples
adaptation_manager = get_global_adaptation_manager()

# UKStandard->EUStandard.
adaptation_manager.register_factory(
factory = ex.UKStandardToEUStandard,
from_protocol = ex.UKStandard,
to_protocol = ex.EUStandard,
)

# Create a UKPlug.
uk_plug = ex.UKPlug()

reset_global_adaptation_manager()
adaptation_manager = get_global_adaptation_manager()

with self.assertRaises(AdaptationError):
adaptation_manager.adapt(uk_plug, ex.EUStandard)

def test_set_adaptation_manager(self):
ex = self.examples
adaptation_manager = AdaptationManager()

# UKStandard->EUStandard.
adaptation_manager.register_factory(
factory = ex.UKStandardToEUStandard,
from_protocol = ex.UKStandard,
to_protocol = ex.EUStandard
)

# Create a UKPlug.
uk_plug = ex.UKPlug()

set_global_adaptation_manager(adaptation_manager)
global_adaptation_manager = get_global_adaptation_manager()

eu_plug = global_adaptation_manager.adapt(uk_plug, ex.EUStandard)
self.assertIsNotNone(eu_plug)
self.assertIsInstance(eu_plug, ex.UKStandardToEUStandard)

def test_global_convenience_functions(self):
ex = self.examples

# Global `register_factory`.
register_factory(
factory = ex.UKStandardToEUStandard,
from_protocol = ex.UKStandard,
to_protocol = ex.EUStandard
)

uk_plug = ex.UKPlug()
# Global `adapt`.
eu_plug = adapt(uk_plug, ex.EUStandard)
self.assertIsNotNone(eu_plug)
self.assertIsInstance(eu_plug, ex.UKStandardToEUStandard)

# Global `provides_protocol`.
self.assertTrue(provides_protocol(ex.UKPlug, ex.UKStandard))

# Global `supports_protocol`.
self.assertTrue(supports_protocol(uk_plug, ex.EUStandard))

def test_global_register_provides(self):
from traits.api import Interface

class IFoo(Interface):
pass

obj = {}
# Global `register_provides`.
register_provides(dict, IFoo)
self.assertEqual(obj, adapt(obj, IFoo))

def test_global_register_offer(self):
ex = self.examples

offer = AdaptationOffer(
factory = ex.UKStandardToEUStandard,
from_protocol = ex.UKStandard,
to_protocol = ex.EUStandard
)

# Global `register_offer`.
register_offer(offer)

uk_plug = ex.UKPlug()
eu_plug = adapt(uk_plug, ex.EUStandard)
self.assertIsNotNone(eu_plug)
self.assertIsInstance(eu_plug, ex.UKStandardToEUStandard)


if __name__ == '__main__':
unittest.main()

#### EOF ######################################################################
2 changes: 2 additions & 0 deletions traits/tests/test_interface_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from traits.testing.unittest_tools import unittest

# Enthought library imports.
from traits.adaptation.api import reset_global_adaptation_manager
from traits.api import Adapter, HasTraits, Instance, Int, Interface, \
provides, register_factory

Expand All @@ -35,6 +36,7 @@ class InterfaceCheckerTestCase(unittest.TestCase):

def setUp(self):
""" Prepares the test fixture before each test method is called. """
reset_global_adaptation_manager()

return

Expand Down
19 changes: 14 additions & 5 deletions traits/tests/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from traits.api import (HasTraits, Adapter, AdaptsTo, Instance, Int, Interface,
List, provides, register_factory, Supports, TraitError)
from traits.adaptation.api import reset_global_adaptation_manager


class IFoo(Interface):
Expand Down Expand Up @@ -144,14 +145,22 @@ def get_foo(self):
def get_foo_plus(self):
return (self.obj.get_foo() + 1)

register_factory(SampleListAdapter, Sample, IList)
register_factory(ListAverageAdapter, IList, IAverage)
register_factory(SampleFooAdapter, Sample, IFoo)
register_factory(FooPlusAdapter, IFoo, IFooPlus)


class InterfacesTest(unittest.TestCase):

#### 'TestCase' protocol ##################################################

def setUp(self):
reset_global_adaptation_manager()

# Register adapters.
register_factory(SampleListAdapter, Sample, IList)
register_factory(ListAverageAdapter, IList, IAverage)
register_factory(SampleFooAdapter, Sample, IFoo)
register_factory(FooPlusAdapter, IFoo, IFooPlus)

#### Tests ################################################################

def test_provides_none(self):
@provides()
class Test(HasTraits):
Expand Down
17 changes: 16 additions & 1 deletion traits/tests/test_interfaces_with_implements.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,19 @@
import sys

from traits.testing.unittest_tools import unittest

from traits.adaptation.api import get_global_adaptation_manager, \
set_global_adaptation_manager
from traits.api import HasTraits, Adapter, adapts, AdaptsTo, \
implements, Instance, Int, Interface, List, Supports, TraitError


# Using the deprecated class advisor "adapts", the registration of adapters
# occurs globally at class definition time. Since other tests will reset the
# global adaptation manager, the registration will be lost.
# That's why we save a reference to the current global adaptation manager.
_adaptation_manager = get_global_adaptation_manager()


if sys.version_info[0] >= 3:
import nose
raise nose.SkipTest("""
Expand Down Expand Up @@ -174,6 +182,13 @@ def get_foo_plus(self):

class InterfacesTest(unittest.TestCase):

#### 'TestCase' protocol ##################################################

def setUp(self):
set_global_adaptation_manager(_adaptation_manager)

#### Tests ################################################################

def test_implements_none(self):
class Test(HasTraits):
implements()
Expand Down

0 comments on commit eb6a4da

Please sign in to comment.