17.12
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Twisted Python: Utilities and Enhancements for Python.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
# Deprecating twisted.python.constants.
|
||||
from .compat import unicode
|
||||
from .versions import Version
|
||||
from .deprecate import deprecatedModuleAttribute
|
||||
|
||||
deprecatedModuleAttribute(
|
||||
Version("Twisted", 16, 5, 0),
|
||||
"Please use constantly from PyPI instead.",
|
||||
"twisted.python", "constants")
|
||||
|
||||
|
||||
deprecatedModuleAttribute(
|
||||
Version('Twisted', 17, 5, 0),
|
||||
"Please use hyperlink from PyPI instead.",
|
||||
"twisted.python", "url")
|
||||
|
||||
|
||||
del Version
|
||||
del deprecatedModuleAttribute
|
||||
del unicode
|
||||
@@ -0,0 +1,32 @@
|
||||
# -*- test-case-name: twisted.python.test.test_appdirs -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Application data directory support.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
import appdirs
|
||||
import inspect
|
||||
|
||||
from twisted.python.compat import currentframe
|
||||
|
||||
|
||||
def getDataDirectory(moduleName=None):
|
||||
"""
|
||||
Get a data directory for the caller function, or C{moduleName} if given.
|
||||
|
||||
@param moduleName: The module name if you don't wish to have the caller's
|
||||
module.
|
||||
@type moduleName: L{str}
|
||||
|
||||
@returns: A directory for putting data in.
|
||||
@rtype: L{str}
|
||||
"""
|
||||
if not moduleName:
|
||||
caller = currentframe(1)
|
||||
moduleName = inspect.getmodule(caller).__name__
|
||||
|
||||
return appdirs.user_data_dir(moduleName)
|
||||
@@ -0,0 +1,462 @@
|
||||
# -*- test-case-name: twisted.python.test.test_setup -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
# pylint: disable=I0011,C0103,C9302,W9401,W9402
|
||||
|
||||
"""
|
||||
Setuptools convenience functionality.
|
||||
|
||||
This file must not import anything from Twisted, as it is loaded by C{exec} in
|
||||
C{setup.py}. If you need compatibility functions for this code, duplicate them
|
||||
here.
|
||||
|
||||
@var _EXTRA_OPTIONS: These are the actual package names and versions that will
|
||||
be used by C{extras_require}. This is not passed to setup directly so that
|
||||
combinations of the packages can be created without the need to copy
|
||||
package names multiple times.
|
||||
|
||||
@var _EXTRAS_REQUIRE: C{extras_require} is a dictionary of items that can be
|
||||
passed to setup.py to install optional dependencies. For example, to
|
||||
install the optional dev dependencies one would type::
|
||||
|
||||
pip install -e ".[dev]"
|
||||
|
||||
This has been supported by setuptools since 0.5a4.
|
||||
|
||||
@var _PLATFORM_INDEPENDENT: A list of all optional cross-platform dependencies,
|
||||
as setuptools version specifiers, used to populate L{_EXTRAS_REQUIRE}.
|
||||
|
||||
@var _EXTENSIONS: The list of L{ConditionalExtension} used by the setup
|
||||
process.
|
||||
|
||||
@var notPortedModules: Modules that are not yet ported to Python 3.
|
||||
"""
|
||||
|
||||
import io
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
|
||||
from distutils.command import build_ext
|
||||
from distutils.errors import CompileError
|
||||
from setuptools import Extension, find_packages
|
||||
from setuptools.command.build_py import build_py
|
||||
|
||||
# Do not replace this with t.p.compat imports, this file must not import
|
||||
# from Twisted. See the docstring.
|
||||
if sys.version_info < (3, 0):
|
||||
_PY3 = False
|
||||
else:
|
||||
_PY3 = True
|
||||
|
||||
STATIC_PACKAGE_METADATA = dict(
|
||||
name="Twisted",
|
||||
description="An asynchronous networking framework written in Python",
|
||||
author="Twisted Matrix Laboratories",
|
||||
author_email="twisted-python@twistedmatrix.com",
|
||||
maintainer="Glyph Lefkowitz",
|
||||
maintainer_email="glyph@twistedmatrix.com",
|
||||
url="https://twistedmatrix.com/",
|
||||
project_urls={
|
||||
'Documentation': 'https://twistedmatrix.com/documents/current/',
|
||||
'Source': 'https://github.com/twisted/twisted',
|
||||
'Issues': 'https://twistedmatrix.com/trac/report',
|
||||
},
|
||||
license="MIT",
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
_dev = [
|
||||
'pyflakes >= 1.0.0',
|
||||
'twisted-dev-tools >= 0.0.2',
|
||||
'python-subunit',
|
||||
'sphinx >= 1.3.1',
|
||||
'towncrier >= 17.4.0'
|
||||
]
|
||||
|
||||
if not _PY3:
|
||||
# These modules do not yet work on Python 3.
|
||||
_dev += [
|
||||
'twistedchecker >= 0.4.0',
|
||||
'pydoctor >= 16.2.0',
|
||||
]
|
||||
|
||||
_EXTRA_OPTIONS = dict(
|
||||
dev=_dev,
|
||||
tls=[
|
||||
'pyopenssl >= 16.0.0',
|
||||
# service_identity 18.1.0 added support for validating IP addresses in
|
||||
# certificate subjectAltNames
|
||||
'service_identity >= 18.1.0',
|
||||
# idna 2.3 introduced some changes that break a few things. Avoid it.
|
||||
# The problems were fixed in 2.4.
|
||||
'idna >= 0.6, != 2.3',
|
||||
],
|
||||
conch=[
|
||||
'pyasn1',
|
||||
'cryptography >= 2.5',
|
||||
'appdirs >= 1.4.0',
|
||||
'bcrypt >= 3.0.0',
|
||||
],
|
||||
soap=['soappy'],
|
||||
serial=['pyserial >= 3.0',
|
||||
'pywin32; platform_system == "Windows"'],
|
||||
macos=['pyobjc-core',
|
||||
'pyobjc-framework-CFNetwork',
|
||||
'pyobjc-framework-Cocoa'],
|
||||
windows=['pywin32'],
|
||||
http2=['h2 >= 3.0, < 4.0',
|
||||
'priority >= 1.1.0, < 2.0'],
|
||||
)
|
||||
|
||||
_PLATFORM_INDEPENDENT = (
|
||||
_EXTRA_OPTIONS['tls'] +
|
||||
_EXTRA_OPTIONS['conch'] +
|
||||
_EXTRA_OPTIONS['soap'] +
|
||||
_EXTRA_OPTIONS['serial'] +
|
||||
_EXTRA_OPTIONS['http2']
|
||||
)
|
||||
|
||||
_EXTRAS_REQUIRE = {
|
||||
'dev': _EXTRA_OPTIONS['dev'],
|
||||
'tls': _EXTRA_OPTIONS['tls'],
|
||||
'conch': _EXTRA_OPTIONS['conch'],
|
||||
'soap': _EXTRA_OPTIONS['soap'],
|
||||
'serial': _EXTRA_OPTIONS['serial'],
|
||||
'http2': _EXTRA_OPTIONS['http2'],
|
||||
'all_non_platform': _PLATFORM_INDEPENDENT,
|
||||
'macos_platform': (
|
||||
_EXTRA_OPTIONS['macos'] + _PLATFORM_INDEPENDENT
|
||||
),
|
||||
'windows_platform': (
|
||||
_EXTRA_OPTIONS['windows'] + _PLATFORM_INDEPENDENT
|
||||
),
|
||||
}
|
||||
_EXTRAS_REQUIRE['osx_platform'] = _EXTRAS_REQUIRE['macos_platform']
|
||||
|
||||
# Scripts provided by Twisted on Python 2 and 3.
|
||||
_CONSOLE_SCRIPTS = [
|
||||
"ckeygen = twisted.conch.scripts.ckeygen:run",
|
||||
"cftp = twisted.conch.scripts.cftp:run",
|
||||
"conch = twisted.conch.scripts.conch:run",
|
||||
"mailmail = twisted.mail.scripts.mailmail:run",
|
||||
"pyhtmlizer = twisted.scripts.htmlizer:run",
|
||||
"tkconch = twisted.conch.scripts.tkconch:run",
|
||||
"trial = twisted.scripts.trial:run",
|
||||
"twist = twisted.application.twist._twist:Twist.main",
|
||||
"twistd = twisted.scripts.twistd:run",
|
||||
]
|
||||
|
||||
|
||||
|
||||
class ConditionalExtension(Extension, object):
|
||||
"""
|
||||
An extension module that will only be compiled if certain conditions are
|
||||
met.
|
||||
|
||||
@param condition: A callable of one argument which returns True or False to
|
||||
indicate whether the extension should be built. The argument is an
|
||||
instance of L{build_ext_twisted}, which has useful methods for checking
|
||||
things about the platform.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.condition = kwargs.pop("condition", lambda builder: True)
|
||||
Extension.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
|
||||
# The C extensions used for Twisted.
|
||||
_EXTENSIONS = [
|
||||
ConditionalExtension(
|
||||
"twisted.test.raiser",
|
||||
sources=["src/twisted/test/raiser.c"],
|
||||
condition=lambda _: _isCPython),
|
||||
|
||||
ConditionalExtension(
|
||||
"twisted.internet.iocpreactor.iocpsupport",
|
||||
sources=[
|
||||
"src/twisted/internet/iocpreactor/iocpsupport/iocpsupport.c",
|
||||
"src/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.c",
|
||||
],
|
||||
libraries=["ws2_32"],
|
||||
condition=lambda _: _isCPython and sys.platform == "win32"),
|
||||
|
||||
ConditionalExtension(
|
||||
"twisted.python._sendmsg",
|
||||
sources=["src/twisted/python/_sendmsg.c"],
|
||||
condition=lambda _: not _PY3 and sys.platform != "win32"),
|
||||
]
|
||||
|
||||
|
||||
|
||||
def _checkPythonVersion():
|
||||
"""
|
||||
Fail if we detect a version of Python we don't support.
|
||||
"""
|
||||
version = getattr(sys, "version_info", (0,))
|
||||
if version < (2, 7):
|
||||
raise ImportError("Twisted requires Python 2.7 or later.")
|
||||
elif version >= (3, 0) and version < (3, 5):
|
||||
raise ImportError("Twisted on Python 3 requires Python 3.5 or later.")
|
||||
|
||||
|
||||
|
||||
def _longDescriptionArgsFromReadme(readme):
|
||||
"""
|
||||
Generate a PyPI long description from the readme.
|
||||
|
||||
@param readme: Path to the readme reStructuredText file.
|
||||
@type readme: C{str}
|
||||
|
||||
@return: Keyword arguments to be passed to C{setuptools.setup()}.
|
||||
@rtype: C{str}
|
||||
"""
|
||||
with io.open(readme, encoding='utf-8') as f:
|
||||
readmeRst = f.read()
|
||||
|
||||
# Munge links of the form `NEWS <NEWS.rst>`_ to point at the appropriate
|
||||
# location on GitHub so that they function when the long description is
|
||||
# displayed on PyPI.
|
||||
longDesc = re.sub(
|
||||
r'`([^`]+)\s+<(?!https?://)([^>]+)>`_',
|
||||
r'`\1 <https://github.com/twisted/twisted/blob/trunk/\2>`_',
|
||||
readmeRst,
|
||||
flags=re.I,
|
||||
)
|
||||
|
||||
return {
|
||||
'long_description': longDesc,
|
||||
'long_description_content_type': 'text/x-rst',
|
||||
}
|
||||
|
||||
|
||||
|
||||
def getSetupArgs(extensions=_EXTENSIONS, readme='README.rst'):
|
||||
"""
|
||||
Generate arguments for C{setuptools.setup()}
|
||||
|
||||
@param extensions: C extension modules to maybe build. This argument is to
|
||||
be used for testing.
|
||||
@type extensions: C{list} of C{ConditionalExtension}
|
||||
|
||||
@param readme: Path to the readme reStructuredText file. This argument is
|
||||
to be used for testing.
|
||||
@type readme: C{str}
|
||||
|
||||
@return: The keyword arguments to be used by the setup method.
|
||||
@rtype: L{dict}
|
||||
"""
|
||||
_checkPythonVersion()
|
||||
|
||||
arguments = STATIC_PACKAGE_METADATA.copy()
|
||||
if readme:
|
||||
arguments.update(_longDescriptionArgsFromReadme(readme))
|
||||
|
||||
# This is a workaround for distutils behavior; ext_modules isn't
|
||||
# actually used by our custom builder. distutils deep-down checks
|
||||
# to see if there are any ext_modules defined before invoking
|
||||
# the build_ext command. We need to trigger build_ext regardless
|
||||
# because it is the thing that does the conditional checks to see
|
||||
# if it should build any extensions. The reason we have to delay
|
||||
# the conditional checks until then is that the compiler objects
|
||||
# are not yet set up when this code is executed.
|
||||
arguments["ext_modules"] = extensions
|
||||
# Use custome class to build the extensions.
|
||||
class my_build_ext(build_ext_twisted):
|
||||
conditionalExtensions = extensions
|
||||
command_classes = {
|
||||
'build_ext': my_build_ext,
|
||||
}
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
command_classes['build_py'] = BuildPy3
|
||||
|
||||
requirements = [
|
||||
"zope.interface >= 4.4.2",
|
||||
"constantly >= 15.1",
|
||||
"incremental >= 16.10.1",
|
||||
"Automat >= 0.3.0",
|
||||
"hyperlink >= 17.1.1",
|
||||
"PyHamcrest >= 1.9.0",
|
||||
"attrs >= 17.4.0",
|
||||
]
|
||||
|
||||
arguments.update(dict(
|
||||
packages=find_packages("src"),
|
||||
use_incremental=True,
|
||||
setup_requires=["incremental >= 16.10.1"],
|
||||
install_requires=requirements,
|
||||
entry_points={
|
||||
'console_scripts': _CONSOLE_SCRIPTS
|
||||
},
|
||||
cmdclass=command_classes,
|
||||
include_package_data=True,
|
||||
exclude_package_data={
|
||||
"": ["*.c", "*.h", "*.pxi", "*.pyx", "build.bat"],
|
||||
},
|
||||
zip_safe=False,
|
||||
extras_require=_EXTRAS_REQUIRE,
|
||||
package_dir={"": "src"},
|
||||
))
|
||||
|
||||
return arguments
|
||||
|
||||
|
||||
|
||||
class BuildPy3(build_py, object):
|
||||
"""
|
||||
A version of build_py that doesn't install the modules that aren't yet
|
||||
ported to Python 3.
|
||||
"""
|
||||
def find_package_modules(self, package, package_dir):
|
||||
modules = [
|
||||
module for module
|
||||
in build_py.find_package_modules(self, package, package_dir)
|
||||
if ".".join([module[0], module[1]]) not in notPortedModules]
|
||||
return modules
|
||||
|
||||
|
||||
|
||||
## Helpers and distutil tweaks
|
||||
|
||||
|
||||
class build_ext_twisted(build_ext.build_ext, object):
|
||||
"""
|
||||
Allow subclasses to easily detect and customize Extensions to
|
||||
build at install-time.
|
||||
"""
|
||||
|
||||
def prepare_extensions(self):
|
||||
"""
|
||||
Prepare the C{self.extensions} attribute (used by
|
||||
L{build_ext.build_ext}) by checking which extensions in
|
||||
I{conditionalExtensions} should be built. In addition, if we are
|
||||
building on NT, define the WIN32 macro to 1.
|
||||
"""
|
||||
# always define WIN32 under Windows
|
||||
if os.name == 'nt':
|
||||
self.define_macros = [("WIN32", 1)]
|
||||
else:
|
||||
self.define_macros = []
|
||||
|
||||
# On Solaris 10, we need to define the _XOPEN_SOURCE and
|
||||
# _XOPEN_SOURCE_EXTENDED macros to build in order to gain access to
|
||||
# the msg_control, msg_controllen, and msg_flags members in
|
||||
# sendmsg.c. (according to
|
||||
# https://stackoverflow.com/questions/1034587). See the documentation
|
||||
# of X/Open CAE in the standards(5) man page of Solaris.
|
||||
if sys.platform.startswith('sunos'):
|
||||
self.define_macros.append(('_XOPEN_SOURCE', 1))
|
||||
self.define_macros.append(('_XOPEN_SOURCE_EXTENDED', 1))
|
||||
|
||||
self.extensions = [
|
||||
x for x in self.conditionalExtensions if x.condition(self)
|
||||
]
|
||||
|
||||
for ext in self.extensions:
|
||||
ext.define_macros.extend(self.define_macros)
|
||||
|
||||
|
||||
def build_extensions(self):
|
||||
"""
|
||||
Check to see which extension modules to build and then build them.
|
||||
"""
|
||||
self.prepare_extensions()
|
||||
build_ext.build_ext.build_extensions(self)
|
||||
|
||||
|
||||
def _remove_conftest(self):
|
||||
for filename in ("conftest.c", "conftest.o", "conftest.obj"):
|
||||
try:
|
||||
os.unlink(filename)
|
||||
except EnvironmentError:
|
||||
pass
|
||||
|
||||
|
||||
def _compile_helper(self, content):
|
||||
conftest = open("conftest.c", "w")
|
||||
try:
|
||||
with conftest:
|
||||
conftest.write(content)
|
||||
|
||||
try:
|
||||
self.compiler.compile(["conftest.c"], output_dir='')
|
||||
except CompileError:
|
||||
return False
|
||||
return True
|
||||
finally:
|
||||
self._remove_conftest()
|
||||
|
||||
|
||||
def _check_header(self, header_name):
|
||||
"""
|
||||
Check if the given header can be included by trying to compile a file
|
||||
that contains only an #include line.
|
||||
"""
|
||||
self.compiler.announce("checking for {} ...".format(header_name), 0)
|
||||
return self._compile_helper("#include <{}>\n".format(header_name))
|
||||
|
||||
|
||||
|
||||
def _checkCPython(sys=sys, platform=platform):
|
||||
"""
|
||||
Checks if this implementation is CPython.
|
||||
|
||||
This uses C{platform.python_implementation}.
|
||||
|
||||
This takes C{sys} and C{platform} kwargs that by default use the real
|
||||
modules. You shouldn't care about these -- they are for testing purposes
|
||||
only.
|
||||
|
||||
@return: C{False} if the implementation is definitely not CPython, C{True}
|
||||
otherwise.
|
||||
"""
|
||||
return platform.python_implementation() == "CPython"
|
||||
|
||||
|
||||
_isCPython = _checkCPython()
|
||||
|
||||
notPortedModules = [
|
||||
"twisted.mail.alias",
|
||||
"twisted.mail.bounce",
|
||||
"twisted.mail.mail",
|
||||
"twisted.mail.maildir",
|
||||
"twisted.mail.pb",
|
||||
"twisted.mail.relaymanager",
|
||||
"twisted.mail.scripts.__init__",
|
||||
"twisted.mail.tap",
|
||||
"twisted.mail.test.test_bounce",
|
||||
"twisted.mail.test.test_mail",
|
||||
"twisted.mail.test.test_options",
|
||||
"twisted.mail.test.test_scripts",
|
||||
"twisted.news.__init__",
|
||||
"twisted.news.database",
|
||||
"twisted.news.news",
|
||||
"twisted.news.nntp",
|
||||
"twisted.news.tap",
|
||||
"twisted.news.test.__init__",
|
||||
"twisted.news.test.test_database",
|
||||
"twisted.news.test.test_news",
|
||||
"twisted.news.test.test_nntp",
|
||||
"twisted.plugins.twisted_mail",
|
||||
"twisted.plugins.twisted_news",
|
||||
"twisted.protocols.shoutcast",
|
||||
"twisted.python._pydoctor",
|
||||
"twisted.python.finalize",
|
||||
"twisted.python.hook",
|
||||
"twisted.python.test.cmodulepullpipe",
|
||||
"twisted.python.test.test_pydoctor",
|
||||
"twisted.python.test.test_win32",
|
||||
"twisted.test.test_hook",
|
||||
"twisted.web.soap",
|
||||
"twisted.web.test.test_soap",
|
||||
]
|
||||
@@ -0,0 +1,320 @@
|
||||
# -*- test-case-name: twisted.python.test.test_textattributes -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
This module provides some common functionality for the manipulation of
|
||||
formatting states.
|
||||
|
||||
Defining the mechanism by which text containing character attributes is
|
||||
constructed begins by subclassing L{CharacterAttributesMixin}.
|
||||
|
||||
Defining how a single formatting state is to be serialized begins by
|
||||
subclassing L{_FormattingStateMixin}.
|
||||
|
||||
Serializing a formatting structure is done with L{flatten}.
|
||||
|
||||
@see: L{twisted.conch.insults.helper._FormattingState}
|
||||
@see: L{twisted.conch.insults.text._CharacterAttributes}
|
||||
@see: L{twisted.words.protocols.irc._FormattingState}
|
||||
@see: L{twisted.words.protocols.irc._CharacterAttributes}
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from twisted.python.util import FancyEqMixin
|
||||
|
||||
|
||||
|
||||
class _Attribute(FancyEqMixin, object):
|
||||
"""
|
||||
A text attribute.
|
||||
|
||||
Indexing a text attribute with a C{str} or another text attribute adds that
|
||||
object as a child, indexing with a C{list} or C{tuple} adds the elements as
|
||||
children; in either case C{self} is returned.
|
||||
|
||||
@type children: C{list}
|
||||
@ivar children: Child attributes.
|
||||
"""
|
||||
compareAttributes = ('children',)
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.children = []
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r>' % (type(self).__name__, vars(self))
|
||||
|
||||
|
||||
def __getitem__(self, item):
|
||||
assert isinstance(item, (list, tuple, _Attribute, str))
|
||||
if isinstance(item, (list, tuple)):
|
||||
self.children.extend(item)
|
||||
else:
|
||||
self.children.append(item)
|
||||
return self
|
||||
|
||||
|
||||
def serialize(self, write, attrs=None, attributeRenderer='toVT102'):
|
||||
"""
|
||||
Serialize the text attribute and its children.
|
||||
|
||||
@param write: C{callable}, taking one C{str} argument, called to output
|
||||
a single text attribute at a time.
|
||||
|
||||
@param attrs: A formatting state instance used to determine how to
|
||||
serialize the attribute children.
|
||||
|
||||
@type attributeRenderer: C{str}
|
||||
@param attributeRenderer: Name of the method on I{attrs} that should be
|
||||
called to render the attributes during serialization. Defaults to
|
||||
C{'toVT102'}.
|
||||
"""
|
||||
if attrs is None:
|
||||
attrs = DefaultFormattingState()
|
||||
for ch in self.children:
|
||||
if isinstance(ch, _Attribute):
|
||||
ch.serialize(write, attrs.copy(), attributeRenderer)
|
||||
else:
|
||||
renderMeth = getattr(attrs, attributeRenderer)
|
||||
write(renderMeth())
|
||||
write(ch)
|
||||
|
||||
|
||||
|
||||
class _NormalAttr(_Attribute):
|
||||
"""
|
||||
A text attribute for normal text.
|
||||
"""
|
||||
def serialize(self, write, attrs, attributeRenderer):
|
||||
attrs.__init__()
|
||||
_Attribute.serialize(self, write, attrs, attributeRenderer)
|
||||
|
||||
|
||||
|
||||
class _OtherAttr(_Attribute):
|
||||
"""
|
||||
A text attribute for text with formatting attributes.
|
||||
|
||||
The unary minus operator returns the inverse of this attribute, where that
|
||||
makes sense.
|
||||
|
||||
@type attrname: C{str}
|
||||
@ivar attrname: Text attribute name.
|
||||
|
||||
@ivar attrvalue: Text attribute value.
|
||||
"""
|
||||
compareAttributes = ('attrname', 'attrvalue', 'children')
|
||||
|
||||
|
||||
def __init__(self, attrname, attrvalue):
|
||||
_Attribute.__init__(self)
|
||||
self.attrname = attrname
|
||||
self.attrvalue = attrvalue
|
||||
|
||||
|
||||
def __neg__(self):
|
||||
result = _OtherAttr(self.attrname, not self.attrvalue)
|
||||
result.children.extend(self.children)
|
||||
return result
|
||||
|
||||
|
||||
def serialize(self, write, attrs, attributeRenderer):
|
||||
attrs = attrs._withAttribute(self.attrname, self.attrvalue)
|
||||
_Attribute.serialize(self, write, attrs, attributeRenderer)
|
||||
|
||||
|
||||
|
||||
class _ColorAttr(_Attribute):
|
||||
"""
|
||||
Generic color attribute.
|
||||
|
||||
@param color: Color value.
|
||||
|
||||
@param ground: Foreground or background attribute name.
|
||||
"""
|
||||
compareAttributes = ('color', 'ground', 'children')
|
||||
|
||||
|
||||
def __init__(self, color, ground):
|
||||
_Attribute.__init__(self)
|
||||
self.color = color
|
||||
self.ground = ground
|
||||
|
||||
|
||||
def serialize(self, write, attrs, attributeRenderer):
|
||||
attrs = attrs._withAttribute(self.ground, self.color)
|
||||
_Attribute.serialize(self, write, attrs, attributeRenderer)
|
||||
|
||||
|
||||
|
||||
class _ForegroundColorAttr(_ColorAttr):
|
||||
"""
|
||||
Foreground color attribute.
|
||||
"""
|
||||
def __init__(self, color):
|
||||
_ColorAttr.__init__(self, color, 'foreground')
|
||||
|
||||
|
||||
|
||||
class _BackgroundColorAttr(_ColorAttr):
|
||||
"""
|
||||
Background color attribute.
|
||||
"""
|
||||
def __init__(self, color):
|
||||
_ColorAttr.__init__(self, color, 'background')
|
||||
|
||||
|
||||
|
||||
class _ColorAttribute(object):
|
||||
"""
|
||||
A color text attribute.
|
||||
|
||||
Attribute access results in a color value lookup, by name, in
|
||||
I{_ColorAttribute.attrs}.
|
||||
|
||||
@type ground: L{_ColorAttr}
|
||||
@param ground: Foreground or background color attribute to look color names
|
||||
up from.
|
||||
|
||||
@param attrs: Mapping of color names to color values.
|
||||
@type attrs: Dict like object.
|
||||
"""
|
||||
def __init__(self, ground, attrs):
|
||||
self.ground = ground
|
||||
self.attrs = attrs
|
||||
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.ground(self.attrs[name])
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
|
||||
class CharacterAttributesMixin(object):
|
||||
"""
|
||||
Mixin for character attributes that implements a C{__getattr__} method
|
||||
returning a new C{_NormalAttr} instance when attempting to access
|
||||
a C{'normal'} attribute; otherwise a new C{_OtherAttr} instance is returned
|
||||
for names that appears in the C{'attrs'} attribute.
|
||||
"""
|
||||
def __getattr__(self, name):
|
||||
if name == 'normal':
|
||||
return _NormalAttr()
|
||||
if name in self.attrs:
|
||||
return _OtherAttr(name, True)
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
|
||||
class DefaultFormattingState(FancyEqMixin, object):
|
||||
"""
|
||||
A character attribute that does nothing, thus applying no attributes to
|
||||
text.
|
||||
"""
|
||||
compareAttributes = ('_dummy',)
|
||||
|
||||
_dummy = 0
|
||||
|
||||
|
||||
def copy(self):
|
||||
"""
|
||||
Make a copy of this formatting state.
|
||||
|
||||
@return: A formatting state instance.
|
||||
"""
|
||||
return type(self)()
|
||||
|
||||
|
||||
def _withAttribute(self, name, value):
|
||||
"""
|
||||
Add a character attribute to a copy of this formatting state.
|
||||
|
||||
@param name: Attribute name to be added to formatting state.
|
||||
|
||||
@param value: Attribute value.
|
||||
|
||||
@return: A formatting state instance with the new attribute.
|
||||
"""
|
||||
return self.copy()
|
||||
|
||||
|
||||
def toVT102(self):
|
||||
"""
|
||||
Emit a VT102 control sequence that will set up all the attributes this
|
||||
formatting state has set.
|
||||
|
||||
@return: A string containing VT102 control sequences that mimic this
|
||||
formatting state.
|
||||
"""
|
||||
return ''
|
||||
|
||||
|
||||
|
||||
class _FormattingStateMixin(DefaultFormattingState):
|
||||
"""
|
||||
Mixin for the formatting state/attributes of a single character.
|
||||
"""
|
||||
def copy(self):
|
||||
c = DefaultFormattingState.copy(self)
|
||||
c.__dict__.update(vars(self))
|
||||
return c
|
||||
|
||||
|
||||
def _withAttribute(self, name, value):
|
||||
if getattr(self, name) != value:
|
||||
attr = self.copy()
|
||||
attr._subtracting = not value
|
||||
setattr(attr, name, value)
|
||||
return attr
|
||||
else:
|
||||
return self.copy()
|
||||
|
||||
|
||||
|
||||
def flatten(output, attrs, attributeRenderer='toVT102'):
|
||||
"""
|
||||
Serialize a sequence of characters with attribute information
|
||||
|
||||
The resulting string can be interpreted by compatible software so that the
|
||||
contained characters are displayed and, for those attributes which are
|
||||
supported by the software, the attributes expressed. The exact result of
|
||||
the serialization depends on the behavior of the method specified by
|
||||
I{attributeRenderer}.
|
||||
|
||||
For example, if your terminal is VT102 compatible, you might run
|
||||
this for a colorful variation on the \"hello world\" theme::
|
||||
|
||||
from twisted.conch.insults.text import flatten, attributes as A
|
||||
from twisted.conch.insults.helper import CharacterAttribute
|
||||
print(flatten(
|
||||
A.normal[A.bold[A.fg.red['He'], A.fg.green['ll'], A.fg.magenta['o'], ' ',
|
||||
A.fg.yellow['Wo'], A.fg.blue['rl'], A.fg.cyan['d!']]],
|
||||
CharacterAttribute()))
|
||||
|
||||
@param output: Object returned by accessing attributes of the
|
||||
module-level attributes object.
|
||||
|
||||
@param attrs: A formatting state instance used to determine how to
|
||||
serialize C{output}.
|
||||
|
||||
@type attributeRenderer: C{str}
|
||||
@param attributeRenderer: Name of the method on I{attrs} that should be
|
||||
called to render the attributes during serialization. Defaults to
|
||||
C{'toVT102'}.
|
||||
|
||||
@return: A string expressing the text and display attributes specified by
|
||||
L{output}.
|
||||
"""
|
||||
flattened = []
|
||||
output.serialize(flattened.append, attrs, attributeRenderer)
|
||||
return ''.join(flattened)
|
||||
|
||||
|
||||
|
||||
__all__ = [
|
||||
'flatten', 'DefaultFormattingState', 'CharacterAttributesMixin']
|
||||
@@ -0,0 +1,119 @@
|
||||
# -*- test-case-name: twisted.python.test.test_tzhelper -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Time zone utilities.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, tzinfo
|
||||
|
||||
__all__ = [
|
||||
"FixedOffsetTimeZone",
|
||||
"UTC",
|
||||
]
|
||||
|
||||
|
||||
|
||||
class FixedOffsetTimeZone(tzinfo):
|
||||
"""
|
||||
Represents a fixed timezone offset (without daylight saving time).
|
||||
|
||||
@ivar name: A L{str} giving the name of this timezone; the name just
|
||||
includes how much time this offset represents.
|
||||
|
||||
@ivar offset: A L{timedelta} giving the amount of time this timezone is
|
||||
offset.
|
||||
"""
|
||||
|
||||
def __init__(self, offset, name=None):
|
||||
"""
|
||||
Construct a L{FixedOffsetTimeZone} with a fixed offset.
|
||||
|
||||
@param offset: a delta representing the offset from UTC.
|
||||
@type offset: L{timedelta}
|
||||
|
||||
@param name: A name to be given for this timezone.
|
||||
@type name: L{str} or L{None}
|
||||
"""
|
||||
self.offset = offset
|
||||
self.name = name
|
||||
|
||||
|
||||
@classmethod
|
||||
def fromSignHoursMinutes(cls, sign, hours, minutes):
|
||||
"""
|
||||
Construct a L{FixedOffsetTimeZone} from an offset described by sign
|
||||
('+' or '-'), hours, and minutes.
|
||||
|
||||
@note: For protocol compatibility with AMP, this method never uses 'Z'
|
||||
|
||||
@param sign: A string describing the positive or negative-ness of the
|
||||
offset.
|
||||
|
||||
@param hours: The number of hours in the offset.
|
||||
@type hours: L{int}
|
||||
|
||||
@param minutes: The number of minutes in the offset
|
||||
@type minutes: L{int}
|
||||
|
||||
@return: A time zone with the given offset, and a name describing the
|
||||
offset.
|
||||
@rtype: L{FixedOffsetTimeZone}
|
||||
"""
|
||||
name = "%s%02i:%02i" % (sign, hours, minutes)
|
||||
if sign == "-":
|
||||
hours = -hours
|
||||
minutes = -minutes
|
||||
elif sign != "+":
|
||||
raise ValueError("Invalid sign for timezone %r" % (sign,))
|
||||
return cls(timedelta(hours=hours, minutes=minutes), name)
|
||||
|
||||
|
||||
@classmethod
|
||||
def fromLocalTimeStamp(cls, timeStamp):
|
||||
"""
|
||||
Create a time zone with a fixed offset corresponding to a time stamp in
|
||||
the system's locally configured time zone.
|
||||
|
||||
@param timeStamp: a time stamp
|
||||
@type timeStamp: L{int}
|
||||
|
||||
@return: a time zone
|
||||
@rtype: L{FixedOffsetTimeZone}
|
||||
"""
|
||||
offset = (
|
||||
datetime.fromtimestamp(timeStamp) -
|
||||
datetime.utcfromtimestamp(timeStamp)
|
||||
)
|
||||
return cls(offset)
|
||||
|
||||
|
||||
def utcoffset(self, dt):
|
||||
"""
|
||||
Return this timezone's offset from UTC.
|
||||
"""
|
||||
return self.offset
|
||||
|
||||
|
||||
def dst(self, dt):
|
||||
"""
|
||||
Return a zero C{datetime.timedelta} for the daylight saving time
|
||||
offset, since there is never one.
|
||||
"""
|
||||
return timedelta(0)
|
||||
|
||||
|
||||
def tzname(self, dt):
|
||||
"""
|
||||
Return a string describing this timezone.
|
||||
"""
|
||||
if self.name is not None:
|
||||
return self.name
|
||||
# XXX this is wrong; the tests are
|
||||
dt = datetime.fromtimestamp(0, self)
|
||||
return dt.strftime("UTC%z")
|
||||
|
||||
|
||||
|
||||
UTC = FixedOffsetTimeZone.fromSignHoursMinutes("+", 0, 0)
|
||||
@@ -0,0 +1,18 @@
|
||||
# -*- test-case-name: twisted.python.test.test_constants -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Symbolic constant support, including collections and constants with text,
|
||||
numeric, and bit flag values.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
# Import and re-export Constantly
|
||||
from constantly import (NamedConstant, ValueConstant, FlagConstant, Names,
|
||||
Values, Flags)
|
||||
|
||||
__all__ = [
|
||||
'NamedConstant', 'ValueConstant', 'FlagConstant',
|
||||
'Names', 'Values', 'Flags']
|
||||
@@ -0,0 +1,137 @@
|
||||
# -*- test-case-name: twisted.test.test_context -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Dynamic pseudo-scoping for Python.
|
||||
|
||||
Call functions with context.call({key: value}, func); func and
|
||||
functions that it calls will be able to use 'context.get(key)' to
|
||||
retrieve 'value'.
|
||||
|
||||
This is thread-safe.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
from threading import local
|
||||
|
||||
from twisted.python._oldstyle import _oldStyle
|
||||
|
||||
|
||||
defaultContextDict = {}
|
||||
|
||||
setDefault = defaultContextDict.__setitem__
|
||||
|
||||
@_oldStyle
|
||||
class ContextTracker:
|
||||
"""
|
||||
A L{ContextTracker} provides a way to pass arbitrary key/value data up and
|
||||
down a call stack without passing them as parameters to the functions on
|
||||
that call stack.
|
||||
|
||||
This can be useful when functions on the top and bottom of the call stack
|
||||
need to cooperate but the functions in between them do not allow passing the
|
||||
necessary state. For example::
|
||||
|
||||
from twisted.python.context import call, get
|
||||
|
||||
def handleRequest(request):
|
||||
call({'request-id': request.id}, renderRequest, request.url)
|
||||
|
||||
def renderRequest(url):
|
||||
renderHeader(url)
|
||||
renderBody(url)
|
||||
|
||||
def renderHeader(url):
|
||||
return "the header"
|
||||
|
||||
def renderBody(url):
|
||||
return "the body (request id=%r)" % (get("request-id"),)
|
||||
|
||||
This should be used sparingly, since the lack of a clear connection between
|
||||
the two halves can result in code which is difficult to understand and
|
||||
maintain.
|
||||
|
||||
@ivar contexts: A C{list} of C{dict}s tracking the context state. Each new
|
||||
L{ContextTracker.callWithContext} pushes a new C{dict} onto this stack
|
||||
for the duration of the call, making the data available to the function
|
||||
called and restoring the previous data once it is complete..
|
||||
"""
|
||||
def __init__(self):
|
||||
self.contexts = [defaultContextDict]
|
||||
|
||||
|
||||
def callWithContext(self, newContext, func, *args, **kw):
|
||||
"""
|
||||
Call C{func(*args, **kw)} such that the contents of C{newContext} will
|
||||
be available for it to retrieve using L{getContext}.
|
||||
|
||||
@param newContext: A C{dict} of data to push onto the context for the
|
||||
duration of the call to C{func}.
|
||||
|
||||
@param func: A callable which will be called.
|
||||
|
||||
@param *args: Any additional positional arguments to pass to C{func}.
|
||||
|
||||
@param **kw: Any additional keyword arguments to pass to C{func}.
|
||||
|
||||
@return: Whatever is returned by C{func}
|
||||
|
||||
@raise: Whatever is raised by C{func}.
|
||||
"""
|
||||
self.contexts.append(newContext)
|
||||
try:
|
||||
return func(*args,**kw)
|
||||
finally:
|
||||
self.contexts.pop()
|
||||
|
||||
|
||||
def getContext(self, key, default=None):
|
||||
"""
|
||||
Retrieve the value for a key from the context.
|
||||
|
||||
@param key: The key to look up in the context.
|
||||
|
||||
@param default: The value to return if C{key} is not found in the
|
||||
context.
|
||||
|
||||
@return: The value most recently remembered in the context for C{key}.
|
||||
"""
|
||||
for ctx in reversed(self.contexts):
|
||||
try:
|
||||
return ctx[key]
|
||||
except KeyError:
|
||||
pass
|
||||
return default
|
||||
|
||||
|
||||
|
||||
class ThreadedContextTracker(object):
|
||||
def __init__(self):
|
||||
self.storage = local()
|
||||
|
||||
def currentContext(self):
|
||||
try:
|
||||
return self.storage.ct
|
||||
except AttributeError:
|
||||
ct = self.storage.ct = ContextTracker()
|
||||
return ct
|
||||
|
||||
def callWithContext(self, ctx, func, *args, **kw):
|
||||
return self.currentContext().callWithContext(ctx, func, *args, **kw)
|
||||
|
||||
def getContext(self, key, default=None):
|
||||
return self.currentContext().getContext(key, default)
|
||||
|
||||
|
||||
def installContextTracker(ctr):
|
||||
global theContextTracker
|
||||
global call
|
||||
global get
|
||||
|
||||
theContextTracker = ctr
|
||||
call = theContextTracker.callWithContext
|
||||
get = theContextTracker.getContext
|
||||
|
||||
installContextTracker(ThreadedContextTracker())
|
||||
@@ -0,0 +1,797 @@
|
||||
# -*- test-case-name: twisted.python.test.test_deprecate -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Deprecation framework for Twisted.
|
||||
|
||||
To mark a method, function, or class as being deprecated do this::
|
||||
|
||||
from incremental import Version
|
||||
from twisted.python.deprecate import deprecated
|
||||
|
||||
@deprecated(Version("Twisted", 8, 0, 0))
|
||||
def badAPI(self, first, second):
|
||||
'''
|
||||
Docstring for badAPI.
|
||||
'''
|
||||
...
|
||||
|
||||
@deprecated(Version("Twisted", 16, 0, 0))
|
||||
class BadClass(object):
|
||||
'''
|
||||
Docstring for BadClass.
|
||||
'''
|
||||
|
||||
The newly-decorated badAPI will issue a warning when called, and BadClass will
|
||||
issue a warning when instantiated. Both will also have a deprecation notice
|
||||
appended to their docstring.
|
||||
|
||||
To deprecate properties you can use::
|
||||
|
||||
from incremental import Version
|
||||
from twisted.python.deprecate import deprecatedProperty
|
||||
|
||||
class OtherwiseUndeprecatedClass(object):
|
||||
|
||||
@deprecatedProperty(Version('Twisted', 16, 0, 0))
|
||||
def badProperty(self):
|
||||
'''
|
||||
Docstring for badProperty.
|
||||
'''
|
||||
|
||||
@badProperty.setter
|
||||
def badProperty(self, value):
|
||||
'''
|
||||
Setter sill also raise the deprecation warning.
|
||||
'''
|
||||
|
||||
|
||||
To mark module-level attributes as being deprecated you can use::
|
||||
|
||||
badAttribute = "someValue"
|
||||
|
||||
...
|
||||
|
||||
deprecatedModuleAttribute(
|
||||
Version("Twisted", 8, 0, 0),
|
||||
"Use goodAttribute instead.",
|
||||
"your.full.module.name",
|
||||
"badAttribute")
|
||||
|
||||
The deprecated attributes will issue a warning whenever they are accessed. If
|
||||
the attributes being deprecated are in the same module as the
|
||||
L{deprecatedModuleAttribute} call is being made from, the C{__name__} global
|
||||
can be used as the C{moduleName} parameter.
|
||||
|
||||
See also L{incremental.Version}.
|
||||
|
||||
@type DEPRECATION_WARNING_FORMAT: C{str}
|
||||
@var DEPRECATION_WARNING_FORMAT: The default deprecation warning string format
|
||||
to use when one is not provided by the user.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
__all__ = [
|
||||
'deprecated',
|
||||
'deprecatedProperty',
|
||||
'getDeprecationWarningString',
|
||||
'getWarningMethod',
|
||||
'setWarningMethod',
|
||||
'deprecatedModuleAttribute',
|
||||
]
|
||||
|
||||
|
||||
import sys, inspect
|
||||
from warnings import warn, warn_explicit
|
||||
from dis import findlinestarts
|
||||
from functools import wraps
|
||||
|
||||
from incremental import getVersionString
|
||||
from twisted.python.compat import _PY3
|
||||
|
||||
DEPRECATION_WARNING_FORMAT = '%(fqpn)s was deprecated in %(version)s'
|
||||
|
||||
# Notionally, part of twisted.python.reflect, but defining it there causes a
|
||||
# cyclic dependency between this module and that module. Define it here,
|
||||
# instead, and let reflect import it to re-expose to the public.
|
||||
def _fullyQualifiedName(obj):
|
||||
"""
|
||||
Return the fully qualified name of a module, class, method or function.
|
||||
Classes and functions need to be module level ones to be correctly
|
||||
qualified.
|
||||
|
||||
@rtype: C{str}.
|
||||
"""
|
||||
try:
|
||||
name = obj.__qualname__
|
||||
except AttributeError:
|
||||
name = obj.__name__
|
||||
|
||||
if inspect.isclass(obj) or inspect.isfunction(obj):
|
||||
moduleName = obj.__module__
|
||||
return "%s.%s" % (moduleName, name)
|
||||
elif inspect.ismethod(obj):
|
||||
try:
|
||||
cls = obj.im_class
|
||||
except AttributeError:
|
||||
# Python 3 eliminates im_class, substitutes __module__ and
|
||||
# __qualname__ to provide similar information.
|
||||
return "%s.%s" % (obj.__module__, obj.__qualname__)
|
||||
else:
|
||||
className = _fullyQualifiedName(cls)
|
||||
return "%s.%s" % (className, name)
|
||||
return name
|
||||
# Try to keep it looking like something in twisted.python.reflect.
|
||||
_fullyQualifiedName.__module__ = 'twisted.python.reflect'
|
||||
_fullyQualifiedName.__name__ = 'fullyQualifiedName'
|
||||
_fullyQualifiedName.__qualname__ = 'fullyQualifiedName'
|
||||
|
||||
|
||||
def _getReplacementString(replacement):
|
||||
"""
|
||||
Surround a replacement for a deprecated API with some polite text exhorting
|
||||
the user to consider it as an alternative.
|
||||
|
||||
@type replacement: C{str} or callable
|
||||
|
||||
@return: a string like "please use twisted.python.modules.getModule
|
||||
instead".
|
||||
"""
|
||||
if callable(replacement):
|
||||
replacement = _fullyQualifiedName(replacement)
|
||||
return "please use %s instead" % (replacement,)
|
||||
|
||||
|
||||
|
||||
def _getDeprecationDocstring(version, replacement=None):
|
||||
"""
|
||||
Generate an addition to a deprecated object's docstring that explains its
|
||||
deprecation.
|
||||
|
||||
@param version: the version it was deprecated.
|
||||
@type version: L{incremental.Version}
|
||||
|
||||
@param replacement: The replacement, if specified.
|
||||
@type replacement: C{str} or callable
|
||||
|
||||
@return: a string like "Deprecated in Twisted 27.2.0; please use
|
||||
twisted.timestream.tachyon.flux instead."
|
||||
"""
|
||||
doc = "Deprecated in %s" % (getVersionString(version),)
|
||||
if replacement:
|
||||
doc = "%s; %s" % (doc, _getReplacementString(replacement))
|
||||
return doc + "."
|
||||
|
||||
|
||||
|
||||
def _getDeprecationWarningString(fqpn, version, format=None, replacement=None):
|
||||
"""
|
||||
Return a string indicating that the Python name was deprecated in the given
|
||||
version.
|
||||
|
||||
@param fqpn: Fully qualified Python name of the thing being deprecated
|
||||
@type fqpn: C{str}
|
||||
|
||||
@param version: Version that C{fqpn} was deprecated in.
|
||||
@type version: L{incremental.Version}
|
||||
|
||||
@param format: A user-provided format to interpolate warning values into, or
|
||||
L{DEPRECATION_WARNING_FORMAT
|
||||
<twisted.python.deprecate.DEPRECATION_WARNING_FORMAT>} if L{None} is
|
||||
given.
|
||||
@type format: C{str}
|
||||
|
||||
@param replacement: what should be used in place of C{fqpn}. Either pass in
|
||||
a string, which will be inserted into the warning message, or a
|
||||
callable, which will be expanded to its full import path.
|
||||
@type replacement: C{str} or callable
|
||||
|
||||
@return: A textual description of the deprecation
|
||||
@rtype: C{str}
|
||||
"""
|
||||
if format is None:
|
||||
format = DEPRECATION_WARNING_FORMAT
|
||||
warningString = format % {
|
||||
'fqpn': fqpn,
|
||||
'version': getVersionString(version)}
|
||||
if replacement:
|
||||
warningString = "%s; %s" % (
|
||||
warningString, _getReplacementString(replacement))
|
||||
return warningString
|
||||
|
||||
|
||||
|
||||
def getDeprecationWarningString(callableThing, version, format=None,
|
||||
replacement=None):
|
||||
"""
|
||||
Return a string indicating that the callable was deprecated in the given
|
||||
version.
|
||||
|
||||
@type callableThing: C{callable}
|
||||
@param callableThing: Callable object to be deprecated
|
||||
|
||||
@type version: L{incremental.Version}
|
||||
@param version: Version that C{callableThing} was deprecated in
|
||||
|
||||
@type format: C{str}
|
||||
@param format: A user-provided format to interpolate warning values into,
|
||||
or L{DEPRECATION_WARNING_FORMAT
|
||||
<twisted.python.deprecate.DEPRECATION_WARNING_FORMAT>} if L{None} is
|
||||
given
|
||||
|
||||
@param callableThing: A callable to be deprecated.
|
||||
|
||||
@param version: The L{incremental.Version} that the callable
|
||||
was deprecated in.
|
||||
|
||||
@param replacement: what should be used in place of the callable. Either
|
||||
pass in a string, which will be inserted into the warning message,
|
||||
or a callable, which will be expanded to its full import path.
|
||||
@type replacement: C{str} or callable
|
||||
|
||||
@return: A string describing the deprecation.
|
||||
@rtype: C{str}
|
||||
"""
|
||||
return _getDeprecationWarningString(
|
||||
_fullyQualifiedName(callableThing), version, format, replacement)
|
||||
|
||||
|
||||
|
||||
def _appendToDocstring(thingWithDoc, textToAppend):
|
||||
"""
|
||||
Append the given text to the docstring of C{thingWithDoc}.
|
||||
|
||||
If C{thingWithDoc} has no docstring, then the text just replaces the
|
||||
docstring. If it has a single-line docstring then it appends a blank line
|
||||
and the message text. If it has a multi-line docstring, then in appends a
|
||||
blank line a the message text, and also does the indentation correctly.
|
||||
"""
|
||||
if thingWithDoc.__doc__:
|
||||
docstringLines = thingWithDoc.__doc__.splitlines()
|
||||
else:
|
||||
docstringLines = []
|
||||
|
||||
if len(docstringLines) == 0:
|
||||
docstringLines.append(textToAppend)
|
||||
elif len(docstringLines) == 1:
|
||||
docstringLines.extend(['', textToAppend, ''])
|
||||
else:
|
||||
spaces = docstringLines.pop()
|
||||
docstringLines.extend(['',
|
||||
spaces + textToAppend,
|
||||
spaces])
|
||||
thingWithDoc.__doc__ = '\n'.join(docstringLines)
|
||||
|
||||
|
||||
|
||||
def deprecated(version, replacement=None):
|
||||
"""
|
||||
Return a decorator that marks callables as deprecated. To deprecate a
|
||||
property, see L{deprecatedProperty}.
|
||||
|
||||
@type version: L{incremental.Version}
|
||||
@param version: The version in which the callable will be marked as
|
||||
having been deprecated. The decorated function will be annotated
|
||||
with this version, having it set as its C{deprecatedVersion}
|
||||
attribute.
|
||||
|
||||
@param version: the version that the callable was deprecated in.
|
||||
@type version: L{incremental.Version}
|
||||
|
||||
@param replacement: what should be used in place of the callable. Either
|
||||
pass in a string, which will be inserted into the warning message,
|
||||
or a callable, which will be expanded to its full import path.
|
||||
@type replacement: C{str} or callable
|
||||
"""
|
||||
def deprecationDecorator(function):
|
||||
"""
|
||||
Decorator that marks C{function} as deprecated.
|
||||
"""
|
||||
warningString = getDeprecationWarningString(
|
||||
function, version, None, replacement)
|
||||
|
||||
@wraps(function)
|
||||
def deprecatedFunction(*args, **kwargs):
|
||||
warn(
|
||||
warningString,
|
||||
DeprecationWarning,
|
||||
stacklevel=2)
|
||||
return function(*args, **kwargs)
|
||||
|
||||
_appendToDocstring(deprecatedFunction,
|
||||
_getDeprecationDocstring(version, replacement))
|
||||
deprecatedFunction.deprecatedVersion = version
|
||||
return deprecatedFunction
|
||||
|
||||
return deprecationDecorator
|
||||
|
||||
|
||||
|
||||
def deprecatedProperty(version, replacement=None):
|
||||
"""
|
||||
Return a decorator that marks a property as deprecated. To deprecate a
|
||||
regular callable or class, see L{deprecated}.
|
||||
|
||||
@type version: L{incremental.Version}
|
||||
@param version: The version in which the callable will be marked as
|
||||
having been deprecated. The decorated function will be annotated
|
||||
with this version, having it set as its C{deprecatedVersion}
|
||||
attribute.
|
||||
|
||||
@param version: the version that the callable was deprecated in.
|
||||
@type version: L{incremental.Version}
|
||||
|
||||
@param replacement: what should be used in place of the callable.
|
||||
Either pass in a string, which will be inserted into the warning
|
||||
message, or a callable, which will be expanded to its full import
|
||||
path.
|
||||
@type replacement: C{str} or callable
|
||||
|
||||
@return: A new property with deprecated setter and getter.
|
||||
@rtype: C{property}
|
||||
|
||||
@since: 16.1.0
|
||||
"""
|
||||
|
||||
class _DeprecatedProperty(property):
|
||||
"""
|
||||
Extension of the build-in property to allow deprecated setters.
|
||||
"""
|
||||
|
||||
def _deprecatedWrapper(self, function):
|
||||
@wraps(function)
|
||||
def deprecatedFunction(*args, **kwargs):
|
||||
warn(
|
||||
self.warningString,
|
||||
DeprecationWarning,
|
||||
stacklevel=2)
|
||||
return function(*args, **kwargs)
|
||||
return deprecatedFunction
|
||||
|
||||
|
||||
def setter(self, function):
|
||||
return property.setter(self, self._deprecatedWrapper(function))
|
||||
|
||||
|
||||
def deprecationDecorator(function):
|
||||
if _PY3:
|
||||
warningString = getDeprecationWarningString(
|
||||
function, version, None, replacement)
|
||||
else:
|
||||
# Because Python 2 sucks, we need to implement our own here -- lack
|
||||
# of __qualname__ means that we kinda have to stack walk. It maybe
|
||||
# probably works. Probably. -Amber
|
||||
functionName = function.__name__
|
||||
className = inspect.stack()[1][3] # wow hax
|
||||
moduleName = function.__module__
|
||||
|
||||
fqdn = "%s.%s.%s" % (moduleName, className, functionName)
|
||||
|
||||
warningString = _getDeprecationWarningString(
|
||||
fqdn, version, None, replacement)
|
||||
|
||||
@wraps(function)
|
||||
def deprecatedFunction(*args, **kwargs):
|
||||
warn(
|
||||
warningString,
|
||||
DeprecationWarning,
|
||||
stacklevel=2)
|
||||
return function(*args, **kwargs)
|
||||
|
||||
_appendToDocstring(deprecatedFunction,
|
||||
_getDeprecationDocstring(version, replacement))
|
||||
deprecatedFunction.deprecatedVersion = version
|
||||
|
||||
result = _DeprecatedProperty(deprecatedFunction)
|
||||
result.warningString = warningString
|
||||
return result
|
||||
|
||||
return deprecationDecorator
|
||||
|
||||
|
||||
|
||||
def getWarningMethod():
|
||||
"""
|
||||
Return the warning method currently used to record deprecation warnings.
|
||||
"""
|
||||
return warn
|
||||
|
||||
|
||||
|
||||
def setWarningMethod(newMethod):
|
||||
"""
|
||||
Set the warning method to use to record deprecation warnings.
|
||||
|
||||
The callable should take message, category and stacklevel. The return
|
||||
value is ignored.
|
||||
"""
|
||||
global warn
|
||||
warn = newMethod
|
||||
|
||||
|
||||
|
||||
class _InternalState(object):
|
||||
"""
|
||||
An L{_InternalState} is a helper object for a L{_ModuleProxy}, so that it
|
||||
can easily access its own attributes, bypassing its logic for delegating to
|
||||
another object that it's proxying for.
|
||||
|
||||
@ivar proxy: a L{_ModuleProxy}
|
||||
"""
|
||||
def __init__(self, proxy):
|
||||
object.__setattr__(self, 'proxy', proxy)
|
||||
|
||||
|
||||
def __getattribute__(self, name):
|
||||
return object.__getattribute__(object.__getattribute__(self, 'proxy'),
|
||||
name)
|
||||
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
return object.__setattr__(object.__getattribute__(self, 'proxy'),
|
||||
name, value)
|
||||
|
||||
|
||||
|
||||
class _ModuleProxy(object):
|
||||
"""
|
||||
Python module wrapper to hook module-level attribute access.
|
||||
|
||||
Access to deprecated attributes first checks
|
||||
L{_ModuleProxy._deprecatedAttributes}, if the attribute does not appear
|
||||
there then access falls through to L{_ModuleProxy._module}, the wrapped
|
||||
module object.
|
||||
|
||||
@ivar _module: Module on which to hook attribute access.
|
||||
@type _module: C{module}
|
||||
|
||||
@ivar _deprecatedAttributes: Mapping of attribute names to objects that
|
||||
retrieve the module attribute's original value.
|
||||
@type _deprecatedAttributes: C{dict} mapping C{str} to
|
||||
L{_DeprecatedAttribute}
|
||||
|
||||
@ivar _lastWasPath: Heuristic guess as to whether warnings about this
|
||||
package should be ignored for the next call. If the last attribute
|
||||
access of this module was a C{getattr} of C{__path__}, we will assume
|
||||
that it was the import system doing it and we won't emit a warning for
|
||||
the next access, even if it is to a deprecated attribute. The CPython
|
||||
import system always tries to access C{__path__}, then the attribute
|
||||
itself, then the attribute itself again, in both successful and failed
|
||||
cases.
|
||||
@type _lastWasPath: C{bool}
|
||||
"""
|
||||
def __init__(self, module):
|
||||
state = _InternalState(self)
|
||||
state._module = module
|
||||
state._deprecatedAttributes = {}
|
||||
state._lastWasPath = False
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Get a string containing the type of the module proxy and a
|
||||
representation of the wrapped module object.
|
||||
"""
|
||||
state = _InternalState(self)
|
||||
return '<%s module=%r>' % (type(self).__name__, state._module)
|
||||
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
"""
|
||||
Set an attribute on the wrapped module object.
|
||||
"""
|
||||
state = _InternalState(self)
|
||||
state._lastWasPath = False
|
||||
setattr(state._module, name, value)
|
||||
|
||||
|
||||
def __getattribute__(self, name):
|
||||
"""
|
||||
Get an attribute from the module object, possibly emitting a warning.
|
||||
|
||||
If the specified name has been deprecated, then a warning is issued.
|
||||
(Unless certain obscure conditions are met; see
|
||||
L{_ModuleProxy._lastWasPath} for more information about what might quash
|
||||
such a warning.)
|
||||
"""
|
||||
state = _InternalState(self)
|
||||
if state._lastWasPath:
|
||||
deprecatedAttribute = None
|
||||
else:
|
||||
deprecatedAttribute = state._deprecatedAttributes.get(name)
|
||||
|
||||
if deprecatedAttribute is not None:
|
||||
# If we have a _DeprecatedAttribute object from the earlier lookup,
|
||||
# allow it to issue the warning.
|
||||
value = deprecatedAttribute.get()
|
||||
else:
|
||||
# Otherwise, just retrieve the underlying value directly; it's not
|
||||
# deprecated, there's no warning to issue.
|
||||
value = getattr(state._module, name)
|
||||
if name == '__path__':
|
||||
state._lastWasPath = True
|
||||
else:
|
||||
state._lastWasPath = False
|
||||
return value
|
||||
|
||||
|
||||
|
||||
class _DeprecatedAttribute(object):
|
||||
"""
|
||||
Wrapper for deprecated attributes.
|
||||
|
||||
This is intended to be used by L{_ModuleProxy}. Calling
|
||||
L{_DeprecatedAttribute.get} will issue a warning and retrieve the
|
||||
underlying attribute's value.
|
||||
|
||||
@type module: C{module}
|
||||
@ivar module: The original module instance containing this attribute
|
||||
|
||||
@type fqpn: C{str}
|
||||
@ivar fqpn: Fully qualified Python name for the deprecated attribute
|
||||
|
||||
@type version: L{incremental.Version}
|
||||
@ivar version: Version that the attribute was deprecated in
|
||||
|
||||
@type message: C{str}
|
||||
@ivar message: Deprecation message
|
||||
"""
|
||||
def __init__(self, module, name, version, message):
|
||||
"""
|
||||
Initialise a deprecated name wrapper.
|
||||
"""
|
||||
self.module = module
|
||||
self.__name__ = name
|
||||
self.fqpn = module.__name__ + '.' + name
|
||||
self.version = version
|
||||
self.message = message
|
||||
|
||||
|
||||
def get(self):
|
||||
"""
|
||||
Get the underlying attribute value and issue a deprecation warning.
|
||||
"""
|
||||
# This might fail if the deprecated thing is a module inside a package.
|
||||
# In that case, don't emit the warning this time. The import system
|
||||
# will come back again when it's not an AttributeError and we can emit
|
||||
# the warning then.
|
||||
result = getattr(self.module, self.__name__)
|
||||
message = _getDeprecationWarningString(self.fqpn, self.version,
|
||||
DEPRECATION_WARNING_FORMAT + ': ' + self.message)
|
||||
warn(message, DeprecationWarning, stacklevel=3)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def _deprecateAttribute(proxy, name, version, message):
|
||||
"""
|
||||
Mark a module-level attribute as being deprecated.
|
||||
|
||||
@type proxy: L{_ModuleProxy}
|
||||
@param proxy: The module proxy instance proxying the deprecated attributes
|
||||
|
||||
@type name: C{str}
|
||||
@param name: Attribute name
|
||||
|
||||
@type version: L{incremental.Version}
|
||||
@param version: Version that the attribute was deprecated in
|
||||
|
||||
@type message: C{str}
|
||||
@param message: Deprecation message
|
||||
"""
|
||||
_module = object.__getattribute__(proxy, '_module')
|
||||
attr = _DeprecatedAttribute(_module, name, version, message)
|
||||
# Add a deprecated attribute marker for this module's attribute. When this
|
||||
# attribute is accessed via _ModuleProxy a warning is emitted.
|
||||
_deprecatedAttributes = object.__getattribute__(
|
||||
proxy, '_deprecatedAttributes')
|
||||
_deprecatedAttributes[name] = attr
|
||||
|
||||
|
||||
|
||||
def deprecatedModuleAttribute(version, message, moduleName, name):
|
||||
"""
|
||||
Declare a module-level attribute as being deprecated.
|
||||
|
||||
@type version: L{incremental.Version}
|
||||
@param version: Version that the attribute was deprecated in
|
||||
|
||||
@type message: C{str}
|
||||
@param message: Deprecation message
|
||||
|
||||
@type moduleName: C{str}
|
||||
@param moduleName: Fully-qualified Python name of the module containing
|
||||
the deprecated attribute; if called from the same module as the
|
||||
attributes are being deprecated in, using the C{__name__} global can
|
||||
be helpful
|
||||
|
||||
@type name: C{str}
|
||||
@param name: Attribute name to deprecate
|
||||
"""
|
||||
module = sys.modules[moduleName]
|
||||
if not isinstance(module, _ModuleProxy):
|
||||
module = _ModuleProxy(module)
|
||||
sys.modules[moduleName] = module
|
||||
|
||||
_deprecateAttribute(module, name, version, message)
|
||||
|
||||
|
||||
def warnAboutFunction(offender, warningString):
|
||||
"""
|
||||
Issue a warning string, identifying C{offender} as the responsible code.
|
||||
|
||||
This function is used to deprecate some behavior of a function. It differs
|
||||
from L{warnings.warn} in that it is not limited to deprecating the behavior
|
||||
of a function currently on the call stack.
|
||||
|
||||
@param function: The function that is being deprecated.
|
||||
|
||||
@param warningString: The string that should be emitted by this warning.
|
||||
@type warningString: C{str}
|
||||
|
||||
@since: 11.0
|
||||
"""
|
||||
# inspect.getmodule() is attractive, but somewhat
|
||||
# broken in Python < 2.6. See Python bug 4845.
|
||||
offenderModule = sys.modules[offender.__module__]
|
||||
filename = inspect.getabsfile(offenderModule)
|
||||
lineStarts = list(findlinestarts(offender.__code__))
|
||||
lastLineNo = lineStarts[-1][1]
|
||||
globals = offender.__globals__
|
||||
|
||||
kwargs = dict(
|
||||
category=DeprecationWarning,
|
||||
filename=filename,
|
||||
lineno=lastLineNo,
|
||||
module=offenderModule.__name__,
|
||||
registry=globals.setdefault("__warningregistry__", {}),
|
||||
module_globals=None)
|
||||
|
||||
warn_explicit(warningString, **kwargs)
|
||||
|
||||
|
||||
|
||||
def _passedArgSpec(argspec, positional, keyword):
|
||||
"""
|
||||
Take an I{inspect.ArgSpec}, a tuple of positional arguments, and a dict of
|
||||
keyword arguments, and return a mapping of arguments that were actually
|
||||
passed to their passed values.
|
||||
|
||||
@param argspec: The argument specification for the function to inspect.
|
||||
@type argspec: I{inspect.ArgSpec}
|
||||
|
||||
@param positional: The positional arguments that were passed.
|
||||
@type positional: L{tuple}
|
||||
|
||||
@param keyword: The keyword arguments that were passed.
|
||||
@type keyword: L{dict}
|
||||
|
||||
@return: A dictionary mapping argument names (those declared in C{argspec})
|
||||
to values that were passed explicitly by the user.
|
||||
@rtype: L{dict} mapping L{str} to L{object}
|
||||
"""
|
||||
result = {}
|
||||
unpassed = len(argspec.args) - len(positional)
|
||||
if argspec.keywords is not None:
|
||||
kwargs = result[argspec.keywords] = {}
|
||||
if unpassed < 0:
|
||||
if argspec.varargs is None:
|
||||
raise TypeError("Too many arguments.")
|
||||
else:
|
||||
result[argspec.varargs] = positional[len(argspec.args):]
|
||||
for name, value in zip(argspec.args, positional):
|
||||
result[name] = value
|
||||
for name, value in keyword.items():
|
||||
if name in argspec.args:
|
||||
if name in result:
|
||||
raise TypeError("Already passed.")
|
||||
result[name] = value
|
||||
elif argspec.keywords is not None:
|
||||
kwargs[name] = value
|
||||
else:
|
||||
raise TypeError("no such param")
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def _passedSignature(signature, positional, keyword):
|
||||
"""
|
||||
Take an L{inspect.Signature}, a tuple of positional arguments, and a dict of
|
||||
keyword arguments, and return a mapping of arguments that were actually
|
||||
passed to their passed values.
|
||||
|
||||
@param signature: The signature of the function to inspect.
|
||||
@type signature: L{inspect.Signature}
|
||||
|
||||
@param positional: The positional arguments that were passed.
|
||||
@type positional: L{tuple}
|
||||
|
||||
@param keyword: The keyword arguments that were passed.
|
||||
@type keyword: L{dict}
|
||||
|
||||
@return: A dictionary mapping argument names (those declared in
|
||||
C{signature}) to values that were passed explicitly by the user.
|
||||
@rtype: L{dict} mapping L{str} to L{object}
|
||||
"""
|
||||
result = {}
|
||||
kwargs = None
|
||||
numPositional = 0
|
||||
for (n, (name, param)) in enumerate(signature.parameters.items()):
|
||||
if param.kind == inspect.Parameter.VAR_POSITIONAL:
|
||||
# Varargs, for example: *args
|
||||
result[name] = positional[n:]
|
||||
numPositional = len(result[name]) + 1
|
||||
elif param.kind == inspect.Parameter.VAR_KEYWORD:
|
||||
# Variable keyword args, for example: **my_kwargs
|
||||
kwargs = result[name] = {}
|
||||
elif param.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||
inspect.Parameter.POSITIONAL_ONLY):
|
||||
if n < len(positional):
|
||||
result[name] = positional[n]
|
||||
numPositional += 1
|
||||
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
|
||||
if name not in keyword:
|
||||
if param.default == inspect.Parameter.empty:
|
||||
raise TypeError("missing keyword arg {}".format(name))
|
||||
else:
|
||||
result[name] = param.default
|
||||
else:
|
||||
raise TypeError("'{}' parameter is invalid kind: {}".format(
|
||||
name, param.kind))
|
||||
|
||||
if len(positional) > numPositional:
|
||||
raise TypeError("Too many arguments.")
|
||||
for name, value in keyword.items():
|
||||
if name in signature.parameters.keys():
|
||||
if name in result:
|
||||
raise TypeError("Already passed.")
|
||||
result[name] = value
|
||||
elif kwargs is not None:
|
||||
kwargs[name] = value
|
||||
else:
|
||||
raise TypeError("no such param")
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def _mutuallyExclusiveArguments(argumentPairs):
|
||||
"""
|
||||
Decorator which causes its decoratee to raise a L{TypeError} if two of the
|
||||
given arguments are passed at the same time.
|
||||
|
||||
@param argumentPairs: pairs of argument identifiers, each pair indicating
|
||||
an argument that may not be passed in conjunction with another.
|
||||
@type argumentPairs: sequence of 2-sequences of L{str}
|
||||
|
||||
@return: A decorator, used like so::
|
||||
|
||||
@_mutuallyExclusiveArguments([["tweedledum", "tweedledee"]])
|
||||
def function(tweedledum=1, tweedledee=2):
|
||||
"Don't pass tweedledum and tweedledee at the same time."
|
||||
|
||||
@rtype: 1-argument callable taking a callable and returning a callable.
|
||||
"""
|
||||
def wrapper(wrappee):
|
||||
if getattr(inspect, "signature", None):
|
||||
# Python 3
|
||||
spec = inspect.signature(wrappee)
|
||||
_passed = _passedSignature
|
||||
else:
|
||||
# Python 2
|
||||
spec = inspect.getargspec(wrappee)
|
||||
_passed = _passedArgSpec
|
||||
|
||||
@wraps(wrappee)
|
||||
def wrapped(*args, **kwargs):
|
||||
arguments = _passed(spec, args, kwargs)
|
||||
for this, that in argumentPairs:
|
||||
if this in arguments and that in arguments:
|
||||
raise TypeError(
|
||||
("The %r and %r arguments to %s "
|
||||
"are mutually exclusive.") %
|
||||
(this, that, _fullyQualifiedName(wrappee)))
|
||||
return wrappee(*args, **kwargs)
|
||||
return wrapped
|
||||
return wrapper
|
||||
@@ -0,0 +1,798 @@
|
||||
# -*- test-case-name: twisted.test.test_failure -*-
|
||||
# See also test suite twisted.test.test_pbfailure
|
||||
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
|
||||
"""
|
||||
Asynchronous-friendly error mechanism.
|
||||
|
||||
See L{Failure}.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
# System Imports
|
||||
import copy
|
||||
import sys
|
||||
import linecache
|
||||
import inspect
|
||||
import opcode
|
||||
from inspect import getmro
|
||||
|
||||
from twisted.python import reflect
|
||||
from twisted.python.compat import _PY3, NativeStringIO as StringIO
|
||||
|
||||
count = 0
|
||||
traceupLength = 4
|
||||
|
||||
class DefaultException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def format_frames(frames, write, detail="default"):
|
||||
"""
|
||||
Format and write frames.
|
||||
|
||||
@param frames: is a list of frames as used by Failure.frames, with
|
||||
each frame being a list of
|
||||
(funcName, fileName, lineNumber, locals.items(), globals.items())
|
||||
@type frames: list
|
||||
@param write: this will be called with formatted strings.
|
||||
@type write: callable
|
||||
@param detail: Four detail levels are available:
|
||||
default, brief, verbose, and verbose-vars-not-captured.
|
||||
C{Failure.printDetailedTraceback} uses the latter when the caller asks
|
||||
for verbose, but no vars were captured, so that an explicit warning
|
||||
about the missing data is shown.
|
||||
@type detail: string
|
||||
"""
|
||||
if detail not in ('default', 'brief', 'verbose',
|
||||
'verbose-vars-not-captured'):
|
||||
raise ValueError(
|
||||
"Detail must be default, brief, verbose, or "
|
||||
"verbose-vars-not-captured. (not %r)" % (detail,))
|
||||
w = write
|
||||
if detail == "brief":
|
||||
for method, filename, lineno, localVars, globalVars in frames:
|
||||
w('%s:%s:%s\n' % (filename, lineno, method))
|
||||
elif detail == "default":
|
||||
for method, filename, lineno, localVars, globalVars in frames:
|
||||
w(' File "%s", line %s, in %s\n' % (filename, lineno, method))
|
||||
w(' %s\n' % linecache.getline(filename, lineno).strip())
|
||||
elif detail == "verbose-vars-not-captured":
|
||||
for method, filename, lineno, localVars, globalVars in frames:
|
||||
w("%s:%d: %s(...)\n" % (filename, lineno, method))
|
||||
w(' [Capture of Locals and Globals disabled (use captureVars=True)]\n')
|
||||
elif detail == "verbose":
|
||||
for method, filename, lineno, localVars, globalVars in frames:
|
||||
w("%s:%d: %s(...)\n" % (filename, lineno, method))
|
||||
w(' [ Locals ]\n')
|
||||
# Note: the repr(val) was (self.pickled and val) or repr(val)))
|
||||
for name, val in localVars:
|
||||
w(" %s : %s\n" % (name, repr(val)))
|
||||
w(' ( Globals )\n')
|
||||
for name, val in globalVars:
|
||||
w(" %s : %s\n" % (name, repr(val)))
|
||||
|
||||
# slyphon: i have a need to check for this value in trial
|
||||
# so I made it a module-level constant
|
||||
EXCEPTION_CAUGHT_HERE = "--- <exception caught here> ---"
|
||||
|
||||
|
||||
|
||||
class NoCurrentExceptionError(Exception):
|
||||
"""
|
||||
Raised when trying to create a Failure from the current interpreter
|
||||
exception state and there is no current exception state.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def _Traceback(stackFrames, tbFrames):
|
||||
"""
|
||||
Construct a fake traceback object using a list of frames. Note that
|
||||
although frames generally include locals and globals, this information
|
||||
is not kept by this method, since locals and globals are not used in
|
||||
standard tracebacks.
|
||||
|
||||
@param stackFrames: [(methodname, filename, lineno, locals, globals), ...]
|
||||
@param tbFrames: [(methodname, filename, lineno, locals, globals), ...]
|
||||
"""
|
||||
assert len(tbFrames) > 0, "Must pass some frames"
|
||||
# We deliberately avoid using recursion here, as the frames list may be
|
||||
# long.
|
||||
|
||||
# 'stackFrames' is a list of frames above (ie, older than) the point the
|
||||
# exception was caught, with oldest at the start. Start by building these
|
||||
# into a linked list of _Frame objects (with the f_back links pointing back
|
||||
# towards the oldest frame).
|
||||
stack = None
|
||||
for sf in stackFrames:
|
||||
stack = _Frame(sf, stack)
|
||||
|
||||
# 'tbFrames' is a list of frames from the point the exception was caught,
|
||||
# down to where it was thrown, with the oldest at the start. Add these to
|
||||
# the linked list of _Frames, but also wrap each one with a _Traceback
|
||||
# frame which is linked in the opposite direction (towards the newest
|
||||
# frame).
|
||||
stack = _Frame(tbFrames[0], stack)
|
||||
firstTb = tb = _TracebackFrame(stack)
|
||||
for sf in tbFrames[1:]:
|
||||
stack = _Frame(sf, stack)
|
||||
tb.tb_next = _TracebackFrame(stack)
|
||||
tb = tb.tb_next
|
||||
|
||||
# Return the first _TracebackFrame.
|
||||
return firstTb
|
||||
|
||||
|
||||
|
||||
class _TracebackFrame(object):
|
||||
"""
|
||||
Fake traceback object which can be passed to functions in the standard
|
||||
library L{traceback} module.
|
||||
"""
|
||||
|
||||
def __init__(self, frame):
|
||||
"""
|
||||
@param frame: _Frame object
|
||||
"""
|
||||
self.tb_frame = frame
|
||||
self.tb_lineno = frame.f_lineno
|
||||
self.tb_next = None
|
||||
|
||||
|
||||
|
||||
class _Frame(object):
|
||||
"""
|
||||
A fake frame object, used by L{_Traceback}.
|
||||
|
||||
@ivar f_code: fake L{code<types.CodeType>} object
|
||||
@ivar f_lineno: line number
|
||||
@ivar f_globals: fake f_globals dictionary (usually empty)
|
||||
@ivar f_locals: fake f_locals dictionary (usually empty)
|
||||
@ivar f_back: previous stack frame (towards the caller)
|
||||
"""
|
||||
|
||||
def __init__(self, frameinfo, back):
|
||||
"""
|
||||
@param frameinfo: (methodname, filename, lineno, locals, globals)
|
||||
@param back: previous (older) stack frame
|
||||
@type back: C{frame}
|
||||
"""
|
||||
name, filename, lineno, localz, globalz = frameinfo
|
||||
self.f_code = _Code(name, filename)
|
||||
self.f_lineno = lineno
|
||||
self.f_globals = {}
|
||||
self.f_locals = {}
|
||||
self.f_back = back
|
||||
|
||||
|
||||
|
||||
class _Code(object):
|
||||
"""
|
||||
A fake code object, used by L{_Traceback} via L{_Frame}.
|
||||
"""
|
||||
def __init__(self, name, filename):
|
||||
self.co_name = name
|
||||
self.co_filename = filename
|
||||
|
||||
|
||||
|
||||
_inlineCallbacksExtraneous = []
|
||||
|
||||
def _extraneous(f):
|
||||
"""
|
||||
Mark the given callable as extraneous to inlineCallbacks exception
|
||||
reporting; don't show these functions.
|
||||
|
||||
@param f: a function that you NEVER WANT TO SEE AGAIN in ANY TRACEBACK
|
||||
reported by Failure.
|
||||
|
||||
@type f: function
|
||||
|
||||
@return: f
|
||||
"""
|
||||
_inlineCallbacksExtraneous.append(f.__code__)
|
||||
return f
|
||||
|
||||
|
||||
|
||||
class Failure(BaseException):
|
||||
"""
|
||||
A basic abstraction for an error that has occurred.
|
||||
|
||||
This is necessary because Python's built-in error mechanisms are
|
||||
inconvenient for asynchronous communication.
|
||||
|
||||
The C{stack} and C{frame} attributes contain frames. Each frame is a tuple
|
||||
of (funcName, fileName, lineNumber, localsItems, globalsItems), where
|
||||
localsItems and globalsItems are the contents of
|
||||
C{locals().items()}/C{globals().items()} for that frame, or an empty tuple
|
||||
if those details were not captured.
|
||||
|
||||
@ivar value: The exception instance responsible for this failure.
|
||||
@ivar type: The exception's class.
|
||||
@ivar stack: list of frames, innermost last, excluding C{Failure.__init__}.
|
||||
@ivar frames: list of frames, innermost first.
|
||||
"""
|
||||
|
||||
pickled = 0
|
||||
stack = None
|
||||
|
||||
# The opcode of "yield" in Python bytecode. We need this in
|
||||
# _findFailure in order to identify whether an exception was
|
||||
# thrown by a throwExceptionIntoGenerator.
|
||||
# on PY3, b'a'[0] == 97 while in py2 b'a'[0] == b'a' opcodes
|
||||
# are stored in bytes so we need to properly account for this
|
||||
# difference.
|
||||
if _PY3:
|
||||
_yieldOpcode = opcode.opmap["YIELD_VALUE"]
|
||||
else:
|
||||
_yieldOpcode = chr(opcode.opmap["YIELD_VALUE"])
|
||||
|
||||
|
||||
def __init__(self, exc_value=None, exc_type=None, exc_tb=None,
|
||||
captureVars=False):
|
||||
"""
|
||||
Initialize me with an explanation of the error.
|
||||
|
||||
By default, this will use the current C{exception}
|
||||
(L{sys.exc_info}()). However, if you want to specify a
|
||||
particular kind of failure, you can pass an exception as an
|
||||
argument.
|
||||
|
||||
If no C{exc_value} is passed, then an "original" C{Failure} will
|
||||
be searched for. If the current exception handler that this
|
||||
C{Failure} is being constructed in is handling an exception
|
||||
raised by L{raiseException}, then this C{Failure} will act like
|
||||
the original C{Failure}.
|
||||
|
||||
For C{exc_tb} only L{traceback} instances or L{None} are allowed.
|
||||
If L{None} is supplied for C{exc_value}, the value of C{exc_tb} is
|
||||
ignored, otherwise if C{exc_tb} is L{None}, it will be found from
|
||||
execution context (ie, L{sys.exc_info}).
|
||||
|
||||
@param captureVars: if set, capture locals and globals of stack
|
||||
frames. This is pretty slow, and makes no difference unless you
|
||||
are going to use L{printDetailedTraceback}.
|
||||
"""
|
||||
global count
|
||||
count = count + 1
|
||||
self.count = count
|
||||
self.type = self.value = tb = None
|
||||
self.captureVars = captureVars
|
||||
|
||||
if isinstance(exc_value, str) and exc_type is None:
|
||||
raise TypeError("Strings are not supported by Failure")
|
||||
|
||||
stackOffset = 0
|
||||
|
||||
if exc_value is None:
|
||||
exc_value = self._findFailure()
|
||||
|
||||
if exc_value is None:
|
||||
self.type, self.value, tb = sys.exc_info()
|
||||
if self.type is None:
|
||||
raise NoCurrentExceptionError()
|
||||
stackOffset = 1
|
||||
elif exc_type is None:
|
||||
if isinstance(exc_value, Exception):
|
||||
self.type = exc_value.__class__
|
||||
else:
|
||||
# Allow arbitrary objects.
|
||||
self.type = type(exc_value)
|
||||
self.value = exc_value
|
||||
else:
|
||||
self.type = exc_type
|
||||
self.value = exc_value
|
||||
|
||||
if isinstance(self.value, Failure):
|
||||
self._extrapolate(self.value)
|
||||
return
|
||||
|
||||
if hasattr(self.value, "__failure__"):
|
||||
|
||||
# For exceptions propagated through coroutine-awaiting (see
|
||||
# Deferred.send, AKA Deferred.__next__), which can't be raised as
|
||||
# Failure because that would mess up the ability to except: them:
|
||||
self._extrapolate(self.value.__failure__)
|
||||
|
||||
# Clean up the inherently circular reference established by storing
|
||||
# the failure there. This should make the common case of a Twisted
|
||||
# / Deferred-returning coroutine somewhat less hard on the garbage
|
||||
# collector.
|
||||
del self.value.__failure__
|
||||
return
|
||||
|
||||
if tb is None:
|
||||
if exc_tb:
|
||||
tb = exc_tb
|
||||
elif getattr(self.value, "__traceback__", None):
|
||||
# Python 3
|
||||
tb = self.value.__traceback__
|
||||
|
||||
frames = self.frames = []
|
||||
stack = self.stack = []
|
||||
|
||||
# Added 2003-06-23 by Chris Armstrong. Yes, I actually have a
|
||||
# use case where I need this traceback object, and I've made
|
||||
# sure that it'll be cleaned up.
|
||||
self.tb = tb
|
||||
|
||||
if tb:
|
||||
f = tb.tb_frame
|
||||
elif not isinstance(self.value, Failure):
|
||||
# We don't do frame introspection since it's expensive,
|
||||
# and if we were passed a plain exception with no
|
||||
# traceback, it's not useful anyway
|
||||
f = stackOffset = None
|
||||
|
||||
while stackOffset and f:
|
||||
# This excludes this Failure.__init__ frame from the
|
||||
# stack, leaving it to start with our caller instead.
|
||||
f = f.f_back
|
||||
stackOffset -= 1
|
||||
|
||||
# Keeps the *full* stack. Formerly in spread.pb.print_excFullStack:
|
||||
#
|
||||
# The need for this function arises from the fact that several
|
||||
# PB classes have the peculiar habit of discarding exceptions
|
||||
# with bareword "except:"s. This premature exception
|
||||
# catching means tracebacks generated here don't tend to show
|
||||
# what called upon the PB object.
|
||||
|
||||
while f:
|
||||
if captureVars:
|
||||
localz = f.f_locals.copy()
|
||||
if f.f_locals is f.f_globals:
|
||||
globalz = {}
|
||||
else:
|
||||
globalz = f.f_globals.copy()
|
||||
for d in globalz, localz:
|
||||
if "__builtins__" in d:
|
||||
del d["__builtins__"]
|
||||
localz = localz.items()
|
||||
globalz = globalz.items()
|
||||
else:
|
||||
localz = globalz = ()
|
||||
stack.insert(0, (
|
||||
f.f_code.co_name,
|
||||
f.f_code.co_filename,
|
||||
f.f_lineno,
|
||||
localz,
|
||||
globalz,
|
||||
))
|
||||
f = f.f_back
|
||||
|
||||
while tb is not None:
|
||||
f = tb.tb_frame
|
||||
if captureVars:
|
||||
localz = f.f_locals.copy()
|
||||
if f.f_locals is f.f_globals:
|
||||
globalz = {}
|
||||
else:
|
||||
globalz = f.f_globals.copy()
|
||||
for d in globalz, localz:
|
||||
if "__builtins__" in d:
|
||||
del d["__builtins__"]
|
||||
localz = list(localz.items())
|
||||
globalz = list(globalz.items())
|
||||
else:
|
||||
localz = globalz = ()
|
||||
frames.append((
|
||||
f.f_code.co_name,
|
||||
f.f_code.co_filename,
|
||||
tb.tb_lineno,
|
||||
localz,
|
||||
globalz,
|
||||
))
|
||||
tb = tb.tb_next
|
||||
if inspect.isclass(self.type) and issubclass(self.type, Exception):
|
||||
parentCs = getmro(self.type)
|
||||
self.parents = list(map(reflect.qual, parentCs))
|
||||
else:
|
||||
self.parents = [self.type]
|
||||
|
||||
|
||||
def _extrapolate(self, otherFailure):
|
||||
"""
|
||||
Extrapolate from one failure into another, copying its stack frames.
|
||||
|
||||
@param otherFailure: Another L{Failure}, whose traceback information,
|
||||
if any, should be preserved as part of the stack presented by this
|
||||
one.
|
||||
@type otherFailure: L{Failure}
|
||||
"""
|
||||
# Copy all infos from that failure (including self.frames).
|
||||
self.__dict__ = copy.copy(otherFailure.__dict__)
|
||||
|
||||
# If we are re-throwing a Failure, we merge the stack-trace stored in
|
||||
# the failure with the current exception's stack. This integrated with
|
||||
# throwExceptionIntoGenerator and allows to provide full stack trace,
|
||||
# even if we go through several layers of inlineCallbacks.
|
||||
_, _, tb = sys.exc_info()
|
||||
frames = []
|
||||
while tb is not None:
|
||||
f = tb.tb_frame
|
||||
if f.f_code not in _inlineCallbacksExtraneous:
|
||||
frames.append((
|
||||
f.f_code.co_name,
|
||||
f.f_code.co_filename,
|
||||
tb.tb_lineno, (), ()
|
||||
))
|
||||
tb = tb.tb_next
|
||||
# Merging current stack with stack stored in the Failure.
|
||||
frames.extend(self.frames)
|
||||
self.frames = frames
|
||||
|
||||
|
||||
def trap(self, *errorTypes):
|
||||
"""
|
||||
Trap this failure if its type is in a predetermined list.
|
||||
|
||||
This allows you to trap a Failure in an error callback. It will be
|
||||
automatically re-raised if it is not a type that you expect.
|
||||
|
||||
The reason for having this particular API is because it's very useful
|
||||
in Deferred errback chains::
|
||||
|
||||
def _ebFoo(self, failure):
|
||||
r = failure.trap(Spam, Eggs)
|
||||
print('The Failure is due to either Spam or Eggs!')
|
||||
if r == Spam:
|
||||
print('Spam did it!')
|
||||
elif r == Eggs:
|
||||
print('Eggs did it!')
|
||||
|
||||
If the failure is not a Spam or an Eggs, then the Failure will be
|
||||
'passed on' to the next errback. In Python 2 the Failure will be
|
||||
raised; in Python 3 the underlying exception will be re-raised.
|
||||
|
||||
@type errorTypes: L{Exception}
|
||||
"""
|
||||
error = self.check(*errorTypes)
|
||||
if not error:
|
||||
if _PY3:
|
||||
self.raiseException()
|
||||
else:
|
||||
raise self
|
||||
return error
|
||||
|
||||
|
||||
def check(self, *errorTypes):
|
||||
"""
|
||||
Check if this failure's type is in a predetermined list.
|
||||
|
||||
@type errorTypes: list of L{Exception} classes or
|
||||
fully-qualified class names.
|
||||
@returns: the matching L{Exception} type, or None if no match.
|
||||
"""
|
||||
for error in errorTypes:
|
||||
err = error
|
||||
if inspect.isclass(error) and issubclass(error, Exception):
|
||||
err = reflect.qual(error)
|
||||
if err in self.parents:
|
||||
return error
|
||||
return None
|
||||
|
||||
# It would be nice to use twisted.python.compat.reraise, but that breaks
|
||||
# the stack exploration in _findFailure; possibly this can be fixed in
|
||||
# #5931.
|
||||
if getattr(BaseException, "with_traceback", None):
|
||||
# Python 3
|
||||
def raiseException(self):
|
||||
raise self.value.with_traceback(self.tb)
|
||||
else:
|
||||
exec("""def raiseException(self):
|
||||
raise self.type, self.value, self.tb""")
|
||||
|
||||
raiseException.__doc__ = (
|
||||
"""
|
||||
raise the original exception, preserving traceback
|
||||
information if available.
|
||||
""")
|
||||
|
||||
|
||||
@_extraneous
|
||||
def throwExceptionIntoGenerator(self, g):
|
||||
"""
|
||||
Throw the original exception into the given generator,
|
||||
preserving traceback information if available.
|
||||
|
||||
@return: The next value yielded from the generator.
|
||||
@raise StopIteration: If there are no more values in the generator.
|
||||
@raise anything else: Anything that the generator raises.
|
||||
"""
|
||||
# Note that the actual magic to find the traceback information
|
||||
# is done in _findFailure.
|
||||
return g.throw(self.type, self.value, self.tb)
|
||||
|
||||
|
||||
def _findFailure(cls):
|
||||
"""
|
||||
Find the failure that represents the exception currently in context.
|
||||
"""
|
||||
tb = sys.exc_info()[-1]
|
||||
if not tb:
|
||||
return
|
||||
|
||||
secondLastTb = None
|
||||
lastTb = tb
|
||||
while lastTb.tb_next:
|
||||
secondLastTb = lastTb
|
||||
lastTb = lastTb.tb_next
|
||||
|
||||
lastFrame = lastTb.tb_frame
|
||||
|
||||
# NOTE: f_locals.get('self') is used rather than
|
||||
# f_locals['self'] because psyco frames do not contain
|
||||
# anything in their locals() dicts. psyco makes debugging
|
||||
# difficult anyhow, so losing the Failure objects (and thus
|
||||
# the tracebacks) here when it is used is not that big a deal.
|
||||
|
||||
# Handle raiseException-originated exceptions
|
||||
if lastFrame.f_code is cls.raiseException.__code__:
|
||||
return lastFrame.f_locals.get('self')
|
||||
|
||||
# Handle throwExceptionIntoGenerator-originated exceptions
|
||||
# this is tricky, and differs if the exception was caught
|
||||
# inside the generator, or above it:
|
||||
|
||||
# It is only really originating from
|
||||
# throwExceptionIntoGenerator if the bottom of the traceback
|
||||
# is a yield.
|
||||
# Pyrex and Cython extensions create traceback frames
|
||||
# with no co_code, but they can't yield so we know it's okay to
|
||||
# just return here.
|
||||
if ((not lastFrame.f_code.co_code) or
|
||||
lastFrame.f_code.co_code[lastTb.tb_lasti] != cls._yieldOpcode):
|
||||
return
|
||||
|
||||
# If the exception was caught above the generator.throw
|
||||
# (outside the generator), it will appear in the tb (as the
|
||||
# second last item):
|
||||
if secondLastTb:
|
||||
frame = secondLastTb.tb_frame
|
||||
if frame.f_code is cls.throwExceptionIntoGenerator.__code__:
|
||||
return frame.f_locals.get('self')
|
||||
|
||||
# If the exception was caught below the generator.throw
|
||||
# (inside the generator), it will appear in the frames' linked
|
||||
# list, above the top-level traceback item (which must be the
|
||||
# generator frame itself, thus its caller is
|
||||
# throwExceptionIntoGenerator).
|
||||
frame = tb.tb_frame.f_back
|
||||
if frame and frame.f_code is cls.throwExceptionIntoGenerator.__code__:
|
||||
return frame.f_locals.get('self')
|
||||
|
||||
_findFailure = classmethod(_findFailure)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %s: %s>" % (reflect.qual(self.__class__),
|
||||
reflect.qual(self.type),
|
||||
self.getErrorMessage())
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "[Failure instance: %s]" % self.getBriefTraceback()
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
"""Avoid pickling objects in the traceback.
|
||||
"""
|
||||
if self.pickled:
|
||||
return self.__dict__
|
||||
c = self.__dict__.copy()
|
||||
|
||||
c['frames'] = [
|
||||
[
|
||||
v[0], v[1], v[2],
|
||||
_safeReprVars(v[3]),
|
||||
_safeReprVars(v[4]),
|
||||
] for v in self.frames
|
||||
]
|
||||
|
||||
# Added 2003-06-23. See comment above in __init__
|
||||
c['tb'] = None
|
||||
|
||||
if self.stack is not None:
|
||||
# XXX: This is a band-aid. I can't figure out where these
|
||||
# (failure.stack is None) instances are coming from.
|
||||
c['stack'] = [
|
||||
[
|
||||
v[0], v[1], v[2],
|
||||
_safeReprVars(v[3]),
|
||||
_safeReprVars(v[4]),
|
||||
] for v in self.stack
|
||||
]
|
||||
|
||||
c['pickled'] = 1
|
||||
return c
|
||||
|
||||
|
||||
def cleanFailure(self):
|
||||
"""
|
||||
Remove references to other objects, replacing them with strings.
|
||||
|
||||
On Python 3, this will also set the C{__traceback__} attribute of the
|
||||
exception instance to L{None}.
|
||||
"""
|
||||
self.__dict__ = self.__getstate__()
|
||||
if getattr(self.value, "__traceback__", None):
|
||||
# Python 3
|
||||
self.value.__traceback__ = None
|
||||
|
||||
|
||||
def getTracebackObject(self):
|
||||
"""
|
||||
Get an object that represents this Failure's stack that can be passed
|
||||
to traceback.extract_tb.
|
||||
|
||||
If the original traceback object is still present, return that. If this
|
||||
traceback object has been lost but we still have the information,
|
||||
return a fake traceback object (see L{_Traceback}). If there is no
|
||||
traceback information at all, return None.
|
||||
"""
|
||||
if self.tb is not None:
|
||||
return self.tb
|
||||
elif len(self.frames) > 0:
|
||||
return _Traceback(self.stack, self.frames)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def getErrorMessage(self):
|
||||
"""
|
||||
Get a string of the exception which caused this Failure.
|
||||
"""
|
||||
if isinstance(self.value, Failure):
|
||||
return self.value.getErrorMessage()
|
||||
return reflect.safe_str(self.value)
|
||||
|
||||
|
||||
def getBriefTraceback(self):
|
||||
io = StringIO()
|
||||
self.printBriefTraceback(file=io)
|
||||
return io.getvalue()
|
||||
|
||||
|
||||
def getTraceback(self, elideFrameworkCode=0, detail='default'):
|
||||
io = StringIO()
|
||||
self.printTraceback(file=io, elideFrameworkCode=elideFrameworkCode,
|
||||
detail=detail)
|
||||
return io.getvalue()
|
||||
|
||||
|
||||
def printTraceback(self, file=None, elideFrameworkCode=False,
|
||||
detail='default'):
|
||||
"""
|
||||
Emulate Python's standard error reporting mechanism.
|
||||
|
||||
@param file: If specified, a file-like object to which to write the
|
||||
traceback.
|
||||
|
||||
@param elideFrameworkCode: A flag indicating whether to attempt to
|
||||
remove uninteresting frames from within Twisted itself from the
|
||||
output.
|
||||
|
||||
@param detail: A string indicating how much information to include
|
||||
in the traceback. Must be one of C{'brief'}, C{'default'}, or
|
||||
C{'verbose'}.
|
||||
"""
|
||||
if file is None:
|
||||
from twisted.python import log
|
||||
file = log.logerr
|
||||
w = file.write
|
||||
|
||||
if detail == 'verbose' and not self.captureVars:
|
||||
# We don't have any locals or globals, so rather than show them as
|
||||
# empty make the output explicitly say that we don't have them at
|
||||
# all.
|
||||
formatDetail = 'verbose-vars-not-captured'
|
||||
else:
|
||||
formatDetail = detail
|
||||
|
||||
# Preamble
|
||||
if detail == 'verbose':
|
||||
w('*--- Failure #%d%s---\n' %
|
||||
(self.count,
|
||||
(self.pickled and ' (pickled) ') or ' '))
|
||||
elif detail == 'brief':
|
||||
if self.frames:
|
||||
hasFrames = 'Traceback'
|
||||
else:
|
||||
hasFrames = 'Traceback (failure with no frames)'
|
||||
w("%s: %s: %s\n" % (
|
||||
hasFrames,
|
||||
reflect.safe_str(self.type),
|
||||
reflect.safe_str(self.value)))
|
||||
else:
|
||||
w('Traceback (most recent call last):\n')
|
||||
|
||||
# Frames, formatted in appropriate style
|
||||
if self.frames:
|
||||
if not elideFrameworkCode:
|
||||
format_frames(self.stack[-traceupLength:], w, formatDetail)
|
||||
w("%s\n" % (EXCEPTION_CAUGHT_HERE,))
|
||||
format_frames(self.frames, w, formatDetail)
|
||||
elif not detail == 'brief':
|
||||
# Yeah, it's not really a traceback, despite looking like one...
|
||||
w("Failure: ")
|
||||
|
||||
# Postamble, if any
|
||||
if not detail == 'brief':
|
||||
w("%s: %s\n" % (reflect.qual(self.type),
|
||||
reflect.safe_str(self.value)))
|
||||
|
||||
# Chaining
|
||||
if isinstance(self.value, Failure):
|
||||
# TODO: indentation for chained failures?
|
||||
file.write(" (chained Failure)\n")
|
||||
self.value.printTraceback(file, elideFrameworkCode, detail)
|
||||
if detail == 'verbose':
|
||||
w('*--- End of Failure #%d ---\n' % self.count)
|
||||
|
||||
|
||||
def printBriefTraceback(self, file=None, elideFrameworkCode=0):
|
||||
"""
|
||||
Print a traceback as densely as possible.
|
||||
"""
|
||||
self.printTraceback(file, elideFrameworkCode, detail='brief')
|
||||
|
||||
|
||||
def printDetailedTraceback(self, file=None, elideFrameworkCode=0):
|
||||
"""
|
||||
Print a traceback with detailed locals and globals information.
|
||||
"""
|
||||
self.printTraceback(file, elideFrameworkCode, detail='verbose')
|
||||
|
||||
|
||||
|
||||
def _safeReprVars(varsDictItems):
|
||||
"""
|
||||
Convert a list of (name, object) pairs into (name, repr) pairs.
|
||||
|
||||
L{twisted.python.reflect.safe_repr} is used to generate the repr, so no
|
||||
exceptions will be raised by faulty C{__repr__} methods.
|
||||
|
||||
@param varsDictItems: a sequence of (name, value) pairs as returned by e.g.
|
||||
C{locals().items()}.
|
||||
@returns: a sequence of (name, repr) pairs.
|
||||
"""
|
||||
return [(name, reflect.safe_repr(obj)) for (name, obj) in varsDictItems]
|
||||
|
||||
|
||||
# slyphon: make post-morteming exceptions tweakable
|
||||
|
||||
DO_POST_MORTEM = True
|
||||
|
||||
def _debuginit(self, exc_value=None, exc_type=None, exc_tb=None,
|
||||
captureVars=False,
|
||||
Failure__init__=Failure.__init__):
|
||||
"""
|
||||
Initialize failure object, possibly spawning pdb.
|
||||
"""
|
||||
if (exc_value, exc_type, exc_tb) == (None, None, None):
|
||||
exc = sys.exc_info()
|
||||
if not exc[0] == self.__class__ and DO_POST_MORTEM:
|
||||
try:
|
||||
strrepr = str(exc[1])
|
||||
except:
|
||||
strrepr = "broken str"
|
||||
print("Jumping into debugger for post-mortem of exception '%s':" %
|
||||
(strrepr,))
|
||||
import pdb
|
||||
pdb.post_mortem(exc[2])
|
||||
Failure__init__(self, exc_value, exc_type, exc_tb, captureVars)
|
||||
|
||||
|
||||
|
||||
def startDebugMode():
|
||||
"""
|
||||
Enable debug hooks for Failures.
|
||||
"""
|
||||
Failure.__init__ = _debuginit
|
||||
@@ -0,0 +1,248 @@
|
||||
# -*- test-case-name: twisted.test.test_lockfile -*-
|
||||
# Copyright (c) 2005 Divmod, Inc.
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Filesystem-based interprocess mutex.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
import errno
|
||||
import os
|
||||
|
||||
from time import time as _uniquefloat
|
||||
|
||||
from twisted.python.runtime import platform
|
||||
from twisted.python.compat import _PY3
|
||||
|
||||
def unique():
|
||||
return str(int(_uniquefloat() * 1000))
|
||||
|
||||
from os import rename
|
||||
|
||||
if not platform.isWindows():
|
||||
from os import kill
|
||||
from os import symlink
|
||||
from os import readlink
|
||||
from os import remove as rmlink
|
||||
_windows = False
|
||||
else:
|
||||
_windows = True
|
||||
|
||||
# On UNIX, a symlink can be made to a nonexistent location, and
|
||||
# FilesystemLock uses this by making the target of the symlink an
|
||||
# imaginary, non-existing file named that of the PID of the process with
|
||||
# the lock. This has some benefits on UNIX -- making and removing this
|
||||
# symlink is atomic. However, because Windows doesn't support symlinks (at
|
||||
# least as how we know them), we have to fake this and actually write a
|
||||
# file with the PID of the process holding the lock instead.
|
||||
# These functions below perform that unenviable, probably-fraught-with-
|
||||
# race-conditions duty. - hawkie
|
||||
|
||||
try:
|
||||
from win32api import OpenProcess
|
||||
import pywintypes
|
||||
except ImportError:
|
||||
kill = None
|
||||
else:
|
||||
ERROR_ACCESS_DENIED = 5
|
||||
ERROR_INVALID_PARAMETER = 87
|
||||
|
||||
def kill(pid, signal):
|
||||
try:
|
||||
OpenProcess(0, 0, pid)
|
||||
except pywintypes.error as e:
|
||||
if e.args[0] == ERROR_ACCESS_DENIED:
|
||||
return
|
||||
elif e.args[0] == ERROR_INVALID_PARAMETER:
|
||||
raise OSError(errno.ESRCH, None)
|
||||
raise
|
||||
else:
|
||||
raise RuntimeError("OpenProcess is required to fail.")
|
||||
|
||||
# For monkeypatching in tests
|
||||
_open = open
|
||||
|
||||
|
||||
def symlink(value, filename):
|
||||
"""
|
||||
Write a file at C{filename} with the contents of C{value}. See the
|
||||
above comment block as to why this is needed.
|
||||
"""
|
||||
# XXX Implement an atomic thingamajig for win32
|
||||
newlinkname = filename + "." + unique() + '.newlink'
|
||||
newvalname = os.path.join(newlinkname, "symlink")
|
||||
os.mkdir(newlinkname)
|
||||
|
||||
# Python 3 does not support the 'commit' flag of fopen in the MSVCRT
|
||||
# (http://msdn.microsoft.com/en-us/library/yeby3zcb%28VS.71%29.aspx)
|
||||
if _PY3:
|
||||
mode = 'w'
|
||||
else:
|
||||
mode = 'wc'
|
||||
|
||||
with _open(newvalname, mode) as f:
|
||||
f.write(value)
|
||||
f.flush()
|
||||
|
||||
try:
|
||||
rename(newlinkname, filename)
|
||||
except:
|
||||
os.remove(newvalname)
|
||||
os.rmdir(newlinkname)
|
||||
raise
|
||||
|
||||
|
||||
def readlink(filename):
|
||||
"""
|
||||
Read the contents of C{filename}. See the above comment block as to why
|
||||
this is needed.
|
||||
"""
|
||||
try:
|
||||
fObj = _open(os.path.join(filename, 'symlink'), 'r')
|
||||
except IOError as e:
|
||||
if e.errno == errno.ENOENT or e.errno == errno.EIO:
|
||||
raise OSError(e.errno, None)
|
||||
raise
|
||||
else:
|
||||
with fObj:
|
||||
result = fObj.read()
|
||||
return result
|
||||
|
||||
|
||||
def rmlink(filename):
|
||||
os.remove(os.path.join(filename, 'symlink'))
|
||||
os.rmdir(filename)
|
||||
|
||||
|
||||
|
||||
class FilesystemLock(object):
|
||||
"""
|
||||
A mutex.
|
||||
|
||||
This relies on the filesystem property that creating
|
||||
a symlink is an atomic operation and that it will
|
||||
fail if the symlink already exists. Deleting the
|
||||
symlink will release the lock.
|
||||
|
||||
@ivar name: The name of the file associated with this lock.
|
||||
|
||||
@ivar clean: Indicates whether this lock was released cleanly by its
|
||||
last owner. Only meaningful after C{lock} has been called and
|
||||
returns True.
|
||||
|
||||
@ivar locked: Indicates whether the lock is currently held by this
|
||||
object.
|
||||
"""
|
||||
|
||||
clean = None
|
||||
locked = False
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
|
||||
def lock(self):
|
||||
"""
|
||||
Acquire this lock.
|
||||
|
||||
@rtype: C{bool}
|
||||
@return: True if the lock is acquired, false otherwise.
|
||||
|
||||
@raise: Any exception os.symlink() may raise, other than
|
||||
EEXIST.
|
||||
"""
|
||||
clean = True
|
||||
while True:
|
||||
try:
|
||||
symlink(str(os.getpid()), self.name)
|
||||
except OSError as e:
|
||||
if _windows and e.errno in (errno.EACCES, errno.EIO):
|
||||
# The lock is in the middle of being deleted because we're
|
||||
# on Windows where lock removal isn't atomic. Give up, we
|
||||
# don't know how long this is going to take.
|
||||
return False
|
||||
if e.errno == errno.EEXIST:
|
||||
try:
|
||||
pid = readlink(self.name)
|
||||
except (IOError, OSError) as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
# The lock has vanished, try to claim it in the
|
||||
# next iteration through the loop.
|
||||
continue
|
||||
elif _windows and e.errno == errno.EACCES:
|
||||
# The lock is in the middle of being
|
||||
# deleted because we're on Windows where
|
||||
# lock removal isn't atomic. Give up, we
|
||||
# don't know how long this is going to
|
||||
# take.
|
||||
return False
|
||||
raise
|
||||
try:
|
||||
if kill is not None:
|
||||
kill(int(pid), 0)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ESRCH:
|
||||
# The owner has vanished, try to claim it in the
|
||||
# next iteration through the loop.
|
||||
try:
|
||||
rmlink(self.name)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
# Another process cleaned up the lock.
|
||||
# Race them to acquire it in the next
|
||||
# iteration through the loop.
|
||||
continue
|
||||
raise
|
||||
clean = False
|
||||
continue
|
||||
raise
|
||||
return False
|
||||
raise
|
||||
self.locked = True
|
||||
self.clean = clean
|
||||
return True
|
||||
|
||||
|
||||
def unlock(self):
|
||||
"""
|
||||
Release this lock.
|
||||
|
||||
This deletes the directory with the given name.
|
||||
|
||||
@raise: Any exception os.readlink() may raise, or
|
||||
ValueError if the lock is not owned by this process.
|
||||
"""
|
||||
pid = readlink(self.name)
|
||||
if int(pid) != os.getpid():
|
||||
raise ValueError(
|
||||
"Lock %r not owned by this process" % (self.name,))
|
||||
rmlink(self.name)
|
||||
self.locked = False
|
||||
|
||||
|
||||
|
||||
def isLocked(name):
|
||||
"""
|
||||
Determine if the lock of the given name is held or not.
|
||||
|
||||
@type name: C{str}
|
||||
@param name: The filesystem path to the lock to test
|
||||
|
||||
@rtype: C{bool}
|
||||
@return: True if the lock is held, False otherwise.
|
||||
"""
|
||||
l = FilesystemLock(name)
|
||||
result = None
|
||||
try:
|
||||
result = l.lock()
|
||||
finally:
|
||||
if result:
|
||||
l.unlock()
|
||||
return not result
|
||||
|
||||
|
||||
|
||||
__all__ = ['FilesystemLock', 'isLocked']
|
||||
@@ -0,0 +1,767 @@
|
||||
# -*- test-case-name: twisted.test.test_log -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Logging and metrics infrastructure.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from zope.interface import Interface
|
||||
|
||||
from twisted.python.compat import unicode, _PY3
|
||||
from twisted.python import context
|
||||
from twisted.python import reflect
|
||||
from twisted.python import util
|
||||
from twisted.python import failure
|
||||
from twisted.python._oldstyle import _oldStyle
|
||||
from twisted.python.threadable import synchronize
|
||||
from twisted.logger import (
|
||||
Logger as NewLogger, LogLevel as NewLogLevel,
|
||||
STDLibLogObserver as NewSTDLibLogObserver,
|
||||
LegacyLogObserverWrapper, LoggingFile, LogPublisher as NewPublisher,
|
||||
globalLogPublisher as newGlobalLogPublisher,
|
||||
globalLogBeginner as newGlobalLogBeginner,
|
||||
)
|
||||
|
||||
from twisted.logger._global import LogBeginner
|
||||
from twisted.logger._legacy import publishToNewObserver as _publishNew
|
||||
|
||||
|
||||
|
||||
@_oldStyle
|
||||
class ILogContext:
|
||||
"""
|
||||
Actually, this interface is just a synonym for the dictionary interface,
|
||||
but it serves as a key for the default information in a log.
|
||||
|
||||
I do not inherit from C{Interface} because the world is a cruel place.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class ILogObserver(Interface):
|
||||
"""
|
||||
An observer which can do something with log events.
|
||||
|
||||
Given that most log observers are actually bound methods, it's okay to not
|
||||
explicitly declare provision of this interface.
|
||||
"""
|
||||
def __call__(eventDict):
|
||||
"""
|
||||
Log an event.
|
||||
|
||||
@type eventDict: C{dict} with C{str} keys.
|
||||
@param eventDict: A dictionary with arbitrary keys. However, these
|
||||
keys are often available:
|
||||
- C{message}: A C{tuple} of C{str} containing messages to be
|
||||
logged.
|
||||
- C{system}: A C{str} which indicates the "system" which is
|
||||
generating this event.
|
||||
- C{isError}: A C{bool} indicating whether this event represents
|
||||
an error.
|
||||
- C{failure}: A L{failure.Failure} instance
|
||||
- C{why}: Used as header of the traceback in case of errors.
|
||||
- C{format}: A string format used in place of C{message} to
|
||||
customize the event. The intent is for the observer to format
|
||||
a message by doing something like C{format % eventDict}.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
context.setDefault(ILogContext,
|
||||
{"system": "-"})
|
||||
|
||||
|
||||
def callWithContext(ctx, func, *args, **kw):
|
||||
newCtx = context.get(ILogContext).copy()
|
||||
newCtx.update(ctx)
|
||||
return context.call({ILogContext: newCtx}, func, *args, **kw)
|
||||
|
||||
|
||||
|
||||
def callWithLogger(logger, func, *args, **kw):
|
||||
"""
|
||||
Utility method which wraps a function in a try:/except:, logs a failure if
|
||||
one occurs, and uses the system's logPrefix.
|
||||
"""
|
||||
try:
|
||||
lp = logger.logPrefix()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
lp = '(buggy logPrefix method)'
|
||||
err(system=lp)
|
||||
try:
|
||||
return callWithContext({"system": lp}, func, *args, **kw)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
err(system=lp)
|
||||
|
||||
|
||||
|
||||
def err(_stuff=None, _why=None, **kw):
|
||||
"""
|
||||
Write a failure to the log.
|
||||
|
||||
The C{_stuff} and C{_why} parameters use an underscore prefix to lessen
|
||||
the chance of colliding with a keyword argument the application wishes
|
||||
to pass. It is intended that they be supplied with arguments passed
|
||||
positionally, not by keyword.
|
||||
|
||||
@param _stuff: The failure to log. If C{_stuff} is L{None} a new
|
||||
L{Failure} will be created from the current exception state. If
|
||||
C{_stuff} is an C{Exception} instance it will be wrapped in a
|
||||
L{Failure}.
|
||||
@type _stuff: L{None}, C{Exception}, or L{Failure}.
|
||||
|
||||
@param _why: The source of this failure. This will be logged along with
|
||||
C{_stuff} and should describe the context in which the failure
|
||||
occurred.
|
||||
@type _why: C{str}
|
||||
"""
|
||||
if _stuff is None:
|
||||
_stuff = failure.Failure()
|
||||
if isinstance(_stuff, failure.Failure):
|
||||
msg(failure=_stuff, why=_why, isError=1, **kw)
|
||||
elif isinstance(_stuff, Exception):
|
||||
msg(failure=failure.Failure(_stuff), why=_why, isError=1, **kw)
|
||||
else:
|
||||
msg(repr(_stuff), why=_why, isError=1, **kw)
|
||||
|
||||
deferr = err
|
||||
|
||||
|
||||
@_oldStyle
|
||||
class Logger:
|
||||
"""
|
||||
This represents a class which may 'own' a log. Used by subclassing.
|
||||
"""
|
||||
def logPrefix(self):
|
||||
"""
|
||||
Override this method to insert custom logging behavior. Its
|
||||
return value will be inserted in front of every line. It may
|
||||
be called more times than the number of output lines.
|
||||
"""
|
||||
return '-'
|
||||
|
||||
|
||||
|
||||
@_oldStyle
|
||||
class LogPublisher:
|
||||
"""
|
||||
Class for singleton log message publishing.
|
||||
"""
|
||||
|
||||
synchronized = ['msg']
|
||||
|
||||
|
||||
def __init__(self, observerPublisher=None, publishPublisher=None,
|
||||
logBeginner=None, warningsModule=warnings):
|
||||
if publishPublisher is None:
|
||||
publishPublisher = NewPublisher()
|
||||
if observerPublisher is None:
|
||||
observerPublisher = publishPublisher
|
||||
if observerPublisher is None:
|
||||
observerPublisher = NewPublisher()
|
||||
self._observerPublisher = observerPublisher
|
||||
self._publishPublisher = publishPublisher
|
||||
self._legacyObservers = []
|
||||
if logBeginner is None:
|
||||
# This default behavior is really only used for testing.
|
||||
beginnerPublisher = NewPublisher()
|
||||
beginnerPublisher.addObserver(observerPublisher)
|
||||
logBeginner = LogBeginner(beginnerPublisher, NullFile(), sys,
|
||||
warnings)
|
||||
self._logBeginner = logBeginner
|
||||
self._warningsModule = warningsModule
|
||||
self._oldshowwarning = warningsModule.showwarning
|
||||
self.showwarning = self._logBeginner.showwarning
|
||||
|
||||
|
||||
@property
|
||||
def observers(self):
|
||||
"""
|
||||
Property returning all observers registered on this L{LogPublisher}.
|
||||
|
||||
@return: observers previously added with L{LogPublisher.addObserver}
|
||||
@rtype: L{list} of L{callable}
|
||||
"""
|
||||
return [x.legacyObserver for x in self._legacyObservers]
|
||||
|
||||
|
||||
def _startLogging(self, other, setStdout):
|
||||
"""
|
||||
Begin logging to the L{LogBeginner} associated with this
|
||||
L{LogPublisher}.
|
||||
|
||||
@param other: the observer to log to.
|
||||
@type other: L{LogBeginner}
|
||||
|
||||
@param setStdout: if true, send standard I/O to the observer as well.
|
||||
@type setStdout: L{bool}
|
||||
"""
|
||||
wrapped = LegacyLogObserverWrapper(other)
|
||||
self._legacyObservers.append(wrapped)
|
||||
self._logBeginner.beginLoggingTo([wrapped], True, setStdout)
|
||||
|
||||
|
||||
def _stopLogging(self):
|
||||
"""
|
||||
Clean-up hook for fixing potentially global state. Only for testing of
|
||||
this module itself. If you want less global state, use the new
|
||||
warnings system in L{twisted.logger}.
|
||||
"""
|
||||
if self._warningsModule.showwarning == self.showwarning:
|
||||
self._warningsModule.showwarning = self._oldshowwarning
|
||||
|
||||
|
||||
def addObserver(self, other):
|
||||
"""
|
||||
Add a new observer.
|
||||
|
||||
@type other: Provider of L{ILogObserver}
|
||||
@param other: A callable object that will be called with each new log
|
||||
message (a dict).
|
||||
"""
|
||||
wrapped = LegacyLogObserverWrapper(other)
|
||||
self._legacyObservers.append(wrapped)
|
||||
self._observerPublisher.addObserver(wrapped)
|
||||
|
||||
|
||||
def removeObserver(self, other):
|
||||
"""
|
||||
Remove an observer.
|
||||
"""
|
||||
for observer in self._legacyObservers:
|
||||
if observer.legacyObserver == other:
|
||||
self._legacyObservers.remove(observer)
|
||||
self._observerPublisher.removeObserver(observer)
|
||||
break
|
||||
|
||||
|
||||
def msg(self, *message, **kw):
|
||||
"""
|
||||
Log a new message.
|
||||
|
||||
The message should be a native string, i.e. bytes on Python 2 and
|
||||
Unicode on Python 3. For compatibility with both use the native string
|
||||
syntax, for example::
|
||||
|
||||
>>> log.msg('Hello, world.')
|
||||
|
||||
You MUST avoid passing in Unicode on Python 2, and the form::
|
||||
|
||||
>>> log.msg('Hello ', 'world.')
|
||||
|
||||
This form only works (sometimes) by accident.
|
||||
|
||||
Keyword arguments will be converted into items in the event
|
||||
dict that is passed to L{ILogObserver} implementations.
|
||||
Each implementation, in turn, can define keys that are used
|
||||
by it specifically, in addition to common keys listed at
|
||||
L{ILogObserver.__call__}.
|
||||
|
||||
For example, to set the C{system} parameter while logging
|
||||
a message::
|
||||
|
||||
>>> log.msg('Started', system='Foo')
|
||||
|
||||
"""
|
||||
actualEventDict = (context.get(ILogContext) or {}).copy()
|
||||
actualEventDict.update(kw)
|
||||
actualEventDict['message'] = message
|
||||
actualEventDict['time'] = time.time()
|
||||
if "isError" not in actualEventDict:
|
||||
actualEventDict["isError"] = 0
|
||||
|
||||
_publishNew(self._publishPublisher, actualEventDict, textFromEventDict)
|
||||
|
||||
|
||||
synchronize(LogPublisher)
|
||||
|
||||
|
||||
|
||||
if 'theLogPublisher' not in globals():
|
||||
def _actually(something):
|
||||
"""
|
||||
A decorator that returns its argument rather than the thing it is
|
||||
decorating.
|
||||
|
||||
This allows the documentation generator to see an alias for a method or
|
||||
constant as an object with a docstring and thereby document it and
|
||||
allow references to it statically.
|
||||
|
||||
@param something: An object to create an alias for.
|
||||
@type something: L{object}
|
||||
|
||||
@return: a 1-argument callable that returns C{something}
|
||||
@rtype: L{object}
|
||||
"""
|
||||
def decorate(thingWithADocstring):
|
||||
return something
|
||||
return decorate
|
||||
|
||||
theLogPublisher = LogPublisher(
|
||||
observerPublisher=newGlobalLogPublisher,
|
||||
publishPublisher=newGlobalLogPublisher,
|
||||
logBeginner=newGlobalLogBeginner,
|
||||
)
|
||||
|
||||
|
||||
@_actually(theLogPublisher.addObserver)
|
||||
def addObserver(observer):
|
||||
"""
|
||||
Add a log observer to the global publisher.
|
||||
|
||||
@see: L{LogPublisher.addObserver}
|
||||
|
||||
@param observer: a log observer
|
||||
@type observer: L{callable}
|
||||
"""
|
||||
|
||||
|
||||
@_actually(theLogPublisher.removeObserver)
|
||||
def removeObserver(observer):
|
||||
"""
|
||||
Remove a log observer from the global publisher.
|
||||
|
||||
@see: L{LogPublisher.removeObserver}
|
||||
|
||||
@param observer: a log observer previously added with L{addObserver}
|
||||
@type observer: L{callable}
|
||||
"""
|
||||
|
||||
|
||||
@_actually(theLogPublisher.msg)
|
||||
def msg(*message, **event):
|
||||
"""
|
||||
Publish a message to the global log publisher.
|
||||
|
||||
@see: L{LogPublisher.msg}
|
||||
|
||||
@param message: the log message
|
||||
@type message: C{tuple} of L{str} (native string)
|
||||
|
||||
@param event: fields for the log event
|
||||
@type event: L{dict} mapping L{str} (native string) to L{object}
|
||||
"""
|
||||
|
||||
|
||||
@_actually(theLogPublisher.showwarning)
|
||||
def showwarning():
|
||||
"""
|
||||
Publish a Python warning through the global log publisher.
|
||||
|
||||
@see: L{LogPublisher.showwarning}
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def _safeFormat(fmtString, fmtDict):
|
||||
"""
|
||||
Try to format a string, swallowing all errors to always return a string.
|
||||
|
||||
@note: For backward-compatibility reasons, this function ensures that it
|
||||
returns a native string, meaning C{bytes} in Python 2 and C{unicode} in
|
||||
Python 3.
|
||||
|
||||
@param fmtString: a C{%}-format string
|
||||
|
||||
@param fmtDict: string formatting arguments for C{fmtString}
|
||||
|
||||
@return: A native string, formatted from C{fmtString} and C{fmtDict}.
|
||||
@rtype: L{str}
|
||||
"""
|
||||
# There's a way we could make this if not safer at least more
|
||||
# informative: perhaps some sort of str/repr wrapper objects
|
||||
# could be wrapped around the things inside of C{fmtDict}. That way
|
||||
# if the event dict contains an object with a bad __repr__, we
|
||||
# can only cry about that individual object instead of the
|
||||
# entire event dict.
|
||||
try:
|
||||
text = fmtString % fmtDict
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
try:
|
||||
text = ('Invalid format string or unformattable object in '
|
||||
'log message: %r, %s' % (fmtString, fmtDict))
|
||||
except:
|
||||
try:
|
||||
text = ('UNFORMATTABLE OBJECT WRITTEN TO LOG with fmt %r, '
|
||||
'MESSAGE LOST' % (fmtString,))
|
||||
except:
|
||||
text = ('PATHOLOGICAL ERROR IN BOTH FORMAT STRING AND '
|
||||
'MESSAGE DETAILS, MESSAGE LOST')
|
||||
|
||||
# Return a native string
|
||||
if _PY3:
|
||||
if isinstance(text, bytes):
|
||||
text = text.decode("utf-8")
|
||||
else:
|
||||
if isinstance(text, unicode):
|
||||
text = text.encode("utf-8")
|
||||
|
||||
return text
|
||||
|
||||
|
||||
|
||||
def textFromEventDict(eventDict):
|
||||
"""
|
||||
Extract text from an event dict passed to a log observer. If it cannot
|
||||
handle the dict, it returns None.
|
||||
|
||||
The possible keys of eventDict are:
|
||||
- C{message}: by default, it holds the final text. It's required, but can
|
||||
be empty if either C{isError} or C{format} is provided (the first
|
||||
having the priority).
|
||||
- C{isError}: boolean indicating the nature of the event.
|
||||
- C{failure}: L{failure.Failure} instance, required if the event is an
|
||||
error.
|
||||
- C{why}: if defined, used as header of the traceback in case of errors.
|
||||
- C{format}: string format used in place of C{message} to customize
|
||||
the event. It uses all keys present in C{eventDict} to format
|
||||
the text.
|
||||
Other keys will be used when applying the C{format}, or ignored.
|
||||
"""
|
||||
edm = eventDict['message']
|
||||
if not edm:
|
||||
if eventDict['isError'] and 'failure' in eventDict:
|
||||
why = eventDict.get('why')
|
||||
if why:
|
||||
why = reflect.safe_str(why)
|
||||
else:
|
||||
why = 'Unhandled Error'
|
||||
try:
|
||||
traceback = eventDict['failure'].getTraceback()
|
||||
except Exception as e:
|
||||
traceback = '(unable to obtain traceback): ' + str(e)
|
||||
text = (why + '\n' + traceback)
|
||||
elif 'format' in eventDict:
|
||||
text = _safeFormat(eventDict['format'], eventDict)
|
||||
else:
|
||||
# We don't know how to log this
|
||||
return None
|
||||
else:
|
||||
text = ' '.join(map(reflect.safe_str, edm))
|
||||
return text
|
||||
|
||||
|
||||
|
||||
@_oldStyle
|
||||
class _GlobalStartStopMixIn:
|
||||
"""
|
||||
Mix-in for global log observers that can start and stop.
|
||||
"""
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start observing log events.
|
||||
"""
|
||||
addObserver(self.emit)
|
||||
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop observing log events.
|
||||
"""
|
||||
removeObserver(self.emit)
|
||||
|
||||
|
||||
|
||||
class FileLogObserver(_GlobalStartStopMixIn):
|
||||
"""
|
||||
Log observer that writes to a file-like object.
|
||||
|
||||
@type timeFormat: C{str} or L{None}
|
||||
@ivar timeFormat: If not L{None}, the format string passed to strftime().
|
||||
"""
|
||||
|
||||
timeFormat = None
|
||||
|
||||
def __init__(self, f):
|
||||
# Compatibility
|
||||
self.write = f.write
|
||||
self.flush = f.flush
|
||||
|
||||
|
||||
def getTimezoneOffset(self, when):
|
||||
"""
|
||||
Return the current local timezone offset from UTC.
|
||||
|
||||
@type when: C{int}
|
||||
@param when: POSIX (ie, UTC) timestamp for which to find the offset.
|
||||
|
||||
@rtype: C{int}
|
||||
@return: The number of seconds offset from UTC. West is positive,
|
||||
east is negative.
|
||||
"""
|
||||
offset = datetime.utcfromtimestamp(when) - datetime.fromtimestamp(when)
|
||||
return offset.days * (60 * 60 * 24) + offset.seconds
|
||||
|
||||
|
||||
def formatTime(self, when):
|
||||
"""
|
||||
Format the given UTC value as a string representing that time in the
|
||||
local timezone.
|
||||
|
||||
By default it's formatted as an ISO8601-like string (ISO8601 date and
|
||||
ISO8601 time separated by a space). It can be customized using the
|
||||
C{timeFormat} attribute, which will be used as input for the underlying
|
||||
L{datetime.datetime.strftime} call.
|
||||
|
||||
@type when: C{int}
|
||||
@param when: POSIX (ie, UTC) timestamp for which to find the offset.
|
||||
|
||||
@rtype: C{str}
|
||||
"""
|
||||
if self.timeFormat is not None:
|
||||
return datetime.fromtimestamp(when).strftime(self.timeFormat)
|
||||
|
||||
tzOffset = -self.getTimezoneOffset(when)
|
||||
when = datetime.utcfromtimestamp(when + tzOffset)
|
||||
tzHour = abs(int(tzOffset / 60 / 60))
|
||||
tzMin = abs(int(tzOffset / 60 % 60))
|
||||
if tzOffset < 0:
|
||||
tzSign = '-'
|
||||
else:
|
||||
tzSign = '+'
|
||||
return '%d-%02d-%02d %02d:%02d:%02d%s%02d%02d' % (
|
||||
when.year, when.month, when.day,
|
||||
when.hour, when.minute, when.second,
|
||||
tzSign, tzHour, tzMin)
|
||||
|
||||
|
||||
def emit(self, eventDict):
|
||||
"""
|
||||
Format the given log event as text and write it to the output file.
|
||||
|
||||
@param eventDict: a log event
|
||||
@type eventDict: L{dict} mapping L{str} (native string) to L{object}
|
||||
"""
|
||||
text = textFromEventDict(eventDict)
|
||||
if text is None:
|
||||
return
|
||||
|
||||
timeStr = self.formatTime(eventDict["time"])
|
||||
fmtDict = {
|
||||
"system": eventDict["system"],
|
||||
"text": text.replace("\n", "\n\t")
|
||||
}
|
||||
msgStr = _safeFormat("[%(system)s] %(text)s\n", fmtDict)
|
||||
|
||||
util.untilConcludes(self.write, timeStr + " " + msgStr)
|
||||
util.untilConcludes(self.flush) # Hoorj!
|
||||
|
||||
|
||||
|
||||
class PythonLoggingObserver(_GlobalStartStopMixIn, object):
|
||||
"""
|
||||
Output twisted messages to Python standard library L{logging} module.
|
||||
|
||||
WARNING: specific logging configurations (example: network) can lead to
|
||||
a blocking system. Nothing is done here to prevent that, so be sure to not
|
||||
use this: code within Twisted, such as twisted.web, assumes that logging
|
||||
does not block.
|
||||
"""
|
||||
|
||||
def __init__(self, loggerName="twisted"):
|
||||
"""
|
||||
@param loggerName: identifier used for getting logger.
|
||||
@type loggerName: C{str}
|
||||
"""
|
||||
self._newObserver = NewSTDLibLogObserver(loggerName)
|
||||
|
||||
|
||||
def emit(self, eventDict):
|
||||
"""
|
||||
Receive a twisted log entry, format it and bridge it to python.
|
||||
|
||||
By default the logging level used is info; log.err produces error
|
||||
level, and you can customize the level by using the C{logLevel} key::
|
||||
|
||||
>>> log.msg('debugging', logLevel=logging.DEBUG)
|
||||
"""
|
||||
if 'log_format' in eventDict:
|
||||
_publishNew(self._newObserver, eventDict, textFromEventDict)
|
||||
|
||||
|
||||
|
||||
@_oldStyle
|
||||
class StdioOnnaStick:
|
||||
"""
|
||||
Class that pretends to be stdout/err, and turns writes into log messages.
|
||||
|
||||
@ivar isError: boolean indicating whether this is stderr, in which cases
|
||||
log messages will be logged as errors.
|
||||
|
||||
@ivar encoding: unicode encoding used to encode any unicode strings
|
||||
written to this object.
|
||||
"""
|
||||
|
||||
closed = 0
|
||||
softspace = 0
|
||||
mode = 'wb'
|
||||
name = '<stdio (log)>'
|
||||
|
||||
def __init__(self, isError=0, encoding=None):
|
||||
self.isError = isError
|
||||
if encoding is None:
|
||||
encoding = sys.getdefaultencoding()
|
||||
self.encoding = encoding
|
||||
self.buf = ''
|
||||
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
def fileno(self):
|
||||
return -1
|
||||
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
|
||||
def read(self):
|
||||
raise IOError("can't read from the log!")
|
||||
|
||||
readline = read
|
||||
readlines = read
|
||||
seek = read
|
||||
tell = read
|
||||
|
||||
|
||||
def write(self, data):
|
||||
if not _PY3 and isinstance(data, unicode):
|
||||
data = data.encode(self.encoding)
|
||||
d = (self.buf + data).split('\n')
|
||||
self.buf = d[-1]
|
||||
messages = d[0:-1]
|
||||
for message in messages:
|
||||
msg(message, printed=1, isError=self.isError)
|
||||
|
||||
|
||||
def writelines(self, lines):
|
||||
for line in lines:
|
||||
if not _PY3 and isinstance(line, unicode):
|
||||
line = line.encode(self.encoding)
|
||||
msg(line, printed=1, isError=self.isError)
|
||||
|
||||
|
||||
|
||||
def startLogging(file, *a, **kw):
|
||||
"""
|
||||
Initialize logging to a specified file.
|
||||
|
||||
@return: A L{FileLogObserver} if a new observer is added, None otherwise.
|
||||
"""
|
||||
if isinstance(file, LoggingFile):
|
||||
return
|
||||
flo = FileLogObserver(file)
|
||||
startLoggingWithObserver(flo.emit, *a, **kw)
|
||||
return flo
|
||||
|
||||
|
||||
|
||||
def startLoggingWithObserver(observer, setStdout=1):
|
||||
"""
|
||||
Initialize logging to a specified observer. If setStdout is true
|
||||
(defaults to yes), also redirect sys.stdout and sys.stderr
|
||||
to the specified file.
|
||||
"""
|
||||
theLogPublisher._startLogging(observer, setStdout)
|
||||
msg("Log opened.")
|
||||
|
||||
|
||||
|
||||
@_oldStyle
|
||||
class NullFile:
|
||||
"""
|
||||
A file-like object that discards everything.
|
||||
"""
|
||||
softspace = 0
|
||||
|
||||
def read(self):
|
||||
"""
|
||||
Do nothing.
|
||||
"""
|
||||
|
||||
|
||||
def write(self, bytes):
|
||||
"""
|
||||
Do nothing.
|
||||
|
||||
@param bytes: data
|
||||
@type bytes: L{bytes}
|
||||
"""
|
||||
|
||||
|
||||
def flush(self):
|
||||
"""
|
||||
Do nothing.
|
||||
"""
|
||||
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Do nothing.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def discardLogs():
|
||||
"""
|
||||
Discard messages logged via the global C{logfile} object.
|
||||
"""
|
||||
global logfile
|
||||
logfile = NullFile()
|
||||
|
||||
|
||||
|
||||
# Prevent logfile from being erased on reload. This only works in cpython.
|
||||
if 'logfile' not in globals():
|
||||
logfile = LoggingFile(logger=NewLogger(),
|
||||
level=NewLogLevel.info,
|
||||
encoding=getattr(sys.stdout, "encoding", None))
|
||||
logerr = LoggingFile(logger=NewLogger(),
|
||||
level=NewLogLevel.error,
|
||||
encoding=getattr(sys.stderr, "encoding", None))
|
||||
|
||||
|
||||
|
||||
class DefaultObserver(_GlobalStartStopMixIn):
|
||||
"""
|
||||
Default observer.
|
||||
|
||||
Will ignore all non-error messages and send error messages to sys.stderr.
|
||||
Will be removed when startLogging() is called for the first time.
|
||||
"""
|
||||
stderr = sys.stderr
|
||||
|
||||
def emit(self, eventDict):
|
||||
"""
|
||||
Emit an event dict.
|
||||
|
||||
@param eventDict: an event dict
|
||||
@type eventDict: dict
|
||||
"""
|
||||
if eventDict["isError"]:
|
||||
text = textFromEventDict(eventDict)
|
||||
self.stderr.write(text)
|
||||
self.stderr.flush()
|
||||
|
||||
|
||||
|
||||
if 'defaultObserver' not in globals():
|
||||
defaultObserver = DefaultObserver()
|
||||
@@ -0,0 +1,75 @@
|
||||
# -*- test-case-name: twisted.test.test_monkey -*-
|
||||
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
|
||||
class MonkeyPatcher(object):
|
||||
"""
|
||||
Cover up attributes with new objects. Neat for monkey-patching things for
|
||||
unit-testing purposes.
|
||||
"""
|
||||
|
||||
def __init__(self, *patches):
|
||||
# List of patches to apply in (obj, name, value).
|
||||
self._patchesToApply = []
|
||||
# List of the original values for things that have been patched.
|
||||
# (obj, name, value) format.
|
||||
self._originals = []
|
||||
for patch in patches:
|
||||
self.addPatch(*patch)
|
||||
|
||||
|
||||
def addPatch(self, obj, name, value):
|
||||
"""
|
||||
Add a patch so that the attribute C{name} on C{obj} will be assigned to
|
||||
C{value} when C{patch} is called or during C{runWithPatches}.
|
||||
|
||||
You can restore the original values with a call to restore().
|
||||
"""
|
||||
self._patchesToApply.append((obj, name, value))
|
||||
|
||||
|
||||
def _alreadyPatched(self, obj, name):
|
||||
"""
|
||||
Has the C{name} attribute of C{obj} already been patched by this
|
||||
patcher?
|
||||
"""
|
||||
for o, n, v in self._originals:
|
||||
if (o, n) == (obj, name):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def patch(self):
|
||||
"""
|
||||
Apply all of the patches that have been specified with L{addPatch}.
|
||||
Reverse this operation using L{restore}.
|
||||
"""
|
||||
for obj, name, value in self._patchesToApply:
|
||||
if not self._alreadyPatched(obj, name):
|
||||
self._originals.append((obj, name, getattr(obj, name)))
|
||||
setattr(obj, name, value)
|
||||
|
||||
|
||||
def restore(self):
|
||||
"""
|
||||
Restore all original values to any patched objects.
|
||||
"""
|
||||
while self._originals:
|
||||
obj, name, value = self._originals.pop()
|
||||
setattr(obj, name, value)
|
||||
|
||||
|
||||
def runWithPatches(self, f, *args, **kw):
|
||||
"""
|
||||
Apply each patch already specified. Then run the function f with the
|
||||
given args and kwargs. Restore everything when done.
|
||||
"""
|
||||
self.patch()
|
||||
try:
|
||||
return f(*args, **kw)
|
||||
finally:
|
||||
self.restore()
|
||||
@@ -0,0 +1,310 @@
|
||||
# -*- test-case-name: twisted.test.test_rebuild -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
|
||||
"""
|
||||
*Real* reloading support for Python.
|
||||
"""
|
||||
|
||||
# System Imports
|
||||
import sys
|
||||
import types
|
||||
import time
|
||||
import linecache
|
||||
|
||||
from imp import reload
|
||||
|
||||
try:
|
||||
# Python 2
|
||||
from types import InstanceType
|
||||
except ImportError:
|
||||
# Python 3
|
||||
pass
|
||||
|
||||
# Sibling Imports
|
||||
from twisted.python import log, reflect
|
||||
from twisted.python.compat import _PY3
|
||||
|
||||
lastRebuild = time.time()
|
||||
|
||||
def _isClassType(t):
|
||||
"""
|
||||
Compare to types.ClassType in a py2/3-compatible way
|
||||
|
||||
Python 2 used comparison to types.ClassType to check for old-style
|
||||
classes Python 3 has no concept of old-style classes, so if
|
||||
ClassType doesn't exist, it can't be an old-style class - return
|
||||
False in that case.
|
||||
|
||||
Note that the type() of new-style classes is NOT ClassType, and
|
||||
so this should return False for new-style classes in python 2
|
||||
as well.
|
||||
"""
|
||||
_ClassType = getattr(types, 'ClassType', None)
|
||||
if _ClassType is None:
|
||||
return False
|
||||
return t == _ClassType
|
||||
|
||||
|
||||
|
||||
class Sensitive(object):
|
||||
"""
|
||||
A utility mixin that's sensitive to rebuilds.
|
||||
|
||||
This is a mixin for classes (usually those which represent collections of
|
||||
callbacks) to make sure that their code is up-to-date before running.
|
||||
"""
|
||||
|
||||
lastRebuild = lastRebuild
|
||||
|
||||
def needRebuildUpdate(self):
|
||||
yn = (self.lastRebuild < lastRebuild)
|
||||
return yn
|
||||
|
||||
|
||||
def rebuildUpToDate(self):
|
||||
self.lastRebuild = time.time()
|
||||
|
||||
|
||||
def latestVersionOf(self, anObject):
|
||||
"""
|
||||
Get the latest version of an object.
|
||||
|
||||
This can handle just about anything callable; instances, functions,
|
||||
methods, and classes.
|
||||
"""
|
||||
t = type(anObject)
|
||||
if t == types.FunctionType:
|
||||
return latestFunction(anObject)
|
||||
elif t == types.MethodType:
|
||||
if anObject.__self__ is None:
|
||||
return getattr(anObject.im_class, anObject.__name__)
|
||||
else:
|
||||
return getattr(anObject.__self__, anObject.__name__)
|
||||
elif not _PY3 and t == InstanceType:
|
||||
# Kick it, if it's out of date.
|
||||
getattr(anObject, 'nothing', None)
|
||||
return anObject
|
||||
elif _isClassType(t):
|
||||
return latestClass(anObject)
|
||||
else:
|
||||
log.msg('warning returning anObject!')
|
||||
return anObject
|
||||
|
||||
_modDictIDMap = {}
|
||||
|
||||
def latestFunction(oldFunc):
|
||||
"""
|
||||
Get the latest version of a function.
|
||||
"""
|
||||
# This may be CPython specific, since I believe jython instantiates a new
|
||||
# module upon reload.
|
||||
dictID = id(oldFunc.__globals__)
|
||||
module = _modDictIDMap.get(dictID)
|
||||
if module is None:
|
||||
return oldFunc
|
||||
return getattr(module, oldFunc.__name__)
|
||||
|
||||
|
||||
|
||||
def latestClass(oldClass):
|
||||
"""
|
||||
Get the latest version of a class.
|
||||
"""
|
||||
module = reflect.namedModule(oldClass.__module__)
|
||||
newClass = getattr(module, oldClass.__name__)
|
||||
newBases = [latestClass(base) for base in newClass.__bases__]
|
||||
|
||||
try:
|
||||
# This makes old-style stuff work
|
||||
newClass.__bases__ = tuple(newBases)
|
||||
return newClass
|
||||
except TypeError:
|
||||
if newClass.__module__ in ("__builtin__", "builtins"):
|
||||
# __builtin__ members can't be reloaded sanely
|
||||
return newClass
|
||||
|
||||
ctor = type(newClass)
|
||||
# The value of type(newClass) is the metaclass
|
||||
# in both Python 2 and 3, except if it was old-style.
|
||||
if _isClassType(ctor):
|
||||
ctor = getattr(newClass, '__metaclass__', type)
|
||||
return ctor(newClass.__name__, tuple(newBases),
|
||||
dict(newClass.__dict__))
|
||||
|
||||
|
||||
|
||||
class RebuildError(Exception):
|
||||
"""
|
||||
Exception raised when trying to rebuild a class whereas it's not possible.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def updateInstance(self):
|
||||
"""
|
||||
Updates an instance to be current.
|
||||
"""
|
||||
self.__class__ = latestClass(self.__class__)
|
||||
|
||||
|
||||
|
||||
def __injectedgetattr__(self, name):
|
||||
"""
|
||||
A getattr method to cause a class to be refreshed.
|
||||
"""
|
||||
if name == '__del__':
|
||||
raise AttributeError("Without this, Python segfaults.")
|
||||
updateInstance(self)
|
||||
log.msg("(rebuilding stale {} instance ({}))".format(
|
||||
reflect.qual(self.__class__), name))
|
||||
result = getattr(self, name)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def rebuild(module, doLog=1):
|
||||
"""
|
||||
Reload a module and do as much as possible to replace its references.
|
||||
"""
|
||||
global lastRebuild
|
||||
lastRebuild = time.time()
|
||||
if hasattr(module, 'ALLOW_TWISTED_REBUILD'):
|
||||
# Is this module allowed to be rebuilt?
|
||||
if not module.ALLOW_TWISTED_REBUILD:
|
||||
raise RuntimeError("I am not allowed to be rebuilt.")
|
||||
if doLog:
|
||||
log.msg('Rebuilding {}...'.format(str(module.__name__)))
|
||||
|
||||
# Safely handle adapter re-registration
|
||||
from twisted.python import components
|
||||
components.ALLOW_DUPLICATES = True
|
||||
|
||||
d = module.__dict__
|
||||
_modDictIDMap[id(d)] = module
|
||||
newclasses = {}
|
||||
classes = {}
|
||||
functions = {}
|
||||
values = {}
|
||||
if doLog:
|
||||
log.msg(' (scanning {}): '.format(str(module.__name__)))
|
||||
for k, v in d.items():
|
||||
if _isClassType(type(v)):
|
||||
# ClassType exists on Python 2.x and earlier.
|
||||
# Failure condition -- instances of classes with buggy
|
||||
# __hash__/__cmp__ methods referenced at the module level...
|
||||
if v.__module__ == module.__name__:
|
||||
classes[v] = 1
|
||||
if doLog:
|
||||
log.logfile.write("c")
|
||||
log.logfile.flush()
|
||||
elif type(v) == types.FunctionType:
|
||||
if v.__globals__ is module.__dict__:
|
||||
functions[v] = 1
|
||||
if doLog:
|
||||
log.logfile.write("f")
|
||||
log.logfile.flush()
|
||||
elif isinstance(v, type):
|
||||
if v.__module__ == module.__name__:
|
||||
newclasses[v] = 1
|
||||
if doLog:
|
||||
log.logfile.write("o")
|
||||
log.logfile.flush()
|
||||
|
||||
values.update(classes)
|
||||
values.update(functions)
|
||||
fromOldModule = values.__contains__
|
||||
newclasses = newclasses.keys()
|
||||
classes = classes.keys()
|
||||
functions = functions.keys()
|
||||
|
||||
if doLog:
|
||||
log.msg('')
|
||||
log.msg(' (reload {})'.format(str(module.__name__)))
|
||||
|
||||
# Boom.
|
||||
reload(module)
|
||||
# Make sure that my traceback printing will at least be recent...
|
||||
linecache.clearcache()
|
||||
|
||||
if doLog:
|
||||
log.msg(' (cleaning {}): '.format(str(module.__name__)))
|
||||
|
||||
for clazz in classes:
|
||||
if getattr(module, clazz.__name__) is clazz:
|
||||
log.msg("WARNING: class {} not replaced by reload!".format(
|
||||
reflect.qual(clazz)))
|
||||
else:
|
||||
if doLog:
|
||||
log.logfile.write("x")
|
||||
log.logfile.flush()
|
||||
clazz.__bases__ = ()
|
||||
clazz.__dict__.clear()
|
||||
clazz.__getattr__ = __injectedgetattr__
|
||||
clazz.__module__ = module.__name__
|
||||
if newclasses:
|
||||
import gc
|
||||
for nclass in newclasses:
|
||||
ga = getattr(module, nclass.__name__)
|
||||
if ga is nclass:
|
||||
log.msg("WARNING: new-class {} not replaced by reload!".format(
|
||||
reflect.qual(nclass)))
|
||||
else:
|
||||
for r in gc.get_referrers(nclass):
|
||||
if getattr(r, '__class__', None) is nclass:
|
||||
r.__class__ = ga
|
||||
if doLog:
|
||||
log.msg('')
|
||||
log.msg(' (fixing {}): '.format(str(module.__name__)))
|
||||
modcount = 0
|
||||
for mk, mod in sys.modules.items():
|
||||
modcount = modcount + 1
|
||||
if mod == module or mod is None:
|
||||
continue
|
||||
|
||||
if not hasattr(mod, '__file__'):
|
||||
# It's a builtin module; nothing to replace here.
|
||||
continue
|
||||
|
||||
if hasattr(mod, '__bundle__'):
|
||||
# PyObjC has a few buggy objects which segfault if you hash() them.
|
||||
# It doesn't make sense to try rebuilding extension modules like
|
||||
# this anyway, so don't try.
|
||||
continue
|
||||
|
||||
changed = 0
|
||||
|
||||
for k, v in mod.__dict__.items():
|
||||
try:
|
||||
hash(v)
|
||||
except Exception:
|
||||
continue
|
||||
if fromOldModule(v):
|
||||
if _isClassType(type(v)):
|
||||
if doLog:
|
||||
log.logfile.write("c")
|
||||
log.logfile.flush()
|
||||
nv = latestClass(v)
|
||||
else:
|
||||
if doLog:
|
||||
log.logfile.write("f")
|
||||
log.logfile.flush()
|
||||
nv = latestFunction(v)
|
||||
changed = 1
|
||||
setattr(mod, k, nv)
|
||||
else:
|
||||
# Replace bases of non-module classes just to be sure.
|
||||
if _isClassType(type(v)):
|
||||
for base in v.__bases__:
|
||||
if fromOldModule(base):
|
||||
latestClass(v)
|
||||
if doLog and not changed and ((modcount % 10) == 0) :
|
||||
log.logfile.write(".")
|
||||
log.logfile.flush()
|
||||
|
||||
components.ALLOW_DUPLICATES = False
|
||||
if doLog:
|
||||
log.msg('')
|
||||
log.msg(' Rebuilt {}.'.format(str(module.__name__)))
|
||||
return module
|
||||
@@ -0,0 +1,634 @@
|
||||
# -*- test-case-name: twisted.test.test_reflect -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Standardized versions of various cool and/or strange things that you can do
|
||||
with Python's reflection capabilities.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
import sys
|
||||
import types
|
||||
import os
|
||||
import pickle
|
||||
import weakref
|
||||
import re
|
||||
import traceback
|
||||
from collections import deque
|
||||
|
||||
RegexType = type(re.compile(""))
|
||||
|
||||
|
||||
from twisted.python.compat import reraise, nativeString, NativeStringIO
|
||||
from twisted.python.compat import _PY3
|
||||
from twisted.python import compat
|
||||
from twisted.python.deprecate import _fullyQualifiedName as fullyQualifiedName
|
||||
from twisted.python._oldstyle import _oldStyle
|
||||
|
||||
|
||||
def prefixedMethodNames(classObj, prefix):
|
||||
"""
|
||||
Given a class object C{classObj}, returns a list of method names that match
|
||||
the string C{prefix}.
|
||||
|
||||
@param classObj: A class object from which to collect method names.
|
||||
|
||||
@param prefix: A native string giving a prefix. Each method with a name
|
||||
which begins with this prefix will be returned.
|
||||
@type prefix: L{str}
|
||||
|
||||
@return: A list of the names of matching methods of C{classObj} (and base
|
||||
classes of C{classObj}).
|
||||
@rtype: L{list} of L{str}
|
||||
"""
|
||||
dct = {}
|
||||
addMethodNamesToDict(classObj, dct, prefix)
|
||||
return list(dct.keys())
|
||||
|
||||
|
||||
|
||||
def addMethodNamesToDict(classObj, dict, prefix, baseClass=None):
|
||||
"""
|
||||
This goes through C{classObj} (and its bases) and puts method names
|
||||
starting with 'prefix' in 'dict' with a value of 1. if baseClass isn't
|
||||
None, methods will only be added if classObj is-a baseClass
|
||||
|
||||
If the class in question has the methods 'prefix_methodname' and
|
||||
'prefix_methodname2', the resulting dict should look something like:
|
||||
{"methodname": 1, "methodname2": 1}.
|
||||
|
||||
@param classObj: A class object from which to collect method names.
|
||||
|
||||
@param dict: A L{dict} which will be updated with the results of the
|
||||
accumulation. Items are added to this dictionary, with method names as
|
||||
keys and C{1} as values.
|
||||
@type dict: L{dict}
|
||||
|
||||
@param prefix: A native string giving a prefix. Each method of C{classObj}
|
||||
(and base classes of C{classObj}) with a name which begins with this
|
||||
prefix will be returned.
|
||||
@type prefix: L{str}
|
||||
|
||||
@param baseClass: A class object at which to stop searching upwards for new
|
||||
methods. To collect all method names, do not pass a value for this
|
||||
parameter.
|
||||
|
||||
@return: L{None}
|
||||
"""
|
||||
for base in classObj.__bases__:
|
||||
addMethodNamesToDict(base, dict, prefix, baseClass)
|
||||
|
||||
if baseClass is None or baseClass in classObj.__bases__:
|
||||
for name, method in classObj.__dict__.items():
|
||||
optName = name[len(prefix):]
|
||||
if ((type(method) is types.FunctionType)
|
||||
and (name[:len(prefix)] == prefix)
|
||||
and (len(optName))):
|
||||
dict[optName] = 1
|
||||
|
||||
|
||||
|
||||
def prefixedMethods(obj, prefix=''):
|
||||
"""
|
||||
Given an object C{obj}, returns a list of method objects that match the
|
||||
string C{prefix}.
|
||||
|
||||
@param obj: An arbitrary object from which to collect methods.
|
||||
|
||||
@param prefix: A native string giving a prefix. Each method of C{obj} with
|
||||
a name which begins with this prefix will be returned.
|
||||
@type prefix: L{str}
|
||||
|
||||
@return: A list of the matching method objects.
|
||||
@rtype: L{list}
|
||||
"""
|
||||
dct = {}
|
||||
accumulateMethods(obj, dct, prefix)
|
||||
return list(dct.values())
|
||||
|
||||
|
||||
|
||||
def accumulateMethods(obj, dict, prefix='', curClass=None):
|
||||
"""
|
||||
Given an object C{obj}, add all methods that begin with C{prefix}.
|
||||
|
||||
@param obj: An arbitrary object to collect methods from.
|
||||
|
||||
@param dict: A L{dict} which will be updated with the results of the
|
||||
accumulation. Items are added to this dictionary, with method names as
|
||||
keys and corresponding instance method objects as values.
|
||||
@type dict: L{dict}
|
||||
|
||||
@param prefix: A native string giving a prefix. Each method of C{obj} with
|
||||
a name which begins with this prefix will be returned.
|
||||
@type prefix: L{str}
|
||||
|
||||
@param curClass: The class in the inheritance hierarchy at which to start
|
||||
collecting methods. Collection proceeds up. To collect all methods
|
||||
from C{obj}, do not pass a value for this parameter.
|
||||
|
||||
@return: L{None}
|
||||
"""
|
||||
if not curClass:
|
||||
curClass = obj.__class__
|
||||
for base in curClass.__bases__:
|
||||
# The implementation of the object class is different on PyPy vs.
|
||||
# CPython. This has the side effect of making accumulateMethods()
|
||||
# pick up object methods from all new-style classes -
|
||||
# such as __getattribute__, etc.
|
||||
# If we ignore 'object' when accumulating methods, we can get
|
||||
# consistent behavior on Pypy and CPython.
|
||||
if base is not object:
|
||||
accumulateMethods(obj, dict, prefix, base)
|
||||
|
||||
for name, method in curClass.__dict__.items():
|
||||
optName = name[len(prefix):]
|
||||
if ((type(method) is types.FunctionType)
|
||||
and (name[:len(prefix)] == prefix)
|
||||
and (len(optName))):
|
||||
dict[optName] = getattr(obj, name)
|
||||
|
||||
|
||||
|
||||
def namedModule(name):
|
||||
"""
|
||||
Return a module given its name.
|
||||
"""
|
||||
topLevel = __import__(name)
|
||||
packages = name.split(".")[1:]
|
||||
m = topLevel
|
||||
for p in packages:
|
||||
m = getattr(m, p)
|
||||
return m
|
||||
|
||||
|
||||
|
||||
def namedObject(name):
|
||||
"""
|
||||
Get a fully named module-global object.
|
||||
"""
|
||||
classSplit = name.split('.')
|
||||
module = namedModule('.'.join(classSplit[:-1]))
|
||||
return getattr(module, classSplit[-1])
|
||||
|
||||
namedClass = namedObject # backwards compat
|
||||
|
||||
|
||||
|
||||
def requireModule(name, default=None):
|
||||
"""
|
||||
Try to import a module given its name, returning C{default} value if
|
||||
C{ImportError} is raised during import.
|
||||
|
||||
@param name: Module name as it would have been passed to C{import}.
|
||||
@type name: C{str}.
|
||||
|
||||
@param default: Value returned in case C{ImportError} is raised while
|
||||
importing the module.
|
||||
|
||||
@return: Module or default value.
|
||||
"""
|
||||
try:
|
||||
return namedModule(name)
|
||||
except ImportError:
|
||||
return default
|
||||
|
||||
|
||||
|
||||
class _NoModuleFound(Exception):
|
||||
"""
|
||||
No module was found because none exists.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class InvalidName(ValueError):
|
||||
"""
|
||||
The given name is not a dot-separated list of Python objects.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class ModuleNotFound(InvalidName):
|
||||
"""
|
||||
The module associated with the given name doesn't exist and it can't be
|
||||
imported.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class ObjectNotFound(InvalidName):
|
||||
"""
|
||||
The object associated with the given name doesn't exist and it can't be
|
||||
imported.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def _importAndCheckStack(importName):
|
||||
"""
|
||||
Import the given name as a module, then walk the stack to determine whether
|
||||
the failure was the module not existing, or some code in the module (for
|
||||
example a dependent import) failing. This can be helpful to determine
|
||||
whether any actual application code was run. For example, to distiguish
|
||||
administrative error (entering the wrong module name), from programmer
|
||||
error (writing buggy code in a module that fails to import).
|
||||
|
||||
@param importName: The name of the module to import.
|
||||
@type importName: C{str}
|
||||
@raise Exception: if something bad happens. This can be any type of
|
||||
exception, since nobody knows what loading some arbitrary code might
|
||||
do.
|
||||
@raise _NoModuleFound: if no module was found.
|
||||
"""
|
||||
try:
|
||||
return __import__(importName)
|
||||
except ImportError:
|
||||
excType, excValue, excTraceback = sys.exc_info()
|
||||
while excTraceback:
|
||||
execName = excTraceback.tb_frame.f_globals["__name__"]
|
||||
# in Python 2 execName is None when an ImportError is encountered,
|
||||
# where in Python 3 execName is equal to the importName.
|
||||
if execName is None or execName == importName:
|
||||
reraise(excValue, excTraceback)
|
||||
excTraceback = excTraceback.tb_next
|
||||
raise _NoModuleFound()
|
||||
|
||||
|
||||
|
||||
def namedAny(name):
|
||||
"""
|
||||
Retrieve a Python object by its fully qualified name from the global Python
|
||||
module namespace. The first part of the name, that describes a module,
|
||||
will be discovered and imported. Each subsequent part of the name is
|
||||
treated as the name of an attribute of the object specified by all of the
|
||||
name which came before it. For example, the fully-qualified name of this
|
||||
object is 'twisted.python.reflect.namedAny'.
|
||||
|
||||
@type name: L{str}
|
||||
@param name: The name of the object to return.
|
||||
|
||||
@raise InvalidName: If the name is an empty string, starts or ends with
|
||||
a '.', or is otherwise syntactically incorrect.
|
||||
|
||||
@raise ModuleNotFound: If the name is syntactically correct but the
|
||||
module it specifies cannot be imported because it does not appear to
|
||||
exist.
|
||||
|
||||
@raise ObjectNotFound: If the name is syntactically correct, includes at
|
||||
least one '.', but the module it specifies cannot be imported because
|
||||
it does not appear to exist.
|
||||
|
||||
@raise AttributeError: If an attribute of an object along the way cannot be
|
||||
accessed, or a module along the way is not found.
|
||||
|
||||
@return: the Python object identified by 'name'.
|
||||
"""
|
||||
if not name:
|
||||
raise InvalidName('Empty module name')
|
||||
|
||||
names = name.split('.')
|
||||
|
||||
# if the name starts or ends with a '.' or contains '..', the __import__
|
||||
# will raise an 'Empty module name' error. This will provide a better error
|
||||
# message.
|
||||
if '' in names:
|
||||
raise InvalidName(
|
||||
"name must be a string giving a '.'-separated list of Python "
|
||||
"identifiers, not %r" % (name,))
|
||||
|
||||
topLevelPackage = None
|
||||
moduleNames = names[:]
|
||||
while not topLevelPackage:
|
||||
if moduleNames:
|
||||
trialname = '.'.join(moduleNames)
|
||||
try:
|
||||
topLevelPackage = _importAndCheckStack(trialname)
|
||||
except _NoModuleFound:
|
||||
moduleNames.pop()
|
||||
else:
|
||||
if len(names) == 1:
|
||||
raise ModuleNotFound("No module named %r" % (name,))
|
||||
else:
|
||||
raise ObjectNotFound('%r does not name an object' % (name,))
|
||||
|
||||
obj = topLevelPackage
|
||||
for n in names[1:]:
|
||||
obj = getattr(obj, n)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
|
||||
def filenameToModuleName(fn):
|
||||
"""
|
||||
Convert a name in the filesystem to the name of the Python module it is.
|
||||
|
||||
This is aggressive about getting a module name back from a file; it will
|
||||
always return a string. Aggressive means 'sometimes wrong'; it won't look
|
||||
at the Python path or try to do any error checking: don't use this method
|
||||
unless you already know that the filename you're talking about is a Python
|
||||
module.
|
||||
|
||||
@param fn: A filesystem path to a module or package; C{bytes} on Python 2,
|
||||
C{bytes} or C{unicode} on Python 3.
|
||||
|
||||
@return: A hopefully importable module name.
|
||||
@rtype: C{str}
|
||||
"""
|
||||
if isinstance(fn, bytes):
|
||||
initPy = b"__init__.py"
|
||||
else:
|
||||
initPy = "__init__.py"
|
||||
fullName = os.path.abspath(fn)
|
||||
base = os.path.basename(fn)
|
||||
if not base:
|
||||
# this happens when fn ends with a path separator, just skit it
|
||||
base = os.path.basename(fn[:-1])
|
||||
modName = nativeString(os.path.splitext(base)[0])
|
||||
while 1:
|
||||
fullName = os.path.dirname(fullName)
|
||||
if os.path.exists(os.path.join(fullName, initPy)):
|
||||
modName = "%s.%s" % (
|
||||
nativeString(os.path.basename(fullName)),
|
||||
nativeString(modName))
|
||||
else:
|
||||
break
|
||||
return modName
|
||||
|
||||
|
||||
|
||||
def qual(clazz):
|
||||
"""
|
||||
Return full import path of a class.
|
||||
"""
|
||||
return clazz.__module__ + '.' + clazz.__name__
|
||||
|
||||
|
||||
|
||||
def _determineClass(x):
|
||||
try:
|
||||
return x.__class__
|
||||
except:
|
||||
return type(x)
|
||||
|
||||
|
||||
|
||||
def _determineClassName(x):
|
||||
c = _determineClass(x)
|
||||
try:
|
||||
return c.__name__
|
||||
except:
|
||||
try:
|
||||
return str(c)
|
||||
except:
|
||||
return '<BROKEN CLASS AT 0x%x>' % id(c)
|
||||
|
||||
|
||||
|
||||
def _safeFormat(formatter, o):
|
||||
"""
|
||||
Helper function for L{safe_repr} and L{safe_str}.
|
||||
|
||||
Called when C{repr} or C{str} fail. Returns a string containing info about
|
||||
C{o} and the latest exception.
|
||||
|
||||
@param formatter: C{str} or C{repr}.
|
||||
@type formatter: C{type}
|
||||
@param o: Any object.
|
||||
|
||||
@rtype: C{str}
|
||||
@return: A string containing information about C{o} and the raised
|
||||
exception.
|
||||
"""
|
||||
io = NativeStringIO()
|
||||
traceback.print_exc(file=io)
|
||||
className = _determineClassName(o)
|
||||
tbValue = io.getvalue()
|
||||
return "<%s instance at 0x%x with %s error:\n %s>" % (
|
||||
className, id(o), formatter.__name__, tbValue)
|
||||
|
||||
|
||||
|
||||
def safe_repr(o):
|
||||
"""
|
||||
Returns a string representation of an object, or a string containing a
|
||||
traceback, if that object's __repr__ raised an exception.
|
||||
|
||||
@param o: Any object.
|
||||
|
||||
@rtype: C{str}
|
||||
"""
|
||||
try:
|
||||
return repr(o)
|
||||
except:
|
||||
return _safeFormat(repr, o)
|
||||
|
||||
|
||||
|
||||
def safe_str(o):
|
||||
"""
|
||||
Returns a string representation of an object, or a string containing a
|
||||
traceback, if that object's __str__ raised an exception.
|
||||
|
||||
@param o: Any object.
|
||||
|
||||
@rtype: C{str}
|
||||
"""
|
||||
if _PY3 and isinstance(o, bytes):
|
||||
# If o is bytes and seems to holds a utf-8 encoded string,
|
||||
# convert it to str.
|
||||
try:
|
||||
return o.decode('utf-8')
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
return str(o)
|
||||
except:
|
||||
return _safeFormat(str, o)
|
||||
|
||||
|
||||
|
||||
@_oldStyle
|
||||
class QueueMethod:
|
||||
"""
|
||||
I represent a method that doesn't exist yet.
|
||||
"""
|
||||
def __init__(self, name, calls):
|
||||
self.name = name
|
||||
self.calls = calls
|
||||
def __call__(self, *args):
|
||||
self.calls.append((self.name, args))
|
||||
|
||||
|
||||
|
||||
def fullFuncName(func):
|
||||
qualName = (str(pickle.whichmodule(func, func.__name__)) + '.' + func.__name__)
|
||||
if namedObject(qualName) is not func:
|
||||
raise Exception("Couldn't find %s as %s." % (func, qualName))
|
||||
return qualName
|
||||
|
||||
|
||||
|
||||
def getClass(obj):
|
||||
"""
|
||||
Return the class or type of object 'obj'.
|
||||
Returns sensible result for oldstyle and newstyle instances and types.
|
||||
"""
|
||||
if hasattr(obj, '__class__'):
|
||||
return obj.__class__
|
||||
else:
|
||||
return type(obj)
|
||||
|
||||
|
||||
|
||||
def accumulateClassDict(classObj, attr, adict, baseClass=None):
|
||||
"""
|
||||
Accumulate all attributes of a given name in a class hierarchy into a single dictionary.
|
||||
|
||||
Assuming all class attributes of this name are dictionaries.
|
||||
If any of the dictionaries being accumulated have the same key, the
|
||||
one highest in the class hierarchy wins.
|
||||
(XXX: If \"highest\" means \"closest to the starting class\".)
|
||||
|
||||
Ex::
|
||||
|
||||
class Soy:
|
||||
properties = {\"taste\": \"bland\"}
|
||||
|
||||
class Plant:
|
||||
properties = {\"colour\": \"green\"}
|
||||
|
||||
class Seaweed(Plant):
|
||||
pass
|
||||
|
||||
class Lunch(Soy, Seaweed):
|
||||
properties = {\"vegan\": 1 }
|
||||
|
||||
dct = {}
|
||||
|
||||
accumulateClassDict(Lunch, \"properties\", dct)
|
||||
|
||||
print(dct)
|
||||
|
||||
{\"taste\": \"bland\", \"colour\": \"green\", \"vegan\": 1}
|
||||
"""
|
||||
for base in classObj.__bases__:
|
||||
accumulateClassDict(base, attr, adict)
|
||||
if baseClass is None or baseClass in classObj.__bases__:
|
||||
adict.update(classObj.__dict__.get(attr, {}))
|
||||
|
||||
|
||||
def accumulateClassList(classObj, attr, listObj, baseClass=None):
|
||||
"""
|
||||
Accumulate all attributes of a given name in a class hierarchy into a single list.
|
||||
|
||||
Assuming all class attributes of this name are lists.
|
||||
"""
|
||||
for base in classObj.__bases__:
|
||||
accumulateClassList(base, attr, listObj)
|
||||
if baseClass is None or baseClass in classObj.__bases__:
|
||||
listObj.extend(classObj.__dict__.get(attr, []))
|
||||
|
||||
|
||||
def isSame(a, b):
|
||||
return (a is b)
|
||||
|
||||
|
||||
def isLike(a, b):
|
||||
return (a == b)
|
||||
|
||||
|
||||
def modgrep(goal):
|
||||
return objgrep(sys.modules, goal, isLike, 'sys.modules')
|
||||
|
||||
|
||||
def isOfType(start, goal):
|
||||
return ((type(start) is goal) or
|
||||
(isinstance(start, compat.InstanceType) and
|
||||
start.__class__ is goal))
|
||||
|
||||
|
||||
def findInstances(start, t):
|
||||
return objgrep(start, t, isOfType)
|
||||
|
||||
|
||||
if not _PY3:
|
||||
# The function objgrep() currently doesn't work on Python 3 due to some
|
||||
# edge cases, as described in #6986.
|
||||
# twisted.python.reflect is quite important and objgrep is not used in
|
||||
# Twisted itself, so in #5929, we decided to port everything but objgrep()
|
||||
# and to finish the porting in #6986
|
||||
def objgrep(start, goal, eq=isLike, path='', paths=None, seen=None,
|
||||
showUnknowns=0, maxDepth=None):
|
||||
"""
|
||||
An insanely CPU-intensive process for finding stuff.
|
||||
"""
|
||||
if paths is None:
|
||||
paths = []
|
||||
if seen is None:
|
||||
seen = {}
|
||||
if eq(start, goal):
|
||||
paths.append(path)
|
||||
if id(start) in seen:
|
||||
if seen[id(start)] is start:
|
||||
return
|
||||
if maxDepth is not None:
|
||||
if maxDepth == 0:
|
||||
return
|
||||
maxDepth -= 1
|
||||
seen[id(start)] = start
|
||||
# Make an alias for those arguments which are passed recursively to
|
||||
# objgrep for container objects.
|
||||
args = (paths, seen, showUnknowns, maxDepth)
|
||||
if isinstance(start, dict):
|
||||
for k, v in start.items():
|
||||
objgrep(k, goal, eq, path+'{'+repr(v)+'}', *args)
|
||||
objgrep(v, goal, eq, path+'['+repr(k)+']', *args)
|
||||
elif isinstance(start, (list, tuple, deque)):
|
||||
for idx, _elem in enumerate(start):
|
||||
objgrep(start[idx], goal, eq, path+'['+str(idx)+']', *args)
|
||||
elif isinstance(start, types.MethodType):
|
||||
objgrep(start.__self__, goal, eq, path+'.__self__', *args)
|
||||
objgrep(start.__func__, goal, eq, path+'.__func__', *args)
|
||||
objgrep(start.__self__.__class__, goal, eq,
|
||||
path+'.__self__.__class__', *args)
|
||||
elif hasattr(start, '__dict__'):
|
||||
for k, v in start.__dict__.items():
|
||||
objgrep(v, goal, eq, path+'.'+k, *args)
|
||||
if isinstance(start, compat.InstanceType):
|
||||
objgrep(start.__class__, goal, eq, path+'.__class__', *args)
|
||||
elif isinstance(start, weakref.ReferenceType):
|
||||
objgrep(start(), goal, eq, path+'()', *args)
|
||||
elif (isinstance(start, (compat.StringType,
|
||||
int, types.FunctionType,
|
||||
types.BuiltinMethodType, RegexType, float,
|
||||
type(None), compat.FileType)) or
|
||||
type(start).__name__ in ('wrapper_descriptor',
|
||||
'method_descriptor', 'member_descriptor',
|
||||
'getset_descriptor')):
|
||||
pass
|
||||
elif showUnknowns:
|
||||
print('unknown type', type(start), start)
|
||||
return paths
|
||||
|
||||
|
||||
|
||||
__all__ = [
|
||||
'InvalidName', 'ModuleNotFound', 'ObjectNotFound',
|
||||
|
||||
'QueueMethod',
|
||||
|
||||
'namedModule', 'namedObject', 'namedClass', 'namedAny', 'requireModule',
|
||||
'safe_repr', 'safe_str', 'prefixedMethodNames', 'addMethodNamesToDict',
|
||||
'prefixedMethods', 'accumulateMethods', 'fullFuncName', 'qual', 'getClass',
|
||||
'accumulateClassDict', 'accumulateClassList', 'isSame', 'isLike',
|
||||
'modgrep', 'isOfType', 'findInstances', 'objgrep', 'filenameToModuleName',
|
||||
'fullyQualifiedName']
|
||||
|
||||
|
||||
if _PY3:
|
||||
# This is to be removed when fixing #6986
|
||||
__all__.remove('objgrep')
|
||||
@@ -0,0 +1,67 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
A release-automation toolkit.
|
||||
|
||||
Don't use this outside of Twisted.
|
||||
|
||||
Maintainer: Christopher Armstrong
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
from twisted.python.compat import raw_input
|
||||
|
||||
|
||||
# errors
|
||||
|
||||
class DirectoryExists(OSError):
|
||||
"""
|
||||
Some directory exists when it shouldn't.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class DirectoryDoesntExist(OSError):
|
||||
"""
|
||||
Some directory doesn't exist when it should.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class CommandFailed(OSError):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# utilities
|
||||
|
||||
def sh(command, null=True, prompt=False):
|
||||
"""
|
||||
I'll try to execute C{command}, and if C{prompt} is true, I'll
|
||||
ask before running it. If the command returns something other
|
||||
than 0, I'll raise C{CommandFailed(command)}.
|
||||
"""
|
||||
print("--$", command)
|
||||
|
||||
if prompt:
|
||||
if raw_input("run ?? ").startswith('n'):
|
||||
return
|
||||
if null:
|
||||
command = "%s > /dev/null" % command
|
||||
if os.system(command) != 0:
|
||||
raise CommandFailed(command)
|
||||
|
||||
|
||||
|
||||
def runChdirSafe(f, *args, **kw):
|
||||
origdir = os.path.abspath('.')
|
||||
try:
|
||||
return f(*args, **kw)
|
||||
finally:
|
||||
os.chdir(origdir)
|
||||
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Unit tests for L{twisted.python}.
|
||||
"""
|
||||
@@ -0,0 +1,55 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Facilities for helping test code which interacts with Python's module system
|
||||
to load code.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
import sys
|
||||
|
||||
from twisted.python.filepath import FilePath
|
||||
|
||||
|
||||
class TwistedModulesMixin(object):
|
||||
"""
|
||||
A mixin for C{twisted.trial.unittest.SynchronousTestCase} providing useful
|
||||
methods for manipulating Python's module system.
|
||||
"""
|
||||
|
||||
def replaceSysPath(self, sysPath):
|
||||
"""
|
||||
Replace sys.path, for the duration of the test, with the given value.
|
||||
"""
|
||||
originalSysPath = sys.path[:]
|
||||
def cleanUpSysPath():
|
||||
sys.path[:] = originalSysPath
|
||||
self.addCleanup(cleanUpSysPath)
|
||||
sys.path[:] = sysPath
|
||||
|
||||
|
||||
def replaceSysModules(self, sysModules):
|
||||
"""
|
||||
Replace sys.modules, for the duration of the test, with the given value.
|
||||
"""
|
||||
originalSysModules = sys.modules.copy()
|
||||
def cleanUpSysModules():
|
||||
sys.modules.clear()
|
||||
sys.modules.update(originalSysModules)
|
||||
self.addCleanup(cleanUpSysModules)
|
||||
sys.modules.clear()
|
||||
sys.modules.update(sysModules)
|
||||
|
||||
|
||||
def pathEntryWithOnePackage(self, pkgname="test_package"):
|
||||
"""
|
||||
Generate a L{FilePath} with one package, named C{pkgname}, on it, and
|
||||
return the L{FilePath} of the path entry.
|
||||
"""
|
||||
entry = FilePath(self.mktemp())
|
||||
pkg = entry.child("test_package")
|
||||
pkg.makedirs()
|
||||
pkg.child("__init__.py").setContent(b"")
|
||||
return entry
|
||||
@@ -0,0 +1,39 @@
|
||||
# -*- test-case-name: twisted.python.test.test_sendmsg -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
from struct import unpack
|
||||
|
||||
from twisted.python.sendmsg import recvmsg
|
||||
|
||||
|
||||
def recvfd(socketfd):
|
||||
"""
|
||||
Receive a file descriptor from a L{sendmsg} message on the given C{AF_UNIX}
|
||||
socket.
|
||||
|
||||
@param socketfd: An C{AF_UNIX} socket, attached to another process waiting
|
||||
to send sockets via the ancillary data mechanism in L{send1msg}.
|
||||
|
||||
@param fd: C{int}
|
||||
|
||||
@return: a 2-tuple of (new file descriptor, description).
|
||||
@rtype: 2-tuple of (C{int}, C{bytes})
|
||||
"""
|
||||
ourSocket = socket.fromfd(socketfd, socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
data, ancillary, flags = recvmsg(ourSocket)
|
||||
[(cmsgLevel, cmsgType, packedFD)] = ancillary
|
||||
# cmsgLevel and cmsgType really need to be SOL_SOCKET / SCM_RIGHTS, but
|
||||
# since those are the *only* standard values, there's not much point in
|
||||
# checking.
|
||||
[unpackedFD] = unpack("i", packedFD)
|
||||
return (unpackedFD, data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
fd, description = recvfd(int(sys.argv[1]))
|
||||
os.write(fd, b"Test fixture data: " + description + b".\n")
|
||||
os.close(fd)
|
||||
@@ -0,0 +1,290 @@
|
||||
# -*- test-case-name: twisted.python.test.test_urlpath -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.python.urlpath}.
|
||||
"""
|
||||
|
||||
from twisted.trial import unittest
|
||||
from twisted.python import urlpath
|
||||
from twisted.python.compat import _PY3
|
||||
|
||||
|
||||
class _BaseURLPathTests(object):
|
||||
"""
|
||||
Tests for instantiated L{urlpath.URLPath}s.
|
||||
"""
|
||||
def test_partsAreBytes(self):
|
||||
"""
|
||||
All of the attributes of L{urlpath.URLPath} should be L{bytes}.
|
||||
"""
|
||||
self.assertIsInstance(self.path.scheme, bytes)
|
||||
self.assertIsInstance(self.path.netloc, bytes)
|
||||
self.assertIsInstance(self.path.path, bytes)
|
||||
self.assertIsInstance(self.path.query, bytes)
|
||||
self.assertIsInstance(self.path.fragment, bytes)
|
||||
|
||||
|
||||
def test_strReturnsStr(self):
|
||||
"""
|
||||
Calling C{str()} with a L{URLPath} will always return a L{str}.
|
||||
"""
|
||||
self.assertEqual(type(self.path.__str__()), str)
|
||||
|
||||
|
||||
def test_mutabilityWithText(self, stringType=type(u"")):
|
||||
"""
|
||||
Setting attributes on L{urlpath.URLPath} should change the value
|
||||
returned by L{str}.
|
||||
|
||||
@param stringType: a callable to parameterize this test for different
|
||||
text types.
|
||||
@type stringType: 1-argument callable taking L{unicode} and returning
|
||||
L{str} or L{bytes}.
|
||||
"""
|
||||
self.path.scheme = stringType(u"https")
|
||||
self.assertEqual(
|
||||
str(self.path),
|
||||
"https://example.com/foo/bar?yes=no&no=yes#footer")
|
||||
self.path.netloc = stringType(u"another.example.invalid")
|
||||
self.assertEqual(
|
||||
str(self.path),
|
||||
"https://another.example.invalid/foo/bar?yes=no&no=yes#footer")
|
||||
self.path.path = stringType(u"/hello")
|
||||
self.assertEqual(
|
||||
str(self.path),
|
||||
"https://another.example.invalid/hello?yes=no&no=yes#footer")
|
||||
self.path.query = stringType(u"alpha=omega&opposites=same")
|
||||
self.assertEqual(
|
||||
str(self.path),
|
||||
"https://another.example.invalid/hello?alpha=omega&opposites=same"
|
||||
"#footer")
|
||||
self.path.fragment = stringType(u"header")
|
||||
self.assertEqual(
|
||||
str(self.path),
|
||||
"https://another.example.invalid/hello?alpha=omega&opposites=same"
|
||||
"#header")
|
||||
|
||||
|
||||
def test_mutabilityWithBytes(self):
|
||||
"""
|
||||
Same as L{test_mutabilityWithText} but for bytes.
|
||||
"""
|
||||
self.test_mutabilityWithText(lambda x: x.encode("ascii"))
|
||||
|
||||
|
||||
def test_allAttributesAreBytes(self):
|
||||
"""
|
||||
A created L{URLPath} has bytes attributes.
|
||||
"""
|
||||
self.assertIsInstance(self.path.scheme, bytes)
|
||||
self.assertIsInstance(self.path.netloc, bytes)
|
||||
self.assertIsInstance(self.path.path, bytes)
|
||||
self.assertIsInstance(self.path.query, bytes)
|
||||
self.assertIsInstance(self.path.fragment, bytes)
|
||||
|
||||
|
||||
def test_stringConversion(self):
|
||||
"""
|
||||
Calling C{str()} with a L{URLPath} will return the same URL that it was
|
||||
constructed with.
|
||||
"""
|
||||
self.assertEqual(str(self.path),
|
||||
"http://example.com/foo/bar?yes=no&no=yes#footer")
|
||||
|
||||
|
||||
def test_childString(self):
|
||||
"""
|
||||
Calling C{str()} with a C{URLPath.child()} will return a URL which is
|
||||
the child of the URL it was instantiated with.
|
||||
"""
|
||||
self.assertEqual(str(self.path.child(b'hello')),
|
||||
"http://example.com/foo/bar/hello")
|
||||
self.assertEqual(str(self.path.child(b'hello').child(b'')),
|
||||
"http://example.com/foo/bar/hello/")
|
||||
self.assertEqual(str(self.path.child(b'hello', keepQuery=True)),
|
||||
"http://example.com/foo/bar/hello?yes=no&no=yes")
|
||||
|
||||
|
||||
def test_siblingString(self):
|
||||
"""
|
||||
Calling C{str()} with a C{URLPath.sibling()} will return a URL which is
|
||||
the sibling of the URL it was instantiated with.
|
||||
"""
|
||||
self.assertEqual(str(self.path.sibling(b'baz')),
|
||||
'http://example.com/foo/baz')
|
||||
self.assertEqual(str(self.path.sibling(b'baz', keepQuery=True)),
|
||||
"http://example.com/foo/baz?yes=no&no=yes")
|
||||
|
||||
# The sibling of http://example.com/foo/bar/
|
||||
# is http://example.comf/foo/bar/baz
|
||||
# because really we are constructing a sibling of
|
||||
# http://example.com/foo/bar/index.html
|
||||
self.assertEqual(str(self.path.child(b'').sibling(b'baz')),
|
||||
'http://example.com/foo/bar/baz')
|
||||
|
||||
|
||||
def test_parentString(self):
|
||||
"""
|
||||
Calling C{str()} with a C{URLPath.parent()} will return a URL which is
|
||||
the parent of the URL it was instantiated with.
|
||||
"""
|
||||
# .parent() should be equivalent to '..'
|
||||
# 'foo' is the current directory, '/' is the parent directory
|
||||
self.assertEqual(str(self.path.parent()),
|
||||
'http://example.com/')
|
||||
self.assertEqual(str(self.path.parent(keepQuery=True)),
|
||||
'http://example.com/?yes=no&no=yes')
|
||||
self.assertEqual(str(self.path.child(b'').parent()),
|
||||
'http://example.com/foo/')
|
||||
self.assertEqual(str(self.path.child(b'baz').parent()),
|
||||
'http://example.com/foo/')
|
||||
self.assertEqual(
|
||||
str(self.path.parent().parent().parent().parent().parent()),
|
||||
'http://example.com/')
|
||||
|
||||
|
||||
def test_hereString(self):
|
||||
"""
|
||||
Calling C{str()} with a C{URLPath.here()} will return a URL which is
|
||||
the URL that it was instantiated with, without any file, query, or
|
||||
fragment.
|
||||
"""
|
||||
# .here() should be equivalent to '.'
|
||||
self.assertEqual(str(self.path.here()), 'http://example.com/foo/')
|
||||
self.assertEqual(str(self.path.here(keepQuery=True)),
|
||||
'http://example.com/foo/?yes=no&no=yes')
|
||||
self.assertEqual(str(self.path.child(b'').here()),
|
||||
'http://example.com/foo/bar/')
|
||||
|
||||
|
||||
def test_doubleSlash(self):
|
||||
"""
|
||||
Calling L{urlpath.URLPath.click} on a L{urlpath.URLPath} with a
|
||||
trailing slash with a relative URL containing a leading slash will
|
||||
result in a URL with a single slash at the start of the path portion.
|
||||
"""
|
||||
self.assertEqual(
|
||||
str(self.path.click(b"/hello/world")).encode("ascii"),
|
||||
b"http://example.com/hello/world"
|
||||
)
|
||||
|
||||
|
||||
def test_pathList(self):
|
||||
"""
|
||||
L{urlpath.URLPath.pathList} returns a L{list} of L{bytes}.
|
||||
"""
|
||||
self.assertEqual(
|
||||
self.path.child(b"%00%01%02").pathList(),
|
||||
[b"", b"foo", b"bar", b"%00%01%02"]
|
||||
)
|
||||
|
||||
# Just testing that the 'copy' argument exists for compatibility; it
|
||||
# was originally provided for performance reasons, and its behavioral
|
||||
# contract is kind of nonsense (where is the state shared? who with?)
|
||||
# so it doesn't actually *do* anything any more.
|
||||
self.assertEqual(
|
||||
self.path.child(b"%00%01%02").pathList(copy=False),
|
||||
[b"", b"foo", b"bar", b"%00%01%02"]
|
||||
)
|
||||
self.assertEqual(
|
||||
self.path.child(b"%00%01%02").pathList(unquote=True),
|
||||
[b"", b"foo", b"bar", b"\x00\x01\x02"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
class BytesURLPathTests(_BaseURLPathTests, unittest.TestCase):
|
||||
"""
|
||||
Tests for interacting with a L{URLPath} created with C{fromBytes}.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.path = urlpath.URLPath.fromBytes(
|
||||
b"http://example.com/foo/bar?yes=no&no=yes#footer")
|
||||
|
||||
|
||||
def test_mustBeBytes(self):
|
||||
"""
|
||||
L{URLPath.fromBytes} must take a L{bytes} argument.
|
||||
"""
|
||||
with self.assertRaises(ValueError):
|
||||
urlpath.URLPath.fromBytes(None)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
urlpath.URLPath.fromBytes(u"someurl")
|
||||
|
||||
|
||||
def test_withoutArguments(self):
|
||||
"""
|
||||
An instantiation with no arguments creates a usable L{URLPath} with
|
||||
default arguments.
|
||||
"""
|
||||
url = urlpath.URLPath()
|
||||
self.assertEqual(str(url), "http://localhost/")
|
||||
|
||||
|
||||
def test_partialArguments(self):
|
||||
"""
|
||||
Leaving some optional arguments unfilled makes a L{URLPath} with those
|
||||
optional arguments filled with defaults.
|
||||
"""
|
||||
# Not a "full" URL given to fromBytes, no /
|
||||
# / is filled in
|
||||
url = urlpath.URLPath.fromBytes(b"http://google.com")
|
||||
self.assertEqual(url.scheme, b"http")
|
||||
self.assertEqual(url.netloc, b"google.com")
|
||||
self.assertEqual(url.path, b"/")
|
||||
self.assertEqual(url.fragment, b"")
|
||||
self.assertEqual(url.query, b"")
|
||||
self.assertEqual(str(url), "http://google.com/")
|
||||
|
||||
|
||||
def test_nonASCIIBytes(self):
|
||||
"""
|
||||
L{URLPath.fromBytes} can interpret non-ASCII bytes as percent-encoded
|
||||
"""
|
||||
url = urlpath.URLPath.fromBytes(b"http://example.com/\xff\x00")
|
||||
self.assertEqual(str(url), "http://example.com/%FF%00")
|
||||
|
||||
|
||||
|
||||
class StringURLPathTests(_BaseURLPathTests, unittest.TestCase):
|
||||
"""
|
||||
Tests for interacting with a L{URLPath} created with C{fromString} and a
|
||||
L{str} argument.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.path = urlpath.URLPath.fromString(
|
||||
"http://example.com/foo/bar?yes=no&no=yes#footer")
|
||||
|
||||
|
||||
def test_mustBeStr(self):
|
||||
"""
|
||||
C{URLPath.fromString} must take a L{str} or L{unicode} argument.
|
||||
"""
|
||||
with self.assertRaises(ValueError):
|
||||
urlpath.URLPath.fromString(None)
|
||||
|
||||
if _PY3:
|
||||
with self.assertRaises(ValueError):
|
||||
urlpath.URLPath.fromString(b"someurl")
|
||||
|
||||
|
||||
|
||||
class UnicodeURLPathTests(_BaseURLPathTests, unittest.TestCase):
|
||||
"""
|
||||
Tests for interacting with a L{URLPath} created with C{fromString} and a
|
||||
L{unicode} argument.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.path = urlpath.URLPath.fromString(
|
||||
u"http://example.com/foo/bar?yes=no&no=yes#footer")
|
||||
|
||||
|
||||
def test_nonASCIICharacters(self):
|
||||
"""
|
||||
L{URLPath.fromString} can load non-ASCII characters.
|
||||
"""
|
||||
url = urlpath.URLPath.fromString(u"http://example.com/\xff\x00")
|
||||
self.assertEqual(str(url), "http://example.com/%C3%BF%00")
|
||||
@@ -0,0 +1,101 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test cases covering L{twisted.python.zippath}.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
from twisted.test.test_paths import AbstractFilePathTests
|
||||
from twisted.python.zippath import ZipArchive
|
||||
from twisted.python.filepath import _coerceToFilesystemEncoding
|
||||
|
||||
|
||||
def zipit(dirname, zfname):
|
||||
"""
|
||||
Create a zipfile on zfname, containing the contents of dirname'
|
||||
"""
|
||||
dirname = _coerceToFilesystemEncoding('', dirname)
|
||||
zfname = _coerceToFilesystemEncoding('', zfname)
|
||||
|
||||
with zipfile.ZipFile(zfname, "w") as zf:
|
||||
for root, ignored, files, in os.walk(dirname):
|
||||
for fname in files:
|
||||
fspath = os.path.join(root, fname)
|
||||
arcpath = os.path.join(root, fname)[len(dirname)+1:]
|
||||
zf.write(fspath, arcpath)
|
||||
|
||||
|
||||
|
||||
class ZipFilePathTests(AbstractFilePathTests):
|
||||
"""
|
||||
Test various L{ZipPath} path manipulations as well as reprs for L{ZipPath}
|
||||
and L{ZipArchive}.
|
||||
"""
|
||||
def setUp(self):
|
||||
AbstractFilePathTests.setUp(self)
|
||||
zipit(self.cmn, self.cmn + b'.zip')
|
||||
self.nativecmn = _coerceToFilesystemEncoding('', self.cmn)
|
||||
self.path = ZipArchive(self.cmn + b'.zip')
|
||||
self.root = self.path
|
||||
self.all = [x.replace(self.cmn, self.cmn + b'.zip')
|
||||
for x in self.all]
|
||||
|
||||
|
||||
def test_zipPathRepr(self):
|
||||
"""
|
||||
Make sure that invoking ZipPath's repr prints the correct class name
|
||||
and an absolute path to the zip file.
|
||||
"""
|
||||
child = self.path.child("foo")
|
||||
pathRepr = "ZipPath(%r)" % (
|
||||
os.path.abspath(self.nativecmn + ".zip" + os.sep + 'foo'),)
|
||||
|
||||
# Check for an absolute path
|
||||
self.assertEqual(repr(child), pathRepr)
|
||||
|
||||
# Create a path to the file rooted in the current working directory
|
||||
relativeCommon = self.nativecmn.replace(os.getcwd() + os.sep,
|
||||
"", 1) + ".zip"
|
||||
relpath = ZipArchive(relativeCommon)
|
||||
child = relpath.child("foo")
|
||||
|
||||
# Check using a path without the cwd prepended
|
||||
self.assertEqual(repr(child), pathRepr)
|
||||
|
||||
|
||||
def test_zipPathReprParentDirSegment(self):
|
||||
"""
|
||||
The repr of a ZipPath with C{".."} in the internal part of its path
|
||||
includes the C{".."} rather than applying the usual parent directory
|
||||
meaning.
|
||||
"""
|
||||
child = self.path.child("foo").child("..").child("bar")
|
||||
pathRepr = "ZipPath(%r)" % (
|
||||
self.nativecmn + ".zip" + os.sep.join(["", "foo", "..", "bar"]))
|
||||
self.assertEqual(repr(child), pathRepr)
|
||||
|
||||
|
||||
def test_zipArchiveRepr(self):
|
||||
"""
|
||||
Make sure that invoking ZipArchive's repr prints the correct class
|
||||
name and an absolute path to the zip file.
|
||||
"""
|
||||
path = ZipArchive(self.nativecmn + '.zip')
|
||||
pathRepr = 'ZipArchive(%r)' % (os.path.abspath(
|
||||
self.nativecmn + '.zip'),)
|
||||
|
||||
# Check for an absolute path
|
||||
self.assertEqual(repr(path), pathRepr)
|
||||
|
||||
# Create a path to the file rooted in the current working directory
|
||||
relativeCommon = self.nativecmn.replace(os.getcwd() + os.sep,
|
||||
"", 1) + ".zip"
|
||||
relpath = ZipArchive(relativeCommon)
|
||||
|
||||
# Check using a path without the cwd prepended
|
||||
self.assertEqual(repr(relpath), pathRepr)
|
||||
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Versions for Python packages.
|
||||
|
||||
See L{incremental}.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
from incremental import IncomparableVersions, Version, getVersionString
|
||||
|
||||
__all__ = ["Version", "getVersionString", "IncomparableVersions"]
|
||||
Reference in New Issue
Block a user