zope.hookable 8.1__cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of zope.hookable might be problematic. Click here for more details.
- zope/hookable/__init__.py +79 -0
- zope/hookable/_zope_hookable.c +285 -0
- zope/hookable/_zope_hookable.cpython-314-i386-linux-gnu.so +0 -0
- zope/hookable/tests/__init__.py +1 -0
- zope/hookable/tests/test_compile_flags.py +29 -0
- zope/hookable/tests/test_hookable.py +203 -0
- zope_hookable-8.1.dist-info/METADATA +252 -0
- zope_hookable-8.1.dist-info/RECORD +11 -0
- zope_hookable-8.1.dist-info/WHEEL +8 -0
- zope_hookable-8.1.dist-info/licenses/LICENSE.txt +44 -0
- zope_hookable-8.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
##############################################################################
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2003 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
|
+
"""Hookable object support
|
|
15
|
+
"""
|
|
16
|
+
import os
|
|
17
|
+
import platform
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Keep these two flags separate: we want the `_PURE_PYTHON` one
|
|
21
|
+
# to represent that the flag is explicitly set to '1' in the environment,
|
|
22
|
+
# since our 'tox.ini' sets it to '0' for its environments which expect
|
|
23
|
+
# to test the C extension.
|
|
24
|
+
_PYPY_OR_JAVA = platform.python_implementation() in ('PyPy', 'Jython')
|
|
25
|
+
_PURE_PYTHON = os.environ.get('PURE_PYTHON') == '1'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _py_hookable:
|
|
29
|
+
__slots__ = ('_original', '_implementation')
|
|
30
|
+
|
|
31
|
+
def __init__(self, *args, **kw):
|
|
32
|
+
if not args and 'implementation' in kw:
|
|
33
|
+
args = (kw.pop('implementation'),)
|
|
34
|
+
if kw:
|
|
35
|
+
raise TypeError('Unknown keyword arguments')
|
|
36
|
+
if len(args) != 1:
|
|
37
|
+
raise TypeError('Exactly one argument required')
|
|
38
|
+
self._original = self._implementation = args[0]
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def original(self):
|
|
42
|
+
return self._original
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def implementation(self):
|
|
46
|
+
return self._implementation
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def __doc__(self):
|
|
50
|
+
return self._original.__doc__
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def __dict__(self):
|
|
54
|
+
return getattr(self._original, '__dict__', {})
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def __bases__(self):
|
|
58
|
+
return getattr(self._original, '__bases__', ())
|
|
59
|
+
|
|
60
|
+
def sethook(self, new_callable):
|
|
61
|
+
old, self._implementation = self._implementation, new_callable
|
|
62
|
+
return old
|
|
63
|
+
|
|
64
|
+
def reset(self):
|
|
65
|
+
self._implementation = self._original
|
|
66
|
+
|
|
67
|
+
def __call__(self, *args, **kw):
|
|
68
|
+
return self._implementation(*args, **kw)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
from zope.hookable._zope_hookable import hookable as _c_hookable
|
|
73
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
74
|
+
_c_hookable = None
|
|
75
|
+
|
|
76
|
+
if _PYPY_OR_JAVA or _PURE_PYTHON or _c_hookable is None:
|
|
77
|
+
hookable = _py_hookable
|
|
78
|
+
else: # pragma: no cover
|
|
79
|
+
hookable = _c_hookable
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/*############################################################################
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2003 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
|
+
|
|
15
|
+
static char module__doc__[] = (
|
|
16
|
+
"Provide an efficient implementation for hookable objects"
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
#include "Python.h"
|
|
20
|
+
#include "structmember.h"
|
|
21
|
+
|
|
22
|
+
typedef struct
|
|
23
|
+
{
|
|
24
|
+
PyObject_HEAD
|
|
25
|
+
PyObject* original;
|
|
26
|
+
PyObject* implementation;
|
|
27
|
+
} hookable;
|
|
28
|
+
|
|
29
|
+
static int
|
|
30
|
+
hookable_init(hookable* self, PyObject* args, PyObject* kwds)
|
|
31
|
+
{
|
|
32
|
+
static char* kwlist[] = { "implementation", NULL };
|
|
33
|
+
PyObject* implementation;
|
|
34
|
+
|
|
35
|
+
if (!PyArg_ParseTupleAndKeywords(
|
|
36
|
+
args, kwds, "O:hookable", kwlist, &implementation))
|
|
37
|
+
return -1;
|
|
38
|
+
|
|
39
|
+
/* Both 'self->original' and 'self->implementation' are originally
|
|
40
|
+
* set to the passed-in 'implementation', hence the need for
|
|
41
|
+
* two increfs.
|
|
42
|
+
*/
|
|
43
|
+
Py_INCREF(implementation);
|
|
44
|
+
Py_XDECREF(self->original);
|
|
45
|
+
self->original = implementation;
|
|
46
|
+
|
|
47
|
+
Py_INCREF(implementation);
|
|
48
|
+
Py_XDECREF(self->implementation);
|
|
49
|
+
self->implementation = implementation;
|
|
50
|
+
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static int
|
|
55
|
+
hookable_traverse(hookable* self, visitproc visit, void* arg)
|
|
56
|
+
{
|
|
57
|
+
#if PY_VERSION_HEX >= 0x03090000
|
|
58
|
+
Py_VISIT(Py_TYPE(self));
|
|
59
|
+
#endif
|
|
60
|
+
Py_VISIT(self->implementation);
|
|
61
|
+
Py_VISIT(self->original);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static int
|
|
66
|
+
hookable_clear(hookable* self)
|
|
67
|
+
{
|
|
68
|
+
Py_XDECREF(self->original);
|
|
69
|
+
self->original = NULL;
|
|
70
|
+
|
|
71
|
+
Py_XDECREF(self->implementation);
|
|
72
|
+
self->implementation = NULL;
|
|
73
|
+
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static void
|
|
78
|
+
hookable_dealloc(hookable* self)
|
|
79
|
+
{
|
|
80
|
+
PyObject_GC_UnTrack((PyObject*)self);
|
|
81
|
+
PyTypeObject* tp = Py_TYPE(self);
|
|
82
|
+
|
|
83
|
+
Py_XDECREF(self->original);
|
|
84
|
+
Py_XDECREF(self->implementation);
|
|
85
|
+
|
|
86
|
+
tp->tp_free((PyObject*)self);
|
|
87
|
+
|
|
88
|
+
/* heap types must decref their type when dealloc'ed */
|
|
89
|
+
Py_DECREF(tp);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static PyObject*
|
|
93
|
+
hookable_call(hookable* self, PyObject* args, PyObject* kw)
|
|
94
|
+
{
|
|
95
|
+
if (self->implementation != NULL)
|
|
96
|
+
return PyObject_Call(self->implementation, args, kw);
|
|
97
|
+
|
|
98
|
+
PyErr_SetString(PyExc_TypeError, "Hookable has no implementation");
|
|
99
|
+
return NULL;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static PyObject*
|
|
103
|
+
hookable_getattro(hookable* self, PyObject* name)
|
|
104
|
+
{
|
|
105
|
+
PyObject* result = NULL;
|
|
106
|
+
const char* name_as_string;
|
|
107
|
+
int maybe_special_name;
|
|
108
|
+
|
|
109
|
+
name_as_string = PyUnicode_AsUTF8(name);
|
|
110
|
+
if (name_as_string == NULL) { return NULL; }
|
|
111
|
+
|
|
112
|
+
maybe_special_name = name_as_string[0] == '_' && name_as_string[1] == '_';
|
|
113
|
+
|
|
114
|
+
if (maybe_special_name) {
|
|
115
|
+
/* pass through __doc__ to the original implementation */
|
|
116
|
+
if (strcmp("__doc__", name_as_string) == 0) {
|
|
117
|
+
return PyObject_GetAttr(self->original, name);
|
|
118
|
+
}
|
|
119
|
+
/* synthesize __bases__ and __dict__ if the original fails */
|
|
120
|
+
if (strcmp("__bases__", name_as_string) == 0) {
|
|
121
|
+
result = PyObject_GetAttr(self->original, name);
|
|
122
|
+
if (result == NULL) {
|
|
123
|
+
PyErr_Clear();
|
|
124
|
+
result = PyTuple_New(0);
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
if (strcmp("__dict__", name_as_string) == 0) {
|
|
129
|
+
result = PyObject_GetAttr(self->original, name);
|
|
130
|
+
if (result == NULL) {
|
|
131
|
+
PyErr_Clear();
|
|
132
|
+
result = PyDict_New();
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return PyObject_GenericGetAttr((PyObject*)self, name);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
static char hookable_sethook__doc__[] = (
|
|
142
|
+
"Set the hook implementation for the hookable object\n\n"
|
|
143
|
+
"Return the previous hook implementation, or None."
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
static PyObject*
|
|
147
|
+
hookable_sethook(hookable* self, PyObject* implementation)
|
|
148
|
+
{
|
|
149
|
+
PyObject* current;
|
|
150
|
+
|
|
151
|
+
current = self->implementation;
|
|
152
|
+
Py_INCREF(implementation);
|
|
153
|
+
self->implementation = implementation;
|
|
154
|
+
|
|
155
|
+
if (current == NULL) {
|
|
156
|
+
Py_INCREF(Py_None);
|
|
157
|
+
return Py_None;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return current;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static char hookable_reset__doc__[] = (
|
|
164
|
+
"Reset the hook to the original value"
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
static PyObject*
|
|
168
|
+
hookable_reset(hookable* self)
|
|
169
|
+
{
|
|
170
|
+
Py_XINCREF(self->original);
|
|
171
|
+
Py_XDECREF(self->implementation);
|
|
172
|
+
|
|
173
|
+
self->implementation = self->original;
|
|
174
|
+
|
|
175
|
+
Py_INCREF(Py_None);
|
|
176
|
+
return Py_None;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static struct PyMethodDef hookable_methods[] = {
|
|
180
|
+
{ "sethook",
|
|
181
|
+
(PyCFunction)hookable_sethook, METH_O, hookable_sethook__doc__ },
|
|
182
|
+
{ "reset",
|
|
183
|
+
(PyCFunction)hookable_reset, METH_NOARGS, hookable_reset__doc__},
|
|
184
|
+
{ NULL, NULL } /* sentinel */
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
static PyMemberDef hookable_members[] = {
|
|
188
|
+
{ "original",
|
|
189
|
+
T_OBJECT_EX, offsetof(hookable, original), READONLY },
|
|
190
|
+
{ "implementation",
|
|
191
|
+
T_OBJECT_EX, offsetof(hookable, implementation), READONLY },
|
|
192
|
+
{ NULL } /* Sentinel */
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
static char hookable__name__[] = "zope.hookable.hookable";
|
|
196
|
+
static char hookable__doc__[] =
|
|
197
|
+
"Callable objects that support being overridden";
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
/*
|
|
201
|
+
* Heap type: hookable
|
|
202
|
+
*/
|
|
203
|
+
static PyType_Slot hookable_type_slots[] = {
|
|
204
|
+
{Py_tp_doc, hookable__doc__},
|
|
205
|
+
{Py_tp_init, hookable_init},
|
|
206
|
+
{Py_tp_call, hookable_call},
|
|
207
|
+
{Py_tp_getattro, hookable_getattro},
|
|
208
|
+
{Py_tp_traverse, hookable_traverse},
|
|
209
|
+
{Py_tp_clear, hookable_clear},
|
|
210
|
+
{Py_tp_dealloc, hookable_dealloc},
|
|
211
|
+
{Py_tp_members, hookable_members},
|
|
212
|
+
{Py_tp_methods, hookable_methods},
|
|
213
|
+
{0, NULL}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
static PyType_Spec hookable_type_spec = {
|
|
217
|
+
.name = hookable__name__,
|
|
218
|
+
.basicsize = sizeof(hookable),
|
|
219
|
+
.flags = Py_TPFLAGS_DEFAULT |
|
|
220
|
+
Py_TPFLAGS_BASETYPE |
|
|
221
|
+
#if PY_VERSION_HEX >= 0x030c0000
|
|
222
|
+
Py_TPFLAGS_MANAGED_WEAKREF |
|
|
223
|
+
#endif
|
|
224
|
+
Py_TPFLAGS_HAVE_GC,
|
|
225
|
+
.slots = hookable_type_slots
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/*
|
|
229
|
+
* Module initialization
|
|
230
|
+
*/
|
|
231
|
+
|
|
232
|
+
static struct PyMethodDef hookable_module_methods[] = {
|
|
233
|
+
{ NULL, NULL } /* sentinel */
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
/* Handler for the 'execute' phase of multi-phase initialization
|
|
238
|
+
*
|
|
239
|
+
* See: https://docs.python.org/3/c-api/module.html#multi-phase-initialization
|
|
240
|
+
* and: https://peps.python.org/pep-0489/#module-execution-phase
|
|
241
|
+
*/
|
|
242
|
+
static int
|
|
243
|
+
hookable_module_exec(PyObject* module)
|
|
244
|
+
{
|
|
245
|
+
PyObject* hookable_type;
|
|
246
|
+
|
|
247
|
+
hookable_type = PyType_FromSpec(&hookable_type_spec);
|
|
248
|
+
if (hookable_type == NULL) { return -1; }
|
|
249
|
+
|
|
250
|
+
if (PyModule_AddObject(module, "hookable", hookable_type) < 0)
|
|
251
|
+
return -1;
|
|
252
|
+
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
/* Slot definitions for multi-phase initialization
|
|
258
|
+
*
|
|
259
|
+
* See: https://docs.python.org/3/c-api/module.html#multi-phase-initialization
|
|
260
|
+
* and: https://peps.python.org/pep-0489
|
|
261
|
+
*/
|
|
262
|
+
static PyModuleDef_Slot hookable_module_slots[] = {
|
|
263
|
+
{Py_mod_exec, hookable_module_exec},
|
|
264
|
+
{0, NULL}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
static struct PyModuleDef hookable_module_def = {
|
|
268
|
+
PyModuleDef_HEAD_INIT,
|
|
269
|
+
.m_name = "_zope_hookable",
|
|
270
|
+
.m_doc = module__doc__,
|
|
271
|
+
.m_methods = hookable_module_methods,
|
|
272
|
+
.m_slots = hookable_module_slots
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
static PyObject*
|
|
276
|
+
init(void)
|
|
277
|
+
{
|
|
278
|
+
return PyModuleDef_Init(&hookable_module_def);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
PyMODINIT_FUNC
|
|
282
|
+
PyInit__zope_hookable(void)
|
|
283
|
+
{
|
|
284
|
+
return init();
|
|
285
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This line required to prevent an empty file.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
##############################################################################
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2022 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
|
+
import struct
|
|
15
|
+
import unittest
|
|
16
|
+
|
|
17
|
+
import zope.hookable # noqa: try to load a C module for side effects
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestFloatingPoint(unittest.TestCase):
|
|
21
|
+
|
|
22
|
+
def test_no_fast_math_optimization(self):
|
|
23
|
+
# Building with -Ofast enables -ffast-math, which sets certain FPU
|
|
24
|
+
# flags that can cause breakage elsewhere. A library such as BTrees
|
|
25
|
+
# has no business changing global FPU flags for the entire process.
|
|
26
|
+
zero_bits = struct.unpack("!Q", struct.pack("!d", 0.0))[0]
|
|
27
|
+
next_up = zero_bits + 1
|
|
28
|
+
smallest_subnormal = struct.unpack("!d", struct.pack("!Q", next_up))[0]
|
|
29
|
+
self.assertNotEqual(smallest_subnormal, 0.0)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
##############################################################################
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2003 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
|
+
"""Test the hookable support Extension
|
|
15
|
+
"""
|
|
16
|
+
import unittest
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def return_foo():
|
|
20
|
+
return 'FOO'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def return_bar():
|
|
24
|
+
return 'BAR'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def not_called():
|
|
28
|
+
raise AssertionError("This should not be called")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PyHookableMixin:
|
|
32
|
+
|
|
33
|
+
def _callFUT(self, *args, **kw):
|
|
34
|
+
from zope.hookable import _py_hookable
|
|
35
|
+
return _py_hookable(*args, **kw)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class HookableMixin:
|
|
39
|
+
|
|
40
|
+
def _callFUT(self, *args, **kw):
|
|
41
|
+
from zope.hookable import _py_hookable
|
|
42
|
+
from zope.hookable import hookable
|
|
43
|
+
if hookable is _py_hookable:
|
|
44
|
+
raise unittest.SkipTest("Hookable and PyHookable are the same")
|
|
45
|
+
return hookable(*args, **kw) # pragma: no cover
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PyHookableTests(PyHookableMixin,
|
|
49
|
+
unittest.TestCase):
|
|
50
|
+
|
|
51
|
+
def test_pure_python(self):
|
|
52
|
+
from zope.hookable import _PURE_PYTHON
|
|
53
|
+
from zope.hookable import _PYPY_OR_JAVA
|
|
54
|
+
from zope.hookable import _c_hookable
|
|
55
|
+
from zope.hookable import _py_hookable
|
|
56
|
+
from zope.hookable import hookable
|
|
57
|
+
|
|
58
|
+
if _PYPY_OR_JAVA or _PURE_PYTHON:
|
|
59
|
+
self.assertIs(hookable, _py_hookable)
|
|
60
|
+
else:
|
|
61
|
+
self.assertIs(hookable, _c_hookable)
|
|
62
|
+
|
|
63
|
+
def test_before_hook(self):
|
|
64
|
+
hooked = self._callFUT(return_foo)
|
|
65
|
+
self.assertIs(hooked.original, return_foo)
|
|
66
|
+
self.assertIs(hooked.implementation, return_foo)
|
|
67
|
+
self.assertEqual(hooked(), 'FOO')
|
|
68
|
+
|
|
69
|
+
def test_after_hook(self):
|
|
70
|
+
hooked = self._callFUT(not_called)
|
|
71
|
+
old = hooked.sethook(return_bar)
|
|
72
|
+
self.assertIs(old, not_called)
|
|
73
|
+
self.assertIs(hooked.original, not_called)
|
|
74
|
+
self.assertIs(hooked.implementation, return_bar)
|
|
75
|
+
self.assertEqual(hooked(), 'BAR')
|
|
76
|
+
|
|
77
|
+
def test_after_hook_and_reset(self):
|
|
78
|
+
hooked = self._callFUT(return_foo)
|
|
79
|
+
old = hooked.sethook(not_called)
|
|
80
|
+
hooked.reset()
|
|
81
|
+
self.assertIs(old, return_foo)
|
|
82
|
+
self.assertIs(hooked.original, return_foo)
|
|
83
|
+
self.assertIs(hooked.implementation, return_foo)
|
|
84
|
+
self.assertEqual(hooked(), 'FOO')
|
|
85
|
+
|
|
86
|
+
def test_original_cannot_be_deleted(self):
|
|
87
|
+
hooked = self._callFUT(not_called)
|
|
88
|
+
with self.assertRaises((TypeError, AttributeError)):
|
|
89
|
+
del hooked.original
|
|
90
|
+
|
|
91
|
+
def test_implementation_cannot_be_deleted(self):
|
|
92
|
+
hooked = self._callFUT(not_called)
|
|
93
|
+
with self.assertRaises((TypeError, AttributeError)):
|
|
94
|
+
del hooked.implementation
|
|
95
|
+
|
|
96
|
+
def test_no_args(self):
|
|
97
|
+
with self.assertRaises(TypeError):
|
|
98
|
+
self._callFUT()
|
|
99
|
+
|
|
100
|
+
def test_too_many_args(self):
|
|
101
|
+
with self.assertRaises(TypeError):
|
|
102
|
+
self._callFUT(not_called, not_called)
|
|
103
|
+
|
|
104
|
+
def test_w_implementation_kwarg(self):
|
|
105
|
+
hooked = self._callFUT(implementation=return_foo)
|
|
106
|
+
self.assertIs(hooked.original, return_foo)
|
|
107
|
+
self.assertIs(hooked.implementation, return_foo)
|
|
108
|
+
self.assertEqual(hooked(), 'FOO')
|
|
109
|
+
|
|
110
|
+
def test_w_unknown_kwarg(self):
|
|
111
|
+
with self.assertRaises(TypeError):
|
|
112
|
+
self._callFUT(nonesuch=42)
|
|
113
|
+
|
|
114
|
+
def test_class(self):
|
|
115
|
+
class C:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
hooked = self._callFUT(C)
|
|
119
|
+
self.assertIsInstance(hooked(), C)
|
|
120
|
+
|
|
121
|
+
hooked.sethook(return_bar)
|
|
122
|
+
self.assertEqual(hooked(), 'BAR')
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class TestIssue6Py(PyHookableMixin,
|
|
126
|
+
unittest.TestCase):
|
|
127
|
+
# Make sphinx docs for hooked objects work.
|
|
128
|
+
# https://github.com/zopefoundation/zope.hookable/issues/6
|
|
129
|
+
# We need to proxy __doc__ to the original,
|
|
130
|
+
# and synthesize an empty __bases__ and a __dict__ attribute
|
|
131
|
+
# if they're not present.
|
|
132
|
+
|
|
133
|
+
def _check_preserves_doc(self, docs):
|
|
134
|
+
self.assertEqual("I have some docs", docs.__doc__)
|
|
135
|
+
|
|
136
|
+
hooked = self._callFUT(docs)
|
|
137
|
+
self.assertEqual(hooked.__doc__, docs.__doc__)
|
|
138
|
+
|
|
139
|
+
def test_preserves_doc_function(self):
|
|
140
|
+
def docs():
|
|
141
|
+
"""I have some docs"""
|
|
142
|
+
self._check_preserves_doc(docs)
|
|
143
|
+
|
|
144
|
+
def test_preserves_doc_class(self):
|
|
145
|
+
class Docs:
|
|
146
|
+
"""I have some docs"""
|
|
147
|
+
|
|
148
|
+
self._check_preserves_doc(Docs)
|
|
149
|
+
|
|
150
|
+
def test_empty_bases_function(self):
|
|
151
|
+
hooked = self._callFUT(return_foo)
|
|
152
|
+
self.assertEqual((), hooked.__bases__)
|
|
153
|
+
|
|
154
|
+
def test_empty_dict_function(self):
|
|
155
|
+
hooked = self._callFUT(return_foo)
|
|
156
|
+
self.assertEqual({}, hooked.__dict__)
|
|
157
|
+
|
|
158
|
+
def test_bases_class(self):
|
|
159
|
+
class C:
|
|
160
|
+
pass
|
|
161
|
+
self.assertEqual(C.__bases__, (object,))
|
|
162
|
+
hooked = self._callFUT(C)
|
|
163
|
+
self.assertEqual(hooked.__bases__, (object,))
|
|
164
|
+
|
|
165
|
+
def test_dict_class(self):
|
|
166
|
+
class C:
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
hooked = self._callFUT(C)
|
|
170
|
+
self.assertEqual(hooked.__dict__, C.__dict__)
|
|
171
|
+
|
|
172
|
+
def test_non_string_attr_name(self):
|
|
173
|
+
# Specifically for the C implementation, which has to deal with this
|
|
174
|
+
hooked = self._callFUT(return_foo)
|
|
175
|
+
with self.assertRaises(TypeError):
|
|
176
|
+
getattr(hooked, 42)
|
|
177
|
+
|
|
178
|
+
with self.assertRaises(TypeError):
|
|
179
|
+
hooked.__getattribute__(42)
|
|
180
|
+
|
|
181
|
+
def test_unicode_attribute_name(self):
|
|
182
|
+
# Specifically for the C implementation, which has to deal with this
|
|
183
|
+
hooked = self._callFUT(return_foo)
|
|
184
|
+
result = hooked.__getattribute__('__bases__')
|
|
185
|
+
self.assertEqual(result, ())
|
|
186
|
+
|
|
187
|
+
def test_short_name(self):
|
|
188
|
+
# Specifically for the C implementation, which has to deal with this
|
|
189
|
+
hooked = self._callFUT(return_foo)
|
|
190
|
+
with self.assertRaises(AttributeError):
|
|
191
|
+
hooked.__getattribute__('')
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class HookableTests(HookableMixin, PyHookableTests):
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class TestIssue6(HookableMixin, TestIssue6Py):
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_suite():
|
|
203
|
+
return unittest.defaultTestLoader.loadTestsFromName(__name__)
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zope.hookable
|
|
3
|
+
Version: 8.1
|
|
4
|
+
Summary: Zope hookable
|
|
5
|
+
Home-page: http://github.com/zopefoundation/zope.hookable
|
|
6
|
+
Author: Zope Foundation and Contributors
|
|
7
|
+
Author-email: zope-dev@zope.dev
|
|
8
|
+
License: ZPL-2.1
|
|
9
|
+
Keywords: function hook replacement loose coupled
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Zope Public License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
22
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
23
|
+
Classifier: Framework :: Zope :: 3
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
License-File: LICENSE.txt
|
|
27
|
+
Provides-Extra: docs
|
|
28
|
+
Requires-Dist: Sphinx; extra == "docs"
|
|
29
|
+
Requires-Dist: sphinx_rtd_theme; extra == "docs"
|
|
30
|
+
Provides-Extra: testing
|
|
31
|
+
Requires-Dist: zope.testing; extra == "testing"
|
|
32
|
+
Requires-Dist: zope.testrunner>=6.4; extra == "testing"
|
|
33
|
+
Requires-Dist: coverage; extra == "testing"
|
|
34
|
+
Provides-Extra: test
|
|
35
|
+
Requires-Dist: zope.testing; extra == "test"
|
|
36
|
+
Requires-Dist: zope.testrunner>=6.4; extra == "test"
|
|
37
|
+
Dynamic: author
|
|
38
|
+
Dynamic: author-email
|
|
39
|
+
Dynamic: classifier
|
|
40
|
+
Dynamic: description
|
|
41
|
+
Dynamic: home-page
|
|
42
|
+
Dynamic: keywords
|
|
43
|
+
Dynamic: license
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
Dynamic: provides-extra
|
|
46
|
+
Dynamic: requires-python
|
|
47
|
+
Dynamic: summary
|
|
48
|
+
|
|
49
|
+
===============
|
|
50
|
+
zope.hookable
|
|
51
|
+
===============
|
|
52
|
+
|
|
53
|
+
.. image:: https://img.shields.io/pypi/v/zope.hookable.svg
|
|
54
|
+
:target: https://pypi.python.org/pypi/zope.hookable/
|
|
55
|
+
:alt: Latest release
|
|
56
|
+
|
|
57
|
+
.. image:: https://img.shields.io/pypi/pyversions/zope.hookable.svg
|
|
58
|
+
:target: https://pypi.org/project/zope.hookable/
|
|
59
|
+
:alt: Supported Python versions
|
|
60
|
+
|
|
61
|
+
.. image:: https://github.com/zopefoundation/zope.hookable/actions/workflows/tests.yml/badge.svg
|
|
62
|
+
:target: https://github.com/zopefoundation/zope.hookable/actions/workflows/tests.yml
|
|
63
|
+
|
|
64
|
+
.. image:: https://readthedocs.org/projects/zopehookable/badge/?version=latest
|
|
65
|
+
:target: https://zopehookable.readthedocs.io/en/latest/
|
|
66
|
+
:alt: Documentation Status
|
|
67
|
+
|
|
68
|
+
.. image:: https://coveralls.io/repos/github/zopefoundation/zope.hookable/badge.svg?branch=master
|
|
69
|
+
:target: https://coveralls.io/github/zopefoundation/zope.hookable?branch=master
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
This package supports the efficient creation of "hookable" objects, which
|
|
73
|
+
are callable objects that are meant to be optionally replaced.
|
|
74
|
+
|
|
75
|
+
The idea is that you create a function that does some default thing and make it
|
|
76
|
+
hookable. Later, someone can modify what it does by calling its sethook method
|
|
77
|
+
and changing its implementation. All users of the function, including those
|
|
78
|
+
that imported it, will see the change.
|
|
79
|
+
|
|
80
|
+
Documentation is hosted at https://zopehookable.readthedocs.io
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
=========
|
|
84
|
+
Changes
|
|
85
|
+
=========
|
|
86
|
+
|
|
87
|
+
8.1 (2025-10-28)
|
|
88
|
+
================
|
|
89
|
+
|
|
90
|
+
- Remove unnecessary ``setuptools`` runtime dependency.
|
|
91
|
+
|
|
92
|
+
- Drop support for Python 3.9.
|
|
93
|
+
|
|
94
|
+
- Add support for Python 3.14.
|
|
95
|
+
|
|
96
|
+
8.0 (2025-09-12)
|
|
97
|
+
================
|
|
98
|
+
|
|
99
|
+
- Replace ``pkg_resources`` namespace with PEP 420 native namespace.
|
|
100
|
+
|
|
101
|
+
- Drop support for Python 3.8.
|
|
102
|
+
|
|
103
|
+
- Add preliminary support for Python 3.14.
|
|
104
|
+
|
|
105
|
+
7.0 (2024-09-17)
|
|
106
|
+
================
|
|
107
|
+
|
|
108
|
+
- C extension now enables multi-phase module initialization (PEP 489).
|
|
109
|
+
For CPython >= 3.11, the ``hookable`` type is now a heap-allocated
|
|
110
|
+
type. See:
|
|
111
|
+
https://docs.python.org/3.13/howto/isolating-extensions.html
|
|
112
|
+
|
|
113
|
+
- Drop support for Python 3.7.
|
|
114
|
+
|
|
115
|
+
- Add support for Python 3.13.
|
|
116
|
+
|
|
117
|
+
- Build windows wheels on GHA.
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
6.0 (2023-10-05)
|
|
121
|
+
================
|
|
122
|
+
|
|
123
|
+
- Drop support for Python 2.7, 3.5, 3.6.
|
|
124
|
+
|
|
125
|
+
- Add support for Python 3.12.
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
5.4 (2022-11-17)
|
|
129
|
+
================
|
|
130
|
+
|
|
131
|
+
- Add support for building arm64 wheels on macOS.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
5.3 (2022-11-03)
|
|
135
|
+
================
|
|
136
|
+
|
|
137
|
+
- Add support for the final release of Python 3.11.
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
5.2 (2022-09-13)
|
|
141
|
+
================
|
|
142
|
+
|
|
143
|
+
- Add support for Python 3.10 and 3.11 (as of 3.11.0rc1).
|
|
144
|
+
|
|
145
|
+
- Disable unsafe math optimizations in C code. See `pull request 25
|
|
146
|
+
<https://github.com/zopefoundation/zope.hookable/pull/25>`_.
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
5.1.0 (2021-07-20)
|
|
150
|
+
==================
|
|
151
|
+
|
|
152
|
+
- Add support for Python 3.9.
|
|
153
|
+
|
|
154
|
+
- Create Linux aarch64 wheels.
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
5.0.1 (2020-03-10)
|
|
158
|
+
==================
|
|
159
|
+
|
|
160
|
+
- Stop using the setuptools ``Feature`` class, allowing this
|
|
161
|
+
project to be built from source with newer versions of setuptools
|
|
162
|
+
that remove that functionality.
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
5.0.0 (2019-11-12)
|
|
166
|
+
==================
|
|
167
|
+
|
|
168
|
+
- Add support for Python 3.7 and 3.8.
|
|
169
|
+
|
|
170
|
+
- Drop support for Python 3.4.
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
4.2.0 (2017-11-07)
|
|
174
|
+
==================
|
|
175
|
+
|
|
176
|
+
- Expose the ``__doc__`` (and, where applicable, ``__bases__`` and
|
|
177
|
+
``__dict__``) of the hooked object. This lets Sphinx document them.
|
|
178
|
+
See `issue 6 <https://github.com/zopefoundation/zope.hookable/issues/6>`_.
|
|
179
|
+
|
|
180
|
+
- Respect ``PURE_PYTHON`` at runtime. At build time, always try to
|
|
181
|
+
build the C extensions on supported platforms, but allow it to fail.
|
|
182
|
+
See `issue 7
|
|
183
|
+
<https://github.com/zopefoundation/zope.hookable/issues/7>`_.
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
4.1.0 (2017-07-26)
|
|
187
|
+
==================
|
|
188
|
+
|
|
189
|
+
- Drop support for Python 2.6, 3.2 and 3.3.
|
|
190
|
+
|
|
191
|
+
- Add support for Python 3.5 and 3.6.
|
|
192
|
+
|
|
193
|
+
4.0.4 (2014-03-19)
|
|
194
|
+
==================
|
|
195
|
+
|
|
196
|
+
- Add support for Python 3.4.
|
|
197
|
+
|
|
198
|
+
4.0.3 (2014-03-17)
|
|
199
|
+
==================
|
|
200
|
+
|
|
201
|
+
- Update ``boostrap.py`` to version 2.2.
|
|
202
|
+
|
|
203
|
+
- Fix extension compilation on Py3k.
|
|
204
|
+
|
|
205
|
+
4.0.2 (2012-12-31)
|
|
206
|
+
==================
|
|
207
|
+
|
|
208
|
+
- Flesh out PyPI Trove classifiers.
|
|
209
|
+
|
|
210
|
+
4.0.1 (2012-11-21)
|
|
211
|
+
==================
|
|
212
|
+
|
|
213
|
+
- Add support for Python 3.3.
|
|
214
|
+
|
|
215
|
+
- Avoid building the C extension explicitly (use the "feature" indirection
|
|
216
|
+
instead). https://bugs.launchpad.net/zope.hookable/+bug/1025470
|
|
217
|
+
|
|
218
|
+
4.0.0 (2012-06-04)
|
|
219
|
+
==================
|
|
220
|
+
|
|
221
|
+
- Add support for PyPy.
|
|
222
|
+
|
|
223
|
+
- Add support for continuous integration using ``tox`` and ``jenkins``.
|
|
224
|
+
|
|
225
|
+
- Add a pure-Python reference implementation.
|
|
226
|
+
|
|
227
|
+
- Move doctests to Sphinx documentation.
|
|
228
|
+
|
|
229
|
+
- Bring unit test coverage to 100%.
|
|
230
|
+
|
|
231
|
+
- Add 'setup.py docs' alias (installs ``Sphinx`` and dependencies).
|
|
232
|
+
|
|
233
|
+
- Add 'setup.py dev' alias (runs ``setup.py develop`` plus installs
|
|
234
|
+
``nose`` and ``coverage``).
|
|
235
|
+
|
|
236
|
+
- Drop support for Python 2.4 / 2.5.
|
|
237
|
+
|
|
238
|
+
- Remove of 'zope.testing.doctestunit' in favor of stdlib's 'doctest.
|
|
239
|
+
|
|
240
|
+
- Add Python 3 support.
|
|
241
|
+
|
|
242
|
+
3.4.1 (2009-04-05)
|
|
243
|
+
==================
|
|
244
|
+
|
|
245
|
+
- Update for compatibility with Python 2.6 traceback formats.
|
|
246
|
+
|
|
247
|
+
- Use Jython-compatible ``bootstrap.py``.
|
|
248
|
+
|
|
249
|
+
3.4.0 (2007-07-20)
|
|
250
|
+
==================
|
|
251
|
+
|
|
252
|
+
- Initial release as a separate project.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
zope/hookable/__init__.py,sha256=AlGd7CUrQFdO8X82n8At-Fb4oVU1woiE2AoKQUPiNYg,2460
|
|
2
|
+
zope/hookable/_zope_hookable.c,sha256=N9LOxTLZ-fZN8o_iNg8mjUNCX4na4UoGO0eDrkIqLic,7452
|
|
3
|
+
zope/hookable/_zope_hookable.cpython-314-i386-linux-gnu.so,sha256=8t4u_vb-YknMsz69kg6UDjdCYyd7Ad3sLC4aKm2lUBA,34868
|
|
4
|
+
zope/hookable/tests/__init__.py,sha256=Z9EJNKBQorYcdV6oaIRTRgF41SMRZEEoLltZCKyVPI8,47
|
|
5
|
+
zope/hookable/tests/test_compile_flags.py,sha256=91siNUs2kotDUYpVV1vAZ_opWhZ50X4_DYkIiNMh6h0,1289
|
|
6
|
+
zope/hookable/tests/test_hookable.py,sha256=3FdXSprA60j5eX3LftT6MZG80moqG-0tTwQn_KjNLQc,6330
|
|
7
|
+
zope_hookable-8.1.dist-info/METADATA,sha256=ElY1p3uExRuj2OTiKcYkt4E73I1qOUWknXZvA8ACN4U,6663
|
|
8
|
+
zope_hookable-8.1.dist-info/WHEEL,sha256=UJfiWKwdC8w_IJXDILsHLpcSiwfnglfxTCNV21KBels,216
|
|
9
|
+
zope_hookable-8.1.dist-info/top_level.txt,sha256=QpUHvpO4wIuZDeEgKY8qZCtD-tAukB0fn_f6utzlb98,5
|
|
10
|
+
zope_hookable-8.1.dist-info/RECORD,,
|
|
11
|
+
zope_hookable-8.1.dist-info/licenses/LICENSE.txt,sha256=PmcdsR32h1FswdtbPWXkqjg-rKPCDOo_r1Og9zNdCjw,2070
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Zope Public License (ZPL) Version 2.1
|
|
2
|
+
|
|
3
|
+
A copyright notice accompanies this license document that identifies the
|
|
4
|
+
copyright holders.
|
|
5
|
+
|
|
6
|
+
This license has been certified as open source. It has also been designated as
|
|
7
|
+
GPL compatible by the Free Software Foundation (FSF).
|
|
8
|
+
|
|
9
|
+
Redistribution and use in source and binary forms, with or without
|
|
10
|
+
modification, are permitted provided that the following conditions are met:
|
|
11
|
+
|
|
12
|
+
1. Redistributions in source code must retain the accompanying copyright
|
|
13
|
+
notice, this list of conditions, and the following disclaimer.
|
|
14
|
+
|
|
15
|
+
2. Redistributions in binary form must reproduce the accompanying copyright
|
|
16
|
+
notice, this list of conditions, and the following disclaimer in the
|
|
17
|
+
documentation and/or other materials provided with the distribution.
|
|
18
|
+
|
|
19
|
+
3. Names of the copyright holders must not be used to endorse or promote
|
|
20
|
+
products derived from this software without prior written permission from the
|
|
21
|
+
copyright holders.
|
|
22
|
+
|
|
23
|
+
4. The right to distribute this software or to use it for any purpose does not
|
|
24
|
+
give you the right to use Servicemarks (sm) or Trademarks (tm) of the
|
|
25
|
+
copyright
|
|
26
|
+
holders. Use of them is covered by separate agreement with the copyright
|
|
27
|
+
holders.
|
|
28
|
+
|
|
29
|
+
5. If any files are modified, you must cause the modified files to carry
|
|
30
|
+
prominent notices stating that you changed the files and the date of any
|
|
31
|
+
change.
|
|
32
|
+
|
|
33
|
+
Disclaimer
|
|
34
|
+
|
|
35
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED
|
|
36
|
+
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
37
|
+
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
38
|
+
EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
39
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
40
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
41
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
42
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
43
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
44
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zope
|