mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 13:51:09 -05:00
okay fine
This commit is contained in:
147
.venv/lib/python3.12/site-packages/txaio/__init__.py
Normal file
147
.venv/lib/python3.12/site-packages/txaio/__init__.py
Normal file
@@ -0,0 +1,147 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
|
||||
from txaio._version import __version__
|
||||
from txaio.interfaces import IFailedFuture, ILogger
|
||||
|
||||
version = __version__
|
||||
|
||||
# This is the API
|
||||
# see tx.py for Twisted implementation
|
||||
# see aio.py for asyncio implementation
|
||||
|
||||
|
||||
class _Config(object):
|
||||
"""
|
||||
This holds all valid configuration options, accessed as
|
||||
class-level variables. For example, if you were using asyncio:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
txaio.config.loop = asyncio.get_event_loop()
|
||||
|
||||
``loop`` is populated automatically (while importing one of the
|
||||
framework-specific libraries) but can be changed before any call
|
||||
into this library. Currently, it's only used by :meth:`call_later`
|
||||
If using asyncio, you must set this to an event-loop (by default,
|
||||
we use asyncio.get_event_loop). If using Twisted, set this to a
|
||||
reactor instance (by default we "from twisted.internet import
|
||||
reactor" on the first call to call_later)
|
||||
"""
|
||||
#: the event-loop object to use
|
||||
loop = None
|
||||
|
||||
|
||||
__all__ = (
|
||||
'with_config', # allow mutliple custom configurations at once
|
||||
'using_twisted', # True if we're using Twisted
|
||||
'using_asyncio', # True if we're using asyncio
|
||||
'use_twisted', # sets the library to use Twisted, or exception
|
||||
'use_asyncio', # sets the library to use asyncio, or exception
|
||||
|
||||
'config', # the config instance, access via attributes
|
||||
|
||||
'create_future', # create a Future (can be already resolved/errored)
|
||||
'create_future_success',
|
||||
'create_future_error',
|
||||
'create_failure', # return an object implementing IFailedFuture
|
||||
'as_future', # call a method, and always return a Future
|
||||
'is_future', # True for Deferreds in tx and Futures, @coroutines in asyncio
|
||||
'reject', # errback a Future
|
||||
'resolve', # callback a Future
|
||||
'cancel', # cancel a Future
|
||||
'add_callbacks', # add callback and/or errback
|
||||
'gather', # return a Future waiting for several other Futures
|
||||
'is_called', # True if the Future has a result
|
||||
|
||||
'call_later', # call the callback after the given delay seconds
|
||||
|
||||
'failure_message', # a printable error-message from a IFailedFuture
|
||||
'failure_traceback', # returns a traceback instance from an IFailedFuture
|
||||
'failure_format_traceback', # a string, the formatted traceback
|
||||
|
||||
'make_batched_timer', # create BatchedTimer/IBatchedTimer instances
|
||||
|
||||
'make_logger', # creates an object implementing ILogger
|
||||
'start_logging', # initializes logging (may grab stdin at this point)
|
||||
'set_global_log_level', # Set the global log level
|
||||
'get_global_log_level', # Get the global log level
|
||||
'add_log_categories',
|
||||
|
||||
'IFailedFuture', # describes API for arg to errback()s
|
||||
'ILogger', # API for logging
|
||||
|
||||
'sleep', # little helper for inline sleeping
|
||||
'time_ns', # helper: current time (UTC) in ns
|
||||
'perf_counter_ns', # helper: current performance counter in ns
|
||||
)
|
||||
|
||||
|
||||
_explicit_framework = None
|
||||
|
||||
|
||||
def use_twisted():
|
||||
global _explicit_framework
|
||||
if _explicit_framework is not None and _explicit_framework != 'twisted':
|
||||
raise RuntimeError("Explicitly using '{}' already".format(_explicit_framework))
|
||||
_explicit_framework = 'twisted'
|
||||
from txaio import tx
|
||||
_use_framework(tx)
|
||||
import txaio
|
||||
txaio.using_twisted = True
|
||||
txaio.using_asyncio = False
|
||||
|
||||
|
||||
def use_asyncio():
|
||||
global _explicit_framework
|
||||
if _explicit_framework is not None and _explicit_framework != 'asyncio':
|
||||
raise RuntimeError("Explicitly using '{}' already".format(_explicit_framework))
|
||||
_explicit_framework = 'asyncio'
|
||||
from txaio import aio
|
||||
_use_framework(aio)
|
||||
import txaio
|
||||
txaio.using_twisted = False
|
||||
txaio.using_asyncio = True
|
||||
|
||||
|
||||
def _use_framework(module):
|
||||
"""
|
||||
Internal helper, to set this modules methods to a specified
|
||||
framework helper-methods.
|
||||
"""
|
||||
import txaio
|
||||
for method_name in __all__:
|
||||
if method_name in ['use_twisted', 'use_asyncio']:
|
||||
continue
|
||||
setattr(txaio, method_name, getattr(module, method_name))
|
||||
|
||||
|
||||
# use the "un-framework", which is neither asyncio nor twisted and
|
||||
# just throws an exception -- this forces you to call .use_twisted()
|
||||
# or .use_asyncio() to use the library.
|
||||
from txaio import _unframework # noqa
|
||||
_use_framework(_unframework)
|
||||
154
.venv/lib/python3.12/site-packages/txaio/_common.py
Normal file
154
.venv/lib/python3.12/site-packages/txaio/_common.py
Normal file
@@ -0,0 +1,154 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import math
|
||||
from txaio.interfaces import IBatchedTimer
|
||||
|
||||
|
||||
class _BatchedCall(object):
|
||||
"""
|
||||
Wraps IDelayedCall-implementing objects, implementing only the API
|
||||
which txaio promised in the first place: .cancel
|
||||
|
||||
Do not create these yourself; use _BatchedTimer.call_later()
|
||||
"""
|
||||
|
||||
def __init__(self, timer, index, the_call):
|
||||
# XXX WeakRef?
|
||||
self._timer = timer
|
||||
self._index = index
|
||||
self._call = the_call
|
||||
|
||||
def cancel(self):
|
||||
self._timer._remove_call(self._index, self)
|
||||
self._timer = None
|
||||
|
||||
def __call__(self):
|
||||
return self._call()
|
||||
|
||||
|
||||
class _BatchedTimer(IBatchedTimer):
|
||||
"""
|
||||
Internal helper.
|
||||
|
||||
Instances of this are returned from
|
||||
:meth:`txaio.make_batched_timer` and that is the only way they
|
||||
should be instantiated. You may depend on methods from the
|
||||
interface class only (:class:`txaio.IBatchedTimer`)
|
||||
|
||||
**NOTE** that the times are in milliseconds in this class!
|
||||
"""
|
||||
|
||||
def __init__(self, bucket_milliseconds, chunk_size,
|
||||
seconds_provider, delayed_call_creator, loop=None):
|
||||
if bucket_milliseconds <= 0.0:
|
||||
raise ValueError(
|
||||
"bucket_milliseconds must be > 0.0"
|
||||
)
|
||||
self._bucket_milliseconds = float(bucket_milliseconds)
|
||||
self._chunk_size = chunk_size
|
||||
self._get_seconds = seconds_provider
|
||||
self._create_delayed_call = delayed_call_creator
|
||||
self._buckets = dict() # real seconds -> (IDelayedCall, list)
|
||||
self._loop = loop
|
||||
|
||||
def call_later(self, delay, func, *args, **kwargs):
|
||||
"""
|
||||
IBatchedTimer API
|
||||
"""
|
||||
# "quantize" the delay to the nearest bucket
|
||||
now = self._get_seconds()
|
||||
real_time = int(now + delay) * 1000
|
||||
real_time -= int(real_time % self._bucket_milliseconds)
|
||||
call = _BatchedCall(self, real_time, lambda: func(*args, **kwargs))
|
||||
try:
|
||||
self._buckets[real_time][1].append(call)
|
||||
except KeyError:
|
||||
# new bucket; need to add "actual" underlying IDelayedCall
|
||||
diff = (real_time / 1000.0) - now
|
||||
# we need to clamp this because if we quantized to the
|
||||
# nearest second, but that second is actually (slightly)
|
||||
# less than the current time 'diff' will be negative.
|
||||
delayed_call = self._create_delayed_call(
|
||||
max(0.0, diff),
|
||||
self._notify_bucket, real_time,
|
||||
)
|
||||
self._buckets[real_time] = (delayed_call, [call])
|
||||
return call
|
||||
|
||||
def _notify_bucket(self, real_time):
|
||||
"""
|
||||
Internal helper. This 'does' the callbacks in a particular bucket.
|
||||
|
||||
:param real_time: the bucket to do callbacks on
|
||||
"""
|
||||
(delayed_call, calls) = self._buckets[real_time]
|
||||
del self._buckets[real_time]
|
||||
errors = []
|
||||
|
||||
def notify_one_chunk(calls, chunk_size, chunk_delay_ms):
|
||||
for call in calls[:chunk_size]:
|
||||
try:
|
||||
call()
|
||||
except Exception as e:
|
||||
errors.append(e)
|
||||
calls = calls[chunk_size:]
|
||||
if calls:
|
||||
self._create_delayed_call(
|
||||
chunk_delay_ms / 1000.0,
|
||||
notify_one_chunk, calls, chunk_size, chunk_delay_ms,
|
||||
)
|
||||
else:
|
||||
# done all calls; make sure there were no errors
|
||||
if len(errors):
|
||||
msg = "Error(s) processing call_later bucket:\n"
|
||||
for e in errors:
|
||||
msg += "{}\n".format(e)
|
||||
raise RuntimeError(msg)
|
||||
# ceil()ing because we want the number of chunks, and a
|
||||
# partial chunk is still a chunk
|
||||
delay_ms = self._bucket_milliseconds / math.ceil(float(len(calls)) / self._chunk_size)
|
||||
# I can't imagine any scenario in which chunk_delay_ms is
|
||||
# actually less than zero, but just being safe here
|
||||
notify_one_chunk(calls, self._chunk_size, max(0.0, delay_ms))
|
||||
|
||||
def _remove_call(self, real_time, call):
|
||||
"""
|
||||
Internal helper. Removes a (possibly still pending) call from a
|
||||
bucket. It is *not* an error of the bucket is gone (e.g. the
|
||||
call has already happened).
|
||||
"""
|
||||
try:
|
||||
(delayed_call, calls) = self._buckets[real_time]
|
||||
except KeyError:
|
||||
# no such bucket ... error? swallow?
|
||||
return
|
||||
# remove call; if we're empty, cancel underlying
|
||||
# bucket-timeout IDelayedCall
|
||||
calls.remove(call)
|
||||
if not calls:
|
||||
del self._buckets[real_time]
|
||||
delayed_call.cancel()
|
||||
65
.venv/lib/python3.12/site-packages/txaio/_iotype.py
Normal file
65
.venv/lib/python3.12/site-packages/txaio/_iotype.py
Normal file
@@ -0,0 +1,65 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
|
||||
def guess_stream_needs_encoding(fileobj, default=True):
|
||||
"""
|
||||
Guess the type (bytes/unicode) of this stream, and return whether or not it
|
||||
requires text to be encoded before written into it.
|
||||
"""
|
||||
# XXX: Unicode
|
||||
# On Python 2, stdout is bytes. However, we can't wrap it in a
|
||||
# TextIOWrapper, as it's not from IOBase, so it doesn't have .seekable.
|
||||
# It does, however, have a mode, and we can cheese it base on that.
|
||||
# On Python 3, stdout is a TextIOWrapper, and so we can safely write
|
||||
# str to it, and it will encode it correctly for the target terminal or
|
||||
# whatever.
|
||||
# If it's a io.BytesIO or StringIO, then it won't have a mode, but it
|
||||
# is a read/write stream, so we can get its type by reading 0 bytes and
|
||||
# checking the type.
|
||||
try:
|
||||
# If it's a r/w stream, this will give us the type of it
|
||||
t = type(fileobj.read(0))
|
||||
|
||||
if t is bytes:
|
||||
return True
|
||||
elif t is str:
|
||||
return False
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
mode = fileobj.mode
|
||||
|
||||
if "b" in mode:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return default
|
||||
82
.venv/lib/python3.12/site-packages/txaio/_unframework.py
Normal file
82
.venv/lib/python3.12/site-packages/txaio/_unframework.py
Normal file
@@ -0,0 +1,82 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
This provides a version of the API which only throws exceptions;
|
||||
this is the default so that you have to pick asyncio or twisted
|
||||
explicitly by calling .use_twisted() or .use_asyncio()
|
||||
"""
|
||||
|
||||
from txaio import _Config
|
||||
|
||||
using_twisted = False
|
||||
using_asyncio = False
|
||||
config = _Config()
|
||||
|
||||
|
||||
def _throw_usage_error(*args, **kw):
|
||||
raise RuntimeError(
|
||||
"To use txaio, you must first select a framework "
|
||||
"with .use_twisted() or .use_asyncio()"
|
||||
)
|
||||
|
||||
|
||||
# all the txaio API methods just raise the error
|
||||
with_config = _throw_usage_error
|
||||
create_future = _throw_usage_error
|
||||
create_future_success = _throw_usage_error
|
||||
create_future_error = _throw_usage_error
|
||||
create_failure = _throw_usage_error
|
||||
as_future = _throw_usage_error
|
||||
is_future = _throw_usage_error
|
||||
reject = _throw_usage_error
|
||||
cancel = _throw_usage_error
|
||||
resolve = _throw_usage_error
|
||||
add_callbacks = _throw_usage_error
|
||||
gather = _throw_usage_error
|
||||
is_called = _throw_usage_error
|
||||
|
||||
call_later = _throw_usage_error
|
||||
|
||||
failure_message = _throw_usage_error
|
||||
failure_traceback = _throw_usage_error
|
||||
failure_format_traceback = _throw_usage_error
|
||||
|
||||
make_batched_timer = _throw_usage_error
|
||||
|
||||
make_logger = _throw_usage_error
|
||||
start_logging = _throw_usage_error
|
||||
set_global_log_level = _throw_usage_error
|
||||
get_global_log_level = _throw_usage_error
|
||||
|
||||
add_log_categories = _throw_usage_error
|
||||
|
||||
IFailedFuture = _throw_usage_error
|
||||
ILogger = _throw_usage_error
|
||||
|
||||
sleep = _throw_usage_error
|
||||
time_ns = _throw_usage_error
|
||||
perf_counter_ns = _throw_usage_error
|
||||
48
.venv/lib/python3.12/site-packages/txaio/_util.py
Normal file
48
.venv/lib/python3.12/site-packages/txaio/_util.py
Normal file
@@ -0,0 +1,48 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
if sys.version_info >= (3, 7):
|
||||
|
||||
time_ns = time.time_ns
|
||||
perf_counter_ns = time.perf_counter_ns
|
||||
|
||||
else:
|
||||
|
||||
def time_ns():
|
||||
"""
|
||||
Shim for standard library time.time_ns for Python < 3.7.
|
||||
"""
|
||||
return int(time.time() * 1000000000.)
|
||||
|
||||
def perf_counter_ns():
|
||||
"""
|
||||
Shim for standard library time.perf_counter for Python < 3.7.
|
||||
"""
|
||||
return int(time.perf_counter() * 1000000000.)
|
||||
27
.venv/lib/python3.12/site-packages/txaio/_version.py
Normal file
27
.venv/lib/python3.12/site-packages/txaio/_version.py
Normal file
@@ -0,0 +1,27 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
__version__ = '23.1.1'
|
||||
548
.venv/lib/python3.12/site-packages/txaio/aio.py
Normal file
548
.venv/lib/python3.12/site-packages/txaio/aio.py
Normal file
@@ -0,0 +1,548 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import asyncio
|
||||
from asyncio import iscoroutine
|
||||
from asyncio import Future
|
||||
try:
|
||||
from types import AsyncGeneratorType
|
||||
except ImportError:
|
||||
class AsyncGeneratorType:
|
||||
pass
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import weakref
|
||||
import functools
|
||||
import traceback
|
||||
import logging
|
||||
import inspect
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from txaio.interfaces import IFailedFuture, ILogger, log_levels
|
||||
from txaio._iotype import guess_stream_needs_encoding
|
||||
from txaio._common import _BatchedTimer
|
||||
from txaio import _util
|
||||
from txaio import _Config
|
||||
|
||||
|
||||
config = _Config()
|
||||
|
||||
|
||||
def with_config(loop=None):
|
||||
"""
|
||||
:return: an instance of the txaio API with the given
|
||||
configuration. This won't affect anything using the 'gloabl'
|
||||
config nor other instances created using this function.
|
||||
|
||||
If you need to customize txaio configuration separately (e.g. to
|
||||
use multiple event-loops in asyncio), you can take code like this:
|
||||
|
||||
import txaio
|
||||
|
||||
|
||||
class FunTimes(object):
|
||||
|
||||
def something_async(self):
|
||||
return txaio.call_later(1, lambda: 'some result')
|
||||
|
||||
and instead do this:
|
||||
|
||||
import txaio
|
||||
|
||||
|
||||
class FunTimes(object):
|
||||
txaio = txaio
|
||||
|
||||
def something_async(self):
|
||||
# this will run in the local/new event loop created in the constructor
|
||||
return self.txaio.call_later(1, lambda: 'some result')
|
||||
|
||||
fun0 = FunTimes()
|
||||
fun1 = FunTimes()
|
||||
fun1.txaio = txaio.with_config(loop=asyncio.new_event_loop())
|
||||
|
||||
So `fun1` will run its futures on the newly-created event loop,
|
||||
while `fun0` will work just as it did before this `with_config`
|
||||
method was introduced (after 2.6.2).
|
||||
"""
|
||||
cfg = _Config()
|
||||
if loop is not None:
|
||||
cfg.loop = loop
|
||||
return _AsyncioApi(cfg)
|
||||
|
||||
|
||||
# logging should probably all be folded into _AsyncioApi as well
|
||||
_stderr, _stdout = sys.stderr, sys.stdout
|
||||
_loggers = weakref.WeakSet() # weak-ref's of each logger we've created before start_logging()
|
||||
_log_level = 'info' # re-set by start_logging
|
||||
_started_logging = False
|
||||
_categories = {}
|
||||
|
||||
|
||||
def add_log_categories(categories):
|
||||
_categories.update(categories)
|
||||
|
||||
|
||||
class FailedFuture(IFailedFuture):
|
||||
"""
|
||||
This provides an object with any features from Twisted's Failure
|
||||
that we might need in Autobahn classes that use FutureMixin.
|
||||
|
||||
We need to encapsulate information from exceptions so that
|
||||
errbacks still have access to the traceback (in case they want to
|
||||
print it out) outside of "except" blocks.
|
||||
"""
|
||||
|
||||
def __init__(self, type_, value, traceback):
|
||||
"""
|
||||
These are the same parameters as returned from ``sys.exc_info()``
|
||||
|
||||
:param type_: exception type
|
||||
:param value: the Exception instance
|
||||
:param traceback: a traceback object
|
||||
"""
|
||||
self._type = type_
|
||||
self._value = value
|
||||
self._traceback = traceback
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
|
||||
# logging API methods
|
||||
|
||||
|
||||
def _log(logger, level, format=u'', **kwargs):
|
||||
|
||||
# Look for a log_category, switch it in if we have it
|
||||
if "log_category" in kwargs and kwargs["log_category"] in _categories:
|
||||
format = _categories.get(kwargs["log_category"])
|
||||
|
||||
kwargs['log_time'] = time.time()
|
||||
kwargs['log_level'] = level
|
||||
kwargs['log_format'] = format
|
||||
# NOTE: turning kwargs into a single "argument which
|
||||
# is a dict" on purpose, since a LogRecord only keeps
|
||||
# args, not kwargs.
|
||||
if level == 'trace':
|
||||
level = 'debug'
|
||||
kwargs['txaio_trace'] = True
|
||||
|
||||
msg = format.format(**kwargs)
|
||||
getattr(logger._logger, level)(msg)
|
||||
|
||||
|
||||
def _no_op(*args, **kw):
|
||||
pass
|
||||
|
||||
|
||||
class _TxaioLogWrapper(ILogger):
|
||||
def __init__(self, logger):
|
||||
self._logger = logger
|
||||
self._set_log_level(_log_level)
|
||||
|
||||
def emit(self, level, *args, **kwargs):
|
||||
func = getattr(self, level)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
def _set_log_level(self, level):
|
||||
target_level = log_levels.index(level)
|
||||
# this binds either _log or _no_op above to this instance,
|
||||
# depending on the desired level.
|
||||
for (idx, name) in enumerate(log_levels):
|
||||
if idx <= target_level:
|
||||
log_method = functools.partial(_log, self, name)
|
||||
else:
|
||||
log_method = _no_op
|
||||
setattr(self, name, log_method)
|
||||
self._log_level = level
|
||||
|
||||
|
||||
class _TxaioFileHandler(logging.Handler, object):
|
||||
def __init__(self, fileobj, **kw):
|
||||
super(_TxaioFileHandler, self).__init__(**kw)
|
||||
self._file = fileobj
|
||||
self._encode = guess_stream_needs_encoding(fileobj)
|
||||
|
||||
def emit(self, record):
|
||||
if isinstance(record.args, dict):
|
||||
fmt = record.args.get(
|
||||
'log_format',
|
||||
record.args.get('log_message', '')
|
||||
)
|
||||
message = fmt.format(**record.args)
|
||||
dt = datetime.fromtimestamp(record.args.get('log_time', 0))
|
||||
else:
|
||||
message = record.getMessage()
|
||||
if record.levelno == logging.ERROR and record.exc_info:
|
||||
message += '\n'
|
||||
for line in traceback.format_exception(*record.exc_info):
|
||||
message = message + line
|
||||
dt = datetime.fromtimestamp(record.created)
|
||||
msg = '{0} {1}{2}'.format(
|
||||
dt.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||
message,
|
||||
os.linesep
|
||||
)
|
||||
if self._encode:
|
||||
msg = msg.encode('utf8')
|
||||
self._file.write(msg)
|
||||
|
||||
|
||||
def make_logger():
|
||||
# we want the namespace to be the calling context of "make_logger"
|
||||
# otherwise the root logger will be returned
|
||||
cf = inspect.currentframe().f_back
|
||||
if "self" in cf.f_locals:
|
||||
# We're probably in a class init or method
|
||||
cls = cf.f_locals["self"].__class__
|
||||
namespace = '{0}.{1}'.format(cls.__module__, cls.__name__)
|
||||
else:
|
||||
namespace = cf.f_globals["__name__"]
|
||||
if cf.f_code.co_name != "<module>":
|
||||
# If it's not the module, and not in a class instance, add the code
|
||||
# object's name.
|
||||
namespace = namespace + "." + cf.f_code.co_name
|
||||
|
||||
logger = _TxaioLogWrapper(logging.getLogger(name=namespace))
|
||||
# remember this so we can set their levels properly once
|
||||
# start_logging is actually called
|
||||
_loggers.add(logger)
|
||||
return logger
|
||||
|
||||
|
||||
def start_logging(out=_stdout, level='info'):
|
||||
"""
|
||||
Begin logging.
|
||||
|
||||
:param out: if provided, a file-like object to log to. By default, this is
|
||||
stdout.
|
||||
:param level: the maximum log-level to emit (a string)
|
||||
"""
|
||||
global _log_level, _loggers, _started_logging
|
||||
if level not in log_levels:
|
||||
raise RuntimeError(
|
||||
"Invalid log level '{0}'; valid are: {1}".format(
|
||||
level, ', '.join(log_levels)
|
||||
)
|
||||
)
|
||||
|
||||
if _started_logging:
|
||||
return
|
||||
|
||||
_started_logging = True
|
||||
_log_level = level
|
||||
|
||||
handler = _TxaioFileHandler(out)
|
||||
logging.getLogger().addHandler(handler)
|
||||
# note: Don't need to call basicConfig() or similar, because we've
|
||||
# now added at least one handler to the root logger
|
||||
logging.raiseExceptions = True # FIXME
|
||||
level_to_stdlib = {
|
||||
'critical': logging.CRITICAL,
|
||||
'error': logging.ERROR,
|
||||
'warn': logging.WARNING,
|
||||
'info': logging.INFO,
|
||||
'debug': logging.DEBUG,
|
||||
'trace': logging.DEBUG,
|
||||
}
|
||||
logging.getLogger().setLevel(level_to_stdlib[level])
|
||||
# make sure any loggers we created before now have their log-level
|
||||
# set (any created after now will get it from _log_level
|
||||
for logger in _loggers:
|
||||
logger._set_log_level(level)
|
||||
|
||||
|
||||
def set_global_log_level(level):
|
||||
"""
|
||||
Set the global log level on all loggers instantiated by txaio.
|
||||
"""
|
||||
for logger in _loggers:
|
||||
logger._set_log_level(level)
|
||||
global _log_level
|
||||
_log_level = level
|
||||
|
||||
|
||||
def get_global_log_level():
|
||||
return _log_level
|
||||
|
||||
|
||||
# asyncio API methods; the module-level functions are (now, for
|
||||
# backwards-compat) exported from a default instance of this class
|
||||
|
||||
|
||||
_unspecified = object()
|
||||
|
||||
|
||||
class _AsyncioApi(object):
|
||||
using_twisted = False
|
||||
using_asyncio = True
|
||||
|
||||
def __init__(self, config):
|
||||
self._config = config
|
||||
|
||||
@property
|
||||
def _loop(self):
|
||||
# if configured explicetly, then use this loop
|
||||
if self._config.loop:
|
||||
return self._config.loop
|
||||
|
||||
# otherwise give out the event loop of the thread this is called in
|
||||
# rather fetching the loop once in __init__, which may not neccessarily
|
||||
# be called from the thread we now run the event loop in.
|
||||
return asyncio.get_event_loop()
|
||||
|
||||
def failure_message(self, fail):
|
||||
"""
|
||||
:param fail: must be an IFailedFuture
|
||||
returns a unicode error-message
|
||||
"""
|
||||
try:
|
||||
return '{0}: {1}'.format(
|
||||
fail._value.__class__.__name__,
|
||||
str(fail._value),
|
||||
)
|
||||
except Exception:
|
||||
return 'Failed to produce failure message for "{0}"'.format(fail)
|
||||
|
||||
def failure_traceback(self, fail):
|
||||
"""
|
||||
:param fail: must be an IFailedFuture
|
||||
returns a traceback instance
|
||||
"""
|
||||
return fail._traceback
|
||||
|
||||
def failure_format_traceback(self, fail):
|
||||
"""
|
||||
:param fail: must be an IFailedFuture
|
||||
returns a string
|
||||
"""
|
||||
try:
|
||||
f = io.StringIO()
|
||||
traceback.print_exception(
|
||||
fail._type,
|
||||
fail.value,
|
||||
fail._traceback,
|
||||
file=f,
|
||||
)
|
||||
return f.getvalue()
|
||||
except Exception:
|
||||
return "Failed to format failure traceback for '{0}'".format(fail)
|
||||
|
||||
def create_future(self, result=_unspecified, error=_unspecified, canceller=_unspecified):
|
||||
if result is not _unspecified and error is not _unspecified:
|
||||
raise ValueError("Cannot have both result and error.")
|
||||
|
||||
f = self._loop.create_future()
|
||||
if result is not _unspecified:
|
||||
resolve(f, result)
|
||||
elif error is not _unspecified:
|
||||
reject(f, error)
|
||||
|
||||
# Twisted's only API for cancelling is to pass a
|
||||
# single-argument callable to the Deferred constructor, so
|
||||
# txaio apes that here for asyncio. The argument is the Future
|
||||
# that has been cancelled.
|
||||
if canceller is not _unspecified:
|
||||
def done(f):
|
||||
try:
|
||||
f.exception()
|
||||
except asyncio.CancelledError:
|
||||
canceller(f)
|
||||
f.add_done_callback(done)
|
||||
|
||||
return f
|
||||
|
||||
def create_future_success(self, result):
|
||||
return self.create_future(result=result)
|
||||
|
||||
def create_future_error(self, error=None):
|
||||
f = self.create_future()
|
||||
reject(f, error)
|
||||
return f
|
||||
|
||||
def as_future(self, fun, *args, **kwargs):
|
||||
try:
|
||||
res = fun(*args, **kwargs)
|
||||
except Exception:
|
||||
return create_future_error(create_failure())
|
||||
else:
|
||||
if isinstance(res, Future):
|
||||
return res
|
||||
elif iscoroutine(res):
|
||||
return self._loop.create_task(res)
|
||||
elif isinstance(res, AsyncGeneratorType):
|
||||
raise RuntimeError(
|
||||
"as_future() received an async generator function; does "
|
||||
"'{}' use 'yield' when you meant 'await'?".format(
|
||||
str(fun)
|
||||
)
|
||||
)
|
||||
else:
|
||||
return create_future_success(res)
|
||||
|
||||
def is_future(self, obj):
|
||||
return iscoroutine(obj) or isinstance(obj, Future)
|
||||
|
||||
def call_later(self, delay, fun, *args, **kwargs):
|
||||
# loop.call_later doesn't support kwargs
|
||||
real_call = functools.partial(fun, *args, **kwargs)
|
||||
return self._loop.call_later(delay, real_call)
|
||||
|
||||
def make_batched_timer(self, bucket_seconds, chunk_size=100):
|
||||
"""
|
||||
Creates and returns an object implementing
|
||||
:class:`txaio.IBatchedTimer`.
|
||||
|
||||
:param bucket_seconds: the number of seconds in each bucket. That
|
||||
is, a value of 5 means that any timeout within a 5 second
|
||||
window will be in the same bucket, and get notified at the
|
||||
same time. This is only accurate to "milliseconds".
|
||||
|
||||
:param chunk_size: when "doing" the callbacks in a particular
|
||||
bucket, this controls how many we do at once before yielding to
|
||||
the reactor.
|
||||
"""
|
||||
|
||||
def get_seconds():
|
||||
return self._loop.time()
|
||||
|
||||
return _BatchedTimer(
|
||||
bucket_seconds * 1000.0, chunk_size,
|
||||
seconds_provider=get_seconds,
|
||||
delayed_call_creator=self.call_later,
|
||||
)
|
||||
|
||||
def is_called(self, future):
|
||||
return future.done()
|
||||
|
||||
def resolve(self, future, result=None):
|
||||
future.set_result(result)
|
||||
|
||||
def reject(self, future, error=None):
|
||||
if error is None:
|
||||
error = create_failure() # will be error if we're not in an "except"
|
||||
elif isinstance(error, Exception):
|
||||
error = FailedFuture(type(error), error, None)
|
||||
else:
|
||||
if not isinstance(error, IFailedFuture):
|
||||
raise RuntimeError("reject requires an IFailedFuture or Exception")
|
||||
future.set_exception(error.value)
|
||||
|
||||
def cancel(self, future, msg=None):
|
||||
if sys.version_info >= (3, 9):
|
||||
future.cancel(msg)
|
||||
else:
|
||||
future.cancel()
|
||||
|
||||
def create_failure(self, exception=None):
|
||||
"""
|
||||
This returns an object implementing IFailedFuture.
|
||||
|
||||
If exception is None (the default) we MUST be called within an
|
||||
"except" block (such that sys.exc_info() returns useful
|
||||
information).
|
||||
"""
|
||||
if exception:
|
||||
return FailedFuture(type(exception), exception, None)
|
||||
return FailedFuture(*sys.exc_info())
|
||||
|
||||
def add_callbacks(self, future, callback, errback):
|
||||
"""
|
||||
callback or errback may be None, but at least one must be
|
||||
non-None.
|
||||
"""
|
||||
def done(f):
|
||||
try:
|
||||
res = f.result()
|
||||
if callback:
|
||||
callback(res)
|
||||
except (Exception, asyncio.CancelledError):
|
||||
if errback:
|
||||
errback(create_failure())
|
||||
return future.add_done_callback(done)
|
||||
|
||||
def gather(self, futures, consume_exceptions=True):
|
||||
"""
|
||||
This returns a Future that waits for all the Futures in the list
|
||||
``futures``
|
||||
|
||||
:param futures: a list of Futures (or coroutines?)
|
||||
|
||||
:param consume_exceptions: if True, any errors are eaten and
|
||||
returned in the result list.
|
||||
"""
|
||||
|
||||
# from the asyncio docs: "If return_exceptions is True, exceptions
|
||||
# in the tasks are treated the same as successful results, and
|
||||
# gathered in the result list; otherwise, the first raised
|
||||
# exception will be immediately propagated to the returned
|
||||
# future."
|
||||
return asyncio.gather(*futures, return_exceptions=consume_exceptions)
|
||||
|
||||
def sleep(self, delay):
|
||||
"""
|
||||
Inline sleep for use in co-routines.
|
||||
|
||||
:param delay: Time to sleep in seconds.
|
||||
:type delay: float
|
||||
"""
|
||||
return asyncio.ensure_future(asyncio.sleep(delay))
|
||||
|
||||
|
||||
_default_api = _AsyncioApi(config)
|
||||
|
||||
|
||||
using_twisted = _default_api.using_twisted
|
||||
using_asyncio = _default_api.using_asyncio
|
||||
sleep = _default_api.sleep
|
||||
failure_message = _default_api.failure_message
|
||||
failure_traceback = _default_api.failure_traceback
|
||||
failure_format_traceback = _default_api.failure_format_traceback
|
||||
create_future = _default_api.create_future
|
||||
create_future_success = _default_api.create_future_success
|
||||
create_future_error = _default_api.create_future_error
|
||||
as_future = _default_api.as_future
|
||||
is_future = _default_api.is_future
|
||||
call_later = _default_api.call_later
|
||||
make_batched_timer = _default_api.make_batched_timer
|
||||
is_called = _default_api.is_called
|
||||
resolve = _default_api.resolve
|
||||
reject = _default_api.reject
|
||||
cancel = _default_api.cancel
|
||||
create_failure = _default_api.create_failure
|
||||
add_callbacks = _default_api.add_callbacks
|
||||
gather = _default_api.gather
|
||||
sleep = _default_api.sleep
|
||||
time_ns = _util.time_ns
|
||||
perf_counter_ns = _util.perf_counter_ns
|
||||
177
.venv/lib/python3.12/site-packages/txaio/interfaces.py
Normal file
177
.venv/lib/python3.12/site-packages/txaio/interfaces.py
Normal file
@@ -0,0 +1,177 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import abc
|
||||
|
||||
#: all the log-levels that txaio recognizes
|
||||
log_levels = [
|
||||
'none',
|
||||
'critical',
|
||||
'error',
|
||||
'warn',
|
||||
'info',
|
||||
'debug',
|
||||
'trace',
|
||||
]
|
||||
|
||||
|
||||
class IBatchedTimer(abc.ABC):
|
||||
"""
|
||||
Objects created by :met:`txaio.make_batched_timer` implement this
|
||||
interface.
|
||||
|
||||
These APIs allow you to put call_later()'s into "buckets",
|
||||
reducing the number of actual underlying delayed calls that the
|
||||
event-loop (asyncio or Twisted) needs to deal with. Obviously, you
|
||||
lose some amount of precision in when the timers fire in exchange
|
||||
for less memory use, and fewer objects on the queues for the
|
||||
underlying event-loop/reactor.
|
||||
|
||||
As a concrete example, in Autobahn we're using this to batch
|
||||
together timers for the "auto ping" feature. In this case, it is
|
||||
not vital when precisely the timers fire, but as the
|
||||
connection-count increases the number of outstanding timers
|
||||
becomes quite large.
|
||||
|
||||
It is intended to be used like so:
|
||||
|
||||
class Something(object):
|
||||
timers = txaio.make_batched_timer()
|
||||
|
||||
def a_method(self):
|
||||
self.timers.call_later() # drop-in API from txaio.call_later
|
||||
"""
|
||||
|
||||
def call_later(self, delay, func, *args, **kw):
|
||||
"""
|
||||
This speaks the same API as :meth:`txaio.call_later` and also
|
||||
returns an object that has a ``.cancel`` method.
|
||||
|
||||
You cannot rely on any other methods/attributes of the
|
||||
returned object. The timeout will actually fire at an
|
||||
aribtrary time "close" to the delay specified, depening upon
|
||||
the arguments this IBatchedTimer was created with.
|
||||
"""
|
||||
|
||||
|
||||
class ILogger(abc.ABC):
|
||||
"""
|
||||
This defines the methods you can call on the object returned from
|
||||
:meth:`txaio.make_logger` -- although the actual object may have
|
||||
additional methods, you should *only* call the methods listed
|
||||
here.
|
||||
|
||||
All the log methods have the same signature, they just differ in
|
||||
what "log level" they represent to the handlers/emitters. The
|
||||
``message`` argument is a format string using `PEP3101
|
||||
<https://www.python.org/dev/peps/pep-3101/>`_-style references to
|
||||
things from the ``kwargs``. Note that there are also the following
|
||||
keys added to the ``kwargs``: ``log_time`` and ``log_level``.
|
||||
|
||||
For example::
|
||||
|
||||
class MyThing(object):
|
||||
log = txaio.make_logger()
|
||||
|
||||
def something_interesting(self, things=dict(one=1, two=2)):
|
||||
try:
|
||||
self.log.debug("Called with {things[one]}", things=things)
|
||||
result = self._method_call()
|
||||
self.log.info("Got '{result}'.", result=result)
|
||||
except Exception:
|
||||
fail = txaio.create_failure()
|
||||
self.log.critical(txaio.failure_format_traceback(fail))
|
||||
|
||||
The philsophy behind txaio's interface is fairly similar to
|
||||
Twisted's logging APIs after version 15. See `Twisted's
|
||||
documentation
|
||||
<http://twistedmatrix.com/documents/current/core/howto/logger.html>`_
|
||||
for details.
|
||||
"""
|
||||
|
||||
# stdlib notes:
|
||||
# levels:
|
||||
# CRITICAL 50
|
||||
# ERROR 40
|
||||
# WARNING 30
|
||||
# INFO 20
|
||||
# DEBUG 10
|
||||
# NOTSET 0
|
||||
|
||||
|
||||
# NOTES
|
||||
# things in Twisted's event:
|
||||
# - log_level
|
||||
# - log_failure (sometimes?)
|
||||
# - log_format (can be None)
|
||||
# - log_source (sometimes? no, always, but sometimes None)
|
||||
# - log_namespace
|
||||
#
|
||||
# .warn not warning!
|
||||
|
||||
def critical(self, message, **kwargs):
|
||||
"log a critical-level message"
|
||||
|
||||
def error(self, message, **kwargs):
|
||||
"log a error-level message"
|
||||
|
||||
def warn(self, message, **kwargs):
|
||||
"log a error-level message"
|
||||
|
||||
def info(self, message, **kwargs):
|
||||
"log an info-level message"
|
||||
|
||||
def debug(self, message, **kwargs):
|
||||
"log an debug-level message"
|
||||
|
||||
def trace(self, message, **kwargs):
|
||||
"log a trace-level message"
|
||||
|
||||
|
||||
class IFailedFuture(abc.ABC):
|
||||
"""
|
||||
This defines the interface for a common object encapsulating a
|
||||
failure from either an asyncio task/coroutine or a Twisted
|
||||
Deferred.
|
||||
|
||||
An instance implementing this interface is given to any
|
||||
``errback`` callables you provide via :meth:`txaio.add_callbacks`
|
||||
|
||||
In your errback you can extract information from an IFailedFuture
|
||||
with :meth:`txaio.failure_message` and
|
||||
:meth:`txaio.failure_traceback` or use ``.value`` to get the
|
||||
Exception instance.
|
||||
|
||||
Depending on other details or methods will probably cause
|
||||
incompatibilities between asyncio and Twisted.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def value(self):
|
||||
"""
|
||||
An actual Exception instance. Same as the second item returned from
|
||||
``sys.exc_info()``
|
||||
"""
|
||||
56
.venv/lib/python3.12/site-packages/txaio/testutil.py
Normal file
56
.venv/lib/python3.12/site-packages/txaio/testutil.py
Normal file
@@ -0,0 +1,56 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import txaio
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
@contextmanager
|
||||
def replace_loop(new_loop):
|
||||
"""
|
||||
This is a context-manager that sets the txaio event-loop to the
|
||||
one supplied temporarily. It's up to you to ensure you pass an
|
||||
event_loop or a reactor instance depending upon asyncio/Twisted.
|
||||
|
||||
Use like so:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from twisted.internet import task
|
||||
with replace_loop(task.Clock()) as fake_reactor:
|
||||
f = txaio.call_later(5, foo)
|
||||
fake_reactor.advance(10)
|
||||
# ...etc
|
||||
"""
|
||||
|
||||
# setup
|
||||
orig = txaio.config.loop
|
||||
txaio.config.loop = new_loop
|
||||
|
||||
yield new_loop
|
||||
|
||||
# cleanup
|
||||
txaio.config.loop = orig
|
||||
531
.venv/lib/python3.12/site-packages/txaio/tx.py
Normal file
531
.venv/lib/python3.12/site-packages/txaio/tx.py
Normal file
@@ -0,0 +1,531 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import weakref
|
||||
import inspect
|
||||
|
||||
from functools import partial
|
||||
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.internet.defer import maybeDeferred, Deferred, DeferredList
|
||||
from twisted.internet.defer import succeed, fail
|
||||
from twisted.internet.interfaces import IReactorTime
|
||||
|
||||
from zope.interface import provider
|
||||
|
||||
from txaio.interfaces import IFailedFuture, ILogger, log_levels
|
||||
from txaio._iotype import guess_stream_needs_encoding
|
||||
from txaio import _Config
|
||||
from txaio._common import _BatchedTimer
|
||||
from txaio import _util
|
||||
from twisted.logger import Logger as _Logger, formatEvent, ILogObserver
|
||||
from twisted.logger import globalLogBeginner, formatTime, LogLevel
|
||||
|
||||
from twisted.internet.defer import ensureDeferred
|
||||
from asyncio import iscoroutinefunction
|
||||
|
||||
using_twisted = True
|
||||
using_asyncio = False
|
||||
|
||||
config = _Config()
|
||||
_stderr, _stdout = sys.stderr, sys.stdout
|
||||
|
||||
# some book-keeping variables here. _observer is used as a global by
|
||||
# the "backwards compatible" (Twisted < 15) loggers. The _loggers object
|
||||
# is a weak-ref set; we add Logger instances to this *until* such
|
||||
# time as start_logging is called (with the desired log-level) and
|
||||
# then we call _set_log_level on each instance. After that,
|
||||
# Logger's ctor uses _log_level directly.
|
||||
_observer = None # for Twisted legacy logging support; see below
|
||||
_loggers = weakref.WeakSet() # weak-references of each logger we've created
|
||||
_log_level = 'info' # global log level; possibly changed in start_logging()
|
||||
_started_logging = False
|
||||
|
||||
_categories = {}
|
||||
|
||||
IFailedFuture.register(Failure)
|
||||
ILogger.register(_Logger)
|
||||
|
||||
|
||||
def _no_op(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def add_log_categories(categories):
|
||||
_categories.update(categories)
|
||||
|
||||
|
||||
def with_config(loop=None):
|
||||
global config
|
||||
if loop is not None:
|
||||
if config.loop is not None and config.loop is not loop:
|
||||
raise RuntimeError(
|
||||
"Twisted has only a single, global reactor. You passed in "
|
||||
"a reactor different from the one already configured "
|
||||
"in txaio.config.loop"
|
||||
)
|
||||
return _TxApi(config)
|
||||
|
||||
|
||||
# NOTE: beware that twisted.logger._logger.Logger copies itself via an
|
||||
# overriden __get__ method when used as recommended as a class
|
||||
# descriptor. So, we override __get__ to just return ``self`` which
|
||||
# means ``log_source`` will be wrong, but we don't document that as a
|
||||
# key that you can depend on anyway :/
|
||||
class Logger(object):
|
||||
|
||||
def __init__(self, level=None, logger=None, namespace=None, observer=None):
|
||||
|
||||
assert logger, "Should not be instantiated directly."
|
||||
|
||||
self._logger = logger(observer=observer, namespace=namespace)
|
||||
self._log_level_set_explicitly = False
|
||||
|
||||
if level:
|
||||
self.set_log_level(level)
|
||||
else:
|
||||
self._set_log_level(_log_level)
|
||||
|
||||
_loggers.add(self)
|
||||
|
||||
def __get__(self, oself, type=None):
|
||||
# this causes the Logger to lie about the "source=", but
|
||||
# otherwise we create a new Logger instance every time we do
|
||||
# "self.log.info()" if we use it like:
|
||||
# class Foo:
|
||||
# log = make_logger
|
||||
return self
|
||||
|
||||
def _log(self, level, *args, **kwargs):
|
||||
|
||||
# Look for a log_category, switch it in if we have it
|
||||
if "log_category" in kwargs and kwargs["log_category"] in _categories:
|
||||
args = tuple()
|
||||
kwargs["format"] = _categories.get(kwargs["log_category"])
|
||||
|
||||
self._logger.emit(level, *args, **kwargs)
|
||||
|
||||
def emit(self, level, *args, **kwargs):
|
||||
|
||||
if log_levels.index(self._log_level) < log_levels.index(level):
|
||||
return
|
||||
|
||||
if level == "trace":
|
||||
return self._trace(*args, **kwargs)
|
||||
|
||||
level = LogLevel.lookupByName(level)
|
||||
return self._log(level, *args, **kwargs)
|
||||
|
||||
def set_log_level(self, level, keep=True):
|
||||
"""
|
||||
Set the log level. If keep is True, then it will not change along with
|
||||
global log changes.
|
||||
"""
|
||||
self._set_log_level(level)
|
||||
self._log_level_set_explicitly = keep
|
||||
|
||||
def _set_log_level(self, level):
|
||||
# up to the desired level, we don't do anything, as we're a
|
||||
# "real" Twisted new-logger; for methods *after* the desired
|
||||
# level, we bind to the no_op method
|
||||
desired_index = log_levels.index(level)
|
||||
|
||||
for (idx, name) in enumerate(log_levels):
|
||||
if name == 'none':
|
||||
continue
|
||||
|
||||
if idx > desired_index:
|
||||
current = getattr(self, name, None)
|
||||
if not current == _no_op or current is None:
|
||||
setattr(self, name, _no_op)
|
||||
if name == 'error':
|
||||
setattr(self, 'failure', _no_op)
|
||||
|
||||
else:
|
||||
if getattr(self, name, None) in (_no_op, None):
|
||||
|
||||
if name == 'trace':
|
||||
setattr(self, "trace", self._trace)
|
||||
else:
|
||||
setattr(self, name,
|
||||
partial(self._log, LogLevel.lookupByName(name)))
|
||||
|
||||
if name == 'error':
|
||||
setattr(self, "failure", self._failure)
|
||||
|
||||
self._log_level = level
|
||||
|
||||
def _failure(self, format=None, *args, **kw):
|
||||
return self._logger.failure(format, *args, **kw)
|
||||
|
||||
def _trace(self, *args, **kw):
|
||||
# there is no "trace" level in Twisted -- but this whole
|
||||
# method will be no-op'd unless we are at the 'trace' level.
|
||||
self.debug(*args, txaio_trace=True, **kw)
|
||||
|
||||
|
||||
def make_logger(level=None, logger=_Logger, observer=None):
|
||||
# we want the namespace to be the calling context of "make_logger"
|
||||
# -- so we *have* to pass namespace kwarg to Logger (or else it
|
||||
# will always say the context is "make_logger")
|
||||
cf = inspect.currentframe().f_back
|
||||
if "self" in cf.f_locals:
|
||||
# We're probably in a class init or method
|
||||
cls = cf.f_locals["self"].__class__
|
||||
namespace = '{0}.{1}'.format(cls.__module__, cls.__name__)
|
||||
else:
|
||||
namespace = cf.f_globals["__name__"]
|
||||
if cf.f_code.co_name != "<module>":
|
||||
# If it's not the module, and not in a class instance, add the code
|
||||
# object's name.
|
||||
namespace = namespace + "." + cf.f_code.co_name
|
||||
logger = Logger(level=level, namespace=namespace, logger=logger,
|
||||
observer=observer)
|
||||
return logger
|
||||
|
||||
|
||||
@provider(ILogObserver)
|
||||
class _LogObserver(object):
|
||||
"""
|
||||
Internal helper.
|
||||
|
||||
An observer which formats events to a given file.
|
||||
"""
|
||||
to_tx = {
|
||||
'critical': LogLevel.critical,
|
||||
'error': LogLevel.error,
|
||||
'warn': LogLevel.warn,
|
||||
'info': LogLevel.info,
|
||||
'debug': LogLevel.debug,
|
||||
'trace': LogLevel.debug,
|
||||
}
|
||||
|
||||
def __init__(self, out):
|
||||
self._file = out
|
||||
self._encode = guess_stream_needs_encoding(out)
|
||||
|
||||
self._levels = None
|
||||
|
||||
def _acceptable_level(self, level):
|
||||
if self._levels is None:
|
||||
target_level = log_levels.index(_log_level)
|
||||
self._levels = [
|
||||
self.to_tx[lvl]
|
||||
for lvl in log_levels
|
||||
if log_levels.index(lvl) <= target_level and lvl != "none"
|
||||
]
|
||||
return level in self._levels
|
||||
|
||||
def __call__(self, event):
|
||||
# it seems if a twisted.logger.Logger() has .failure() called
|
||||
# on it, the log_format will be None for the traceback after
|
||||
# "Unhandled error in Deferred" -- perhaps this is a Twisted
|
||||
# bug?
|
||||
if event['log_format'] is None:
|
||||
msg = '{0} {1}{2}'.format(
|
||||
formatTime(event["log_time"]),
|
||||
failure_format_traceback(event['log_failure']),
|
||||
os.linesep,
|
||||
)
|
||||
if self._encode:
|
||||
msg = msg.encode('utf8')
|
||||
self._file.write(msg)
|
||||
else:
|
||||
# although Logger will already have filtered out unwanted
|
||||
# levels, bare Logger instances from Twisted code won't have.
|
||||
if 'log_level' in event and self._acceptable_level(event['log_level']):
|
||||
msg = '{0} {1}{2}'.format(
|
||||
formatTime(event["log_time"]),
|
||||
formatEvent(event),
|
||||
os.linesep,
|
||||
)
|
||||
if self._encode:
|
||||
msg = msg.encode('utf8')
|
||||
|
||||
self._file.write(msg)
|
||||
|
||||
|
||||
def start_logging(out=_stdout, level='info'):
|
||||
"""
|
||||
Start logging to the file-like object in ``out``. By default, this
|
||||
is stdout.
|
||||
"""
|
||||
global _loggers, _observer, _log_level, _started_logging
|
||||
|
||||
if level not in log_levels:
|
||||
raise RuntimeError(
|
||||
"Invalid log level '{0}'; valid are: {1}".format(
|
||||
level, ', '.join(log_levels)
|
||||
)
|
||||
)
|
||||
|
||||
if _started_logging:
|
||||
return
|
||||
|
||||
_started_logging = True
|
||||
|
||||
_log_level = level
|
||||
set_global_log_level(_log_level)
|
||||
|
||||
if out:
|
||||
_observer = _LogObserver(out)
|
||||
|
||||
_observers = []
|
||||
if _observer:
|
||||
_observers.append(_observer)
|
||||
globalLogBeginner.beginLoggingTo(_observers)
|
||||
|
||||
|
||||
_unspecified = object()
|
||||
|
||||
|
||||
class _TxApi(object):
|
||||
|
||||
def __init__(self, config):
|
||||
self._config = config
|
||||
|
||||
def failure_message(self, fail):
|
||||
"""
|
||||
:param fail: must be an IFailedFuture
|
||||
returns a unicode error-message
|
||||
"""
|
||||
try:
|
||||
return '{0}: {1}'.format(
|
||||
fail.value.__class__.__name__,
|
||||
fail.getErrorMessage(),
|
||||
)
|
||||
except Exception:
|
||||
return 'Failed to produce failure message for "{0}"'.format(fail)
|
||||
|
||||
def failure_traceback(self, fail):
|
||||
"""
|
||||
:param fail: must be an IFailedFuture
|
||||
returns a traceback instance
|
||||
"""
|
||||
return fail.tb
|
||||
|
||||
def failure_format_traceback(self, fail):
|
||||
"""
|
||||
:param fail: must be an IFailedFuture
|
||||
returns a string
|
||||
"""
|
||||
try:
|
||||
f = io.StringIO()
|
||||
fail.printTraceback(file=f)
|
||||
return f.getvalue()
|
||||
except Exception:
|
||||
return "Failed to format failure traceback for '{0}'".format(fail)
|
||||
|
||||
def create_future(self, result=_unspecified, error=_unspecified, canceller=None):
|
||||
if result is not _unspecified and error is not _unspecified:
|
||||
raise ValueError("Cannot have both result and error.")
|
||||
|
||||
f = Deferred(canceller=canceller)
|
||||
if result is not _unspecified:
|
||||
resolve(f, result)
|
||||
elif error is not _unspecified:
|
||||
reject(f, error)
|
||||
return f
|
||||
|
||||
def create_future_success(self, result):
|
||||
return succeed(result)
|
||||
|
||||
def create_future_error(self, error=None):
|
||||
return fail(create_failure(error))
|
||||
|
||||
def as_future(self, fun, *args, **kwargs):
|
||||
# Twisted doesn't automagically deal with coroutines on Py3
|
||||
if iscoroutinefunction(fun):
|
||||
try:
|
||||
return ensureDeferred(fun(*args, **kwargs))
|
||||
except TypeError as e:
|
||||
return create_future_error(e)
|
||||
return maybeDeferred(fun, *args, **kwargs)
|
||||
|
||||
def is_future(self, obj):
|
||||
return isinstance(obj, Deferred)
|
||||
|
||||
def call_later(self, delay, fun, *args, **kwargs):
|
||||
return IReactorTime(self._get_loop()).callLater(delay, fun, *args, **kwargs)
|
||||
|
||||
def make_batched_timer(self, bucket_seconds, chunk_size=100):
|
||||
"""
|
||||
Creates and returns an object implementing
|
||||
:class:`txaio.IBatchedTimer`.
|
||||
|
||||
:param bucket_seconds: the number of seconds in each bucket. That
|
||||
is, a value of 5 means that any timeout within a 5 second
|
||||
window will be in the same bucket, and get notified at the
|
||||
same time. This is only accurate to "milliseconds".
|
||||
|
||||
:param chunk_size: when "doing" the callbacks in a particular
|
||||
bucket, this controls how many we do at once before yielding to
|
||||
the reactor.
|
||||
"""
|
||||
|
||||
def get_seconds():
|
||||
return self._get_loop().seconds()
|
||||
|
||||
def create_delayed_call(delay, fun, *args, **kwargs):
|
||||
return self._get_loop().callLater(delay, fun, *args, **kwargs)
|
||||
|
||||
return _BatchedTimer(
|
||||
bucket_seconds * 1000.0, chunk_size,
|
||||
seconds_provider=get_seconds,
|
||||
delayed_call_creator=create_delayed_call,
|
||||
)
|
||||
|
||||
def is_called(self, future):
|
||||
return future.called
|
||||
|
||||
def resolve(self, future, result=None):
|
||||
future.callback(result)
|
||||
|
||||
def reject(self, future, error=None):
|
||||
if error is None:
|
||||
error = create_failure()
|
||||
elif isinstance(error, Exception):
|
||||
error = Failure(error)
|
||||
else:
|
||||
if not isinstance(error, Failure):
|
||||
raise RuntimeError("reject requires a Failure or Exception")
|
||||
future.errback(error)
|
||||
|
||||
def cancel(self, future, msg=None):
|
||||
future.cancel()
|
||||
|
||||
def create_failure(self, exception=None):
|
||||
"""
|
||||
Create a Failure instance.
|
||||
|
||||
if ``exception`` is None (the default), we MUST be inside an
|
||||
"except" block. This encapsulates the exception into an object
|
||||
that implements IFailedFuture
|
||||
"""
|
||||
if exception:
|
||||
return Failure(exception)
|
||||
return Failure()
|
||||
|
||||
def add_callbacks(self, future, callback, errback):
|
||||
"""
|
||||
callback or errback may be None, but at least one must be
|
||||
non-None.
|
||||
"""
|
||||
assert future is not None
|
||||
if callback is None:
|
||||
assert errback is not None
|
||||
future.addErrback(errback)
|
||||
else:
|
||||
# Twisted allows errback to be None here
|
||||
future.addCallbacks(callback, errback)
|
||||
return future
|
||||
|
||||
def gather(self, futures, consume_exceptions=True):
|
||||
def completed(res):
|
||||
rtn = []
|
||||
for (ok, value) in res:
|
||||
rtn.append(value)
|
||||
if not ok and not consume_exceptions:
|
||||
value.raiseException()
|
||||
return rtn
|
||||
|
||||
# XXX if consume_exceptions is False in asyncio.gather(), it will
|
||||
# abort on the first raised exception -- should we set
|
||||
# fireOnOneErrback=True (if consume_exceptions=False?) -- but then
|
||||
# we'll have to wrap the errback() to extract the "real" failure
|
||||
# from the FirstError that gets thrown if you set that ...
|
||||
|
||||
dl = DeferredList(list(futures), consumeErrors=consume_exceptions)
|
||||
# we unpack the (ok, value) tuples into just a list of values, so
|
||||
# that the callback() gets the same value in asyncio and Twisted.
|
||||
add_callbacks(dl, completed, None)
|
||||
return dl
|
||||
|
||||
def sleep(self, delay):
|
||||
"""
|
||||
Inline sleep for use in co-routines.
|
||||
|
||||
:param delay: Time to sleep in seconds.
|
||||
:type delay: float
|
||||
"""
|
||||
d = Deferred()
|
||||
self._get_loop().callLater(delay, d.callback, None)
|
||||
return d
|
||||
|
||||
def _get_loop(self):
|
||||
"""
|
||||
internal helper
|
||||
"""
|
||||
# we import and assign the default here (and not, e.g., when
|
||||
# making Config) so as to delay importing reactor as long as
|
||||
# possible in case someone is installing a custom one.
|
||||
if self._config.loop is None:
|
||||
from twisted.internet import reactor
|
||||
self._config.loop = reactor
|
||||
return self._config.loop
|
||||
|
||||
|
||||
def set_global_log_level(level):
|
||||
"""
|
||||
Set the global log level on all loggers instantiated by txaio.
|
||||
"""
|
||||
for item in _loggers:
|
||||
if not item._log_level_set_explicitly:
|
||||
item._set_log_level(level)
|
||||
global _log_level
|
||||
_log_level = level
|
||||
|
||||
|
||||
def get_global_log_level():
|
||||
return _log_level
|
||||
|
||||
|
||||
_default_api = _TxApi(config)
|
||||
|
||||
|
||||
failure_message = _default_api.failure_message
|
||||
failure_traceback = _default_api.failure_traceback
|
||||
failure_format_traceback = _default_api.failure_format_traceback
|
||||
create_future = _default_api.create_future
|
||||
create_future_success = _default_api.create_future_success
|
||||
create_future_error = _default_api.create_future_error
|
||||
as_future = _default_api.as_future
|
||||
is_future = _default_api.is_future
|
||||
call_later = _default_api.call_later
|
||||
make_batched_timer = _default_api.make_batched_timer
|
||||
is_called = _default_api.is_called
|
||||
resolve = _default_api.resolve
|
||||
reject = _default_api.reject
|
||||
cancel = _default_api.cancel
|
||||
create_failure = _default_api.create_failure
|
||||
add_callbacks = _default_api.add_callbacks
|
||||
gather = _default_api.gather
|
||||
sleep = _default_api.sleep
|
||||
time_ns = _util.time_ns
|
||||
perf_counter_ns = _util.perf_counter_ns
|
||||
29
.venv/lib/python3.12/site-packages/txaio/with_asyncio.py
Normal file
29
.venv/lib/python3.12/site-packages/txaio/with_asyncio.py
Normal file
@@ -0,0 +1,29 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import txaio
|
||||
|
||||
txaio.use_asyncio()
|
||||
29
.venv/lib/python3.12/site-packages/txaio/with_twisted.py
Normal file
29
.venv/lib/python3.12/site-packages/txaio/with_twisted.py
Normal file
@@ -0,0 +1,29 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import txaio
|
||||
|
||||
txaio.use_twisted()
|
||||
Reference in New Issue
Block a user