From 029af3efc9641f33e58152826a3a46e39b1853cd Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 24 Feb 2014 10:17:16 +0000 Subject: [PATCH 1/7] Allow setting and resetting the global adaptation manager. The global adaptation manager is used by traits for automatic adaptation. Setting and resetting it is useful at application start-up, but especially during testing to reset the global state. --- traits/adaptation/adaptation_manager.py | 62 ++++++++- traits/adaptation/api.py | 5 +- .../tests/test_adaptation_manager.py | 4 +- .../tests/test_global_adaptation_manager.py | 123 ++++++++++++++++++ 4 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 traits/adaptation/tests/test_global_adaptation_manager.py diff --git a/traits/adaptation/adaptation_manager.py b/traits/adaptation/adaptation_manager.py index c8570be58..8d06d646a 100644 --- a/traits/adaptation/adaptation_manager.py +++ b/traits/adaptation/adaptation_manager.py @@ -362,19 +362,69 @@ def _by_weight_then_from_protocol_specificity(edge_1, edge_2): #: 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 ###################################################################### diff --git a/traits/adaptation/api.py b/traits/adaptation/api.py index 465d10970..ebd28d4d5 100644 --- a/traits/adaptation/api.py +++ b/traits/adaptation/api.py @@ -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 diff --git a/traits/adaptation/tests/test_adaptation_manager.py b/traits/adaptation/tests/test_adaptation_manager.py index 0060d3f80..995f971f2 100644 --- a/traits/adaptation/tests/test_adaptation_manager.py +++ b/traits/adaptation/tests/test_adaptation_manager.py @@ -1,4 +1,4 @@ -""" Test the adapter manager. """ +""" Test the adaptation manager. """ import sys @@ -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 diff --git a/traits/adaptation/tests/test_global_adaptation_manager.py b/traits/adaptation/tests/test_global_adaptation_manager.py new file mode 100644 index 000000000..a0a8901cf --- /dev/null +++ b/traits/adaptation/tests/test_global_adaptation_manager.py @@ -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 TestAdapter(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 ###################################################################### From c4755e79ba0006b692c478422848fa48d2f7040e Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 24 Feb 2014 11:17:55 +0000 Subject: [PATCH 2/7] Fix traits tests that were registering adapters to the global manager. --- traits/tests/test_interface_checker.py | 2 ++ traits/tests/test_interfaces.py | 19 ++++++++++++++----- .../tests/test_interfaces_with_implements.py | 17 ++++++++++++++++- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/traits/tests/test_interface_checker.py b/traits/tests/test_interface_checker.py index aa78a6435..b7cbd8610 100644 --- a/traits/tests/test_interface_checker.py +++ b/traits/tests/test_interface_checker.py @@ -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 @@ -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 diff --git a/traits/tests/test_interfaces.py b/traits/tests/test_interfaces.py index 0106e8350..8ebfe4186 100644 --- a/traits/tests/test_interfaces.py +++ b/traits/tests/test_interfaces.py @@ -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): @@ -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): diff --git a/traits/tests/test_interfaces_with_implements.py b/traits/tests/test_interfaces_with_implements.py index 79bc6646f..acb78e318 100644 --- a/traits/tests/test_interfaces_with_implements.py +++ b/traits/tests/test_interfaces_with_implements.py @@ -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 deprecates 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(""" @@ -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() From 1aa8a4fdec948e693263c4bc62f1862f9e95a884 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 24 Feb 2014 11:20:08 +0000 Subject: [PATCH 3/7] Update CHANGES.txt --- CHANGES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.txt b/CHANGES.txt index e8191ad75..b42ea27a8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -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 From a196c9b501cbb79fc3f43ed58687eea433d84580 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 24 Feb 2014 11:37:19 +0000 Subject: [PATCH 4/7] Document changes. --- .../traits.adaptation.rst | 6 ------ docs/source/traits_user_manual/advanced.rst | 19 +++++++++++++++++++ traits/adaptation/adaptation_manager.py | 10 +++++----- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/docs/source/traits_api_reference/traits.adaptation.rst b/docs/source/traits_api_reference/traits.adaptation.rst index 9cc499000..9765fe588 100644 --- a/docs/source/traits_api_reference/traits.adaptation.rst +++ b/docs/source/traits_api_reference/traits.adaptation.rst @@ -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 ------------------------------ diff --git a/docs/source/traits_user_manual/advanced.rst b/docs/source/traits_user_manual/advanced.rst index 9aa94b9c5..2287d4e46 100644 --- a/docs/source/traits_user_manual/advanced.rst +++ b/docs/source/traits_user_manual/advanced.rst @@ -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 troubles, 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 ````````````````````````````````````` diff --git a/traits/adaptation/adaptation_manager.py b/traits/adaptation/adaptation_manager.py index 8d06d646a..e22440107 100644 --- a/traits/adaptation/adaptation_manager.py +++ b/traits/adaptation/adaptation_manager.py @@ -361,11 +361,11 @@ def _by_weight_then_from_protocol_specificity(edge_1, edge_2): return 0 -#: 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`. +# 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() From 5d0a93daef9819d60dc78f23204ad4d41103e907 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 24 Feb 2014 13:50:08 +0000 Subject: [PATCH 5/7] Fix typo in docs. --- docs/source/traits_user_manual/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/traits_user_manual/advanced.rst b/docs/source/traits_user_manual/advanced.rst index 2287d4e46..656693da8 100644 --- a/docs/source/traits_user_manual/advanced.rst +++ b/docs/source/traits_user_manual/advanced.rst @@ -1023,7 +1023,7 @@ Gotchas 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 troubles, for the usual + 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` From ef7ec8fa80a46b580deb1070234abceb3ef2f9e2 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Mon, 24 Feb 2014 13:52:14 +0000 Subject: [PATCH 6/7] Fix typos. --- traits/adaptation/tests/test_global_adaptation_manager.py | 2 +- traits/tests/test_interfaces_with_implements.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/traits/adaptation/tests/test_global_adaptation_manager.py b/traits/adaptation/tests/test_global_adaptation_manager.py index a0a8901cf..4236dbebc 100644 --- a/traits/adaptation/tests/test_global_adaptation_manager.py +++ b/traits/adaptation/tests/test_global_adaptation_manager.py @@ -9,7 +9,7 @@ from traits.testing.unittest_tools import unittest -class TestAdapter(unittest.TestCase): +class TestGlobalAdaptationManager(unittest.TestCase): """ Test the setting/getting/resetting/using the global adaptation manager. """ diff --git a/traits/tests/test_interfaces_with_implements.py b/traits/tests/test_interfaces_with_implements.py index acb78e318..ce4841d7b 100644 --- a/traits/tests/test_interfaces_with_implements.py +++ b/traits/tests/test_interfaces_with_implements.py @@ -29,7 +29,7 @@ implements, Instance, Int, Interface, List, Supports, TraitError -# Using the deprecates class advisor "adapts", the registration of adapters +# 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. From 73fdb517559cb0355dfb5a6444a0c9f442c02572 Mon Sep 17 00:00:00 2001 From: Pietro Berkes Date: Tue, 25 Feb 2014 08:50:59 +0000 Subject: [PATCH 7/7] Fix style issues. --- traits/adaptation/tests/test_global_adaptation_manager.py | 2 +- traits/tests/test_interfaces_with_implements.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/traits/adaptation/tests/test_global_adaptation_manager.py b/traits/adaptation/tests/test_global_adaptation_manager.py index 4236dbebc..6cf256453 100644 --- a/traits/adaptation/tests/test_global_adaptation_manager.py +++ b/traits/adaptation/tests/test_global_adaptation_manager.py @@ -33,7 +33,7 @@ def test_reset_adaptation_manager(self): adaptation_manager.register_factory( factory = ex.UKStandardToEUStandard, from_protocol = ex.UKStandard, - to_protocol = ex.EUStandard + to_protocol = ex.EUStandard, ) # Create a UKPlug. diff --git a/traits/tests/test_interfaces_with_implements.py b/traits/tests/test_interfaces_with_implements.py index ce4841d7b..4d7ededd7 100644 --- a/traits/tests/test_interfaces_with_implements.py +++ b/traits/tests/test_interfaces_with_implements.py @@ -23,7 +23,7 @@ import sys from traits.testing.unittest_tools import unittest -from traits.adaptation.api import get_global_adaptation_manager,\ +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