Skip to content

Commit

Permalink
Begin breaking the Pyramid dependency and expose a hook for users to …
Browse files Browse the repository at this point in the history
…call.

Fixes #27.
  • Loading branch information
jamadden committed Sep 22, 2017
1 parent eb843d6 commit d6afe7f
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 25 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Expand Up @@ -11,3 +11,7 @@
- Drop support for externalizing to plists. See
https://github.com/NextThought/nti.externalization/issues/21
- Reach 100% test coverage and ensure we remain there through CI.
- Introduce ``nti.externalization.extension_points`` to hold hook
functions. Move the Pyramid integration there (and deprecate that).
Also move the NTIID support there (but the old name works too).
See https://github.com/NextThought/nti.externalization/issues/27
6 changes: 6 additions & 0 deletions docs/api.rst
Expand Up @@ -79,3 +79,9 @@ Package IO
.. automodule:: nti.externalization.autopackage
:private-members:
:special-members:

Extension Points
================

.. automodule:: nti.externalization.extension_points
.. autofunction:: nti.externalization.extension_points.get_current_request
2 changes: 2 additions & 0 deletions docs/conf.py
Expand Up @@ -171,7 +171,9 @@
'https://persistent.readthedocs.io/en/latest': None,
'https://zopecomponent.readthedocs.io/en/latest': None,
'https://zopedublincore.readthedocs.io/en/latest': None,
'https://zopehookable.readthedocs.io/en/latest': None,
'https://zodb.readthedocs.io/en/latest': None,
"https://docs.pylonsproject.org/projects/pyramid/en/latest/": None,
}

extlinks = {
Expand Down
@@ -1,10 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Extension points for connecting to Pyramid.
XXX TODO: Define get_current_request as a zope.hookable function
and let the application determine what framework to connect to.
Thread local utilities.
"""

from __future__ import absolute_import
Expand All @@ -14,14 +11,6 @@
# stdlib imports
import threading

try:
from pyramid.threadlocal import get_current_request
except ImportError:
def get_current_request():
return None
else: # pragma: no cover
get_current_request = get_current_request

class ThreadLocalManager(threading.local):

def __init__(self, default=None):
Expand Down
93 changes: 93 additions & 0 deletions src/nti/externalization/extension_points.py
@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
"""
Extension points for integrating with other applications,
frameworks, and libraries.
The normal extension point for this package is :mod:`zope.component`
and :mod:`zope.interface`. The particular extension points found here
are different for two reasons:
1. There is expected to be only one way that a given application will
want to configure the extension point.
2. They are believed to be so performance critical to normal
operations that the use of a component utility lookup would be
noticeably detrimental.
For those two reasons, these extension points are both developed with
:mod:`zope.hookable`, which provides a very low-overhead way to invoke
a function while allowing for it to be extended. Applications that
need to changed the behaviour of the built-in functions supplied here will
need to call their :func:`zope.hookable.hookable.sethook` method at
startup.
.. versionadded:: 1.0
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from zope.hookable import hookable as _base_hookable

from ._compat import to_unicode
from .oids import to_external_oid
from .interfaces import StandardExternalFields

class _hookable(_base_hookable):
# zope.hookable doesn't expose docstrings, so we need to do it
# manually.
# NOTE: You must manually list these with ..autofunction:: in the api
# document.
__doc__ = property(lambda self: self.original.__doc__)
# Unless the first line of the docstring for the function looks like
# a signature, we get a warning without these properties.
__bases__ = property(lambda _: ())
__dict__ = property(lambda _: None)


@_hookable
def get_current_request():
"""
get_current_request() -> request
In a request/response system like a WSGI server,
return an object representing the current request.
In some cases, this may be used to find adapters for objects.
It is also passed to the ``toExternalObject`` function of
each object as a keyword parameter.
In version 1.0, this will default to using Pyramid's
:func:`pyramid.threadlocal.get_current_request` if pyramid is
installed. However, in a future version, an application wishing
to use Pyramid's request will explicitly need to set the hook.
.. deprecated:: 1.0
The automatic fallback to Pyramid. It will be removed
in 1.1 or before.
"""


try:
from pyramid import threadlocal
except ImportError:
pass
else: # pragma: no cover
get_current_request.sethook(threadlocal.get_current_request)
del threadlocal


_StandardExternalFields_OID = StandardExternalFields.OID
_StandardExternalFields_NTIID = StandardExternalFields.NTIID
del StandardExternalFields


@_hookable
def set_external_identifiers(self, result):
# XXX: Document me
ntiid = oid = to_unicode(to_external_oid(self))
if ntiid:
result[_StandardExternalFields_OID] = oid
result[_StandardExternalFields_NTIID] = ntiid
return (oid, ntiid)
19 changes: 6 additions & 13 deletions src/nti/externalization/externalization.py
Expand Up @@ -28,15 +28,14 @@

import zope.deferredimport
from zope.dublincore.interfaces import IDCTimes
from zope.hookable import hookable
from zope.interface.common.sequence import IFiniteSequence
from zope.security.interfaces import IPrincipal
from zope.security.management import system_user

from nti.externalization._compat import to_unicode
from nti.externalization._compat import identity
from nti.externalization._pyramid import ThreadLocalManager
from nti.externalization._pyramid import get_current_request
from nti.externalization._threadlocal import ThreadLocalManager
from nti.externalization.extension_points import get_current_request
from nti.externalization.extension_points import set_external_identifiers
from nti.externalization.interfaces import IExternalMappingDecorator
from nti.externalization.interfaces import IExternalObject
from nti.externalization.interfaces import IExternalObjectDecorator
Expand All @@ -46,7 +45,6 @@
from nti.externalization.interfaces import LocatedExternalDict
from nti.externalization.interfaces import StandardExternalFields
from nti.externalization.interfaces import StandardInternalFields
from nti.externalization.oids import to_external_oid


logger = __import__('logging').getLogger(__name__)
Expand Down Expand Up @@ -402,7 +400,9 @@ def choose_field(result, self, ext_name,
converter=identity,
fields=(),
sup_iface=None, sup_fields=(), sup_converter=identity):

# XXX: We have a public user of this in nti.ntiids.oids. We need
# to document this and probably move it to a different module, or
# provide a cleaner simpler replacement.
for x in fields:
try:
value = getattr(self, x)
Expand Down Expand Up @@ -499,13 +499,6 @@ def _ext_class_if_needed(self, result):
result[StandardExternalFields_CLASS] = self.__class__.__name__


def setExternalIdentifiers(self, result):
ntiid = oid = to_unicode(to_external_oid(self))
if ntiid:
result[StandardExternalFields_OID] = oid
result[StandardExternalFields_NTIID] = ntiid
return (oid, ntiid)
set_external_identifiers = hookable(setExternalIdentifiers)

def _should_never_convert(x):
raise AssertionError("We should not be converting")
Expand Down

0 comments on commit d6afe7f

Please sign in to comment.