Skip to content

Commit

Permalink
Merge pull request #110 from NextThought/issue109
Browse files Browse the repository at this point in the history
Introduce the concept of ExternalizationPolicies.
  • Loading branch information
jamadden committed Aug 3, 2020
2 parents 004a1fc + aa4b4e0 commit 00bac05
Show file tree
Hide file tree
Showing 19 changed files with 445 additions and 65 deletions.
14 changes: 12 additions & 2 deletions CHANGES.rst
Expand Up @@ -3,10 +3,20 @@
=========


2.0.1 (unreleased)
2.1.0 (unreleased)
==================

- Nothing changed yet.
- Add support for "externalization policies." These are instances of
``ExternalizationPolicy`` that can be used to tweak certain
low-level details of externalization without replacing
externalization objects wholesale. They are intended to have a very
low performance impact.

The only supported detail that can be changed right now is whether
the standard created and last modified fields are externalized as
Unix timestamps (the default) or as ISO 8601 strings.

See https://github.com/NextThought/nti.externalization/issues/109


2.0.0 (2020-07-02)
Expand Down
9 changes: 7 additions & 2 deletions docs/api/interfaces.rst
Expand Up @@ -2,8 +2,13 @@
``nti.externalization.interfaces``: Interfaces and constants
==============================================================

.. autoclass:: nti.externalization._base_interfaces.LocatedExternalDict

.. automodule:: nti.externalization.interfaces
..
For some reason, even though these are in __all__, they don't get
documented unless we list them explicitly.
.. autoclass:: nti.externalization._base_interfaces.StandardExternalFields
:member-order: bysource
.. autoclass:: nti.externalization._base_interfaces.StandardInternalFields
.. automodule:: nti.externalization.interfaces
.. autodata:: nti.externalization._base_interfaces.DEFAULT_EXTERNALIZATION_POLICY
10 changes: 9 additions & 1 deletion docs/conf.py
Expand Up @@ -210,7 +210,15 @@
'pr': ('https://github.com/NextThought/nti.externalization/pull/%s',
'pull request #')}

autodoc_default_flags = ['members', 'show-inheritance']
# Sphinx 1.8+ prefers this to `autodoc_default_flags`. It's documented that
# either True or None mean the same thing as just setting the flag, but
# only None works in 1.8 (True works in 2.0)
autodoc_default_options = {
'members': None,
'show-inheritance': None,
}

autoclass_content = 'both'
# This causes the order in __all__ to be ignored :(
# autodoc_member_order = 'bysource'
# autodoc_member_order = 'groupwise'
6 changes: 4 additions & 2 deletions setup.py
Expand Up @@ -118,7 +118,9 @@ def _c(m):
'nti.externalization.' + mod_name,
sources=[source],
depends=deps,
define_macros=[('CYTHON_TRACE', '1')],
define_macros=[
# ('CYTHON_TRACE', '1')
],
))

try:
Expand All @@ -145,7 +147,7 @@ def _c(m):

setup(
name='nti.externalization',
version='2.0.1.dev0',
version='2.1.0.dev0',
author='Jason Madden',
author_email='jason@nextthought.com',
description="NTI Externalization",
Expand Down
8 changes: 8 additions & 0 deletions src/nti/externalization/__base_interfaces.pxd
Expand Up @@ -59,3 +59,11 @@ cdef class StandardInternalFields(object):
cdef StandardInternalFields _standard_internal_fields

cpdef StandardInternalFields get_standard_internal_fields()

@cython.final
cdef class ExternalizationPolicy(object):
cdef readonly bint use_iso8601_for_unix_timestamp

cdef ExternalizationPolicy DEFAULT_EXTERNALIZATION_POLICY

cpdef ExternalizationPolicy get_default_externalization_policy()
44 changes: 44 additions & 0 deletions src/nti/externalization/_base_interfaces.py
Expand Up @@ -20,6 +20,7 @@
__all__ = [
'NotGiven',
'LocatedExternalDict',
'ExternalizationPolicy',
]

class _NotGiven(object):
Expand Down Expand Up @@ -270,6 +271,49 @@ def get_standard_internal_fields():
) + _PRIMITIVE_NUMBER_TYPES


