summaryrefslogtreecommitdiff
path: root/src/zope/interface/_compat.py
blob: f8b7887428461e14f654dfbd83921501e4d5ee30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
##############################################################################
#
# Copyright (c) 2006 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
Support functions for dealing with differences in platforms, including Python
versions and implementations.

This file should have no imports from the rest of zope.interface because it is
used during early bootstrapping.
"""
import os
import sys
import types

if sys.version_info[0] < 3:

    def _normalize_name(name):
        if isinstance(name, basestring):
            return unicode(name)
        raise TypeError("name must be a regular or unicode string")

    CLASS_TYPES = (type, types.ClassType)
    STRING_TYPES = (basestring,)

    _BUILTINS = '__builtin__'

    PYTHON3 = False
    PYTHON2 = True

else:

    def _normalize_name(name):
        if isinstance(name, bytes):
            name = str(name, 'ascii')
        if isinstance(name, str):
            return name
        raise TypeError("name must be a string or ASCII-only bytes")

    CLASS_TYPES = (type,)
    STRING_TYPES = (str,)

    _BUILTINS = 'builtins'

    PYTHON3 = True
    PYTHON2 = False

def _skip_under_py3k(test_method):
    import unittest
    return unittest.skipIf(sys.version_info[0] >= 3, "Only on Python 2")(test_method)


def _skip_under_py2(test_method):
    import unittest
    return unittest.skipIf(sys.version_info[0] < 3, "Only on Python 3")(test_method)


def _c_optimizations_required():
    """
    Return a true value if the C optimizations are required.

    This uses the ``PURE_PYTHON`` variable as documented in `_use_c_impl`.
    """
    pure_env = os.environ.get('PURE_PYTHON')
    require_c = pure_env == "0"
    return require_c


def _c_optimizations_available():
    """
    Return the C optimization module, if available, otherwise
    a false value.

    If the optimizations are required but not available, this
    raises the ImportError.

    This does not say whether they should be used or not.
    """
    catch = () if _c_optimizations_required() else (ImportError,)
    try:
        from zope.interface import _zope_interface_coptimizations as c_opt
        return c_opt
    except catch: # pragma: no cover (only Jython doesn't build extensions)
        return False


def _c_optimizations_ignored():
    """
    The opposite of `_c_optimizations_required`.
    """
    pure_env = os.environ.get('PURE_PYTHON')
    return pure_env is not None and pure_env != "0"


def _should_attempt_c_optimizations():
    """
    Return a true value if we should attempt to use the C optimizations.

    This takes into account whether we're on PyPy and the value of the
    ``PURE_PYTHON`` environment variable, as defined in `_use_c_impl`.
    """
    is_pypy = hasattr(sys, 'pypy_version_info')

    if _c_optimizations_required():
        return True
    if is_pypy:
        return False
    return not _c_optimizations_ignored()


def _use_c_impl(py_impl, name=None, globs=None):
    """
    Decorator. Given an object implemented in Python, with a name like
    ``Foo``, import the corresponding C implementation from
    ``zope.interface._zope_interface_coptimizations`` with the name
    ``Foo`` and use it instead.

    If the ``PURE_PYTHON`` environment variable is set to any value
    other than ``"0"``, or we're on PyPy, ignore the C implementation
    and return the Python version. If the C implementation cannot be
    imported, return the Python version. If ``PURE_PYTHON`` is set to
    0, *require* the C implementation (let the ImportError propagate);
    note that PyPy can import the C implementation in this case (and all
    tests pass).

    In all cases, the Python version is kept available. in the module
    globals with the name ``FooPy`` and the name ``FooFallback`` (both
    conventions have been used; the C implementation of some functions
    looks for the ``Fallback`` version, as do some of the Sphinx
    documents).

    Example::

        @_use_c_impl
        class Foo(object):
            ...
    """
    name = name or py_impl.__name__
    globs = globs or sys._getframe(1).f_globals

    def find_impl():
        if not _should_attempt_c_optimizations():
            return py_impl

        c_opt = _c_optimizations_available()
        if not c_opt: # pragma: no cover (only Jython doesn't build extensions)
            return py_impl

        __traceback_info__ = c_opt
        return getattr(c_opt, name)

    c_impl = find_impl()
    # Always make available by the FooPy name and FooFallback
    # name (for testing and documentation)
    globs[name + 'Py'] = py_impl
    globs[name + 'Fallback'] = py_impl
    return c_impl