This commit is contained in:
Mai Gillmann
2019-12-17 14:09:10 +01:00
parent 66e908fc8a
commit 4791d00a43
2122 changed files with 423791 additions and 0 deletions
@@ -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"]