class ExternalizationPolicy(object):
"""
Adjustment knobs for making tweaks across an entire
externalization.
These knobs will tweak low-level details of the externalization
format, details that are often in a hot code path where overhead
should be kept to a minimum.
Instances of this class are used by registering them as named
components in the global site manager. Certain low-level functions
accept an optional *policy* argument that must be an instance of this class;
higher level functions accept a *policy_name* argument that is used to
find the registered component. If either argument is not given, then
`DEFAULT_EXTERNALIZATION_POLICY` is used instead.
Instances are immutable.
This class must not be subclassed; as such, there is no interface
for it, merely the class itself.
"""

__slots__ = (
'use_iso8601_for_unix_timestamp',
)

def __init__(self, use_iso8601_for_unix_timestamp=False):
#: Should unix timestamp fields be output as their numeric value,
#: or be converted into an ISO 8601 timestamp string? By default,
#: the numeric value is output. This is known to specifically apply
#: to "Created Time" and "Last Modified."
self.use_iso8601_for_unix_timestamp = use_iso8601_for_unix_timestamp

def __repr__(self): # pragma: no cover
return "ExternalizationPolicy(use_iso8601_for_unix_timestamp=%s)" % (
self.use_iso8601_for_unix_timestamp
)

#: The default externalization policy.
DEFAULT_EXTERNALIZATION_POLICY = ExternalizationPolicy()

def get_default_externalization_policy():
return DEFAULT_EXTERNALIZATION_POLICY

from nti.externalization._compat import import_c_accel # pylint:disable=wrong-import-position
import_c_accel(globals(), 'nti.externalization.__base_interfaces')
3 changes: 3 additions & 0 deletions src/nti/externalization/_datastructures.pxd
Expand Up @@ -11,6 +11,8 @@ from nti.externalization.__base_interfaces cimport get_standard_external_fields
from nti.externalization.__base_interfaces cimport StandardExternalFields as SEF
from nti.externalization.__base_interfaces cimport get_standard_internal_fields
from nti.externalization.__base_interfaces cimport StandardInternalFields as SIF
from nti.externalization.__base_interfaces cimport get_default_externalization_policy
from nti.externalization.__base_interfaces cimport ExternalizationPolicy

from nti.externalization.internalization._fields cimport validate_named_field_value
from nti.externalization.internalization._factories cimport find_factory_for
Expand All @@ -24,6 +26,7 @@ cdef IInternalObjectIOFinder
cdef IAnonymousObjectFactory
cdef SEF StandardExternalFields
cdef SIF StandardInternalFields
cdef ExternalizationPolicy DEFAULT_EXTERNALIZATION_POLICY
cdef validate_named_field_value
cdef make_repr
cdef isSyntheticKey
Expand Down
8 changes: 6 additions & 2 deletions src/nti/externalization/datastructures.py
Expand Up @@ -47,12 +47,14 @@

from ._base_interfaces import get_standard_external_fields
from ._base_interfaces import get_standard_internal_fields
from ._base_interfaces import get_default_externalization_policy
from ._base_interfaces import NotGiven

from ._interface_cache import cache_for

StandardExternalFields = get_standard_external_fields()
StandardInternalFields = get_standard_internal_fields()
DEFAULT_EXTERNALIZATION_POLICY = get_default_externalization_policy()
IDict_providedBy = IDict.providedBy
IObject_providedBy = IObject.providedBy

Expand Down Expand Up @@ -91,7 +93,9 @@ def _ext_standard_external_dictionary(self, replacement, mergeFrom=None, **kwarg
mergeFrom=mergeFrom,
decorate=kwargs.get('decorate', True),
request=kwargs.get('request', NotGiven),
decorate_callback=kwargs.get('decorate_callback', NotGiven))
decorate_callback=kwargs.get('decorate_callback', NotGiven),
policy=kwargs.get("policy", DEFAULT_EXTERNALIZATION_POLICY),
)

