1##############################################################################
2#
3# Copyright (c) 2006 Zope Foundation and Contributors.
4# All Rights Reserved.
5#
6# This software is subject to the provisions of the Zope Public License,
7# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11# FOR A PARTICULAR PURPOSE.
12#
13##############################################################################
14"""
15Support functions for dealing with differences in platforms, including Python
16versions and implementations.
17
18This file should have no imports from the rest of zope.interface because it is
19used during early bootstrapping.
20"""
21import os
22import sys
23import types
24
25if sys.version_info[0] < 3:
26
27    def _normalize_name(name):
28        if isinstance(name, basestring):
29            return unicode(name)
30        raise TypeError("name must be a regular or unicode string")
31
32    CLASS_TYPES = (type, types.ClassType)
33    STRING_TYPES = (basestring,)
34
35    _BUILTINS = '__builtin__'
36
37    PYTHON3 = False
38    PYTHON2 = True
39
40else:
41
42    def _normalize_name(name):
43        if isinstance(name, bytes):
44            name = str(name, 'ascii')
45        if isinstance(name, str):
46            return name
47        raise TypeError("name must be a string or ASCII-only bytes")
48
49    CLASS_TYPES = (type,)
50    STRING_TYPES = (str,)
51
52    _BUILTINS = 'builtins'
53
54    PYTHON3 = True
55    PYTHON2 = False
56
57PYPY = hasattr(sys, 'pypy_version_info')
58PYPY2 = PYTHON2 and PYPY
59
60def _skip_under_py3k(test_method):
61    import unittest
62    return unittest.skipIf(sys.version_info[0] >= 3, "Only on Python 2")(test_method)
63
64
65def _skip_under_py2(test_method):
66    import unittest
67    return unittest.skipIf(sys.version_info[0] < 3, "Only on Python 3")(test_method)
68
69
70def _c_optimizations_required():
71    """
72    Return a true value if the C optimizations are required.
73
74    This uses the ``PURE_PYTHON`` variable as documented in `_use_c_impl`.
75    """
76    pure_env = os.environ.get('PURE_PYTHON')
77    require_c = pure_env == "0"
78    return require_c
79
80
81def _c_optimizations_available():
82    """
83    Return the C optimization module, if available, otherwise
84    a false value.
85
86    If the optimizations are required but not available, this
87    raises the ImportError.
88
89    This does not say whether they should be used or not.
90    """
91    catch = () if _c_optimizations_required() else (ImportError,)
92    try:
93        from zope.interface import _zope_interface_coptimizations as c_opt
94        return c_opt
95    except catch: # pragma: no cover (only Jython doesn't build extensions)
96        return False
97
98
99def _c_optimizations_ignored():
100    """
101    The opposite of `_c_optimizations_required`.
102    """
103    pure_env = os.environ.get('PURE_PYTHON')
104    return pure_env is not None and pure_env != "0"
105
106
107def _should_attempt_c_optimizations():
108    """
109    Return a true value if we should attempt to use the C optimizations.
110
111    This takes into account whether we're on PyPy and the value of the
112    ``PURE_PYTHON`` environment variable, as defined in `_use_c_impl`.
113    """
114    is_pypy = hasattr(sys, 'pypy_version_info')
115
116    if _c_optimizations_required():
117        return True
118    if is_pypy:
119        return False
120    return not _c_optimizations_ignored()
121
122
123def _use_c_impl(py_impl, name=None, globs=None):
124    """
125    Decorator. Given an object implemented in Python, with a name like
126    ``Foo``, import the corresponding C implementation from
127    ``zope.interface._zope_interface_coptimizations`` with the name
128    ``Foo`` and use it instead.
129
130    If the ``PURE_PYTHON`` environment variable is set to any value
131    other than ``"0"``, or we're on PyPy, ignore the C implementation
132    and return the Python version. If the C implementation cannot be
133    imported, return the Python version. If ``PURE_PYTHON`` is set to
134    0, *require* the C implementation (let the ImportError propagate);
135    note that PyPy can import the C implementation in this case (and all
136    tests pass).
137
138    In all cases, the Python version is kept available. in the module
139    globals with the name ``FooPy`` and the name ``FooFallback`` (both
140    conventions have been used; the C implementation of some functions
141    looks for the ``Fallback`` version, as do some of the Sphinx
142    documents).
143
144    Example::
145
146        @_use_c_impl
147        class Foo(object):
148            ...
149    """
150    name = name or py_impl.__name__
151    globs = globs or sys._getframe(1).f_globals
152
153    def find_impl():
154        if not _should_attempt_c_optimizations():
155            return py_impl
156
157        c_opt = _c_optimizations_available()
158        if not c_opt: # pragma: no cover (only Jython doesn't build extensions)
159            return py_impl
160
161        __traceback_info__ = c_opt
162        return getattr(c_opt, name)
163
164    c_impl = find_impl()
165    # Always make available by the FooPy name and FooFallback
166    # name (for testing and documentation)
167    globs[name + 'Py'] = py_impl
168    globs[name + 'Fallback'] = py_impl
169
170    return c_impl
171