From c5f708ea1752b3dcdd2ac3eab6b424c50b6c9cca Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Mon, 30 Jul 2018 12:24:06 -0500 Subject: [PATCH] Look for IInternalObjectIO adapters as well as INamedExternalizedObjectFactoryFinder Objects inheriting from ``InterfaceObjectIO`` and registered with the component registry (in ZCML) for ``IInternalObjectIO`` can still be found and used as ``INamedExternalizedObjectFactoryFinder``, an interface implemented by ``InterfaceObjectIO`` through ``IInternalObjectIOFinder``. A warning will be issued to update the registration (which generally means removing the ``provides`` line in ZCML). This should help in legacy cases. See https://github.com/NextThought/nti.xapi/pull/25#discussion_r206176747 --- CHANGES.rst | 8 +++++- .../internalization/_updater.pxd | 2 ++ .../tests/test_externals_wo_class_mimetype.py | 25 +++++++++++++++++ .../internalization/updater.py | 28 +++++++++++++++++-- 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 82db6ed..cce65eb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,7 +6,13 @@ 1.0.0a5 (unreleased) ==================== -- Nothing changed yet. +- Objects inheriting from ``InterfaceObjectIO`` and registered with + the component registry (in ZCML) for ``IInternalObjectIO`` can still + be found and used as ``INamedExternalizedObjectFactoryFinder``, an + interface implemented by ``InterfaceObjectIO`` through + ``IInternalObjectIOFinder``. A warning will be issued to update the + registration (which generally means removing the ``provides`` line + in ZCML). 1.0.0a4 (2018-07-30) diff --git a/src/nti/externalization/internalization/_updater.pxd b/src/nti/externalization/internalization/_updater.pxd index df0c707..2cd4791 100644 --- a/src/nti/externalization/internalization/_updater.pxd +++ b/src/nti/externalization/internalization/_updater.pxd @@ -16,6 +16,7 @@ cdef iteritems cdef component cdef interface cdef IInternalObjectUpdater +cdef IInternalObjectIO cdef INamedExternalizedObjectFactoryFinder # optimizations @@ -53,6 +54,7 @@ cdef _invoke_updater(containedObject, externalObject, updater, cdef _update_sequence(externalObject, _RecallArgs args, destination_name=*, find_factory_for_named_value=*) +cpdef _find_INamedExternalizedObjectFactoryFinder(containedObject, registry) cdef _update_from_external_object(containedObject, externalObject, _RecallArgs args) cpdef update_from_external_object(containedObject, diff --git a/src/nti/externalization/internalization/tests/test_externals_wo_class_mimetype.py b/src/nti/externalization/internalization/tests/test_externals_wo_class_mimetype.py index 8aee9d6..957cb4e 100644 --- a/src/nti/externalization/internalization/tests/test_externals_wo_class_mimetype.py +++ b/src/nti/externalization/internalization/tests/test_externals_wo_class_mimetype.py @@ -17,6 +17,8 @@ from hamcrest import is_ from hamcrest import assert_that from hamcrest import has_property as has_attr +from hamcrest import has_length +from hamcrest import contains_string from zope import interface from zope import component @@ -27,7 +29,9 @@ from zope.testing.cleanup import CleanUp +from nti.externalization import interfaces from nti.externalization.internalization import update_from_external_object +from nti.externalization.internalization import updater from nti.externalization.datastructures import InterfaceObjectIO @@ -214,6 +218,27 @@ def test_nested_single_object_field_names_match_non_primitive_zcml(self): assert_that(root.field, has_attr('nested', has_attr('value', 42))) +class TestLookups(CleanUp, + unittest.TestCase): + + def test_InterfaceObjectIO_subclass_registered_as_IInternalObjectIO(self): + # We still find it even if it's registered with the legacy interface. + import warnings + + class Derived(InterfaceObjectIO): + def __init__(self, context): # pylint:disable=super-init-not-called + pass + + component.provideAdapter(Derived, (object,), provides=interfaces.IInternalObjectIO) + + with warnings.catch_warnings(record=True) as w: + found = updater._find_INamedExternalizedObjectFactoryFinder(self, component) + + assert_that(found, is_(Derived)) + assert_that(w, has_length(1)) + assert_that(str(w[0].message), contains_string('was registered as IInternalObjectIO')) + + class IOBase(object): @classmethod diff --git a/src/nti/externalization/internalization/updater.py b/src/nti/externalization/internalization/updater.py index a8927e5..8c0785d 100644 --- a/src/nti/externalization/internalization/updater.py +++ b/src/nti/externalization/internalization/updater.py @@ -29,6 +29,7 @@ from nti.externalization._base_interfaces import PRIMITIVES from nti.externalization.interfaces import IInternalObjectUpdater +from nti.externalization.interfaces import IInternalObjectIO from nti.externalization.interfaces import INamedExternalizedObjectFactoryFinder from .factories import find_factory_for @@ -287,6 +288,29 @@ def _invoke_updater(containedObject, externalObject, updater, external_keys, _EMPTY_DICT) +def _find_INamedExternalizedObjectFactoryFinder(containedObject, registry): + updater = registry.queryAdapter(containedObject, INamedExternalizedObjectFactoryFinder) + if updater is None: + # Ok, check to see if an instance of the old root interface + # InternalObjectIO is there and also provides INamedExternalizedObjectFactoryFinder; + # if so, there's a bad ZCML registration. + updater = registry.queryAdapter(containedObject, IInternalObjectIO) + if INamedExternalizedObjectFactoryFinder.providedBy(updater): + warnings.warn( + "The adapter %r was registered as IInternalObjectIO when it should be " + "IInternalObjectIOFinder; a provides= ZCML directive is probably outdated. " + "If the object extends InterfacObjectIO, no provides= is usually necessary." + % (updater,), + UserWarning + ) + else: + updater = None + + if updater is None: + updater = _default_factory_finder + return updater + + def _update_from_external_object(containedObject, externalObject, args): # Parse any contained objects @@ -309,8 +333,8 @@ def _update_from_external_object(containedObject, externalObject, args): assert isinstance(externalObject, MutableMapping) - updater = args.registry.queryAdapter(containedObject, INamedExternalizedObjectFactoryFinder, - u'', _default_factory_finder) + updater = _find_INamedExternalizedObjectFactoryFinder(containedObject, args.registry) + find_factory_for_named_value = updater.find_factory_for_named_value # We have to save the list of keys, it's common that they get popped during the update