def toExternalDictionary(self, mergeFrom=None, *unused_args, **kwargs):
"""
Expand Down Expand Up @@ -444,7 +448,7 @@ def toExternalObject(self, mergeFrom=None, *args, **kwargs):

def toExternalDictionary(self, mergeFrom=None, *unused_args, **kwargs):
"See `ExternalizableDictionaryMixin.toExternalDictionary`"
return self.__make_io().toExternalDictionary(mergeFrom)
return self.__make_io().toExternalDictionary(mergeFrom, **kwargs)

__repr__ = make_repr()

Expand Down
16 changes: 11 additions & 5 deletions src/nti/externalization/externalization/_dictionary.pxd
Expand Up @@ -3,8 +3,10 @@ import cython

from nti.externalization.__base_interfaces cimport make_external_dict
from nti.externalization.__base_interfaces cimport get_standard_external_fields
from nti.externalization.__base_interfaces cimport get_default_externalization_policy
from nti.externalization.__base_interfaces cimport StandardExternalFields as SEF
from nti.externalization.__base_interfaces cimport LocatedExternalDict as LED
from nti.externalization.__base_interfaces cimport ExternalizationPolicy

from ._standard_fields cimport get_last_modified_time
from ._standard_fields cimport get_created_time
Expand All @@ -15,6 +17,7 @@ from ._standard_fields cimport get_class
from ._decorate cimport decorate_external_object

cdef SEF StandardExternalFields
cdef ExternalizationPolicy DEFAULT_EXTERNALIZATION_POLICY


# Imports
Expand All @@ -31,11 +34,14 @@ cdef NotGiven
# Constants


cpdef LED internal_to_standard_external_dictionary(self,
mergeFrom=*,
bint decorate=*,
request=*,
decorate_callback=*)
cpdef LED internal_to_standard_external_dictionary(
self,
mergeFrom=*,
bint decorate=*,
request=*,
decorate_callback=*,
ExternalizationPolicy policy=*,
)


cpdef to_minimal_standard_external_dictionary(self, mergeFrom=*)
12 changes: 10 additions & 2 deletions src/nti/externalization/externalization/_externalizer.pxd
Expand Up @@ -3,6 +3,8 @@ import cython

from nti.externalization.externalization._dictionary cimport internal_to_standard_external_dictionary
from nti.externalization.__base_interfaces cimport LocatedExternalDict as LED
from nti.externalization.__base_interfaces cimport ExternalizationPolicy
from nti.externalization.__base_interfaces cimport get_default_externalization_policy
from nti.externalization.externalization._decorate cimport decorate_external_object

# Imports
Expand All @@ -13,6 +15,7 @@ cdef numbers

cdef queryAdapter
cdef getAdapter
cdef getUtility
cdef IFiniteSequence

cdef ThreadLocalManager
Expand All @@ -27,7 +30,8 @@ cdef INonExternalizableReplacer
cdef DefaultNonExternalizableReplacer
cdef NotGiven
cdef IInternalObjectExternalizer

cdef IExternalizationPolicy
cdef ExternalizationPolicy DEFAULT_EXTERNALIZATION_POLICY

# Constants
cdef logger
Expand Down Expand Up @@ -56,6 +60,8 @@ cdef class _ExternalizationState(object):
cdef bint useCache
cdef decorate_callback

cdef ExternalizationPolicy policy

cdef dict _kwargs

cdef dict as_kwargs(self)
Expand All @@ -79,7 +85,9 @@ cpdef to_external_object(
bint decorate=*,
bint useCache=*,
decorate_callback=*,
default_non_externalizable_replacer=*
default_non_externalizable_replacer=*,
policy_name=*,
policy=*
)

cdef LED _externalize_mapping(obj, _ExternalizationState state)
Expand Down
15 changes: 12 additions & 3 deletions src/nti/externalization/externalization/_standard_fields.pxd
Expand Up @@ -4,6 +4,8 @@ from nti.externalization.__base_interfaces cimport get_standard_external_fields
from nti.externalization.__base_interfaces cimport StandardExternalFields as SEF
from nti.externalization.__base_interfaces cimport get_standard_internal_fields
from nti.externalization.__base_interfaces cimport StandardInternalFields as SIF
from nti.externalization.__base_interfaces cimport ExternalizationPolicy
from nti.externalization.__base_interfaces cimport get_default_externalization_policy

from nti.externalization.externalization._fields cimport choose_field

Expand All @@ -15,6 +17,7 @@ cdef type text_type
# Constants
cdef SEF StandardExternalFields
cdef SIF StandardInternalFields
cdef ExternalizationPolicy DEFAULT_EXTERNALIZATION_POLICY

cdef tuple _LAST_MOD_FIELDS
cdef tuple _LAST_MOD_SUP_FIELDS
Expand All @@ -28,11 +31,17 @@ cdef basestring _SYSTEM_USER_NAME
cdef basestring _SYSTEM_USER_ID
cdef IPrincipal_providedBy


# XXX: If we use ``cdef``, then when we pass these functions to
# ``choose_field``, Cython has to construct a new Python function to wrap them.
# If we use ``cpdef``, then it does a module-level lookup by name. Neither is ideal.
# What if we used classes?
cpdef datetime_to_unix_time(dt)
cpdef timestamp_to_string(timestamp)
cdef _datetime_to_string
cpdef datetime_to_string(dt)

cpdef get_last_modified_time(context, default=*, _write_into=*)
cpdef get_created_time(context, default=*, _write_into=*)
cpdef get_last_modified_time(context, default=*, ExternalizationPolicy policy=*, _write_into=*)
cpdef get_created_time(context, default=*, ExternalizationPolicy policy=*, _write_into=*)

cdef _system_user_converter(obj)
cpdef get_creator(context, default=*, _write_into=*)
Expand Down
14 changes: 11 additions & 3 deletions src/nti/externalization/externalization/dictionary.py
Expand Up @@ -25,6 +25,7 @@


from nti.externalization._base_interfaces import get_standard_external_fields
from nti.externalization._base_interfaces import get_default_externalization_policy


from nti.externalization.externalization.standard_fields import get_last_modified_time
Expand All @@ -36,14 +37,15 @@
from nti.externalization.externalization.decorate import decorate_external_object

StandardExternalFields = get_standard_external_fields()

DEFAULT_EXTERNALIZATION_POLICY = get_default_externalization_policy()

def internal_to_standard_external_dictionary(
self,
mergeFrom=None,
decorate=True,
request=NotGiven,
decorate_callback=NotGiven,
policy=DEFAULT_EXTERNALIZATION_POLICY,
):
# The real implementation of this function. Code in this
# package should use this; code outside of this package *MUST NOT*
Expand All @@ -55,8 +57,8 @@ def internal_to_standard_external_dictionary(

get_creator(self, None, result)

get_last_modified_time(self, None, result)
get_created_time(self, None, result)
get_last_modified_time(self, None, policy, result)
get_created_time(self, None, policy, result)

get_container_id(self, None, result)

Expand All @@ -77,6 +79,7 @@ def to_standard_external_dictionary(
decorate=True,
request=NotGiven,
decorate_callback=NotGiven,
policy=DEFAULT_EXTERNALIZATION_POLICY,
# These are ignored, present for BWC
name=NotGiven,
useCache=NotGiven,
Expand Down Expand Up @@ -107,11 +110,15 @@ def to_standard_external_dictionary(
None, then those values will be added to the dictionary
created by this method. The keys and values in *mergeFrom*
should already be external.
:keyword ExternalizationPolicy policy: The :class:`~.ExternalizationPolicy` to
use. Must not be None.
:returns: A `.LocatedExternalDict`.
.. versionchanged:: 1.0a1
Arbitrary keyword arguments not used by this function are deprecated
and produce a warning.
.. versionchanged:: 2.1
Add the *policy* keyword.
"""

if kwargs or name is not NotGiven or useCache is not NotGiven or registry is not NotGiven: # pragma: no cover
Expand All @@ -126,6 +133,7 @@ def to_standard_external_dictionary(
decorate,
request,
decorate_callback,
policy,
)


Expand Down

0 comments on commit 00bac05

Please sign in to comment.