RAHHH
This commit is contained in:
147
backend/lib/python3.12/site-packages/txaio/__init__.py
Normal file
147
backend/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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
154
backend/lib/python3.12/site-packages/txaio/_common.py
Normal file
154
backend/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
backend/lib/python3.12/site-packages/txaio/_iotype.py
Normal file
65
backend/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
backend/lib/python3.12/site-packages/txaio/_unframework.py
Normal file
82
backend/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
backend/lib/python3.12/site-packages/txaio/_util.py
Normal file
48
backend/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
backend/lib/python3.12/site-packages/txaio/_version.py
Normal file
27
backend/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
backend/lib/python3.12/site-packages/txaio/aio.py
Normal file
548
backend/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
backend/lib/python3.12/site-packages/txaio/interfaces.py
Normal file
177
backend/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
backend/lib/python3.12/site-packages/txaio/testutil.py
Normal file
56
backend/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
backend/lib/python3.12/site-packages/txaio/tx.py
Normal file
531
backend/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
backend/lib/python3.12/site-packages/txaio/with_asyncio.py
Normal file
29
backend/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
backend/lib/python3.12/site-packages/txaio/with_twisted.py
Normal file
29
backend/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