17.12
This commit is contained in:
parent
66e908fc8a
commit
4791d00a43
76
venv/bin/activate
Normal file
76
venv/bin/activate
Normal file
@ -0,0 +1,76 @@
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r
|
||||
fi
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
if [ ! "$1" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
VIRTUAL_ENV="/Users/gillmannma68984/PycharmProjects/git/demoweb/venv"
|
||||
export VIRTUAL_ENV
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
if [ "x(venv) " != x ] ; then
|
||||
PS1="(venv) ${PS1:-}"
|
||||
else
|
||||
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
|
||||
else
|
||||
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
|
||||
fi
|
||||
fi
|
||||
export PS1
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r
|
||||
fi
|
37
venv/bin/activate.csh
Normal file
37
venv/bin/activate.csh
Normal file
@ -0,0 +1,37 @@
|
||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV "/Users/gillmannma68984/PycharmProjects/git/demoweb/venv"
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
if ("venv" != "") then
|
||||
set env_name = "venv"
|
||||
else
|
||||
if (`basename "VIRTUAL_ENV"` == "__") then
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
|
||||
else
|
||||
set env_name = `basename "$VIRTUAL_ENV"`
|
||||
endif
|
||||
endif
|
||||
set prompt = "[$env_name] $prompt"
|
||||
unset env_name
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
11
venv/bin/automat-visualize
Executable file
11
venv/bin/automat-visualize
Executable file
@ -0,0 +1,11 @@
|
||||
#!/Users/gillmannma68984/PycharmProjects/git/demoweb/venv/bin/python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from automat._visualize import tool
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(tool())
|
11
venv/bin/django-admin
Executable file
11
venv/bin/django-admin
Executable file
@ -0,0 +1,11 @@
|
||||
#!/Users/gillmannma68984/PycharmProjects/git/demoweb/venv/bin/python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(execute_from_command_line())
|
12
venv/bin/mailmail
Executable file
12
venv/bin/mailmail
Executable file
@ -0,0 +1,12 @@
|
||||
#!/Users/gillmannma68984/PycharmProjects/git/demoweb/venv/bin/python
|
||||
# EASY-INSTALL-ENTRY-SCRIPT: 'Twisted==19.10.0','console_scripts','mailmail'
|
||||
__requires__ = 'Twisted==19.10.0'
|
||||
import re
|
||||
import sys
|
||||
from pkg_resources import load_entry_point
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(
|
||||
load_entry_point('Twisted==19.10.0', 'console_scripts', 'mailmail')()
|
||||
)
|
12
venv/bin/pip
Executable file
12
venv/bin/pip
Executable file
@ -0,0 +1,12 @@
|
||||
#!/Users/gillmannma68984/PycharmProjects/git/demoweb/venv/bin/python
|
||||
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==10.0.1','console_scripts','pip'
|
||||
__requires__ = 'pip==10.0.1'
|
||||
import re
|
||||
import sys
|
||||
from pkg_resources import load_entry_point
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(
|
||||
load_entry_point('pip==10.0.1', 'console_scripts', 'pip')()
|
||||
)
|
12
venv/bin/pip3.7
Executable file
12
venv/bin/pip3.7
Executable file
@ -0,0 +1,12 @@
|
||||
#!/Users/gillmannma68984/PycharmProjects/git/demoweb/venv/bin/python
|
||||
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==10.0.1','console_scripts','pip3.7'
|
||||
__requires__ = 'pip==10.0.1'
|
||||
import re
|
||||
import sys
|
||||
from pkg_resources import load_entry_point
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(
|
||||
load_entry_point('pip==10.0.1', 'console_scripts', 'pip3.7')()
|
||||
)
|
12
venv/bin/trial
Executable file
12
venv/bin/trial
Executable file
@ -0,0 +1,12 @@
|
||||
#!/Users/gillmannma68984/PycharmProjects/git/demoweb/venv/bin/python
|
||||
# EASY-INSTALL-ENTRY-SCRIPT: 'Twisted==19.10.0','console_scripts','trial'
|
||||
__requires__ = 'Twisted==19.10.0'
|
||||
import re
|
||||
import sys
|
||||
from pkg_resources import load_entry_point
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(
|
||||
load_entry_point('Twisted==19.10.0', 'console_scripts', 'trial')()
|
||||
)
|
12
venv/bin/twist
Executable file
12
venv/bin/twist
Executable file
@ -0,0 +1,12 @@
|
||||
#!/Users/gillmannma68984/PycharmProjects/git/demoweb/venv/bin/python
|
||||
# EASY-INSTALL-ENTRY-SCRIPT: 'Twisted==19.10.0','console_scripts','twist'
|
||||
__requires__ = 'Twisted==19.10.0'
|
||||
import re
|
||||
import sys
|
||||
from pkg_resources import load_entry_point
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(
|
||||
load_entry_point('Twisted==19.10.0', 'console_scripts', 'twist')()
|
||||
)
|
11
venv/bin/wamp
Executable file
11
venv/bin/wamp
Executable file
@ -0,0 +1,11 @@
|
||||
#!/Users/gillmannma68984/PycharmProjects/git/demoweb/venv/bin/python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from autobahn.__main__ import _main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(_main())
|
@ -0,0 +1 @@
|
||||
pip
|
@ -0,0 +1,21 @@
|
||||
Copyright (c) 2014
|
||||
Rackspace
|
||||
|
||||
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.
|
@ -0,0 +1,486 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: Automat
|
||||
Version: 0.8.0
|
||||
Summary: Self-service finite-state machines for the programmer on the go.
|
||||
Home-page: https://github.com/glyph/Automat
|
||||
Author: Glyph
|
||||
Author-email: glyph@twistedmatrix.com
|
||||
License: MIT
|
||||
Keywords: fsm finite state machine automata
|
||||
Platform: UNKNOWN
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Requires-Dist: attrs (>=16.1.0)
|
||||
Requires-Dist: six
|
||||
Provides-Extra: visualize
|
||||
Requires-Dist: graphviz (>0.5.1) ; extra == 'visualize'
|
||||
Requires-Dist: Twisted (>=16.1.1) ; extra == 'visualize'
|
||||
|
||||
Automat
|
||||
=======
|
||||
|
||||
|
||||
.. image:: https://readthedocs.org/projects/automat/badge/?version=latest
|
||||
:target: http://automat.readthedocs.io/en/latest/
|
||||
:alt: Documentation Status
|
||||
|
||||
|
||||
.. image:: https://travis-ci.org/glyph/automat.svg?branch=master
|
||||
:target: https://travis-ci.org/glyph/automat
|
||||
:alt: Build Status
|
||||
|
||||
|
||||
.. image:: https://coveralls.io/repos/glyph/automat/badge.png
|
||||
:target: https://coveralls.io/r/glyph/automat
|
||||
:alt: Coverage Status
|
||||
|
||||
|
||||
Self-service finite-state machines for the programmer on the go.
|
||||
----------------------------------------------------------------
|
||||
|
||||
Automat is a library for concise, idiomatic Python expression of finite-state
|
||||
automata (particularly deterministic finite-state transducers).
|
||||
|
||||
Read more here, or on `Read the Docs <https://automat.readthedocs.io/>`_\ , or watch the following videos for an overview and presentation
|
||||
|
||||
Overview and presentation by **Glyph Lefkowitz** at the first talk of the first Pyninsula meetup, on February 21st, 2017:
|
||||
|
||||
.. image:: https://img.youtube.com/vi/0wOZBpD1VVk/0.jpg
|
||||
:target: https://www.youtube.com/watch?v=0wOZBpD1VVk
|
||||
:alt: Glyph Lefkowitz - Automat - Pyninsula #0
|
||||
|
||||
|
||||
Presentation by **Clinton Roy** at PyCon Australia, on August 6th 2017:
|
||||
|
||||
.. image:: https://img.youtube.com/vi/TedUKXhu9kE/0.jpg
|
||||
:target: https://www.youtube.com/watch?v=TedUKXhu9kE
|
||||
:alt: Clinton Roy - State Machines - Pycon Australia 2017
|
||||
|
||||
|
||||
Why use state machines?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Sometimes you have to create an object whose behavior varies with its state,
|
||||
but still wishes to present a consistent interface to its callers.
|
||||
|
||||
For example, let's say you're writing the software for a coffee machine. It
|
||||
has a lid that can be opened or closed, a chamber for water, a chamber for
|
||||
coffee beans, and a button for "brew".
|
||||
|
||||
There are a number of possible states for the coffee machine. It might or
|
||||
might not have water. It might or might not have beans. The lid might be open
|
||||
or closed. The "brew" button should only actually attempt to brew coffee in
|
||||
one of these configurations, and the "open lid" button should only work if the
|
||||
coffee is not, in fact, brewing.
|
||||
|
||||
With diligence and attention to detail, you can implement this correctly using
|
||||
a collection of attributes on an object; ``has_water``\ , ``has_beans``\ ,
|
||||
``is_lid_open`` and so on. However, you have to keep all these attributes
|
||||
consistent. As the coffee maker becomes more complex - perhaps you add an
|
||||
additional chamber for flavorings so you can make hazelnut coffee, for
|
||||
example - you have to keep adding more and more checks and more and more
|
||||
reasoning about which combinations of states are allowed.
|
||||
|
||||
Rather than adding tedious 'if' checks to every single method to make sure that
|
||||
each of these flags are exactly what you expect, you can use a state machine to
|
||||
ensure that if your code runs at all, it will be run with all the required
|
||||
values initialized, because they have to be called in the order you declare
|
||||
them.
|
||||
|
||||
You can read about state machines and their advantages for Python programmers
|
||||
in considerably more detail
|
||||
`in this excellent series of articles from ClusterHQ <https://clusterhq.com/blog/what-is-a-state-machine/>`_.
|
||||
|
||||
What makes Automat different?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
There are
|
||||
`dozens of libraries on PyPI implementing state machines <https://pypi.org/search/?q=finite+state+machine>`_.
|
||||
So it behooves me to say why yet another one would be a good idea.
|
||||
|
||||
Automat is designed around this principle: while organizing your code around
|
||||
state machines is a good idea, your callers don't, and shouldn't have to, care
|
||||
that you've done so. In Python, the "input" to a stateful system is a method
|
||||
call; the "output" may be a method call, if you need to invoke a side effect,
|
||||
or a return value, if you are just performing a computation in memory. Most
|
||||
other state-machine libraries require you to explicitly create an input object,
|
||||
provide that object to a generic "input" method, and then receive results,
|
||||
sometimes in terms of that library's interfaces and sometimes in terms of
|
||||
classes you define yourself.
|
||||
|
||||
For example, a snippet of the coffee-machine example above might be implemented
|
||||
as follows in naive Python:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class CoffeeMachine(object):
|
||||
def brew_button(self):
|
||||
if self.has_water and self.has_beans and not self.is_lid_open:
|
||||
self.heat_the_heating_element()
|
||||
# ...
|
||||
|
||||
With Automat, you'd create a class with a ``MethodicalMachine`` attribute:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from automat import MethodicalMachine
|
||||
|
||||
class CoffeeBrewer(object):
|
||||
_machine = MethodicalMachine()
|
||||
|
||||
and then you would break the above logic into two pieces - the ``brew_button``
|
||||
*input*\ , declared like so:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@_machine.input()
|
||||
def brew_button(self):
|
||||
"The user pressed the 'brew' button."
|
||||
|
||||
It wouldn't do any good to declare a method *body* on this, however, because
|
||||
input methods don't actually execute their bodies when called; doing actual
|
||||
work is the *output*\ 's job:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@_machine.output()
|
||||
def _heat_the_heating_element(self):
|
||||
"Heat up the heating element, which should cause coffee to happen."
|
||||
self._heating_element.turn_on()
|
||||
|
||||
As well as a couple of *states* - and for simplicity's sake let's say that the
|
||||
only two states are ``have_beans`` and ``dont_have_beans``\ :
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@_machine.state()
|
||||
def have_beans(self):
|
||||
"In this state, you have some beans."
|
||||
@_machine.state(initial=True)
|
||||
def dont_have_beans(self):
|
||||
"In this state, you don't have any beans."
|
||||
|
||||
``dont_have_beans`` is the ``initial`` state because ``CoffeeBrewer`` starts without beans
|
||||
in it.
|
||||
|
||||
(And another input to put some beans in:)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@_machine.input()
|
||||
def put_in_beans(self):
|
||||
"The user put in some beans."
|
||||
|
||||
Finally, you hook everything together with the ``upon`` method of the functions
|
||||
decorated with ``_machine.state``\ :
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
||||
# When we don't have beans, upon putting in beans, we will then have beans
|
||||
# (and produce no output)
|
||||
dont_have_beans.upon(put_in_beans, enter=have_beans, outputs=[])
|
||||
|
||||
# When we have beans, upon pressing the brew button, we will then not have
|
||||
# beans any more (as they have been entered into the brewing chamber) and
|
||||
# our output will be heating the heating element.
|
||||
have_beans.upon(brew_button, enter=dont_have_beans,
|
||||
outputs=[_heat_the_heating_element])
|
||||
|
||||
To *users* of this coffee machine class though, it still looks like a POPO
|
||||
(Plain Old Python Object):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> coffee_machine = CoffeeMachine()
|
||||
>>> coffee_machine.put_in_beans()
|
||||
>>> coffee_machine.brew_button()
|
||||
|
||||
All of the *inputs* are provided by calling them like methods, all of the
|
||||
*outputs* are automatically invoked when they are produced according to the
|
||||
outputs specified to ``upon`` and all of the states are simply opaque tokens -
|
||||
although the fact that they're defined as methods like inputs and outputs
|
||||
allows you to put docstrings on them easily to document them.
|
||||
|
||||
How do I get the current state of a state machine?
|
||||
--------------------------------------------------
|
||||
|
||||
Don't do that.
|
||||
|
||||
One major reason for having a state machine is that you want the callers of the
|
||||
state machine to just provide the appropriate input to the machine at the
|
||||
appropriate time, and *not have to check themselves* what state the machine is
|
||||
in. So if you are tempted to write some code like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if connection_state_machine.state == "CONNECTED":
|
||||
connection_state_machine.send_message()
|
||||
else:
|
||||
print("not connected")
|
||||
|
||||
Instead, just make your calling code do this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
connection_state_machine.send_message()
|
||||
|
||||
and then change your state machine to look like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@_machine.state()
|
||||
def connected(self):
|
||||
"connected"
|
||||
@_machine.state()
|
||||
def not_connected(self):
|
||||
"not connected"
|
||||
@_machine.input()
|
||||
def send_message(self):
|
||||
"send a message"
|
||||
@_machine.output()
|
||||
def _actually_send_message(self):
|
||||
self._transport.send(b"message")
|
||||
@_machine.output()
|
||||
def _report_sending_failure(self):
|
||||
print("not connected")
|
||||
connected.upon(send_message, enter=connected, [_actually_send_message])
|
||||
not_connected.upon(send_message, enter=not_connected, [_report_sending_failure])
|
||||
|
||||
so that the responsibility for knowing which state the state machine is in
|
||||
remains within the state machine itself.
|
||||
|
||||
Input for Inputs and Output for Outputs
|
||||
---------------------------------------
|
||||
|
||||
Quite often you want to be able to pass parameters to your methods, as well as
|
||||
inspecting their results. For example, when you brew the coffee, you might
|
||||
expect a cup of coffee to result, and you would like to see what kind of coffee
|
||||
it is. And if you were to put delicious hand-roasted small-batch artisanal
|
||||
beans into the machine, you would expect a *better* cup of coffee than if you
|
||||
were to use mass-produced beans. You would do this in plain old Python by
|
||||
adding a parameter, so that's how you do it in Automat as well.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@_machine.input()
|
||||
def put_in_beans(self, beans):
|
||||
"The user put in some beans."
|
||||
|
||||
However, one important difference here is that *we can't add any
|
||||
implementation code to the input method*. Inputs are purely a declaration of
|
||||
the interface; the behavior must all come from outputs. Therefore, the change
|
||||
in the state of the coffee machine must be represented as an output. We can
|
||||
add an output method like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@_machine.output()
|
||||
def _save_beans(self, beans):
|
||||
"The beans are now in the machine; save them."
|
||||
self._beans = beans
|
||||
|
||||
and then connect it to the ``put_in_beans`` by changing the transition from
|
||||
``dont_have_beans`` to ``have_beans`` like so:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
dont_have_beans.upon(put_in_beans, enter=have_beans,
|
||||
outputs=[_save_beans])
|
||||
|
||||
Now, when you call:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
coffee_machine.put_in_beans("real good beans")
|
||||
|
||||
the machine will remember the beans for later.
|
||||
|
||||
So how do we get the beans back out again? One of our outputs needs to have a
|
||||
return value. It would make sense if our ``brew_button`` method returned the cup
|
||||
of coffee that it made, so we should add an output. So, in addition to heating
|
||||
the heating element, let's add a return value that describes the coffee. First
|
||||
a new output:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@_machine.output()
|
||||
def _describe_coffee(self):
|
||||
return "A cup of coffee made with {}.".format(self._beans)
|
||||
|
||||
Note that we don't need to check first whether ``self._beans`` exists or not,
|
||||
because we can only reach this output method if the state machine says we've
|
||||
gone through a set of states that sets this attribute.
|
||||
|
||||
Now, we need to hook up ``_describe_coffee`` to the process of brewing, so change
|
||||
the brewing transition to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
have_beans.upon(brew_button, enter=dont_have_beans,
|
||||
outputs=[_heat_the_heating_element,
|
||||
_describe_coffee])
|
||||
|
||||
Now, we can call it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> coffee_machine.brew_button()
|
||||
[None, 'A cup of coffee made with real good beans.']
|
||||
|
||||
Except... wait a second, what's that ``None`` doing there?
|
||||
|
||||
Since every input can produce multiple outputs, in automat, the default return
|
||||
value from every input invocation is a ``list``. In this case, we have both
|
||||
``_heat_the_heating_element`` and ``_describe_coffee`` outputs, so we're seeing
|
||||
both of their return values. However, this can be customized, with the
|
||||
``collector`` argument to ``upon``\ ; the ``collector`` is a callable which takes an
|
||||
iterable of all the outputs' return values and "collects" a single return value
|
||||
to return to the caller of the state machine.
|
||||
|
||||
In this case, we only care about the last output, so we can adjust the call to
|
||||
``upon`` like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
have_beans.upon(brew_button, enter=dont_have_beans,
|
||||
outputs=[_heat_the_heating_element,
|
||||
_describe_coffee],
|
||||
collector=lambda iterable: list(iterable)[-1]
|
||||
)
|
||||
|
||||
And now, we'll get just the return value we want:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> coffee_machine.brew_button()
|
||||
'A cup of coffee made with real good beans.'
|
||||
|
||||
If I can't get the state of the state machine, how can I save it to (a database, an API response, a file on disk...)
|
||||
--------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
There are APIs for serializing the state machine.
|
||||
|
||||
First, you have to decide on a persistent representation of each state, via the
|
||||
``serialized=`` argument to the ``MethodicalMachine.state()`` decorator.
|
||||
|
||||
Let's take this very simple "light switch" state machine, which can be on or
|
||||
off, and flipped to reverse its state:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class LightSwitch(object):
|
||||
_machine = MethodicalMachine()
|
||||
@_machine.state(serialized="on")
|
||||
def on_state(self):
|
||||
"the switch is on"
|
||||
@_machine.state(serialized="off", initial=True)
|
||||
def off_state(self):
|
||||
"the switch is off"
|
||||
@_machine.input()
|
||||
def flip(self):
|
||||
"flip the switch"
|
||||
on_state.upon(flip, enter=off_state, outputs=[])
|
||||
off_state.upon(flip, enter=on_state, outputs=[])
|
||||
|
||||
In this case, we've chosen a serialized representation for each state via the
|
||||
``serialized`` argument. The on state is represented by the string ``"on"``\ , and
|
||||
the off state is represented by the string ``"off"``.
|
||||
|
||||
Now, let's just add an input that lets us tell if the switch is on or not.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@_machine.input()
|
||||
def query_power(self):
|
||||
"return True if powered, False otherwise"
|
||||
@_machine.output()
|
||||
def _is_powered(self):
|
||||
return True
|
||||
@_machine.output()
|
||||
def _not_powered(self):
|
||||
return False
|
||||
on_state.upon(query_power, enter=on_state, outputs=[_is_powered],
|
||||
collector=next)
|
||||
off_state.upon(query_power, enter=off_state, outputs=[_not_powered],
|
||||
collector=next)
|
||||
|
||||
To save the state, we have the ``MethodicalMachine.serializer()`` method. A
|
||||
method decorated with ``@serializer()`` gets an extra argument injected at the
|
||||
beginning of its argument list: the serialized identifier for the state. In
|
||||
this case, either ``"on"`` or ``"off"``. Since state machine output methods can
|
||||
also affect other state on the object, a serializer method is expected to
|
||||
return *all* relevant state for serialization.
|
||||
|
||||
For our simple light switch, such a method might look like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@_machine.serializer()
|
||||
def save(self, state):
|
||||
return {"is-it-on": state}
|
||||
|
||||
Serializers can be public methods, and they can return whatever you like. If
|
||||
necessary, you can have different serializers - just multiple methods decorated
|
||||
with ``@_machine.serializer()`` - for different formats; return one data-structure
|
||||
for JSON, one for XML, one for a database row, and so on.
|
||||
|
||||
When it comes time to unserialize, though, you generally want a private method,
|
||||
because an unserializer has to take a not-fully-initialized instance and
|
||||
populate it with state. It is expected to *return* the serialized machine
|
||||
state token that was passed to the serializer, but it can take whatever
|
||||
arguments you like. Of course, in order to return that, it probably has to
|
||||
take it somewhere in its arguments, so it will generally take whatever a paired
|
||||
serializer has returned as an argument.
|
||||
|
||||
So our unserializer would look like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@_machine.unserializer()
|
||||
def _restore(self, blob):
|
||||
return blob["is-it-on"]
|
||||
|
||||
Generally you will want a classmethod deserialization constructor which you
|
||||
write yourself to call this, so that you know how to create an instance of your
|
||||
own object, like so:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@classmethod
|
||||
def from_blob(cls, blob):
|
||||
self = cls()
|
||||
self._restore(blob)
|
||||
return self
|
||||
|
||||
Saving and loading our ``LightSwitch`` along with its state-machine state can now
|
||||
be accomplished as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> switch1 = LightSwitch()
|
||||
>>> switch1.query_power()
|
||||
False
|
||||
>>> switch1.flip()
|
||||
[]
|
||||
>>> switch1.query_power()
|
||||
True
|
||||
>>> blob = switch1.save()
|
||||
>>> switch2 = LightSwitch.from_blob(blob)
|
||||
>>> switch2.query_power()
|
||||
True
|
||||
|
||||
More comprehensive (tested, working) examples are present in ``docs/examples``.
|
||||
|
||||
Go forth and machine all the state!
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
[console_scripts]
|
||||
automat-visualize = automat._visualize:tool
|
||||
|
@ -0,0 +1 @@
|
||||
automat
|
@ -0,0 +1,27 @@
|
||||
Copyright (c) Django Software Foundation and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of Django nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
4212
venv/lib/python3.7/site-packages/Django-2.2.6.dist-info/RECORD
Normal file
4212
venv/lib/python3.7/site-packages/Django-2.2.6.dist-info/RECORD
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@
|
||||
[console_scripts]
|
||||
django-admin = django.core.management:execute_from_command_line
|
||||
|
20
venv/lib/python3.7/site-packages/OpenSSL/__init__.py
Normal file
20
venv/lib/python3.7/site-packages/OpenSSL/__init__.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Copyright (C) AB Strakt
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
pyOpenSSL - A simple wrapper around the OpenSSL library
|
||||
"""
|
||||
|
||||
from OpenSSL import crypto, SSL
|
||||
from OpenSSL.version import (
|
||||
__author__, __copyright__, __email__, __license__, __summary__, __title__,
|
||||
__uri__, __version__,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"SSL", "crypto",
|
||||
|
||||
"__author__", "__copyright__", "__email__", "__license__", "__summary__",
|
||||
"__title__", "__uri__", "__version__",
|
||||
]
|
42
venv/lib/python3.7/site-packages/OpenSSL/debug.py
Normal file
42
venv/lib/python3.7/site-packages/OpenSSL/debug.py
Normal file
@ -0,0 +1,42 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import ssl
|
||||
import sys
|
||||
|
||||
import OpenSSL.SSL
|
||||
import cffi
|
||||
import cryptography
|
||||
|
||||
from . import version
|
||||
|
||||
|
||||
_env_info = u"""\
|
||||
pyOpenSSL: {pyopenssl}
|
||||
cryptography: {cryptography}
|
||||
cffi: {cffi}
|
||||
cryptography's compiled against OpenSSL: {crypto_openssl_compile}
|
||||
cryptography's linked OpenSSL: {crypto_openssl_link}
|
||||
Pythons's OpenSSL: {python_openssl}
|
||||
Python executable: {python}
|
||||
Python version: {python_version}
|
||||
Platform: {platform}
|
||||
sys.path: {sys_path}""".format(
|
||||
pyopenssl=version.__version__,
|
||||
crypto_openssl_compile=OpenSSL._util.ffi.string(
|
||||
OpenSSL._util.lib.OPENSSL_VERSION_TEXT,
|
||||
).decode("ascii"),
|
||||
crypto_openssl_link=OpenSSL.SSL.SSLeay_version(
|
||||
OpenSSL.SSL.SSLEAY_VERSION
|
||||
).decode("ascii"),
|
||||
python_openssl=getattr(ssl, "OPENSSL_VERSION", "n/a"),
|
||||
cryptography=cryptography.__version__,
|
||||
cffi=cffi.__version__,
|
||||
python=sys.executable,
|
||||
python_version=sys.version,
|
||||
platform=sys.platform,
|
||||
sys_path=sys.path,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(_env_info)
|
22
venv/lib/python3.7/site-packages/OpenSSL/version.py
Normal file
22
venv/lib/python3.7/site-packages/OpenSSL/version.py
Normal file
@ -0,0 +1,22 @@
|
||||
# Copyright (C) AB Strakt
|
||||
# Copyright (C) Jean-Paul Calderone
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
pyOpenSSL - A simple wrapper around the OpenSSL library
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"__author__", "__copyright__", "__email__", "__license__", "__summary__",
|
||||
"__title__", "__uri__", "__version__",
|
||||
]
|
||||
|
||||
__version__ = "19.1.0"
|
||||
|
||||
__title__ = "pyOpenSSL"
|
||||
__uri__ = "https://pyopenssl.org/"
|
||||
__summary__ = "Python wrapper module around the OpenSSL library"
|
||||
__author__ = "The pyOpenSSL developers"
|
||||
__email__ = "cryptography-dev@python.org"
|
||||
__license__ = "Apache License, Version 2.0"
|
||||
__copyright__ = "Copyright 2001-2017 {0}".format(__author__)
|
@ -0,0 +1,322 @@
|
||||
PyHamcrest
|
||||
==========
|
||||
|
||||
| |docs| |travis| |coveralls| |landscape| |scrutinizer| |codeclimate|
|
||||
| |version| |downloads| |wheel| |supported-versions| |supported-implementations|
|
||||
|
||||
.. |docs| image:: https://readthedocs.org/projects/pyhamcrest/badge/?style=flat
|
||||
:target: https://pyhamcrest.readthedocs.org/
|
||||
:alt: Documentation Status
|
||||
|
||||
.. |travis| image:: http://img.shields.io/travis/hamcrest/PyHamcrest/master.png?style=flat
|
||||
:alt: Travis-CI Build Status
|
||||
:target: https://travis-ci.org/hamcrest/PyHamcrest
|
||||
|
||||
.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/hamcrest/PyHamcrest?branch=master
|
||||
:alt: AppVeyor Build Status
|
||||
:target: https://ci.appveyor.com/project/hamcrest/PyHamcrest
|
||||
|
||||
.. |coveralls| image:: http://img.shields.io/coveralls/hamcrest/PyHamcrest/master.png?style=flat
|
||||
:alt: Coverage Status
|
||||
:target: https://coveralls.io/r/hamcrest/PyHamcrest
|
||||
|
||||
.. |landscape| image:: https://landscape.io/github/hamcrest/PyHamcrest/master/landscape.svg?style=flat
|
||||
:target: https://landscape.io/github/hamcrest/PyHamcrest/master
|
||||
:alt: Code Quality Status
|
||||
|
||||
.. |codeclimate| image:: https://codeclimate.com/github/hamcrest/PyHamcrest/badges/gpa.svg
|
||||
:target: https://codeclimate.com/github/hamcrest/PyHamcrest
|
||||
:alt: Code Climate
|
||||
|
||||
.. |version| image:: http://img.shields.io/pypi/v/PyHamcrest.png?style=flat
|
||||
:alt: PyPI Package latest release
|
||||
:target: https://pypi.python.org/pypi/PyHamcrest
|
||||
|
||||
.. |downloads| image:: http://img.shields.io/pypi/dm/PyHamcrest.png?style=flat
|
||||
:alt: PyPI Package monthly downloads
|
||||
:target: https://pypi.python.org/pypi/PyHamcrest
|
||||
|
||||
.. |wheel| image:: https://pypip.in/wheel/PyHamcrest/badge.png?style=flat
|
||||
:alt: PyPI Wheel
|
||||
:target: https://pypi.python.org/pypi/PyHamcrest
|
||||
|
||||
.. |supported-versions| image:: https://pypip.in/py_versions/PyHamcrest/badge.png?style=flat
|
||||
:alt: Supported versions
|
||||
:target: https://pypi.python.org/pypi/PyHamcrest
|
||||
|
||||
.. |supported-implementations| image:: https://pypip.in/implementation/PyHamcrest/badge.png?style=flat
|
||||
:alt: Supported imlementations
|
||||
:target: https://pypi.python.org/pypi/PyHamcrest
|
||||
|
||||
.. |scrutinizer| image:: https://img.shields.io/scrutinizer/g/hamcrest/PyHamcrest/master.png?style=flat
|
||||
:alt: Scrtinizer Status
|
||||
:target: https://scrutinizer-ci.com/g/hamcrest/PyHamcrest/
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
PyHamcrest is a framework for writing matcher objects, allowing you to
|
||||
declaratively define "match" rules. There are a number of situations where
|
||||
matchers are invaluable, such as UI validation, or data filtering, but it is in
|
||||
the area of writing flexible tests that matchers are most commonly used. This
|
||||
tutorial shows you how to use PyHamcrest for unit testing.
|
||||
|
||||
When writing tests it is sometimes difficult to get the balance right between
|
||||
overspecifying the test (and making it brittle to changes), and not specifying
|
||||
enough (making the test less valuable since it continues to pass even when the
|
||||
thing being tested is broken). Having a tool that allows you to pick out
|
||||
precisely the aspect under test and describe the values it should have, to a
|
||||
controlled level of precision, helps greatly in writing tests that are "just
|
||||
right." Such tests fail when the behavior of the aspect under test deviates
|
||||
from the expected behavior, yet continue to pass when minor, unrelated changes
|
||||
to the behaviour are made.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Hamcrest can be installed using the usual Python packaging tools. It depends on
|
||||
distribute, but as long as you have a network connection when you install, the
|
||||
installation process will take care of that for you.
|
||||
|
||||
My first PyHamcrest test
|
||||
========================
|
||||
|
||||
We'll start by writing a very simple PyUnit test, but instead of using PyUnit's
|
||||
``assertEqual`` method, we'll use PyHamcrest's ``assert_that`` construct and
|
||||
the standard set of matchers:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from hamcrest import *
|
||||
import unittest
|
||||
|
||||
class BiscuitTest(unittest.TestCase):
|
||||
def testEquals(self):
|
||||
theBiscuit = Biscuit('Ginger')
|
||||
myBiscuit = Biscuit('Ginger')
|
||||
assert_that(theBiscuit, equal_to(myBiscuit))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
The ``assert_that`` function is a stylized sentence for making a test
|
||||
assertion. In this example, the subject of the assertion is the object
|
||||
``theBiscuit``, which is the first method parameter. The second method
|
||||
parameter is a matcher for ``Biscuit`` objects, here a matcher that checks one
|
||||
object is equal to another using the Python ``==`` operator. The test passes
|
||||
since the ``Biscuit`` class defines an ``__eq__`` method.
|
||||
|
||||
If you have more than one assertion in your test you can include an identifier
|
||||
for the tested value in the assertion:
|
||||
|
||||
.. code:: python
|
||||
|
||||
assert_that(theBiscuit.getChocolateChipCount(), equal_to(10), 'chocolate chips')
|
||||
assert_that(theBiscuit.getHazelnutCount(), equal_to(3), 'hazelnuts')
|
||||
|
||||
As a convenience, assert_that can also be used to verify a boolean condition:
|
||||
|
||||
.. code:: python
|
||||
|
||||
assert_that(theBiscuit.isCooked(), 'cooked')
|
||||
|
||||
This is equivalent to the ``assert_`` method of unittest.TestCase, but because
|
||||
it's a standalone function, it offers greater flexibility in test writing.
|
||||
|
||||
|
||||
Predefined matchers
|
||||
===================
|
||||
|
||||
PyHamcrest comes with a library of useful matchers:
|
||||
|
||||
* Object
|
||||
|
||||
* ``equal_to`` - match equal object
|
||||
* ``has_length`` - match ``len()``
|
||||
* ``has_property`` - match value of property with given name
|
||||
* ``has_properties`` - match an object that has all of the given properties.
|
||||
* ``has_string`` - match ``str()``
|
||||
* ``instance_of`` - match object type
|
||||
* ``none``, ``not_none`` - match ``None``, or not ``None``
|
||||
* ``same_instance`` - match same object
|
||||
* ``calling, raises`` - wrap a method call and assert that it raises an exception
|
||||
|
||||
* Number
|
||||
|
||||
* ``close_to`` - match number close to a given value
|
||||
* ``greater_than``, ``greater_than_or_equal_to``, ``less_than``,
|
||||
``less_than_or_equal_to`` - match numeric ordering
|
||||
|
||||
* Text
|
||||
|
||||
* ``contains_string`` - match part of a string
|
||||
* ``ends_with`` - match the end of a string
|
||||
* ``equal_to_ignoring_case`` - match the complete string but ignore case
|
||||
* ``equal_to_ignoring_whitespace`` - match the complete string but ignore extra whitespace
|
||||
* ``matches_regexp`` - match a regular expression in a string
|
||||
* ``starts_with`` - match the beginning of a string
|
||||
* ``string_contains_in_order`` - match parts of a string, in relative order
|
||||
|
||||
* Logical
|
||||
|
||||
* ``all_of`` - ``and`` together all matchers
|
||||
* ``any_of`` - ``or`` together all matchers
|
||||
* ``anything`` - match anything, useful in composite matchers when you don't care about a particular value
|
||||
* ``is_not`` - negate the matcher
|
||||
|
||||
* Sequence
|
||||
|
||||
* ``contains`` - exactly match the entire sequence
|
||||
* ``contains_inanyorder`` - match the entire sequence, but in any order
|
||||
* ``has_item`` - match if given item appears in the sequence
|
||||
* ``has_items`` - match if all given items appear in the sequence, in any order
|
||||
* ``is_in`` - match if item appears in the given sequence
|
||||
* ``only_contains`` - match if sequence's items appear in given list
|
||||
* ``empty`` - match if the sequence is empty
|
||||
|
||||
* Dictionary
|
||||
|
||||
* ``has_entries`` - match dictionary with list of key-value pairs
|
||||
* ``has_entry`` - match dictionary containing a key-value pair
|
||||
* ``has_key`` - match dictionary with a key
|
||||
* ``has_value`` - match dictionary with a value
|
||||
|
||||
* Decorator
|
||||
|
||||
* ``calling`` - wrap a callable in a deffered object, for subsequent matching on calling behaviour
|
||||
* ``raises`` - Ensure that a deferred callable raises as expected
|
||||
* ``described_as`` - give the matcher a custom failure description
|
||||
* ``is_`` - decorator to improve readability - see `Syntactic sugar` below
|
||||
|
||||
The arguments for many of these matchers accept not just a matching value, but
|
||||
another matcher, so matchers can be composed for greater flexibility. For
|
||||
example, ``only_contains(less_than(5))`` will match any sequence where every
|
||||
item is less than 5.
|
||||
|
||||
|
||||
Syntactic sugar
|
||||
===============
|
||||
|
||||
PyHamcrest strives to make your tests as readable as possible. For example, the
|
||||
``is_`` matcher is a wrapper that doesn't add any extra behavior to the
|
||||
underlying matcher. The following assertions are all equivalent:
|
||||
|
||||
.. code:: python
|
||||
|
||||
assert_that(theBiscuit, equal_to(myBiscuit))
|
||||
assert_that(theBiscuit, is_(equal_to(myBiscuit)))
|
||||
assert_that(theBiscuit, is_(myBiscuit))
|
||||
|
||||
The last form is allowed since ``is_(value)`` wraps most non-matcher arguments
|
||||
with ``equal_to``. But if the argument is a type, it is wrapped with
|
||||
``instance_of``, so the following are also equivalent:
|
||||
|
||||
.. code:: python
|
||||
|
||||
assert_that(theBiscuit, instance_of(Biscuit))
|
||||
assert_that(theBiscuit, is_(instance_of(Biscuit)))
|
||||
assert_that(theBiscuit, is_(Biscuit))
|
||||
|
||||
*Note that PyHamcrest's ``is_`` matcher is unrelated to Python's ``is``
|
||||
operator. The matcher for object identity is ``same_instance``.*
|
||||
|
||||
|
||||
Writing custom matchers
|
||||
=======================
|
||||
|
||||
PyHamcrest comes bundled with lots of useful matchers, but you'll probably find
|
||||
that you need to create your own from time to time to fit your testing needs.
|
||||
This commonly occurs when you find a fragment of code that tests the same set
|
||||
of properties over and over again (and in different tests), and you want to
|
||||
bundle the fragment into a single assertion. By writing your own matcher you'll
|
||||
eliminate code duplication and make your tests more readable!
|
||||
|
||||
Let's write our own matcher for testing if a calendar date falls on a Saturday.
|
||||
This is the test we want to write:
|
||||
|
||||
.. code:: python
|
||||
|
||||
def testDateIsOnASaturday(self):
|
||||
d = datetime.date(2008, 04, 26)
|
||||
assert_that(d, is_(on_a_saturday()))
|
||||
|
||||
And here's the implementation:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from hamcrest.core.base_matcher import BaseMatcher
|
||||
from hamcrest.core.helpers.hasmethod import hasmethod
|
||||
|
||||
class IsGivenDayOfWeek(BaseMatcher):
|
||||
|
||||
def __init__(self, day):
|
||||
self.day = day # Monday is 0, Sunday is 6
|
||||
|
||||
def _matches(self, item):
|
||||
if not hasmethod(item, 'weekday'):
|
||||
return False
|
||||
return item.weekday() == self.day
|
||||
|
||||
def describe_to(self, description):
|
||||
day_as_string = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
|
||||
'Friday', 'Saturday', 'Sunday']
|
||||
description.append_text('calendar date falling on ') \
|
||||
.append_text(day_as_string[self.day])
|
||||
|
||||
def on_a_saturday():
|
||||
return IsGivenDayOfWeek(5)
|
||||
|
||||
For our Matcher implementation we implement the ``_matches`` method - which
|
||||
calls the ``weekday`` method after confirming that the argument (which may not
|
||||
be a date) has such a method - and the ``describe_to`` method - which is used
|
||||
to produce a failure message when a test fails. Here's an example of how the
|
||||
failure message looks:
|
||||
|
||||
.. code:: python
|
||||
|
||||
assert_that(datetime.date(2008, 04, 06), is_(on_a_saturday()))
|
||||
|
||||
fails with the message::
|
||||
|
||||
AssertionError:
|
||||
Expected: is calendar date falling on Saturday
|
||||
got: <2008-04-06>
|
||||
|
||||
Let's say this matcher is saved in a module named ``isgivendayofweek``. We
|
||||
could use it in our test by importing the factory function ``on_a_saturday``:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from hamcrest import *
|
||||
import unittest
|
||||
from isgivendayofweek import on_a_saturday
|
||||
|
||||
class DateTest(unittest.TestCase):
|
||||
def testDateIsOnASaturday(self):
|
||||
d = datetime.date(2008, 04, 26)
|
||||
assert_that(d, is_(on_a_saturday()))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Even though the ``on_a_saturday`` function creates a new matcher each time it
|
||||
is called, you should not assume this is the only usage pattern for your
|
||||
matcher. Therefore you should make sure your matcher is stateless, so a single
|
||||
instance can be reused between matches.
|
||||
|
||||
|
||||
More resources
|
||||
==============
|
||||
|
||||
* Documentation_
|
||||
* Package_
|
||||
* Sources_
|
||||
* Hamcrest_
|
||||
|
||||
.. _Documentation: http://readthedocs.org/docs/pyhamcrest/en/V1.8.2/
|
||||
.. _Package: http://pypi.python.org/pypi/PyHamcrest
|
||||
.. _Sources: https://github.com/hamcrest/PyHamcrest
|
||||
.. _Hamcrest: http://hamcrest.org
|
||||
|
||||
|
@ -0,0 +1,353 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: PyHamcrest
|
||||
Version: 1.9.0
|
||||
Summary: Hamcrest framework for matcher objects
|
||||
Home-page: https://github.com/hamcrest/PyHamcrest
|
||||
Author: Chris Rose
|
||||
Author-email: offline@offby1.net
|
||||
License: New BSD
|
||||
Download-URL: http://pypi.python.org/packages/source/P/PyHamcrest/PyHamcrest-1.9.0.tar.gz
|
||||
Keywords: hamcrest matchers pyunit unit test testing unittest unittesting
|
||||
Platform: All
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Console
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Natural Language :: English
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: Jython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Software Development
|
||||
Classifier: Topic :: Software Development :: Quality Assurance
|
||||
Classifier: Topic :: Software Development :: Testing
|
||||
Provides: hamcrest
|
||||
Requires-Dist: setuptools
|
||||
Requires-Dist: six
|
||||
|
||||
PyHamcrest
|
||||
==========
|
||||
|
||||
| |docs| |travis| |coveralls| |landscape| |scrutinizer| |codeclimate|
|
||||
| |version| |downloads| |wheel| |supported-versions| |supported-implementations|
|
||||
|
||||
.. |docs| image:: https://readthedocs.org/projects/pyhamcrest/badge/?style=flat
|
||||
:target: https://pyhamcrest.readthedocs.org/
|
||||
:alt: Documentation Status
|
||||
|
||||
.. |travis| image:: http://img.shields.io/travis/hamcrest/PyHamcrest/master.png?style=flat
|
||||
:alt: Travis-CI Build Status
|
||||
:target: https://travis-ci.org/hamcrest/PyHamcrest
|
||||
|
||||
.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/hamcrest/PyHamcrest?branch=master
|
||||
:alt: AppVeyor Build Status
|
||||
:target: https://ci.appveyor.com/project/hamcrest/PyHamcrest
|
||||
|
||||
.. |coveralls| image:: http://img.shields.io/coveralls/hamcrest/PyHamcrest/master.png?style=flat
|
||||
:alt: Coverage Status
|
||||
:target: https://coveralls.io/r/hamcrest/PyHamcrest
|
||||
|
||||
.. |landscape| image:: https://landscape.io/github/hamcrest/PyHamcrest/master/landscape.svg?style=flat
|
||||
:target: https://landscape.io/github/hamcrest/PyHamcrest/master
|
||||
:alt: Code Quality Status
|
||||
|
||||
.. |codeclimate| image:: https://codeclimate.com/github/hamcrest/PyHamcrest/badges/gpa.svg
|
||||
:target: https://codeclimate.com/github/hamcrest/PyHamcrest
|
||||
:alt: Code Climate
|
||||
|
||||
.. |version| image:: http://img.shields.io/pypi/v/PyHamcrest.png?style=flat
|
||||
:alt: PyPI Package latest release
|
||||
:target: https://pypi.python.org/pypi/PyHamcrest
|
||||
|
||||
.. |downloads| image:: http://img.shields.io/pypi/dm/PyHamcrest.png?style=flat
|
||||
:alt: PyPI Package monthly downloads
|
||||
:target: https://pypi.python.org/pypi/PyHamcrest
|
||||
|
||||
.. |wheel| image:: https://pypip.in/wheel/PyHamcrest/badge.png?style=flat
|
||||
:alt: PyPI Wheel
|
||||
:target: https://pypi.python.org/pypi/PyHamcrest
|
||||
|
||||
.. |supported-versions| image:: https://pypip.in/py_versions/PyHamcrest/badge.png?style=flat
|
||||
:alt: Supported versions
|
||||
:target: https://pypi.python.org/pypi/PyHamcrest
|
||||
|
||||
.. |supported-implementations| image:: https://pypip.in/implementation/PyHamcrest/badge.png?style=flat
|
||||
:alt: Supported imlementations
|
||||
:target: https://pypi.python.org/pypi/PyHamcrest
|
||||
|
||||
.. |scrutinizer| image:: https://img.shields.io/scrutinizer/g/hamcrest/PyHamcrest/master.png?style=flat
|
||||
:alt: Scrtinizer Status
|
||||
:target: https://scrutinizer-ci.com/g/hamcrest/PyHamcrest/
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
PyHamcrest is a framework for writing matcher objects, allowing you to
|
||||
declaratively define "match" rules. There are a number of situations where
|
||||
matchers are invaluable, such as UI validation, or data filtering, but it is in
|
||||
the area of writing flexible tests that matchers are most commonly used. This
|
||||
tutorial shows you how to use PyHamcrest for unit testing.
|
||||
|
||||
When writing tests it is sometimes difficult to get the balance right between
|
||||
overspecifying the test (and making it brittle to changes), and not specifying
|
||||
enough (making the test less valuable since it continues to pass even when the
|
||||
thing being tested is broken). Having a tool that allows you to pick out
|
||||
precisely the aspect under test and describe the values it should have, to a
|
||||
controlled level of precision, helps greatly in writing tests that are "just
|
||||
right." Such tests fail when the behavior of the aspect under test deviates
|
||||
from the expected behavior, yet continue to pass when minor, unrelated changes
|
||||
to the behaviour are made.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Hamcrest can be installed using the usual Python packaging tools. It depends on
|
||||
distribute, but as long as you have a network connection when you install, the
|
||||
installation process will take care of that for you.
|
||||
|
||||
My first PyHamcrest test
|
||||
========================
|
||||
|
||||
We'll start by writing a very simple PyUnit test, but instead of using PyUnit's
|
||||
``assertEqual`` method, we'll use PyHamcrest's ``assert_that`` construct and
|
||||
the standard set of matchers:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from hamcrest import *
|
||||
import unittest
|
||||
|
||||
class BiscuitTest(unittest.TestCase):
|
||||
def testEquals(self):
|
||||
theBiscuit = Biscuit('Ginger')
|
||||
myBiscuit = Biscuit('Ginger')
|
||||
assert_that(theBiscuit, equal_to(myBiscuit))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
The ``assert_that`` function is a stylized sentence for making a test
|
||||
assertion. In this example, the subject of the assertion is the object
|
||||
``theBiscuit``, which is the first method parameter. The second method
|
||||
parameter is a matcher for ``Biscuit`` objects, here a matcher that checks one
|
||||
object is equal to another using the Python ``==`` operator. The test passes
|
||||
since the ``Biscuit`` class defines an ``__eq__`` method.
|
||||
|
||||
If you have more than one assertion in your test you can include an identifier
|
||||
for the tested value in the assertion:
|
||||
|
||||
.. code:: python
|
||||
|
||||
assert_that(theBiscuit.getChocolateChipCount(), equal_to(10), 'chocolate chips')
|
||||
assert_that(theBiscuit.getHazelnutCount(), equal_to(3), 'hazelnuts')
|
||||
|
||||
As a convenience, assert_that can also be used to verify a boolean condition:
|
||||
|
||||
.. code:: python
|
||||
|
||||
assert_that(theBiscuit.isCooked(), 'cooked')
|
||||
|
||||
This is equivalent to the ``assert_`` method of unittest.TestCase, but because
|
||||
it's a standalone function, it offers greater flexibility in test writing.
|
||||
|
||||
|
||||
Predefined matchers
|
||||
===================
|
||||
|
||||
PyHamcrest comes with a library of useful matchers:
|
||||
|
||||
* Object
|
||||
|
||||
* ``equal_to`` - match equal object
|
||||
* ``has_length`` - match ``len()``
|
||||
* ``has_property`` - match value of property with given name
|
||||
* ``has_properties`` - match an object that has all of the given properties.
|
||||
* ``has_string`` - match ``str()``
|
||||
* ``instance_of`` - match object type
|
||||
* ``none``, ``not_none`` - match ``None``, or not ``None``
|
||||
* ``same_instance`` - match same object
|
||||
* ``calling, raises`` - wrap a method call and assert that it raises an exception
|
||||
|
||||
* Number
|
||||
|
||||
* ``close_to`` - match number close to a given value
|
||||
* ``greater_than``, ``greater_than_or_equal_to``, ``less_than``,
|
||||
``less_than_or_equal_to`` - match numeric ordering
|
||||
|
||||
* Text
|
||||
|
||||
* ``contains_string`` - match part of a string
|
||||
* ``ends_with`` - match the end of a string
|
||||
* ``equal_to_ignoring_case`` - match the complete string but ignore case
|
||||
* ``equal_to_ignoring_whitespace`` - match the complete string but ignore extra whitespace
|
||||
* ``matches_regexp`` - match a regular expression in a string
|
||||
* ``starts_with`` - match the beginning of a string
|
||||
* ``string_contains_in_order`` - match parts of a string, in relative order
|
||||
|
||||
* Logical
|
||||
|
||||
* ``all_of`` - ``and`` together all matchers
|
||||
* ``any_of`` - ``or`` together all matchers
|
||||
* ``anything`` - match anything, useful in composite matchers when you don't care about a particular value
|
||||
* ``is_not`` - negate the matcher
|
||||
|
||||
* Sequence
|
||||
|
||||
* ``contains`` - exactly match the entire sequence
|
||||
* ``contains_inanyorder`` - match the entire sequence, but in any order
|
||||
* ``has_item`` - match if given item appears in the sequence
|
||||
* ``has_items`` - match if all given items appear in the sequence, in any order
|
||||
* ``is_in`` - match if item appears in the given sequence
|
||||
* ``only_contains`` - match if sequence's items appear in given list
|
||||
* ``empty`` - match if the sequence is empty
|
||||
|
||||
* Dictionary
|
||||
|
||||
* ``has_entries`` - match dictionary with list of key-value pairs
|
||||
* ``has_entry`` - match dictionary containing a key-value pair
|
||||
* ``has_key`` - match dictionary with a key
|
||||
* ``has_value`` - match dictionary with a value
|
||||
|
||||
* Decorator
|
||||
|
||||
* ``calling`` - wrap a callable in a deffered object, for subsequent matching on calling behaviour
|
||||
* ``raises`` - Ensure that a deferred callable raises as expected
|
||||
* ``described_as`` - give the matcher a custom failure description
|
||||
* ``is_`` - decorator to improve readability - see `Syntactic sugar` below
|
||||
|
||||
The arguments for many of these matchers accept not just a matching value, but
|
||||
another matcher, so matchers can be composed for greater flexibility. For
|
||||
example, ``only_contains(less_than(5))`` will match any sequence where every
|
||||
item is less than 5.
|
||||
|
||||
|
||||
Syntactic sugar
|
||||
===============
|
||||
|
||||
PyHamcrest strives to make your tests as readable as possible. For example, the
|
||||
``is_`` matcher is a wrapper that doesn't add any extra behavior to the
|
||||
underlying matcher. The following assertions are all equivalent:
|
||||
|
||||
.. code:: python
|
||||
|
||||
assert_that(theBiscuit, equal_to(myBiscuit))
|
||||
assert_that(theBiscuit, is_(equal_to(myBiscuit)))
|
||||
assert_that(theBiscuit, is_(myBiscuit))
|
||||
|
||||
The last form is allowed since ``is_(value)`` wraps most non-matcher arguments
|
||||
with ``equal_to``. But if the argument is a type, it is wrapped with
|
||||
``instance_of``, so the following are also equivalent:
|
||||
|
||||
.. code:: python
|
||||
|
||||
assert_that(theBiscuit, instance_of(Biscuit))
|
||||
assert_that(theBiscuit, is_(instance_of(Biscuit)))
|
||||
assert_that(theBiscuit, is_(Biscuit))
|
||||
|
||||
*Note that PyHamcrest's ``is_`` matcher is unrelated to Python's ``is``
|
||||
operator. The matcher for object identity is ``same_instance``.*
|
||||
|
||||
|
||||
Writing custom matchers
|
||||
=======================
|
||||
|
||||
PyHamcrest comes bundled with lots of useful matchers, but you'll probably find
|
||||
that you need to create your own from time to time to fit your testing needs.
|
||||
This commonly occurs when you find a fragment of code that tests the same set
|
||||
of properties over and over again (and in different tests), and you want to
|
||||
bundle the fragment into a single assertion. By writing your own matcher you'll
|
||||
eliminate code duplication and make your tests more readable!
|
||||
|
||||
Let's write our own matcher for testing if a calendar date falls on a Saturday.
|
||||
This is the test we want to write:
|
||||
|
||||
.. code:: python
|
||||
|
||||
def testDateIsOnASaturday(self):
|
||||
d = datetime.date(2008, 04, 26)
|
||||
assert_that(d, is_(on_a_saturday()))
|
||||
|
||||
And here's the implementation:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from hamcrest.core.base_matcher import BaseMatcher
|
||||
from hamcrest.core.helpers.hasmethod import hasmethod
|
||||
|
||||
class IsGivenDayOfWeek(BaseMatcher):
|
||||
|
||||
def __init__(self, day):
|
||||
self.day = day # Monday is 0, Sunday is 6
|
||||
|
||||
def _matches(self, item):
|
||||
if not hasmethod(item, 'weekday'):
|
||||
return False
|
||||
return item.weekday() == self.day
|
||||
|
||||
def describe_to(self, description):
|
||||
day_as_string = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
|
||||
'Friday', 'Saturday', 'Sunday']
|
||||
description.append_text('calendar date falling on ') \
|
||||
.append_text(day_as_string[self.day])
|
||||
|
||||
def on_a_saturday():
|
||||
return IsGivenDayOfWeek(5)
|
||||
|
||||
For our Matcher implementation we implement the ``_matches`` method - which
|
||||
calls the ``weekday`` method after confirming that the argument (which may not
|
||||
be a date) has such a method - and the ``describe_to`` method - which is used
|
||||
to produce a failure message when a test fails. Here's an example of how the
|
||||
failure message looks:
|
||||
|
||||
.. code:: python
|
||||
|
||||
assert_that(datetime.date(2008, 04, 06), is_(on_a_saturday()))
|
||||
|
||||
fails with the message::
|
||||
|
||||
AssertionError:
|
||||
Expected: is calendar date falling on Saturday
|
||||
got: <2008-04-06>
|
||||
|
||||
Let's say this matcher is saved in a module named ``isgivendayofweek``. We
|
||||
could use it in our test by importing the factory function ``on_a_saturday``:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from hamcrest import *
|
||||
import unittest
|
||||
from isgivendayofweek import on_a_saturday
|
||||
|
||||
class DateTest(unittest.TestCase):
|
||||
def testDateIsOnASaturday(self):
|
||||
d = datetime.date(2008, 04, 26)
|
||||
assert_that(d, is_(on_a_saturday()))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Even though the ``on_a_saturday`` function creates a new matcher each time it
|
||||
is called, you should not assume this is the only usage pattern for your
|
||||
matcher. Therefore you should make sure your matcher is stateless, so a single
|
||||
instance can be reused between matches.
|
||||
|
||||
|
||||
More resources
|
||||
==============
|
||||
|
||||
* Documentation_
|
||||
* Package_
|
||||
* Sources_
|
||||
* Hamcrest_
|
||||
|
||||
.. _Documentation: http://readthedocs.org/docs/pyhamcrest/en/V1.8.2/
|
||||
.. _Package: http://pypi.python.org/pypi/PyHamcrest
|
||||
.. _Sources: https://github.com/hamcrest/PyHamcrest
|
||||
.. _Hamcrest: http://hamcrest.org
|
||||
|
||||
|
@ -0,0 +1,120 @@
|
||||
hamcrest/__init__.py,sha256=Uo0mxeePyXP9_yIzBBvKqIL6tzEHqBwBO1eSHfAKies,230
|
||||
hamcrest/core/__init__.py,sha256=nDkYm1E1P7XpHmR8EHu17eahlmC6uYix4HurTyJjhlE,230
|
||||
hamcrest/core/assert_that.py,sha256=mPdTQQVsjUliK3xp8l_Vn3YmMzg_h5rYhWJk2mstMc0,2428
|
||||
hamcrest/core/base_description.py,sha256=auvrelro2vGh_A05TQxwbaBJStvfHiFqm0TL56luxEU,2841
|
||||
hamcrest/core/base_matcher.py,sha256=eRhLv7zFBJGaz68vEsDlvKbrWwU8GRDfZc7pWBNcxwI,1193
|
||||
hamcrest/core/compat.py,sha256=wUi1u_nIhgUA3kFGJDZLQ2D_cRw79E1SxPDMaI4mzG0,642
|
||||
hamcrest/core/description.py,sha256=gbVS-ejZJ783o-WEA66bXnXc9DH5cOlQXg0FlzoVGro,1729
|
||||
hamcrest/core/matcher.py,sha256=BG6e8bIJvwFI0qkaSkt_SOuuLI0VidvoX8GQLadYssU,1851
|
||||
hamcrest/core/selfdescribing.py,sha256=RzwqHRGg00AJYweGiP8JzSxoHFdBo0Q7m5axYfutfG8,574
|
||||
hamcrest/core/selfdescribingvalue.py,sha256=OpGLOjdPA9FSgmLZzmkfYrDvpG261TDDiM56muROFqQ,834
|
||||
hamcrest/core/string_description.py,sha256=Jp-SbuY8LAwPucL4NMrwWqRhkg6CMwYE-pH0ZDMUq8A,908
|
||||
hamcrest/core/core/__init__.py,sha256=7q_pNO9cmtV3BEixiGcueNdbxRxlKEFZ1RSO-5GP58E,770
|
||||
hamcrest/core/core/allof.py,sha256=x2ea18Z5cioRybs1heTd3E8zYtpFbuBJg3vIU3vl9k0,1487
|
||||
hamcrest/core/core/anyof.py,sha256=8j2KAsaToXbv8CA5hed4bqPR3hscODIiaFhAlAyJ2Vs,1097
|
||||
hamcrest/core/core/described_as.py,sha256=kyy_qRpoebWB1bS2ReAkNDt-79iXu6HqHxOG0GT7PtQ,1623
|
||||
hamcrest/core/core/is_.py,sha256=C7UCASfr4AIlaaMZnT5d34OthAZQ3rYv8OhszQjz-Eg,2547
|
||||
hamcrest/core/core/isanything.py,sha256=D2QO5dbDhwlVRGLFrnxOSijTyeKN2iWLRwjpvnumkvg,798
|
||||
hamcrest/core/core/isequal.py,sha256=TAwF_lWIjVokPruXN8IGS6ajzPGlThanTLPdhktKwRQ,893
|
||||
hamcrest/core/core/isinstanceof.py,sha256=dnt8YKLhYwGZvDkJ0OTdLd1PN9cB9ZWCg5k8kdnEuqI,1352
|
||||
hamcrest/core/core/isnone.py,sha256=51ueQKBgg0RzWVLzjFOyt6K0ejk2EbXUlUX90ORPm80,557
|
||||
hamcrest/core/core/isnot.py,sha256=_z_ynAVUXEUGWoiRlynFa_yaCVh9cK9LIOznDfKVZeo,1533
|
||||
hamcrest/core/core/issame.py,sha256=8FvAjud4HTuyz7O-XoJbzLtKhChCr1_5JmzMYMeQt1s,1261
|
||||
hamcrest/core/core/raises.py,sha256=BZXtMlQiEqEjQMYT76HUiuQNRjTECIUmL82_FKYhAvs,3610
|
||||
hamcrest/core/helpers/__init__.py,sha256=mPsycYI18LoGawOo9BfETa7yKtnM-fDjFOr43BIevUg,146
|
||||
hamcrest/core/helpers/hasmethod.py,sha256=LPh_WDRuyKYII3G3fX_x2Ql-ECuPJn4tK5eWMLbetLg,325
|
||||
hamcrest/core/helpers/wrap_matcher.py,sha256=IQTtw98Pp1NXcVTy9boaNh6jayvawKHhX62R3ZwnVwQ,880
|
||||
hamcrest/library/__init__.py,sha256=2atNiBCC2g3c-7jw53CltNgU4wEao1uRcheUPl1ML50,1014
|
||||
hamcrest/library/collection/__init__.py,sha256=iJU6WCsf0R22m11fqMA9Ztb161AZAdrsKG-4Cj38lZ0,635
|
||||
hamcrest/library/collection/is_empty.py,sha256=p3-B7DCmdbVzqiW3D1h3krdeqmu9B0mfYOaa6HehODg,913
|
||||
hamcrest/library/collection/isdict_containing.py,sha256=6QxDtDp_Z2TK-6om8cHnJDh45YdmaNHAEy5n97rzf00,2056
|
||||
hamcrest/library/collection/isdict_containingentries.py,sha256=xKtdFjrwLN32rUdRR4PBUSYa3yACa6jXsmlZv0D9YAU,5168
|
||||
hamcrest/library/collection/isdict_containingkey.py,sha256=aiBpusjpZmkUEMZ_rUFZBB1GxIfsqluMjhXoWNScqZY,1535
|
||||
hamcrest/library/collection/isdict_containingvalue.py,sha256=N7mHKgMnd7q6HsQekHH6DShNYbLiSXN9cpQgcdMIjlw,1565
|
||||
hamcrest/library/collection/isin.py,sha256=bcVslW0fUq0pM_SrT9gdltTlNfeJkrVPZAlg6riV2Ys,774
|
||||
hamcrest/library/collection/issequence_containing.py,sha256=ZwYMm2-Ul_JtvjcgdMjipYdz82yUbGmRNsdPAjO9RX0,3001
|
||||
hamcrest/library/collection/issequence_containinginanyorder.py,sha256=Px_W2-_0XDOXiL2NTTMp16ZTZvBl4m5rXjlMoR4Ulmw,3613
|
||||
hamcrest/library/collection/issequence_containinginorder.py,sha256=x7AT_kOCaPY0LmZw28ln8xLLHBtT-I3NneoCWzMJoYA,3219
|
||||
hamcrest/library/collection/issequence_onlycontaining.py,sha256=Ia17P1HVgb43lZfdUEhmPyUUUWtGLix__fqXIQJTUiI,1626
|
||||
hamcrest/library/integration/__init__.py,sha256=3aiupojVacPksKTXVhqRs9OwUDoUlUw-bjWItJnRg8Q,254
|
||||
hamcrest/library/integration/match_equality.py,sha256=0BMth20YLTqjqTLT4qMVldAe2dQV3CJ2j3zLXDIGl9c,1192
|
||||
hamcrest/library/number/__init__.py,sha256=J3UoFdR9UPq9zXSKe1a9qAlpjaVst8-pnaxsvbCPj78,335
|
||||
hamcrest/library/number/iscloseto.py,sha256=2lQTw3Xvo5MW-aePxpWVUOwyV_ydXtH6cCAIAxeopk8,2259
|
||||
hamcrest/library/number/ordering_comparison.py,sha256=8XxVSOzPK29D14h4wtBZYVYKZf6IcABaQEKihEuzlhI,1700
|
||||
hamcrest/library/object/__init__.py,sha256=pxzCpybBHRaIg7RJUAw7R1Po0llw8QBbVv_R1TXNBhc,319
|
||||
hamcrest/library/object/haslength.py,sha256=mpYVvrBZV548FwEeqlHWYofv9LPgChvnypZ4RhZDMp0,1681
|
||||
hamcrest/library/object/hasproperty.py,sha256=9t8upZxjqSQp6IvGyTm4ftGvcpBeyk0dy36HgvlXZBg,5740
|
||||
hamcrest/library/object/hasstring.py,sha256=_Ht1x-DwV4hk2fRuGo_KoayoLOIoWObKoA30u7HnABU,1250
|
||||
hamcrest/library/text/__init__.py,sha256=3Uuy1lY2p0VEUy1SAIO6IZgDDgyx8ZsB98k-J2FA_R0,548
|
||||
hamcrest/library/text/isequal_ignoring_case.py,sha256=VGkR3PNDOVE1MJT9H4a3SjR2SIYNzT80VoV6NEq3aqw,1257
|
||||
hamcrest/library/text/isequal_ignoring_whitespace.py,sha256=NQzswc7fk5rOhcoHO4zbYSWqn-WQdQ7Hwf0FoDHAwBM,1667
|
||||
hamcrest/library/text/stringcontains.py,sha256=JbkSxdFkpRrNYJfSUghboxe1jRLfHAJvOn6PYvMx7fQ,953
|
||||
hamcrest/library/text/stringcontainsinorder.py,sha256=B3qG7TG24_WyVPGJER2iqi7fzOMZN0UsBRJPtWutBkc,1705
|
||||
hamcrest/library/text/stringendswith.py,sha256=JjukJWSVWgURvTstrbCGCQdQzY0PMWLul__UlDh2NGA,980
|
||||
hamcrest/library/text/stringmatches.py,sha256=AEBn8NI3q-YzRUdXiAfnw1Kmse-LLxJUluDAuy_D9nU,1151
|
||||
hamcrest/library/text/stringstartswith.py,sha256=oOro8G3z8nAOUyOjHHkHhUvY-lt7XRTJzDr9dYxxons,1007
|
||||
hamcrest/library/text/substringmatcher.py,sha256=lSPxE7pTpTJlUkMtd28WgsM1FFm56JVZixSvAldSZXk,695
|
||||
PyHamcrest-1.9.0.dist-info/DESCRIPTION.rst,sha256=13XTDh2baR2aJ91v_lAOjEXD7Ydush_RHhk0Z3azfs8,11973
|
||||
PyHamcrest-1.9.0.dist-info/METADATA,sha256=s0naXZHUZhft0MWcUeZVaZYlREKPOqHp3mUQrhUoC6s,13276
|
||||
PyHamcrest-1.9.0.dist-info/metadata.json,sha256=Jqs-ZSW5kWSnfvxDsRMwSQH6TxEqSJjBJWiQk1G-c_A,1545
|
||||
PyHamcrest-1.9.0.dist-info/pbr.json,sha256=jdAcCmfO0nnMs9-YKXuwDyKnxc4qGwyUxyGulF9Pam4,47
|
||||
PyHamcrest-1.9.0.dist-info/RECORD,,
|
||||
PyHamcrest-1.9.0.dist-info/top_level.txt,sha256=mRc0yPsPQSqFgWBmZBY33u-05Xtm5M4GEve4NjYdloQ,9
|
||||
PyHamcrest-1.9.0.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110
|
||||
PyHamcrest-1.9.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
hamcrest/core/core/__pycache__/issame.cpython-37.pyc,,
|
||||
hamcrest/core/core/__pycache__/isinstanceof.cpython-37.pyc,,
|
||||
hamcrest/core/core/__pycache__/raises.cpython-37.pyc,,
|
||||
hamcrest/core/core/__pycache__/is_.cpython-37.pyc,,
|
||||
hamcrest/core/core/__pycache__/isanything.cpython-37.pyc,,
|
||||
hamcrest/core/core/__pycache__/isnone.cpython-37.pyc,,
|
||||
hamcrest/core/core/__pycache__/isnot.cpython-37.pyc,,
|
||||
hamcrest/core/core/__pycache__/anyof.cpython-37.pyc,,
|
||||
hamcrest/core/core/__pycache__/allof.cpython-37.pyc,,
|
||||
hamcrest/core/core/__pycache__/__init__.cpython-37.pyc,,
|
||||
hamcrest/core/core/__pycache__/described_as.cpython-37.pyc,,
|
||||
hamcrest/core/core/__pycache__/isequal.cpython-37.pyc,,
|
||||
hamcrest/core/__pycache__/string_description.cpython-37.pyc,,
|
||||
hamcrest/core/__pycache__/base_matcher.cpython-37.pyc,,
|
||||
hamcrest/core/__pycache__/compat.cpython-37.pyc,,
|
||||
hamcrest/core/__pycache__/assert_that.cpython-37.pyc,,
|
||||
hamcrest/core/__pycache__/selfdescribing.cpython-37.pyc,,
|
||||
hamcrest/core/__pycache__/selfdescribingvalue.cpython-37.pyc,,
|
||||
hamcrest/core/__pycache__/matcher.cpython-37.pyc,,
|
||||
hamcrest/core/__pycache__/base_description.cpython-37.pyc,,
|
||||
hamcrest/core/__pycache__/description.cpython-37.pyc,,
|
||||
hamcrest/core/__pycache__/__init__.cpython-37.pyc,,
|
||||
hamcrest/core/helpers/__pycache__/hasmethod.cpython-37.pyc,,
|
||||
hamcrest/core/helpers/__pycache__/wrap_matcher.cpython-37.pyc,,
|
||||
hamcrest/core/helpers/__pycache__/__init__.cpython-37.pyc,,
|
||||
hamcrest/library/collection/__pycache__/issequence_containinginanyorder.cpython-37.pyc,,
|
||||
hamcrest/library/collection/__pycache__/issequence_containing.cpython-37.pyc,,
|
||||
hamcrest/library/collection/__pycache__/issequence_containinginorder.cpython-37.pyc,,
|
||||
hamcrest/library/collection/__pycache__/isdict_containingentries.cpython-37.pyc,,
|
||||
hamcrest/library/collection/__pycache__/isdict_containing.cpython-37.pyc,,
|
||||
hamcrest/library/collection/__pycache__/isdict_containingkey.cpython-37.pyc,,
|
||||
hamcrest/library/collection/__pycache__/is_empty.cpython-37.pyc,,
|
||||
hamcrest/library/collection/__pycache__/issequence_onlycontaining.cpython-37.pyc,,
|
||||
hamcrest/library/collection/__pycache__/isin.cpython-37.pyc,,
|
||||
hamcrest/library/collection/__pycache__/isdict_containingvalue.cpython-37.pyc,,
|
||||
hamcrest/library/collection/__pycache__/__init__.cpython-37.pyc,,
|
||||
hamcrest/library/integration/__pycache__/match_equality.cpython-37.pyc,,
|
||||
hamcrest/library/integration/__pycache__/__init__.cpython-37.pyc,,
|
||||
hamcrest/library/__pycache__/__init__.cpython-37.pyc,,
|
||||
hamcrest/library/number/__pycache__/ordering_comparison.cpython-37.pyc,,
|
||||
hamcrest/library/number/__pycache__/__init__.cpython-37.pyc,,
|
||||
hamcrest/library/number/__pycache__/iscloseto.cpython-37.pyc,,
|
||||
hamcrest/library/object/__pycache__/hasstring.cpython-37.pyc,,
|
||||
hamcrest/library/object/__pycache__/haslength.cpython-37.pyc,,
|
||||
hamcrest/library/object/__pycache__/hasproperty.cpython-37.pyc,,
|
||||
hamcrest/library/object/__pycache__/__init__.cpython-37.pyc,,
|
||||
hamcrest/library/text/__pycache__/stringendswith.cpython-37.pyc,,
|
||||
hamcrest/library/text/__pycache__/stringmatches.cpython-37.pyc,,
|
||||
hamcrest/library/text/__pycache__/stringstartswith.cpython-37.pyc,,
|
||||
hamcrest/library/text/__pycache__/substringmatcher.cpython-37.pyc,,
|
||||
hamcrest/library/text/__pycache__/stringcontainsinorder.cpython-37.pyc,,
|
||||
hamcrest/library/text/__pycache__/isequal_ignoring_whitespace.cpython-37.pyc,,
|
||||
hamcrest/library/text/__pycache__/stringcontains.cpython-37.pyc,,
|
||||
hamcrest/library/text/__pycache__/isequal_ignoring_case.cpython-37.pyc,,
|
||||
hamcrest/library/text/__pycache__/__init__.cpython-37.pyc,,
|
||||
hamcrest/__pycache__/__init__.cpython-37.pyc,,
|
@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.24.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
@ -0,0 +1 @@
|
||||
{"license": "New BSD", "download_url": "http://pypi.python.org/packages/source/P/PyHamcrest/PyHamcrest-1.9.0.tar.gz", "name": "PyHamcrest", "provides": "hamcrest", "test_requires": [{"requires": ["hypothesis (>=1.11)", "pytest (>=2.8)", "mock", "pytest-cov"]}], "extensions": {"python.details": {"project_urls": {"Home": "https://github.com/hamcrest/PyHamcrest"}, "contacts": [{"name": "Chris Rose", "role": "author", "email": "offline@offby1.net"}], "document_names": {"description": "DESCRIPTION.rst"}}}, "run_requires": [{"requires": ["setuptools", "six"]}], "generator": "bdist_wheel (0.24.0)", "summary": "Hamcrest framework for matcher objects", "extras": [], "classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: Jython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development", "Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Testing"], "version": "1.9.0", "metadata_version": "2.0", "keywords": ["hamcrest", "matchers", "pyunit", "unit", "test", "testing", "unittest", "unittesting"], "platform": "All"}
|
@ -0,0 +1 @@
|
||||
pip
|
@ -0,0 +1,35 @@
|
||||
jwt/__init__.py,sha256=zzpUkNjnVRNWZKLBgn-t3fR3IWVdCWekrAKtsZWkCoQ,810
|
||||
jwt/__main__.py,sha256=_rMsGakpyw1N023P8QOjCgbCxhXSCNIg92YpmUhQGMk,4162
|
||||
jwt/algorithms.py,sha256=kL1ARjxNL8JeuxEpWS8On14qJWomMX_A_ncIrnZhBrA,13336
|
||||
jwt/api_jws.py,sha256=wQxbg_cYR4hAJl4-9Ijf29B46NrOKhruXS7ANPFqkZ8,8095
|
||||
jwt/api_jwt.py,sha256=NKRiCsTcMd0B5N-74zvqBYpQuxBxC4f6TCLM6P0jxVU,7905
|
||||
jwt/compat.py,sha256=VG2zhmZFQ5spP0AThSVumRogymUXORz6fxA1jTew-cA,1624
|
||||
jwt/exceptions.py,sha256=kGq96NMkyPBmx7-RXvLXq9ddTo2_SJPKPTpPscvGUuA,986
|
||||
jwt/help.py,sha256=w9sYBatZK8-DIAxLPsdxQBVHXnqjOTETJ4dFY5hhEHs,1609
|
||||
jwt/utils.py,sha256=RraFiloy_xsB8NA1CrlHxS9lR73If8amInQ3P1mKXeM,2629
|
||||
jwt/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
jwt/contrib/algorithms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
jwt/contrib/algorithms/py_ecdsa.py,sha256=tSTUrwx-u14DJcqAChRzJG-wf7bEY2Gv2hI5xSZZNjk,1771
|
||||
jwt/contrib/algorithms/pycrypto.py,sha256=mU3vRfk9QKj06ky3XXKXNkxv8-R4mBHbFR3EvbOgJ6k,1249
|
||||
PyJWT-1.7.1.dist-info/AUTHORS,sha256=rahh5ZJ3f4RSF4X1_K1DvxTRm4Hy45QiMP7dDG_-yrE,595
|
||||
PyJWT-1.7.1.dist-info/LICENSE,sha256=7IKvgVtfnahoWvswDMW-t5SeHCK3m2wcBUeWzv32ysY,1080
|
||||
PyJWT-1.7.1.dist-info/METADATA,sha256=wIohFuzbkeGUiMkkD5U98z520aUoY6UnmsfDl4vVHRI,3878
|
||||
PyJWT-1.7.1.dist-info/WHEEL,sha256=_wJFdOYk7i3xxT8ElOkUJvOdOvfNGbR9g-bf6UQT6sU,110
|
||||
PyJWT-1.7.1.dist-info/entry_points.txt,sha256=Xl_tLkGbTgywYa7PwaEY2xSiCtVtM2PdHTL4CW_n9dM,45
|
||||
PyJWT-1.7.1.dist-info/top_level.txt,sha256=RP5DHNyJbMq2ka0FmfTgoSaQzh7e3r5XuCWCO8a00k8,4
|
||||
PyJWT-1.7.1.dist-info/RECORD,,
|
||||
../../../bin/pyjwt,sha256=_ql8FxEP66OfxPNUZ3I2dUPYQkrWDYSMH2EWsj-symU,265
|
||||
PyJWT-1.7.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
jwt/__pycache__/help.cpython-37.pyc,,
|
||||
jwt/__pycache__/exceptions.cpython-37.pyc,,
|
||||
jwt/__pycache__/algorithms.cpython-37.pyc,,
|
||||
jwt/__pycache__/compat.cpython-37.pyc,,
|
||||
jwt/__pycache__/__main__.cpython-37.pyc,,
|
||||
jwt/__pycache__/utils.cpython-37.pyc,,
|
||||
jwt/__pycache__/api_jwt.cpython-37.pyc,,
|
||||
jwt/__pycache__/__init__.cpython-37.pyc,,
|
||||
jwt/__pycache__/api_jws.cpython-37.pyc,,
|
||||
jwt/contrib/algorithms/__pycache__/py_ecdsa.cpython-37.pyc,,
|
||||
jwt/contrib/algorithms/__pycache__/pycrypto.cpython-37.pyc,,
|
||||
jwt/contrib/algorithms/__pycache__/__init__.cpython-37.pyc,,
|
||||
jwt/contrib/__pycache__/__init__.cpython-37.pyc,,
|
@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.32.3)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
@ -0,0 +1,135 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: Twisted
|
||||
Version: 19.10.0
|
||||
Summary: An asynchronous networking framework written in Python
|
||||
Home-page: https://twistedmatrix.com/
|
||||
Author: Twisted Matrix Laboratories
|
||||
Author-email: twisted-python@twistedmatrix.com
|
||||
Maintainer: Glyph Lefkowitz
|
||||
Maintainer-email: glyph@twistedmatrix.com
|
||||
License: MIT
|
||||
Project-URL: Documentation, https://twistedmatrix.com/documents/current/
|
||||
Project-URL: Source, https://github.com/twisted/twisted
|
||||
Project-URL: Issues, https://twistedmatrix.com/trac/report
|
||||
Description: Twisted
|
||||
=======
|
||||
|
||||
|pypi|_
|
||||
|travis|_
|
||||
|circleci|_
|
||||
|
||||
For information on changes in this release, see the `NEWS <https://github.com/twisted/twisted/blob/trunk/NEWS.rst>`_ file.
|
||||
|
||||
|
||||
What is this?
|
||||
-------------
|
||||
|
||||
Twisted is an event-based framework for internet applications, supporting Python 2.7 and Python 3.5+.
|
||||
It includes modules for many different purposes, including the following:
|
||||
|
||||
- ``twisted.web``: HTTP clients and servers, HTML templating, and a WSGI server
|
||||
- ``twisted.conch``: SSHv2 and Telnet clients and servers and terminal emulators
|
||||
- ``twisted.words``: Clients and servers for IRC, XMPP, and other IM protocols
|
||||
- ``twisted.mail``: IMAPv4, POP3, SMTP clients and servers
|
||||
- ``twisted.positioning``: Tools for communicating with NMEA-compatible GPS receivers
|
||||
- ``twisted.names``: DNS client and tools for making your own DNS servers
|
||||
- ``twisted.trial``: A unit testing framework that integrates well with Twisted-based code.
|
||||
|
||||
Twisted supports all major system event loops -- ``select`` (all platforms), ``poll`` (most POSIX platforms), ``epoll`` (Linux), ``kqueue`` (FreeBSD, macOS), IOCP (Windows), and various GUI event loops (GTK+2/3, Qt, wxWidgets).
|
||||
Third-party reactors can plug into Twisted, and provide support for additional event loops.
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
To install the latest version of Twisted using pip::
|
||||
|
||||
$ pip install twisted
|
||||
|
||||
Additional instructions for installing this software are in `the installation instructions <https://github.com/twisted/twisted/blob/trunk/INSTALL.rst>`_.
|
||||
|
||||
|
||||
Documentation and Support
|
||||
-------------------------
|
||||
|
||||
Twisted's documentation is available from the `Twisted Matrix website <https://twistedmatrix.com/documents/current/>`_.
|
||||
This documentation contains how-tos, code examples, and an API reference.
|
||||
|
||||
Help is also available on the `Twisted mailing list <https://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python>`_.
|
||||
|
||||
There is also a pair of very lively IRC channels, ``#twisted`` (for general Twisted questions) and ``#twisted.web`` (for Twisted Web), on ``chat.freenode.net``.
|
||||
|
||||
|
||||
Unit Tests
|
||||
----------
|
||||
|
||||
Twisted has a comprehensive test suite, which can be run by ``tox``::
|
||||
|
||||
$ tox -l # to view all test environments
|
||||
$ tox -e py27-tests # to run the tests for Python 2.7
|
||||
$ tox -e py35-tests # to run the tests for Python 3.5
|
||||
|
||||
|
||||
You can test running the test suite under the different reactors with the ``TWISTED_REACTOR`` environment variable::
|
||||
|
||||
$ env TWISTED_REACTOR=epoll tox -e py27-tests
|
||||
|
||||
|
||||
Some of these tests may fail if you:
|
||||
|
||||
* don't have the dependencies required for a particular subsystem installed,
|
||||
* have a firewall blocking some ports (or things like Multicast, which Linux NAT has shown itself to do), or
|
||||
* run them as root.
|
||||
|
||||
|
||||
Copyright
|
||||
---------
|
||||
|
||||
All of the code in this distribution is Copyright (c) 2001-2019 Twisted Matrix Laboratories.
|
||||
|
||||
Twisted is made available under the MIT license.
|
||||
The included `LICENSE <https://github.com/twisted/twisted/blob/trunk/LICENSE>`_ file describes this in detail.
|
||||
|
||||
|
||||
Warranty
|
||||
--------
|
||||
|
||||
THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE USE OF THIS SOFTWARE IS WITH YOU.
|
||||
|
||||
IN NO EVENT WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY, BE LIABLE TO YOU FOR ANY DAMAGES, EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
Again, see the included `LICENSE <https://github.com/twisted/twisted/blob/trunk/LICENSE>`_ file for specific legal details.
|
||||
|
||||
|
||||
.. |pypi| image:: http://img.shields.io/pypi/v/twisted.svg
|
||||
.. _pypi: https://pypi.python.org/pypi/twisted
|
||||
|
||||
.. |travis| image:: https://travis-ci.org/twisted/twisted.svg?branch=trunk
|
||||
.. _travis: https://travis-ci.org/twisted/twisted
|
||||
|
||||
.. |circleci| image:: https://circleci.com/gh/twisted/twisted.svg?style=svg
|
||||
.. _circleci: https://circleci.com/gh/twisted/twisted
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Description-Content-Type: text/x-rst
|
||||
Provides-Extra: soap
|
||||
Provides-Extra: conch
|
||||
Provides-Extra: windows_platform
|
||||
Provides-Extra: tls
|
||||
Provides-Extra: dev
|
||||
Provides-Extra: all_non_platform
|
||||
Provides-Extra: macos_platform
|
||||
Provides-Extra: serial
|
||||
Provides-Extra: osx_platform
|
||||
Provides-Extra: http2
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1 @@
|
||||
twisted
|
@ -0,0 +1,119 @@
|
||||
werkzeug/__init__.py,sha256=tTlHx8lI6FpqB_X9x_zICTy3Rgikur6yIUJr8AE2XTs,7141
|
||||
werkzeug/_compat.py,sha256=oBEVVrJT4sqYdIZbUWmgV9T9w257RhTSDBlTjh0Zbb0,6431
|
||||
werkzeug/_internal.py,sha256=Wx7cpTRWqeBd0LAqobo0lCO4pNUW4oav6XKf7Taumgk,14590
|
||||
werkzeug/_reloader.py,sha256=I3mg3oRQ0lLzl06oEoVopN3bN7CtINuuUQdqDcmTnEs,11531
|
||||
werkzeug/datastructures.py,sha256=yVH4r-XD8CjOo18tDGVJYiAfezng6pK9hWzzLFy5a94,91761
|
||||
werkzeug/exceptions.py,sha256=7wl3ufZZU23sASp0ciPe8GJssGND9DX6sDbjxvPuGYU,23437
|
||||
werkzeug/filesystem.py,sha256=HzKl-j0Hd8Jl66j778UbPTAYNnY6vUZgYLlBZ0e7uw0,2101
|
||||
werkzeug/formparser.py,sha256=Sto0jZid9im9ZVIf56vilCdyX-arK33wSftkYsLCnzo,21788
|
||||
werkzeug/http.py,sha256=L6r2ehiorjOtsXITW-01zJsvtVa8Emkpkftu9di_cSk,41628
|
||||
werkzeug/local.py,sha256=USVEcgIg-oCiUJFPIecFIW9jkIejfw4Fjf1u5yN-Np4,14456
|
||||
werkzeug/posixemulation.py,sha256=gSSiv1SCmOyzOM_nq1ZaZCtxP__C5MeDJl_4yXJmi4Q,3541
|
||||
werkzeug/routing.py,sha256=BSgjrYNwj2j5dAHQtK4INEp2TOf4OJP8hBncYSRO2ps,73410
|
||||
werkzeug/security.py,sha256=81149MplFq7-hD4RK4sKp9kzXXejjV9D4lWBzaRyeQ8,8106
|
||||
werkzeug/serving.py,sha256=qqdsTMILMt_B8ffBtROWK3RRpZeyTkQ9g-jhtpJodrY,36607
|
||||
werkzeug/test.py,sha256=Cnb5xa3vLDL0hzFCH1fkG_YRpndViGQgCh4D744iSQk,40645
|
||||
werkzeug/testapp.py,sha256=bHekqMsqRfVxwgFbvOMem-DYa_sdB7R47yUXpt1RUTo,9329
|
||||
werkzeug/urls.py,sha256=hWZMk4ABiJmQPP_B5rRibWTp9gOyNLQpTqq6cmQAfeE,39322
|
||||
werkzeug/useragents.py,sha256=0A_Ip74edPv_hy6CouBTpGumi2uyOci01COuzYFOm3U,5622
|
||||
werkzeug/utils.py,sha256=KxCOHhsox7tAVe0m-ZyOGPoCaIbBIy7TxhocaUEHrd4,25050
|
||||
werkzeug/wsgi.py,sha256=iXOR9l1fDd2IgqeTRQZPR6LnBBBx7Xsy97_i2n5HPUo,34666
|
||||
werkzeug/contrib/__init__.py,sha256=EvNyiiCF49j5P0fZYJ3ZGe82ofXdSBvUNqWFwwBMibQ,553
|
||||
werkzeug/contrib/atom.py,sha256=KpPJcTfzNW1J0VNQckCbVtVGBe3V8s451tOUya4qByI,15415
|
||||
werkzeug/contrib/cache.py,sha256=AEh5UIw-Ui7sHZnlpvrD7ueOKUhCaAD55FXiPtXbbRs,32115
|
||||
werkzeug/contrib/fixers.py,sha256=peEtAiIWYT5bh00EWEPOGKzGZXivOzVhhzKPvvzk1RM,9193
|
||||
werkzeug/contrib/iterio.py,sha256=KKHa_8aCF_uhoeQVyPGUwrivuB6y6nNdXYo2D2vzOA8,10928
|
||||
werkzeug/contrib/lint.py,sha256=NdIxP0E2kVt1xDIxoaIz3Rcl8ZdgmHaFbGTOaybGpN4,296
|
||||
werkzeug/contrib/profiler.py,sha256=k_oMLU-AtsVvQ9TxNdermY6FuzSTYr-WE-ZmWb_DMyU,1229
|
||||
werkzeug/contrib/securecookie.py,sha256=xbtElskGmtbiApgOJ5WhGgqGDs_68_PcWzqDIAY_QZY,13076
|
||||
werkzeug/contrib/sessions.py,sha256=CkJ4IWvNqIaZCP83FMKYFszKL7E6Y1m6YEii7RaTYWs,13040
|
||||
werkzeug/contrib/wrappers.py,sha256=ZmNk0wpzD66yomPnQxapndZQs4c0kNJaRzqI-BVxeQk,13199
|
||||
werkzeug/debug/__init__.py,sha256=Bo3HvgTNY4NQ_2jROTSk3r1ScZcT_g_4EnuHTjKyrKM,18275
|
||||
werkzeug/debug/console.py,sha256=HoBL21bbcmtiCLqiLDJLZi1LYnWMZxjoXYH5WaZB1XY,5469
|
||||
werkzeug/debug/repr.py,sha256=lIwuhbyrMwVe3P_cFqNyqzHL7P93TLKod7lw9clydEw,9621
|
||||
werkzeug/debug/tbtools.py,sha256=SkAAA4KKfwsXJinUbf-AEP4GqONTsR4uU7WPUloXcSE,20318
|
||||
werkzeug/debug/shared/FONT_LICENSE,sha256=LwAVEI1oYnvXiNMT9SnCH_TaLCxCpeHziDrMg0gPkAI,4673
|
||||
werkzeug/debug/shared/console.png,sha256=bxax6RXXlvOij_KeqvSNX0ojJf83YbnZ7my-3Gx9w2A,507
|
||||
werkzeug/debug/shared/debugger.js,sha256=rOhqZMRfpZnnu6_XCGn6wMWPhtfwRAcyZKksdIxPJas,6400
|
||||
werkzeug/debug/shared/jquery.js,sha256=CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo,88145
|
||||
werkzeug/debug/shared/less.png,sha256=-4-kNRaXJSONVLahrQKUxMwXGm9R4OnZ9SxDGpHlIR4,191
|
||||
werkzeug/debug/shared/more.png,sha256=GngN7CioHQoV58rH6ojnkYi8c_qED2Aka5FO5UXrReY,200
|
||||
werkzeug/debug/shared/source.png,sha256=RoGcBTE4CyCB85GBuDGTFlAnUqxwTBiIfDqW15EpnUQ,818
|
||||
werkzeug/debug/shared/style.css,sha256=gZ9uhmb5zj3XLuT9RvnMp6jMINgQ-VVBCp-2AZbG3YQ,6604
|
||||
werkzeug/debug/shared/ubuntu.ttf,sha256=1eaHFyepmy4FyDvjLVzpITrGEBu_CZYY94jE0nED1c0,70220
|
||||
werkzeug/middleware/__init__.py,sha256=f1SFZo67IlW4k1uqKzNHxYQlsakUS-D6KK_j0e3jjwQ,549
|
||||
werkzeug/middleware/dispatcher.py,sha256=_-KoMzHtcISHS7ouWKAOraqlCLprdh83YOAn_8DjLp8,2240
|
||||
werkzeug/middleware/http_proxy.py,sha256=lRjTdMmghHiZuZrS7_UJ3gZc-vlFizhBbFZ-XZPLwIA,7117
|
||||
werkzeug/middleware/lint.py,sha256=ItTwuWJnflF8xMT1uqU_Ty1ryhux-CjeUfskqaUpxsw,12967
|
||||
werkzeug/middleware/profiler.py,sha256=8B_s23d6BGrU_q54gJsm6kcCbOJbTSqrXCsioHON0Xs,4471
|
||||
werkzeug/middleware/proxy_fix.py,sha256=1hi6AJH-J2uh2hMm1g0u7XfjRiTOoUeIOOmwWZ2n9t0,8670
|
||||
werkzeug/middleware/shared_data.py,sha256=WtSphPrsUdpEk4E-_09CAILhfOBJ1YtcX1LrxcQfIzw,8224
|
||||
werkzeug/wrappers/__init__.py,sha256=S4VioKAmF_av9Ec9zQvG71X1EOkYfPx1TYck9jyDiyY,1384
|
||||
werkzeug/wrappers/accept.py,sha256=TIvjUc0g73fhTWX54wg_D9NNzKvpnG1X8u1w26tK1o8,1760
|
||||
werkzeug/wrappers/auth.py,sha256=Pmn6iaGHBrUyHbJpW0lZhO_q9RVoAa5QalaTqcavdAI,1158
|
||||
werkzeug/wrappers/base_request.py,sha256=aknREwqVT7WJUxm4weUGdBj90H6rDR3DvsIvmYhaC8A,26943
|
||||
werkzeug/wrappers/base_response.py,sha256=ZA1XlxtsbvG4SpbdOEMT5--z7aZM0w6C5y33W8wOXa4,27906
|
||||
werkzeug/wrappers/common_descriptors.py,sha256=OJ8jOwMun4L-BxCuFPkK1vaefx_-Y5IndVXvvn_ems4,12089
|
||||
werkzeug/wrappers/etag.py,sha256=TwMO1fvluXbBqnFTj2DvrCNa3mYhbHYe1UZAVzfXvuU,12533
|
||||
werkzeug/wrappers/json.py,sha256=HvK_A4NpO0sLqgb10sTJcoZydYOwyNiPCJPV7SVgcgE,4343
|
||||
werkzeug/wrappers/request.py,sha256=qPo2zmmBv4HxboywtWZb2pJL8OPXo07BUXBKw2j9Fi8,1338
|
||||
werkzeug/wrappers/response.py,sha256=vDZFEGzDOG0jjmS0uVVjeT3hqRt1hFaf15npnx7RD28,2329
|
||||
werkzeug/wrappers/user_agent.py,sha256=YJb-vr12cujG7sQMG9V89VsJa-03SWSenhg1W4cT0EY,435
|
||||
Werkzeug-0.16.0.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
|
||||
Werkzeug-0.16.0.dist-info/METADATA,sha256=BH9_q8z1IK2FbYDS7tSWLsd07z7GDReBgRumclV7T08,4712
|
||||
Werkzeug-0.16.0.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110
|
||||
Werkzeug-0.16.0.dist-info/top_level.txt,sha256=QRyj2VjwJoQkrwjwFIOlB8Xg3r9un0NtqVHQF-15xaw,9
|
||||
Werkzeug-0.16.0.dist-info/RECORD,,
|
||||
Werkzeug-0.16.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
werkzeug/middleware/__pycache__/http_proxy.cpython-37.pyc,,
|
||||
werkzeug/middleware/__pycache__/lint.cpython-37.pyc,,
|
||||
werkzeug/middleware/__pycache__/dispatcher.cpython-37.pyc,,
|
||||
werkzeug/middleware/__pycache__/profiler.cpython-37.pyc,,
|
||||
werkzeug/middleware/__pycache__/shared_data.cpython-37.pyc,,
|
||||
werkzeug/middleware/__pycache__/proxy_fix.cpython-37.pyc,,
|
||||
werkzeug/middleware/__pycache__/__init__.cpython-37.pyc,,
|
||||
werkzeug/wrappers/__pycache__/response.cpython-37.pyc,,
|
||||
werkzeug/wrappers/__pycache__/base_response.cpython-37.pyc,,
|
||||
werkzeug/wrappers/__pycache__/request.cpython-37.pyc,,
|
||||
werkzeug/wrappers/__pycache__/base_request.cpython-37.pyc,,
|
||||
werkzeug/wrappers/__pycache__/auth.cpython-37.pyc,,
|
||||
werkzeug/wrappers/__pycache__/user_agent.cpython-37.pyc,,
|
||||
werkzeug/wrappers/__pycache__/etag.cpython-37.pyc,,
|
||||
werkzeug/wrappers/__pycache__/json.cpython-37.pyc,,
|
||||
werkzeug/wrappers/__pycache__/__init__.cpython-37.pyc,,
|
||||
werkzeug/wrappers/__pycache__/common_descriptors.cpython-37.pyc,,
|
||||
werkzeug/wrappers/__pycache__/accept.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/posixemulation.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/datastructures.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/exceptions.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/useragents.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/filesystem.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/serving.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/_internal.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/security.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/_compat.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/testapp.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/local.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/_reloader.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/routing.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/wsgi.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/utils.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/http.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/formparser.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/urls.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/test.cpython-37.pyc,,
|
||||
werkzeug/__pycache__/__init__.cpython-37.pyc,,
|
||||
werkzeug/contrib/__pycache__/sessions.cpython-37.pyc,,
|
||||
werkzeug/contrib/__pycache__/lint.cpython-37.pyc,,
|
||||
werkzeug/contrib/__pycache__/iterio.cpython-37.pyc,,
|
||||
werkzeug/contrib/__pycache__/profiler.cpython-37.pyc,,
|
||||
werkzeug/contrib/__pycache__/fixers.cpython-37.pyc,,
|
||||
werkzeug/contrib/__pycache__/securecookie.cpython-37.pyc,,
|
||||
werkzeug/contrib/__pycache__/cache.cpython-37.pyc,,
|
||||
werkzeug/contrib/__pycache__/wrappers.cpython-37.pyc,,
|
||||
werkzeug/contrib/__pycache__/__init__.cpython-37.pyc,,
|
||||
werkzeug/contrib/__pycache__/atom.cpython-37.pyc,,
|
||||
werkzeug/debug/__pycache__/repr.cpython-37.pyc,,
|
||||
werkzeug/debug/__pycache__/console.cpython-37.pyc,,
|
||||
werkzeug/debug/__pycache__/tbtools.cpython-37.pyc,,
|
||||
werkzeug/debug/__pycache__/__init__.cpython-37.pyc,,
|
@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.33.6)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
@ -0,0 +1,27 @@
|
||||
Copyright (c) Django Software Foundation and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of Django nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,225 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: asgiref
|
||||
Version: 3.2.3
|
||||
Summary: ASGI specs, helper code, and adapters
|
||||
Home-page: http://github.com/django/asgiref/
|
||||
Author: Django Software Foundation
|
||||
Author-email: foundation@djangoproject.com
|
||||
License: BSD
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Topic :: Internet :: WWW/HTTP
|
||||
Description-Content-Type: text/x-rst
|
||||
Provides-Extra: tests
|
||||
Requires-Dist: pytest (~=4.3.0) ; extra == 'tests'
|
||||
Requires-Dist: pytest-asyncio (~=0.10.0) ; extra == 'tests'
|
||||
|
||||
asgiref
|
||||
=======
|
||||
|
||||
.. image:: https://api.travis-ci.org/django/asgiref.svg
|
||||
:target: https://travis-ci.org/django/asgiref
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/asgiref.svg
|
||||
:target: https://pypi.python.org/pypi/asgiref
|
||||
|
||||
ASGI is a standard for Python asynchronous web apps and servers to communicate
|
||||
with each other, and positioned as an asynchronous successor to WSGI. You can
|
||||
read more at https://asgi.readthedocs.io/en/latest/
|
||||
|
||||
This package includes ASGI base libraries, such as:
|
||||
|
||||
* Sync-to-async and async-to-sync function wrappers, ``asgiref.sync``
|
||||
* Server base classes, ``asgiref.server``
|
||||
* A WSGI-to-ASGI adapter, in ``asgiref.wsgi``
|
||||
|
||||
|
||||
Function wrappers
|
||||
-----------------
|
||||
|
||||
These allow you to wrap or decorate async or sync functions to call them from
|
||||
the other style (so you can call async functions from a synchronous thread,
|
||||
or vice-versa).
|
||||
|
||||
In particular:
|
||||
|
||||
* AsyncToSync lets a synchronous subthread stop and wait while the async
|
||||
function is called on the main thread's event loop, and then control is
|
||||
returned to the thread when the async function is finished.
|
||||
|
||||
* SyncToAsync lets async code call a synchronous function, which is run in
|
||||
a threadpool and control returned to the async coroutine when the synchronous
|
||||
function completes.
|
||||
|
||||
The idea is to make it easier to call synchronous APIs from async code and
|
||||
asynchronous APIs from synchronous code so it's easier to transition code from
|
||||
one style to the other. In the case of Channels, we wrap the (synchronous)
|
||||
Django view system with SyncToAsync to allow it to run inside the (asynchronous)
|
||||
ASGI server.
|
||||
|
||||
Note that exactly what threads things run in is very specific, and aimed to
|
||||
keep maximum compatibility with old synchronous code. See
|
||||
"Synchronous code & Threads" below for a full explanation.
|
||||
|
||||
|
||||
Threadlocal replacement
|
||||
-----------------------
|
||||
|
||||
This is a drop-in replacement for ``threading.local`` that works with both
|
||||
threads and asyncio Tasks. Even better, it will proxy values through from a
|
||||
task-local context to a thread-local context when you use ``sync_to_async``
|
||||
to run things in a threadpool, and vice-versa for ``async_to_sync``.
|
||||
|
||||
If you instead want true thread- and task-safety, you can set
|
||||
``thread_critical`` on the Local object to ensure this instead.
|
||||
|
||||
|
||||
Server base classes
|
||||
-------------------
|
||||
|
||||
Includes a ``StatelessServer`` class which provides all the hard work of
|
||||
writing a stateless server (as in, does not handle direct incoming sockets
|
||||
but instead consumes external streams or sockets to work out what is happening).
|
||||
|
||||
An example of such a server would be a chatbot server that connects out to
|
||||
a central chat server and provides a "connection scope" per user chatting to
|
||||
it. There's only one actual connection, but the server has to separate things
|
||||
into several scopes for easier writing of the code.
|
||||
|
||||
You can see an example of this being used in `frequensgi <https://github.com/andrewgodwin/frequensgi>`_.
|
||||
|
||||
|
||||
WSGI-to-ASGI adapter
|
||||
--------------------
|
||||
|
||||
Allows you to wrap a WSGI application so it appears as a valid ASGI application.
|
||||
|
||||
Simply wrap it around your WSGI application like so::
|
||||
|
||||
asgi_application = WsgiToAsgi(wsgi_application)
|
||||
|
||||
The WSGI application will be run in a synchronous threadpool, and the wrapped
|
||||
ASGI application will be one that accepts ``http`` class messages.
|
||||
|
||||
Please note that not all extended features of WSGI may be supported (such as
|
||||
file handles for incoming POST bodies).
|
||||
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
``asgiref`` requires Python 3.5 or higher.
|
||||
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Please refer to the
|
||||
`main Channels contributing docs <https://github.com/django/channels/blob/master/CONTRIBUTING.rst>`_.
|
||||
|
||||
|
||||
Testing
|
||||
'''''''
|
||||
|
||||
To run tests, make sure you have installed the ``tests`` extra with the package::
|
||||
|
||||
cd asgiref/
|
||||
pip install -e .[tests]
|
||||
pytest
|
||||
|
||||
|
||||
Building the documentation
|
||||
''''''''''''''''''''''''''
|
||||
|
||||
The documentation uses `Sphinx <http://www.sphinx-doc.org>`_::
|
||||
|
||||
cd asgiref/docs/
|
||||
pip install sphinx
|
||||
|
||||
To build the docs, you can use the default tools::
|
||||
|
||||
sphinx-build -b html . _build/html # or `make html`, if you've got make set up
|
||||
cd _build/html
|
||||
python -m http.server
|
||||
|
||||
...or you can use ``sphinx-autobuild`` to run a server and rebuild/reload
|
||||
your documentation changes automatically::
|
||||
|
||||
pip install sphinx-autobuild
|
||||
sphinx-autobuild . _build/html
|
||||
|
||||
|
||||
Implementation Details
|
||||
----------------------
|
||||
|
||||
Synchronous code & threads
|
||||
''''''''''''''''''''''''''
|
||||
|
||||
The ``asgiref.sync`` module provides two wrappers that let you go between
|
||||
asynchronous and synchronous code at will, while taking care of the rough edges
|
||||
for you.
|
||||
|
||||
Unfortunately, the rough edges are numerous, and the code has to work especially
|
||||
hard to keep things in the same thread as much as possible. Notably, the
|
||||
restrictions we are working with are:
|
||||
|
||||
* All synchronous code called through ``SyncToAsync`` and marked with
|
||||
``thread_sensitive`` should run in the same thread as each other (and if the
|
||||
outer layer of the program is synchronous, the main thread)
|
||||
|
||||
* If a thread already has a running async loop, ``AsyncToSync`` can't run things
|
||||
on that loop if it's blocked on synchronous code that is above you in the
|
||||
call stack.
|
||||
|
||||
The first compromise you get to might be that ``thread_sensitive`` code should
|
||||
just run in the same thread and not spawn in a sub-thread, fulfilling the first
|
||||
restriction, but that immediately runs you into the second restriction.
|
||||
|
||||
The only real solution is to essentially have a variant of ThreadPoolExecutor
|
||||
that executes any ``thread_sensitive`` code on the outermost synchronous
|
||||
thread - either the main thread, or a single spawned subthread.
|
||||
|
||||
This means you now have two basic states:
|
||||
|
||||
* If the outermost layer of your program is synchronous, then all async code
|
||||
run through ``AsyncToSync`` will run in a per-call event loop in arbitary
|
||||
sub-threads, while all ``thread_sensitive`` code will run in the main thread.
|
||||
|
||||
* If the outermost layer of your program is asynchronous, then all async code
|
||||
runs on the main thread's event loop, and all ``thread_sensitive`` synchronous
|
||||
code will run in a single shared sub-thread.
|
||||
|
||||
Cruicially, this means that in both cases there is a thread which is a shared
|
||||
resource that all ``thread_sensitive`` code must run on, and there is a chance
|
||||
that this thread is currently blocked on its own ``AsyncToSync`` call. Thus,
|
||||
``AsyncToSync`` needs to act as an executor for thread code while it's blocking.
|
||||
|
||||
The ``CurrentThreadExecutor`` class provides this functionality; rather than
|
||||
simply waiting on a Future, you can call its ``run_until_future`` method and
|
||||
it will run submitted code until that Future is done. This means that code
|
||||
inside the call can then run code on your thread.
|
||||
|
||||
|
||||
Maintenance and Security
|
||||
------------------------
|
||||
|
||||
To report security issues, please contact security@djangoproject.com. For GPG
|
||||
signatures and more security process information, see
|
||||
https://docs.djangoproject.com/en/dev/internals/security/.
|
||||
|
||||
To report bugs or request new features, please open a new GitHub issue.
|
||||
|
||||
This repository is part of the Channels project. For the shepherd and maintenance team, please see the
|
||||
`main Channels readme <https://github.com/django/channels/blob/master/README.rst>`_.
|
||||
|
||||
|
@ -0,0 +1,86 @@
|
||||
import queue
|
||||
import threading
|
||||
import time
|
||||
from concurrent.futures import Executor, Future
|
||||
|
||||
|
||||
class _WorkItem(object):
|
||||
"""
|
||||
Represents an item needing to be run in the executor.
|
||||
Copied from ThreadPoolExecutor (but it's private, so we're not going to rely on importing it)
|
||||
"""
|
||||
|
||||
def __init__(self, future, fn, args, kwargs):
|
||||
self.future = future
|
||||
self.fn = fn
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def run(self):
|
||||
if not self.future.set_running_or_notify_cancel():
|
||||
return
|
||||
try:
|
||||
result = self.fn(*self.args, **self.kwargs)
|
||||
except BaseException as exc:
|
||||
self.future.set_exception(exc)
|
||||
# Break a reference cycle with the exception 'exc'
|
||||
self = None
|
||||
else:
|
||||
self.future.set_result(result)
|
||||
|
||||
|
||||
class CurrentThreadExecutor(Executor):
|
||||
"""
|
||||
An Executor that actually runs code in the thread it is instantiated in.
|
||||
Passed to other threads running async code, so they can run sync code in
|
||||
the thread they came from.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._work_thread = threading.current_thread()
|
||||
self._work_queue = queue.Queue()
|
||||
self._broken = False
|
||||
|
||||
def run_until_future(self, future):
|
||||
"""
|
||||
Runs the code in the work queue until a result is available from the future.
|
||||
Should be run from the thread the executor is initialised in.
|
||||
"""
|
||||
# Check we're in the right thread
|
||||
if threading.current_thread() != self._work_thread:
|
||||
raise RuntimeError(
|
||||
"You cannot run CurrentThreadExecutor from a different thread"
|
||||
)
|
||||
# Keep getting work items and checking the future
|
||||
try:
|
||||
while True:
|
||||
# Get a work item and run it
|
||||
try:
|
||||
work_item = self._work_queue.get(block=False)
|
||||
except queue.Empty:
|
||||
# See if the future is done (we only exit if the work queue is empty)
|
||||
if future.done():
|
||||
return
|
||||
# Prevent hot-looping on nothing
|
||||
time.sleep(0.001)
|
||||
else:
|
||||
work_item.run()
|
||||
del work_item
|
||||
finally:
|
||||
self._broken = True
|
||||
|
||||
def submit(self, fn, *args, **kwargs):
|
||||
# Check they're not submitting from the same thread
|
||||
if threading.current_thread() == self._work_thread:
|
||||
raise RuntimeError(
|
||||
"You cannot submit onto CurrentThreadExecutor from its own thread"
|
||||
)
|
||||
# Check they're not too late or the executor errored
|
||||
if self._broken:
|
||||
raise RuntimeError("CurrentThreadExecutor already quit or is broken")
|
||||
# Add to work queue
|
||||
f = Future()
|
||||
work_item = _WorkItem(f, fn, args, kwargs)
|
||||
self._work_queue.put(work_item)
|
||||
# Return the future
|
||||
return f
|
154
venv/lib/python3.7/site-packages/asgiref/server.py
Normal file
154
venv/lib/python3.7/site-packages/asgiref/server.py
Normal file
@ -0,0 +1,154 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
import traceback
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StatelessServer:
|
||||
"""
|
||||
Base server class that handles basic concepts like application instance
|
||||
creation/pooling, exception handling, and similar, for stateless protocols
|
||||
(i.e. ones without actual incoming connections to the process)
|
||||
|
||||
Your code should override the handle() method, doing whatever it needs to,
|
||||
and calling get_or_create_application_instance with a unique `scope_id`
|
||||
and `scope` for the scope it wants to get.
|
||||
|
||||
If an application instance is found with the same `scope_id`, you are
|
||||
given its input queue, otherwise one is made for you with the scope provided
|
||||
and you are given that fresh new input queue. Either way, you should do
|
||||
something like:
|
||||
|
||||
input_queue = self.get_or_create_application_instance(
|
||||
"user-123456",
|
||||
{"type": "testprotocol", "user_id": "123456", "username": "andrew"},
|
||||
)
|
||||
input_queue.put_nowait(message)
|
||||
|
||||
If you try and create an application instance and there are already
|
||||
`max_application` instances, the oldest/least recently used one will be
|
||||
reclaimed and shut down to make space.
|
||||
|
||||
Application coroutines that error will be found periodically (every 100ms
|
||||
by default) and have their exceptions printed to the console. Override
|
||||
application_exception() if you want to do more when this happens.
|
||||
|
||||
If you override run(), make sure you handle things like launching the
|
||||
application checker.
|
||||
"""
|
||||
|
||||
application_checker_interval = 0.1
|
||||
|
||||
def __init__(self, application, max_applications=1000):
|
||||
# Parameters
|
||||
self.application = application
|
||||
self.max_applications = max_applications
|
||||
# Initialisation
|
||||
self.application_instances = {}
|
||||
|
||||
### Mainloop and handling
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Runs the asyncio event loop with our handler loop.
|
||||
"""
|
||||
event_loop = asyncio.get_event_loop()
|
||||
asyncio.ensure_future(self.application_checker())
|
||||
try:
|
||||
event_loop.run_until_complete(self.handle())
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Exiting due to Ctrl-C/interrupt")
|
||||
|
||||
async def handle(self):
|
||||
raise NotImplementedError("You must implement handle()")
|
||||
|
||||
async def application_send(self, scope, message):
|
||||
"""
|
||||
Receives outbound sends from applications and handles them.
|
||||
"""
|
||||
raise NotImplementedError("You must implement application_send()")
|
||||
|
||||
### Application instance management
|
||||
|
||||
def get_or_create_application_instance(self, scope_id, scope):
|
||||
"""
|
||||
Creates an application instance and returns its queue.
|
||||
"""
|
||||
if scope_id in self.application_instances:
|
||||
self.application_instances[scope_id]["last_used"] = time.time()
|
||||
return self.application_instances[scope_id]["input_queue"]
|
||||
# See if we need to delete an old one
|
||||
while len(self.application_instances) > self.max_applications:
|
||||
self.delete_oldest_application_instance()
|
||||
# Make an instance of the application
|
||||
input_queue = asyncio.Queue()
|
||||
application_instance = self.application(scope=scope)
|
||||
# Run it, and stash the future for later checking
|
||||
future = asyncio.ensure_future(
|
||||
application_instance(
|
||||
receive=input_queue.get,
|
||||
send=lambda message: self.application_send(scope, message),
|
||||
)
|
||||
)
|
||||
self.application_instances[scope_id] = {
|
||||
"input_queue": input_queue,
|
||||
"future": future,
|
||||
"scope": scope,
|
||||
"last_used": time.time(),
|
||||
}
|
||||
return input_queue
|
||||
|
||||
def delete_oldest_application_instance(self):
|
||||
"""
|
||||
Finds and deletes the oldest application instance
|
||||
"""
|
||||
oldest_time = min(
|
||||
details["last_used"] for details in self.application_instances.values()
|
||||
)
|
||||
for scope_id, details in self.application_instances.items():
|
||||
if details["last_used"] == oldest_time:
|
||||
self.delete_application_instance(scope_id)
|
||||
# Return to make sure we only delete one in case two have
|
||||
# the same oldest time
|
||||
return
|
||||
|
||||
def delete_application_instance(self, scope_id):
|
||||
"""
|
||||
Removes an application instance (makes sure its task is stopped,
|
||||
then removes it from the current set)
|
||||
"""
|
||||
details = self.application_instances[scope_id]
|
||||
del self.application_instances[scope_id]
|
||||
if not details["future"].done():
|
||||
details["future"].cancel()
|
||||
|
||||
async def application_checker(self):
|
||||
"""
|
||||
Goes through the set of current application instance Futures and cleans up
|
||||
any that are done/prints exceptions for any that errored.
|
||||
"""
|
||||
while True:
|
||||
await asyncio.sleep(self.application_checker_interval)
|
||||
for scope_id, details in list(self.application_instances.items()):
|
||||
if details["future"].done():
|
||||
exception = details["future"].exception()
|
||||
if exception:
|
||||
await self.application_exception(exception, details)
|
||||
try:
|
||||
del self.application_instances[scope_id]
|
||||
except KeyError:
|
||||
# Exception handling might have already got here before us. That's fine.
|
||||
pass
|
||||
|
||||
async def application_exception(self, exception, application_details):
|
||||
"""
|
||||
Called whenever an application coroutine has an exception.
|
||||
"""
|
||||
logging.error(
|
||||
"Exception inside application: %s\n%s%s",
|
||||
exception,
|
||||
"".join(traceback.format_tb(exception.__traceback__)),
|
||||
" {}".format(exception),
|
||||
)
|
304
venv/lib/python3.7/site-packages/asgiref/sync.py
Normal file
304
venv/lib/python3.7/site-packages/asgiref/sync.py
Normal file
@ -0,0 +1,304 @@
|
||||
import asyncio
|
||||
import asyncio.coroutines
|
||||
import functools
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
|
||||
from .current_thread_executor import CurrentThreadExecutor
|
||||
from .local import Local
|
||||
|
||||
try:
|
||||
import contextvars # Python 3.7+ only.
|
||||
except ImportError:
|
||||
contextvars = None
|
||||
|
||||
|
||||
class AsyncToSync:
|
||||
"""
|
||||
Utility class which turns an awaitable that only works on the thread with
|
||||
the event loop into a synchronous callable that works in a subthread.
|
||||
|
||||
If the call stack contains an async loop, the code runs there.
|
||||
Otherwise, the code runs in a new loop in a new thread.
|
||||
|
||||
Either way, this thread then pauses and waits to run any thread_sensitive
|
||||
code called from further down the call stack using SyncToAsync, before
|
||||
finally exiting once the async task returns.
|
||||
"""
|
||||
|
||||
# Maps launched Tasks to the threads that launched them (for locals impl)
|
||||
launch_map = {}
|
||||
|
||||
# Keeps track of which CurrentThreadExecutor to use. This uses an asgiref
|
||||
# Local, not a threadlocal, so that tasks can work out what their parent used.
|
||||
executors = Local()
|
||||
|
||||
def __init__(self, awaitable, force_new_loop=False):
|
||||
self.awaitable = awaitable
|
||||
if force_new_loop:
|
||||
# They have asked that we always run in a new sub-loop.
|
||||
self.main_event_loop = None
|
||||
else:
|
||||
try:
|
||||
self.main_event_loop = asyncio.get_event_loop()
|
||||
except RuntimeError:
|
||||
# There's no event loop in this thread. Look for the threadlocal if
|
||||
# we're inside SyncToAsync
|
||||
self.main_event_loop = getattr(
|
||||
SyncToAsync.threadlocal, "main_event_loop", None
|
||||
)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
# You can't call AsyncToSync from a thread with a running event loop
|
||||
try:
|
||||
event_loop = asyncio.get_event_loop()
|
||||
except RuntimeError:
|
||||
pass
|
||||
else:
|
||||
if event_loop.is_running():
|
||||
raise RuntimeError(
|
||||
"You cannot use AsyncToSync in the same thread as an async event loop - "
|
||||
"just await the async function directly."
|
||||
)
|
||||
# Make a future for the return information
|
||||
call_result = Future()
|
||||
# Get the source thread
|
||||
source_thread = threading.current_thread()
|
||||
# Make a CurrentThreadExecutor we'll use to idle in this thread - we
|
||||
# need one for every sync frame, even if there's one above us in the
|
||||
# same thread.
|
||||
if hasattr(self.executors, "current"):
|
||||
old_current_executor = self.executors.current
|
||||
else:
|
||||
old_current_executor = None
|
||||
current_executor = CurrentThreadExecutor()
|
||||
self.executors.current = current_executor
|
||||
# Use call_soon_threadsafe to schedule a synchronous callback on the
|
||||
# main event loop's thread if it's there, otherwise make a new loop
|
||||
# in this thread.
|
||||
try:
|
||||
if not (self.main_event_loop and self.main_event_loop.is_running()):
|
||||
# Make our own event loop - in a new thread - and run inside that.
|
||||
loop = asyncio.new_event_loop()
|
||||
loop_executor = ThreadPoolExecutor(max_workers=1)
|
||||
loop_future = loop_executor.submit(
|
||||
self._run_event_loop,
|
||||
loop,
|
||||
self.main_wrap(
|
||||
args, kwargs, call_result, source_thread, sys.exc_info()
|
||||
),
|
||||
)
|
||||
if current_executor:
|
||||
# Run the CurrentThreadExecutor until the future is done
|
||||
current_executor.run_until_future(loop_future)
|
||||
# Wait for future and/or allow for exception propagation
|
||||
loop_future.result()
|
||||
else:
|
||||
# Call it inside the existing loop
|
||||
self.main_event_loop.call_soon_threadsafe(
|
||||
self.main_event_loop.create_task,
|
||||
self.main_wrap(
|
||||
args, kwargs, call_result, source_thread, sys.exc_info()
|
||||
),
|
||||
)
|
||||
if current_executor:
|
||||
# Run the CurrentThreadExecutor until the future is done
|
||||
current_executor.run_until_future(call_result)
|
||||
finally:
|
||||
# Clean up any executor we were running
|
||||
if hasattr(self.executors, "current"):
|
||||
del self.executors.current
|
||||
if old_current_executor:
|
||||
self.executors.current = old_current_executor
|
||||
# Wait for results from the future.
|
||||
return call_result.result()
|
||||
|
||||
def _run_event_loop(self, loop, coro):
|
||||
"""
|
||||
Runs the given event loop (designed to be called in a thread).
|
||||
"""
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
loop.run_until_complete(coro)
|
||||
finally:
|
||||
try:
|
||||
if hasattr(loop, "shutdown_asyncgens"):
|
||||
loop.run_until_complete(loop.shutdown_asyncgens())
|
||||
finally:
|
||||
loop.close()
|
||||
asyncio.set_event_loop(self.main_event_loop)
|
||||
|
||||
def __get__(self, parent, objtype):
|
||||
"""
|
||||
Include self for methods
|
||||
"""
|
||||
func = functools.partial(self.__call__, parent)
|
||||
return functools.update_wrapper(func, self.awaitable)
|
||||
|
||||
async def main_wrap(self, args, kwargs, call_result, source_thread, exc_info):
|
||||
"""
|
||||
Wraps the awaitable with something that puts the result into the
|
||||
result/exception future.
|
||||
"""
|
||||
current_task = SyncToAsync.get_current_task()
|
||||
self.launch_map[current_task] = source_thread
|
||||
try:
|
||||
# If we have an exception, run the function inside the except block
|
||||
# after raising it so exc_info is correctly populated.
|
||||
if exc_info[1]:
|
||||
try:
|
||||
raise exc_info[1]
|
||||
except:
|
||||
result = await self.awaitable(*args, **kwargs)
|
||||
else:
|
||||
result = await self.awaitable(*args, **kwargs)
|
||||
except Exception as e:
|
||||
call_result.set_exception(e)
|
||||
else:
|
||||
call_result.set_result(result)
|
||||
finally:
|
||||
del self.launch_map[current_task]
|
||||
|
||||
|
||||
class SyncToAsync:
|
||||
"""
|
||||
Utility class which turns a synchronous callable into an awaitable that
|
||||
runs in a threadpool. It also sets a threadlocal inside the thread so
|
||||
calls to AsyncToSync can escape it.
|
||||
|
||||
If thread_sensitive is passed, the code will run in the same thread as any
|
||||
outer code. This is needed for underlying Python code that is not
|
||||
threadsafe (for example, code which handles SQLite database connections).
|
||||
|
||||
If the outermost program is async (i.e. SyncToAsync is outermost), then
|
||||
this will be a dedicated single sub-thread that all sync code runs in,
|
||||
one after the other. If the outermost program is sync (i.e. AsyncToSync is
|
||||
outermost), this will just be the main thread. This is achieved by idling
|
||||
with a CurrentThreadExecutor while AsyncToSync is blocking its sync parent,
|
||||
rather than just blocking.
|
||||
"""
|
||||
|
||||
# If they've set ASGI_THREADS, update the default asyncio executor for now
|
||||
if "ASGI_THREADS" in os.environ:
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.set_default_executor(
|
||||
ThreadPoolExecutor(max_workers=int(os.environ["ASGI_THREADS"]))
|
||||
)
|
||||
|
||||
# Maps launched threads to the coroutines that spawned them
|
||||
launch_map = {}
|
||||
|
||||
# Storage for main event loop references
|
||||
threadlocal = threading.local()
|
||||
|
||||
# Single-thread executor for thread-sensitive code
|
||||
single_thread_executor = ThreadPoolExecutor(max_workers=1)
|
||||
|
||||
def __init__(self, func, thread_sensitive=False):
|
||||
self.func = func
|
||||
self._thread_sensitive = thread_sensitive
|
||||
self._is_coroutine = asyncio.coroutines._is_coroutine
|
||||
try:
|
||||
self.__self__ = func.__self__
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
async def __call__(self, *args, **kwargs):
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
# Work out what thread to run the code in
|
||||
if self._thread_sensitive:
|
||||
if hasattr(AsyncToSync.executors, "current"):
|
||||
# If we have a parent sync thread above somewhere, use that
|
||||
executor = AsyncToSync.executors.current
|
||||
else:
|
||||
# Otherwise, we run it in a fixed single thread
|
||||
executor = self.single_thread_executor
|
||||
else:
|
||||
executor = None # Use default
|
||||
|
||||
if contextvars is not None:
|
||||
context = contextvars.copy_context()
|
||||
child = functools.partial(self.func, *args, **kwargs)
|
||||
func = context.run
|
||||
args = (child,)
|
||||
kwargs = {}
|
||||
else:
|
||||
func = self.func
|
||||
|
||||
# Run the code in the right thread
|
||||
future = loop.run_in_executor(
|
||||
executor,
|
||||
functools.partial(
|
||||
self.thread_handler,
|
||||
loop,
|
||||
self.get_current_task(),
|
||||
sys.exc_info(),
|
||||
func,
|
||||
*args,
|
||||
**kwargs
|
||||
),
|
||||
)
|
||||
return await asyncio.wait_for(future, timeout=None)
|
||||
|
||||
def __get__(self, parent, objtype):
|
||||
"""
|
||||
Include self for methods
|
||||
"""
|
||||
return functools.partial(self.__call__, parent)
|
||||
|
||||
def thread_handler(self, loop, source_task, exc_info, func, *args, **kwargs):
|
||||
"""
|
||||
Wraps the sync application with exception handling.
|
||||
"""
|
||||
# Set the threadlocal for AsyncToSync
|
||||
self.threadlocal.main_event_loop = loop
|
||||
# Set the task mapping (used for the locals module)
|
||||
current_thread = threading.current_thread()
|
||||
if AsyncToSync.launch_map.get(source_task) == current_thread:
|
||||
# Our parent task was launched from this same thread, so don't make
|
||||
# a launch map entry - let it shortcut over us! (and stop infinite loops)
|
||||
parent_set = False
|
||||
else:
|
||||
self.launch_map[current_thread] = source_task
|
||||
parent_set = True
|
||||
# Run the function
|
||||
try:
|
||||
# If we have an exception, run the function inside the except block
|
||||
# after raising it so exc_info is correctly populated.
|
||||
if exc_info[1]:
|
||||
try:
|
||||
raise exc_info[1]
|
||||
except:
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
# Only delete the launch_map parent if we set it, otherwise it is
|
||||
# from someone else.
|
||||
if parent_set:
|
||||
del self.launch_map[current_thread]
|
||||
|
||||
@staticmethod
|
||||
def get_current_task():
|
||||
"""
|
||||
Cross-version implementation of asyncio.current_task()
|
||||
|
||||
Returns None if there is no task.
|
||||
"""
|
||||
try:
|
||||
if hasattr(asyncio, "current_task"):
|
||||
# Python 3.7 and up
|
||||
return asyncio.current_task()
|
||||
else:
|
||||
# Python 3.6
|
||||
return asyncio.Task.current_task()
|
||||
except RuntimeError:
|
||||
return None
|
||||
|
||||
|
||||
# Lowercase is more sensible for most things
|
||||
sync_to_async = SyncToAsync
|
||||
async_to_sync = AsyncToSync
|
128
venv/lib/python3.7/site-packages/asgiref/timeout.py
Normal file
128
venv/lib/python3.7/site-packages/asgiref/timeout.py
Normal file
@ -0,0 +1,128 @@
|
||||
# This code is originally sourced from the aio-libs project "async_timeout",
|
||||
# under the Apache 2.0 license. You may see the original project at
|
||||
# https://github.com/aio-libs/async-timeout
|
||||
|
||||
# It is vendored here to reduce chain-dependencies on this library, and
|
||||
# modified slightly to remove some features we don't use.
|
||||
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from types import TracebackType
|
||||
from typing import Any, Optional, Type # noqa
|
||||
|
||||
PY_37 = sys.version_info >= (3, 7)
|
||||
|
||||
|
||||
class timeout:
|
||||
"""timeout context manager.
|
||||
|
||||
Useful in cases when you want to apply timeout logic around block
|
||||
of code or in cases when asyncio.wait_for is not suitable. For example:
|
||||
|
||||
>>> with timeout(0.001):
|
||||
... async with aiohttp.get('https://github.com') as r:
|
||||
... await r.text()
|
||||
|
||||
|
||||
timeout - value in seconds or None to disable timeout logic
|
||||
loop - asyncio compatible event loop
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
timeout: Optional[float],
|
||||
*,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
) -> None:
|
||||
self._timeout = timeout
|
||||
if loop is None:
|
||||
loop = asyncio.get_event_loop()
|
||||
self._loop = loop
|
||||
self._task = None # type: Optional[asyncio.Task[Any]]
|
||||
self._cancelled = False
|
||||
self._cancel_handler = None # type: Optional[asyncio.Handle]
|
||||
self._cancel_at = None # type: Optional[float]
|
||||
|
||||
def __enter__(self) -> "timeout":
|
||||
return self._do_enter()
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Type[BaseException],
|
||||
exc_val: BaseException,
|
||||
exc_tb: TracebackType,
|
||||
) -> Optional[bool]:
|
||||
self._do_exit(exc_type)
|
||||
return None
|
||||
|
||||
async def __aenter__(self) -> "timeout":
|
||||
return self._do_enter()
|
||||
|
||||
async def __aexit__(
|
||||
self,
|
||||
exc_type: Type[BaseException],
|
||||
exc_val: BaseException,
|
||||
exc_tb: TracebackType,
|
||||
) -> None:
|
||||
self._do_exit(exc_type)
|
||||
|
||||
@property
|
||||
def expired(self) -> bool:
|
||||
return self._cancelled
|
||||
|
||||
@property
|
||||
def remaining(self) -> Optional[float]:
|
||||
if self._cancel_at is not None:
|
||||
return max(self._cancel_at - self._loop.time(), 0.0)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _do_enter(self) -> "timeout":
|
||||
# Support Tornado 5- without timeout
|
||||
# Details: https://github.com/python/asyncio/issues/392
|
||||
if self._timeout is None:
|
||||
return self
|
||||
|
||||
self._task = current_task(self._loop)
|
||||
if self._task is None:
|
||||
raise RuntimeError(
|
||||
"Timeout context manager should be used " "inside a task"
|
||||
)
|
||||
|
||||
if self._timeout <= 0:
|
||||
self._loop.call_soon(self._cancel_task)
|
||||
return self
|
||||
|
||||
self._cancel_at = self._loop.time() + self._timeout
|
||||
self._cancel_handler = self._loop.call_at(self._cancel_at, self._cancel_task)
|
||||
return self
|
||||
|
||||
def _do_exit(self, exc_type: Type[BaseException]) -> None:
|
||||
if exc_type is asyncio.CancelledError and self._cancelled:
|
||||
self._cancel_handler = None
|
||||
self._task = None
|
||||
raise asyncio.TimeoutError
|
||||
if self._timeout is not None and self._cancel_handler is not None:
|
||||
self._cancel_handler.cancel()
|
||||
self._cancel_handler = None
|
||||
self._task = None
|
||||
return None
|
||||
|
||||
def _cancel_task(self) -> None:
|
||||
if self._task is not None:
|
||||
self._task.cancel()
|
||||
self._cancelled = True
|
||||
|
||||
|
||||
def current_task(loop: asyncio.AbstractEventLoop) -> "asyncio.Task[Any]":
|
||||
if PY_37:
|
||||
task = asyncio.current_task(loop=loop) # type: ignore
|
||||
else:
|
||||
task = asyncio.Task.current_task(loop=loop)
|
||||
if task is None:
|
||||
# this should be removed, tokio must use register_task and family API
|
||||
if hasattr(loop, "current_task"):
|
||||
task = loop.current_task() # type: ignore
|
||||
|
||||
return task
|
278
venv/lib/python3.7/site-packages/attr/__init__.pyi
Normal file
278
venv/lib/python3.7/site-packages/attr/__init__.pyi
Normal file
@ -0,0 +1,278 @@
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Generic,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Mapping,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
|
||||
# `import X as X` is required to make these public
|
||||
from . import exceptions as exceptions
|
||||
from . import filters as filters
|
||||
from . import converters as converters
|
||||
from . import validators as validators
|
||||
|
||||
from ._version_info import VersionInfo
|
||||
|
||||
__version__: str
|
||||
__version_info__: VersionInfo
|
||||
__title__: str
|
||||
__description__: str
|
||||
__url__: str
|
||||
__uri__: str
|
||||
__author__: str
|
||||
__email__: str
|
||||
__license__: str
|
||||
__copyright__: str
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_C = TypeVar("_C", bound=type)
|
||||
|
||||
_ValidatorType = Callable[[Any, Attribute[_T], _T], Any]
|
||||
_ConverterType = Callable[[Any], _T]
|
||||
_FilterType = Callable[[Attribute[_T], _T], bool]
|
||||
_ReprType = Callable[[Any], str]
|
||||
_ReprArgType = Union[bool, _ReprType]
|
||||
# FIXME: in reality, if multiple validators are passed they must be in a list or tuple,
|
||||
# but those are invariant and so would prevent subtypes of _ValidatorType from working
|
||||
# when passed in a list or tuple.
|
||||
_ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]]
|
||||
|
||||
# _make --
|
||||
|
||||
NOTHING: object
|
||||
|
||||
# NOTE: Factory lies about its return type to make this possible: `x: List[int] = Factory(list)`
|
||||
# Work around mypy issue #4554 in the common case by using an overload.
|
||||
@overload
|
||||
def Factory(factory: Callable[[], _T]) -> _T: ...
|
||||
@overload
|
||||
def Factory(
|
||||
factory: Union[Callable[[Any], _T], Callable[[], _T]],
|
||||
takes_self: bool = ...,
|
||||
) -> _T: ...
|
||||
|
||||
class Attribute(Generic[_T]):
|
||||
name: str
|
||||
default: Optional[_T]
|
||||
validator: Optional[_ValidatorType[_T]]
|
||||
repr: _ReprArgType
|
||||
cmp: bool
|
||||
eq: bool
|
||||
order: bool
|
||||
hash: Optional[bool]
|
||||
init: bool
|
||||
converter: Optional[_ConverterType[_T]]
|
||||
metadata: Dict[Any, Any]
|
||||
type: Optional[Type[_T]]
|
||||
kw_only: bool
|
||||
|
||||
# NOTE: We had several choices for the annotation to use for type arg:
|
||||
# 1) Type[_T]
|
||||
# - Pros: Handles simple cases correctly
|
||||
# - Cons: Might produce less informative errors in the case of conflicting TypeVars
|
||||
# e.g. `attr.ib(default='bad', type=int)`
|
||||
# 2) Callable[..., _T]
|
||||
# - Pros: Better error messages than #1 for conflicting TypeVars
|
||||
# - Cons: Terrible error messages for validator checks.
|
||||
# e.g. attr.ib(type=int, validator=validate_str)
|
||||
# -> error: Cannot infer function type argument
|
||||
# 3) type (and do all of the work in the mypy plugin)
|
||||
# - Pros: Simple here, and we could customize the plugin with our own errors.
|
||||
# - Cons: Would need to write mypy plugin code to handle all the cases.
|
||||
# We chose option #1.
|
||||
|
||||
# `attr` lies about its return type to make the following possible:
|
||||
# attr() -> Any
|
||||
# attr(8) -> int
|
||||
# attr(validator=<some callable>) -> Whatever the callable expects.
|
||||
# This makes this type of assignments possible:
|
||||
# x: int = attr(8)
|
||||
#
|
||||
# This form catches explicit None or no default but with no other arguments returns Any.
|
||||
@overload
|
||||
def attrib(
|
||||
default: None = ...,
|
||||
validator: None = ...,
|
||||
repr: _ReprArgType = ...,
|
||||
cmp: Optional[bool] = ...,
|
||||
hash: Optional[bool] = ...,
|
||||
init: bool = ...,
|
||||
metadata: Optional[Mapping[Any, Any]] = ...,
|
||||
type: None = ...,
|
||||
converter: None = ...,
|
||||
factory: None = ...,
|
||||
kw_only: bool = ...,
|
||||
eq: Optional[bool] = ...,
|
||||
order: Optional[bool] = ...,
|
||||
) -> Any: ...
|
||||
|
||||
# This form catches an explicit None or no default and infers the type from the other arguments.
|
||||
@overload
|
||||
def attrib(
|
||||
default: None = ...,
|
||||
validator: Optional[_ValidatorArgType[_T]] = ...,
|
||||
repr: _ReprArgType = ...,
|
||||
cmp: Optional[bool] = ...,
|
||||
hash: Optional[bool] = ...,
|
||||
init: bool = ...,
|
||||
metadata: Optional[Mapping[Any, Any]] = ...,
|
||||
type: Optional[Type[_T]] = ...,
|
||||
converter: Optional[_ConverterType[_T]] = ...,
|
||||
factory: Optional[Callable[[], _T]] = ...,
|
||||
kw_only: bool = ...,
|
||||
eq: Optional[bool] = ...,
|
||||
order: Optional[bool] = ...,
|
||||
) -> _T: ...
|
||||
|
||||
# This form catches an explicit default argument.
|
||||
@overload
|
||||
def attrib(
|
||||
default: _T,
|
||||
validator: Optional[_ValidatorArgType[_T]] = ...,
|
||||
repr: _ReprArgType = ...,
|
||||
cmp: Optional[bool] = ...,
|
||||
hash: Optional[bool] = ...,
|
||||
init: bool = ...,
|
||||
metadata: Optional[Mapping[Any, Any]] = ...,
|
||||
type: Optional[Type[_T]] = ...,
|
||||
converter: Optional[_ConverterType[_T]] = ...,
|
||||
factory: Optional[Callable[[], _T]] = ...,
|
||||
kw_only: bool = ...,
|
||||
eq: Optional[bool] = ...,
|
||||
order: Optional[bool] = ...,
|
||||
) -> _T: ...
|
||||
|
||||
# This form covers type=non-Type: e.g. forward references (str), Any
|
||||
@overload
|
||||
def attrib(
|
||||
default: Optional[_T] = ...,
|
||||
validator: Optional[_ValidatorArgType[_T]] = ...,
|
||||
repr: _ReprArgType = ...,
|
||||
cmp: Optional[bool] = ...,
|
||||
hash: Optional[bool] = ...,
|
||||
init: bool = ...,
|
||||
metadata: Optional[Mapping[Any, Any]] = ...,
|
||||
type: object = ...,
|
||||
converter: Optional[_ConverterType[_T]] = ...,
|
||||
factory: Optional[Callable[[], _T]] = ...,
|
||||
kw_only: bool = ...,
|
||||
eq: Optional[bool] = ...,
|
||||
order: Optional[bool] = ...,
|
||||
) -> Any: ...
|
||||
@overload
|
||||
def attrs(
|
||||
maybe_cls: _C,
|
||||
these: Optional[Dict[str, Any]] = ...,
|
||||
repr_ns: Optional[str] = ...,
|
||||
repr: bool = ...,
|
||||
cmp: Optional[bool] = ...,
|
||||
hash: Optional[bool] = ...,
|
||||
init: bool = ...,
|
||||
slots: bool = ...,
|
||||
frozen: bool = ...,
|
||||
weakref_slot: bool = ...,
|
||||
str: bool = ...,
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
eq: Optional[bool] = ...,
|
||||
order: Optional[bool] = ...,
|
||||
) -> _C: ...
|
||||
@overload
|
||||
def attrs(
|
||||
maybe_cls: None = ...,
|
||||
these: Optional[Dict[str, Any]] = ...,
|
||||
repr_ns: Optional[str] = ...,
|
||||
repr: bool = ...,
|
||||
cmp: Optional[bool] = ...,
|
||||
hash: Optional[bool] = ...,
|
||||
init: bool = ...,
|
||||
slots: bool = ...,
|
||||
frozen: bool = ...,
|
||||
weakref_slot: bool = ...,
|
||||
str: bool = ...,
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
eq: Optional[bool] = ...,
|
||||
order: Optional[bool] = ...,
|
||||
) -> Callable[[_C], _C]: ...
|
||||
|
||||
# TODO: add support for returning NamedTuple from the mypy plugin
|
||||
class _Fields(Tuple[Attribute[Any], ...]):
|
||||
def __getattr__(self, name: str) -> Attribute[Any]: ...
|
||||
|
||||
def fields(cls: type) -> _Fields: ...
|
||||
def fields_dict(cls: type) -> Dict[str, Attribute[Any]]: ...
|
||||
def validate(inst: Any) -> None: ...
|
||||
|
||||
# TODO: add support for returning a proper attrs class from the mypy plugin
|
||||
# we use Any instead of _CountingAttr so that e.g. `make_class('Foo', [attr.ib()])` is valid
|
||||
def make_class(
|
||||
name: str,
|
||||
attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]],
|
||||
bases: Tuple[type, ...] = ...,
|
||||
repr_ns: Optional[str] = ...,
|
||||
repr: bool = ...,
|
||||
cmp: Optional[bool] = ...,
|
||||
hash: Optional[bool] = ...,
|
||||
init: bool = ...,
|
||||
slots: bool = ...,
|
||||
frozen: bool = ...,
|
||||
weakref_slot: bool = ...,
|
||||
str: bool = ...,
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
eq: Optional[bool] = ...,
|
||||
order: Optional[bool] = ...,
|
||||
) -> type: ...
|
||||
|
||||
# _funcs --
|
||||
|
||||
# TODO: add support for returning TypedDict from the mypy plugin
|
||||
# FIXME: asdict/astuple do not honor their factory args. waiting on one of these:
|
||||
# https://github.com/python/mypy/issues/4236
|
||||
# https://github.com/python/typing/issues/253
|
||||
def asdict(
|
||||
inst: Any,
|
||||
recurse: bool = ...,
|
||||
filter: Optional[_FilterType[Any]] = ...,
|
||||
dict_factory: Type[Mapping[Any, Any]] = ...,
|
||||
retain_collection_types: bool = ...,
|
||||
) -> Dict[str, Any]: ...
|
||||
|
||||
# TODO: add support for returning NamedTuple from the mypy plugin
|
||||
def astuple(
|
||||
inst: Any,
|
||||
recurse: bool = ...,
|
||||
filter: Optional[_FilterType[Any]] = ...,
|
||||
tuple_factory: Type[Sequence[Any]] = ...,
|
||||
retain_collection_types: bool = ...,
|
||||
) -> Tuple[Any, ...]: ...
|
||||
def has(cls: type) -> bool: ...
|
||||
def assoc(inst: _T, **changes: Any) -> _T: ...
|
||||
def evolve(inst: _T, **changes: Any) -> _T: ...
|
||||
|
||||
# _config --
|
||||
|
||||
def set_run_validators(run: bool) -> None: ...
|
||||
def get_run_validators() -> bool: ...
|
||||
|
||||
# aliases --
|
||||
|
||||
s = attributes = attrs
|
||||
ib = attr = attrib
|
||||
dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;)
|
230
venv/lib/python3.7/site-packages/attr/_compat.py
Normal file
230
venv/lib/python3.7/site-packages/attr/_compat.py
Normal file
@ -0,0 +1,230 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import platform
|
||||
import sys
|
||||
import types
|
||||
import warnings
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PYPY = platform.python_implementation() == "PyPy"
|
||||
|
||||
|
||||
if PYPY or sys.version_info[:2] >= (3, 6):
|
||||
ordered_dict = dict
|
||||
else:
|
||||
from collections import OrderedDict
|
||||
|
||||
ordered_dict = OrderedDict
|
||||
|
||||
|
||||
if PY2:
|
||||
from UserDict import IterableUserDict
|
||||
from collections import Mapping, Sequence
|
||||
|
||||
# We 'bundle' isclass instead of using inspect as importing inspect is
|
||||
# fairly expensive (order of 10-15 ms for a modern machine in 2016)
|
||||
def isclass(klass):
|
||||
return isinstance(klass, (type, types.ClassType))
|
||||
|
||||
# TYPE is used in exceptions, repr(int) is different on Python 2 and 3.
|
||||
TYPE = "type"
|
||||
|
||||
def iteritems(d):
|
||||
return d.iteritems()
|
||||
|
||||
# Python 2 is bereft of a read-only dict proxy, so we make one!
|
||||
class ReadOnlyDict(IterableUserDict):
|
||||
"""
|
||||
Best-effort read-only dict wrapper.
|
||||
"""
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise TypeError(
|
||||
"'mappingproxy' object does not support item assignment"
|
||||
)
|
||||
|
||||
def update(self, _):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise AttributeError(
|
||||
"'mappingproxy' object has no attribute 'update'"
|
||||
)
|
||||
|
||||
def __delitem__(self, _):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise TypeError(
|
||||
"'mappingproxy' object does not support item deletion"
|
||||
)
|
||||
|
||||
def clear(self):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise AttributeError(
|
||||
"'mappingproxy' object has no attribute 'clear'"
|
||||
)
|
||||
|
||||
def pop(self, key, default=None):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise AttributeError(
|
||||
"'mappingproxy' object has no attribute 'pop'"
|
||||
)
|
||||
|
||||
def popitem(self):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise AttributeError(
|
||||
"'mappingproxy' object has no attribute 'popitem'"
|
||||
)
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise AttributeError(
|
||||
"'mappingproxy' object has no attribute 'setdefault'"
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
# Override to be identical to the Python 3 version.
|
||||
return "mappingproxy(" + repr(self.data) + ")"
|
||||
|
||||
def metadata_proxy(d):
|
||||
res = ReadOnlyDict()
|
||||
res.data.update(d) # We blocked update, so we have to do it like this.
|
||||
return res
|
||||
|
||||
def just_warn(*args, **kw): # pragma: nocover
|
||||
"""
|
||||
We only warn on Python 3 because we are not aware of any concrete
|
||||
consequences of not setting the cell on Python 2.
|
||||
"""
|
||||
|
||||
|
||||
else: # Python 3 and later.
|
||||
from collections.abc import Mapping, Sequence # noqa
|
||||
|
||||
def just_warn(*args, **kw):
|
||||
"""
|
||||
We only warn on Python 3 because we are not aware of any concrete
|
||||
consequences of not setting the cell on Python 2.
|
||||
"""
|
||||
warnings.warn(
|
||||
"Running interpreter doesn't sufficiently support code object "
|
||||
"introspection. Some features like bare super() or accessing "
|
||||
"__class__ will not work with slotted classes.",
|
||||
RuntimeWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
def isclass(klass):
|
||||
return isinstance(klass, type)
|
||||
|
||||
TYPE = "class"
|
||||
|
||||
def iteritems(d):
|
||||
return d.items()
|
||||
|
||||
def metadata_proxy(d):
|
||||
return types.MappingProxyType(dict(d))
|
||||
|
||||
|
||||
def make_set_closure_cell():
|
||||
"""Return a function of two arguments (cell, value) which sets
|
||||
the value stored in the closure cell `cell` to `value`.
|
||||
"""
|
||||
# pypy makes this easy. (It also supports the logic below, but
|
||||
# why not do the easy/fast thing?)
|
||||
if PYPY: # pragma: no cover
|
||||
|
||||
def set_closure_cell(cell, value):
|
||||
cell.__setstate__((value,))
|
||||
|
||||
return set_closure_cell
|
||||
|
||||
# Otherwise gotta do it the hard way.
|
||||
|
||||
# Create a function that will set its first cellvar to `value`.
|
||||
def set_first_cellvar_to(value):
|
||||
x = value
|
||||
return
|
||||
|
||||
# This function will be eliminated as dead code, but
|
||||
# not before its reference to `x` forces `x` to be
|
||||
# represented as a closure cell rather than a local.
|
||||
def force_x_to_be_a_cell(): # pragma: no cover
|
||||
return x
|
||||
|
||||
try:
|
||||
# Extract the code object and make sure our assumptions about
|
||||
# the closure behavior are correct.
|
||||
if PY2:
|
||||
co = set_first_cellvar_to.func_code
|
||||
else:
|
||||
co = set_first_cellvar_to.__code__
|
||||
if co.co_cellvars != ("x",) or co.co_freevars != ():
|
||||
raise AssertionError # pragma: no cover
|
||||
|
||||
# Convert this code object to a code object that sets the
|
||||
# function's first _freevar_ (not cellvar) to the argument.
|
||||
if sys.version_info >= (3, 8):
|
||||
# CPython 3.8+ has an incompatible CodeType signature
|
||||
# (added a posonlyargcount argument) but also added
|
||||
# CodeType.replace() to do this without counting parameters.
|
||||
set_first_freevar_code = co.replace(
|
||||
co_cellvars=co.co_freevars, co_freevars=co.co_cellvars
|
||||
)
|
||||
else:
|
||||
args = [co.co_argcount]
|
||||
if not PY2:
|
||||
args.append(co.co_kwonlyargcount)
|
||||
args.extend(
|
||||
[
|
||||
co.co_nlocals,
|
||||
co.co_stacksize,
|
||||
co.co_flags,
|
||||
co.co_code,
|
||||
co.co_consts,
|
||||
co.co_names,
|
||||
co.co_varnames,
|
||||
co.co_filename,
|
||||
co.co_name,
|
||||
co.co_firstlineno,
|
||||
co.co_lnotab,
|
||||
# These two arguments are reversed:
|
||||
co.co_cellvars,
|
||||
co.co_freevars,
|
||||
]
|
||||
)
|
||||
set_first_freevar_code = types.CodeType(*args)
|
||||
|
||||
def set_closure_cell(cell, value):
|
||||
# Create a function using the set_first_freevar_code,
|
||||
# whose first closure cell is `cell`. Calling it will
|
||||
# change the value of that cell.
|
||||
setter = types.FunctionType(
|
||||
set_first_freevar_code, {}, "setter", (), (cell,)
|
||||
)
|
||||
# And call it to set the cell.
|
||||
setter(value)
|
||||
|
||||
# Make sure it works on this interpreter:
|
||||
def make_func_with_cell():
|
||||
x = None
|
||||
|
||||
def func():
|
||||
return x # pragma: no cover
|
||||
|
||||
return func
|
||||
|
||||
if PY2:
|
||||
cell = make_func_with_cell().func_closure[0]
|
||||
else:
|
||||
cell = make_func_with_cell().__closure__[0]
|
||||
set_closure_cell(cell, 100)
|
||||
if cell.cell_contents != 100:
|
||||
raise AssertionError # pragma: no cover
|
||||
|
||||
except Exception:
|
||||
return just_warn
|
||||
else:
|
||||
return set_closure_cell
|
||||
|
||||
|
||||
set_closure_cell = make_set_closure_cell()
|
23
venv/lib/python3.7/site-packages/attr/_config.py
Normal file
23
venv/lib/python3.7/site-packages/attr/_config.py
Normal file
@ -0,0 +1,23 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
|
||||
__all__ = ["set_run_validators", "get_run_validators"]
|
||||
|
||||
_run_validators = True
|
||||
|
||||
|
||||
def set_run_validators(run):
|
||||
"""
|
||||
Set whether or not validators are run. By default, they are run.
|
||||
"""
|
||||
if not isinstance(run, bool):
|
||||
raise TypeError("'run' must be bool.")
|
||||
global _run_validators
|
||||
_run_validators = run
|
||||
|
||||
|
||||
def get_run_validators():
|
||||
"""
|
||||
Return whether or not validators are run.
|
||||
"""
|
||||
return _run_validators
|
2168
venv/lib/python3.7/site-packages/attr/_make.py
Normal file
2168
venv/lib/python3.7/site-packages/attr/_make.py
Normal file
File diff suppressed because it is too large
Load Diff
78
venv/lib/python3.7/site-packages/attr/converters.py
Normal file
78
venv/lib/python3.7/site-packages/attr/converters.py
Normal file
@ -0,0 +1,78 @@
|
||||
"""
|
||||
Commonly useful converters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from ._make import NOTHING, Factory
|
||||
|
||||
|
||||
def optional(converter):
|
||||
"""
|
||||
A converter that allows an attribute to be optional. An optional attribute
|
||||
is one which can be set to ``None``.
|
||||
|
||||
:param callable converter: the converter that is used for non-``None``
|
||||
values.
|
||||
|
||||
.. versionadded:: 17.1.0
|
||||
"""
|
||||
|
||||
def optional_converter(val):
|
||||
if val is None:
|
||||
return None
|
||||
return converter(val)
|
||||
|
||||
return optional_converter
|
||||
|
||||
|
||||
def default_if_none(default=NOTHING, factory=None):
|
||||
"""
|
||||
A converter that allows to replace ``None`` values by *default* or the
|
||||
result of *factory*.
|
||||
|
||||
:param default: Value to be used if ``None`` is passed. Passing an instance
|
||||
of `attr.Factory` is supported, however the ``takes_self`` option
|
||||
is *not*.
|
||||
:param callable factory: A callable that takes not parameters whose result
|
||||
is used if ``None`` is passed.
|
||||
|
||||
:raises TypeError: If **neither** *default* or *factory* is passed.
|
||||
:raises TypeError: If **both** *default* and *factory* are passed.
|
||||
:raises ValueError: If an instance of `attr.Factory` is passed with
|
||||
``takes_self=True``.
|
||||
|
||||
.. versionadded:: 18.2.0
|
||||
"""
|
||||
if default is NOTHING and factory is None:
|
||||
raise TypeError("Must pass either `default` or `factory`.")
|
||||
|
||||
if default is not NOTHING and factory is not None:
|
||||
raise TypeError(
|
||||
"Must pass either `default` or `factory` but not both."
|
||||
)
|
||||
|
||||
if factory is not None:
|
||||
default = Factory(factory)
|
||||
|
||||
if isinstance(default, Factory):
|
||||
if default.takes_self:
|
||||
raise ValueError(
|
||||
"`takes_self` is not supported by default_if_none."
|
||||
)
|
||||
|
||||
def default_if_none_converter(val):
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
return default.factory()
|
||||
|
||||
else:
|
||||
|
||||
def default_if_none_converter(val):
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
return default
|
||||
|
||||
return default_if_none_converter
|
12
venv/lib/python3.7/site-packages/attr/converters.pyi
Normal file
12
venv/lib/python3.7/site-packages/attr/converters.pyi
Normal file
@ -0,0 +1,12 @@
|
||||
from typing import TypeVar, Optional, Callable, overload
|
||||
from . import _ConverterType
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
def optional(
|
||||
converter: _ConverterType[_T]
|
||||
) -> _ConverterType[Optional[_T]]: ...
|
||||
@overload
|
||||
def default_if_none(default: _T) -> _ConverterType[_T]: ...
|
||||
@overload
|
||||
def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType[_T]: ...
|
15
venv/lib/python3.7/site-packages/attr/exceptions.pyi
Normal file
15
venv/lib/python3.7/site-packages/attr/exceptions.pyi
Normal file
@ -0,0 +1,15 @@
|
||||
from typing import Any
|
||||
|
||||
class FrozenInstanceError(AttributeError):
|
||||
msg: str = ...
|
||||
|
||||
class AttrsAttributeNotFoundError(ValueError): ...
|
||||
class NotAnAttrsClassError(ValueError): ...
|
||||
class DefaultAlreadySetError(RuntimeError): ...
|
||||
class UnannotatedAttributeError(RuntimeError): ...
|
||||
class PythonTooOldError(RuntimeError): ...
|
||||
|
||||
class NotCallableError(TypeError):
|
||||
msg: str = ...
|
||||
value: Any = ...
|
||||
def __init__(self, msg: str, value: Any) -> None: ...
|
52
venv/lib/python3.7/site-packages/attr/filters.py
Normal file
52
venv/lib/python3.7/site-packages/attr/filters.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""
|
||||
Commonly useful filters for `attr.asdict`.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from ._compat import isclass
|
||||
from ._make import Attribute
|
||||
|
||||
|
||||
def _split_what(what):
|
||||
"""
|
||||
Returns a tuple of `frozenset`s of classes and attributes.
|
||||
"""
|
||||
return (
|
||||
frozenset(cls for cls in what if isclass(cls)),
|
||||
frozenset(cls for cls in what if isinstance(cls, Attribute)),
|
||||
)
|
||||
|
||||
|
||||
def include(*what):
|
||||
"""
|
||||
Whitelist *what*.
|
||||
|
||||
:param what: What to whitelist.
|
||||
:type what: `list` of `type` or `attr.Attribute`\\ s
|
||||
|
||||
:rtype: `callable`
|
||||
"""
|
||||
cls, attrs = _split_what(what)
|
||||
|
||||
def include_(attribute, value):
|
||||
return value.__class__ in cls or attribute in attrs
|
||||
|
||||
return include_
|
||||
|
||||
|
||||
def exclude(*what):
|
||||
"""
|
||||
Blacklist *what*.
|
||||
|
||||
:param what: What to blacklist.
|
||||
:type what: `list` of classes or `attr.Attribute`\\ s.
|
||||
|
||||
:rtype: `callable`
|
||||
"""
|
||||
cls, attrs = _split_what(what)
|
||||
|
||||
def exclude_(attribute, value):
|
||||
return value.__class__ not in cls and attribute not in attrs
|
||||
|
||||
return exclude_
|
@ -0,0 +1 @@
|
||||
pip
|
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Hynek Schlawack
|
||||
|
||||
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.
|
229
venv/lib/python3.7/site-packages/attrs-19.3.0.dist-info/METADATA
Normal file
229
venv/lib/python3.7/site-packages/attrs-19.3.0.dist-info/METADATA
Normal file
@ -0,0 +1,229 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: attrs
|
||||
Version: 19.3.0
|
||||
Summary: Classes Without Boilerplate
|
||||
Home-page: https://www.attrs.org/
|
||||
Author: Hynek Schlawack
|
||||
Author-email: hs@ox.cx
|
||||
Maintainer: Hynek Schlawack
|
||||
Maintainer-email: hs@ox.cx
|
||||
License: MIT
|
||||
Project-URL: Documentation, https://www.attrs.org/
|
||||
Project-URL: Bug Tracker, https://github.com/python-attrs/attrs/issues
|
||||
Project-URL: Source Code, https://github.com/python-attrs/attrs
|
||||
Keywords: class,attribute,boilerplate
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Natural Language :: English
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
Description-Content-Type: text/x-rst
|
||||
Provides-Extra: azure-pipelines
|
||||
Requires-Dist: coverage ; extra == 'azure-pipelines'
|
||||
Requires-Dist: hypothesis ; extra == 'azure-pipelines'
|
||||
Requires-Dist: pympler ; extra == 'azure-pipelines'
|
||||
Requires-Dist: pytest (>=4.3.0) ; extra == 'azure-pipelines'
|
||||
Requires-Dist: six ; extra == 'azure-pipelines'
|
||||
Requires-Dist: zope.interface ; extra == 'azure-pipelines'
|
||||
Requires-Dist: pytest-azurepipelines ; extra == 'azure-pipelines'
|
||||
Provides-Extra: dev
|
||||
Requires-Dist: coverage ; extra == 'dev'
|
||||
Requires-Dist: hypothesis ; extra == 'dev'
|
||||
Requires-Dist: pympler ; extra == 'dev'
|
||||
Requires-Dist: pytest (>=4.3.0) ; extra == 'dev'
|
||||
Requires-Dist: six ; extra == 'dev'
|
||||
Requires-Dist: zope.interface ; extra == 'dev'
|
||||
Requires-Dist: sphinx ; extra == 'dev'
|
||||
Requires-Dist: pre-commit ; extra == 'dev'
|
||||
Provides-Extra: docs
|
||||
Requires-Dist: sphinx ; extra == 'docs'
|
||||
Requires-Dist: zope.interface ; extra == 'docs'
|
||||
Provides-Extra: tests
|
||||
Requires-Dist: coverage ; extra == 'tests'
|
||||
Requires-Dist: hypothesis ; extra == 'tests'
|
||||
Requires-Dist: pympler ; extra == 'tests'
|
||||
Requires-Dist: pytest (>=4.3.0) ; extra == 'tests'
|
||||
Requires-Dist: six ; extra == 'tests'
|
||||
Requires-Dist: zope.interface ; extra == 'tests'
|
||||
|
||||
.. image:: https://www.attrs.org/en/latest/_static/attrs_logo.png
|
||||
:alt: attrs Logo
|
||||
|
||||
======================================
|
||||
``attrs``: Classes Without Boilerplate
|
||||
======================================
|
||||
|
||||
.. image:: https://readthedocs.org/projects/attrs/badge/?version=stable
|
||||
:target: https://www.attrs.org/en/stable/?badge=stable
|
||||
:alt: Documentation Status
|
||||
|
||||
.. image:: https://attrs.visualstudio.com/attrs/_apis/build/status/python-attrs.attrs?branchName=master
|
||||
:target: https://attrs.visualstudio.com/attrs/_build/latest?definitionId=1&branchName=master
|
||||
:alt: CI Status
|
||||
|
||||
.. image:: https://codecov.io/github/python-attrs/attrs/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/github/python-attrs/attrs
|
||||
:alt: Test Coverage
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
:alt: Code style: black
|
||||
|
||||
.. teaser-begin
|
||||
|
||||
``attrs`` is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka `dunder <https://nedbatchelder.com/blog/200605/dunder.html>`_ methods).
|
||||
|
||||
Its main goal is to help you to write **concise** and **correct** software without slowing down your code.
|
||||
|
||||
.. -spiel-end-
|
||||
|
||||
For that, it gives you a class decorator and a way to declaratively define the attributes on that class:
|
||||
|
||||
.. -code-begin-
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> import attr
|
||||
|
||||
>>> @attr.s
|
||||
... class SomeClass(object):
|
||||
... a_number = attr.ib(default=42)
|
||||
... list_of_numbers = attr.ib(factory=list)
|
||||
...
|
||||
... def hard_math(self, another_number):
|
||||
... return self.a_number + sum(self.list_of_numbers) * another_number
|
||||
|
||||
|
||||
>>> sc = SomeClass(1, [1, 2, 3])
|
||||
>>> sc
|
||||
SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
|
||||
|
||||
>>> sc.hard_math(3)
|
||||
19
|
||||
>>> sc == SomeClass(1, [1, 2, 3])
|
||||
True
|
||||
>>> sc != SomeClass(2, [3, 2, 1])
|
||||
True
|
||||
|
||||
>>> attr.asdict(sc)
|
||||
{'a_number': 1, 'list_of_numbers': [1, 2, 3]}
|
||||
|
||||
>>> SomeClass()
|
||||
SomeClass(a_number=42, list_of_numbers=[])
|
||||
|
||||
>>> C = attr.make_class("C", ["a", "b"])
|
||||
>>> C("foo", "bar")
|
||||
C(a='foo', b='bar')
|
||||
|
||||
|
||||
After *declaring* your attributes ``attrs`` gives you:
|
||||
|
||||
- a concise and explicit overview of the class's attributes,
|
||||
- a nice human-readable ``__repr__``,
|
||||
- a complete set of comparison methods (equality and ordering),
|
||||
- an initializer,
|
||||
- and much more,
|
||||
|
||||
*without* writing dull boilerplate code again and again and *without* runtime performance penalties.
|
||||
|
||||
On Python 3.6 and later, you can often even drop the calls to ``attr.ib()`` by using `type annotations <https://www.attrs.org/en/latest/types.html>`_.
|
||||
|
||||
This gives you the power to use actual classes with actual types in your code instead of confusing ``tuple``\ s or `confusingly behaving <https://www.attrs.org/en/stable/why.html#namedtuples>`_ ``namedtuple``\ s.
|
||||
Which in turn encourages you to write *small classes* that do `one thing well <https://www.destroyallsoftware.com/talks/boundaries>`_.
|
||||
Never again violate the `single responsibility principle <https://en.wikipedia.org/wiki/Single_responsibility_principle>`_ just because implementing ``__init__`` et al is a painful drag.
|
||||
|
||||
|
||||
.. -testimonials-
|
||||
|
||||
Testimonials
|
||||
============
|
||||
|
||||
**Amber Hawkie Brown**, Twisted Release Manager and Computer Owl:
|
||||
|
||||
Writing a fully-functional class using attrs takes me less time than writing this testimonial.
|
||||
|
||||
|
||||
**Glyph Lefkowitz**, creator of `Twisted <https://twistedmatrix.com/>`_, `Automat <https://pypi.org/project/Automat/>`_, and other open source software, in `The One Python Library Everyone Needs <https://glyph.twistedmatrix.com/2016/08/attrs.html>`_:
|
||||
|
||||
I’m looking forward to is being able to program in Python-with-attrs everywhere.
|
||||
It exerts a subtle, but positive, design influence in all the codebases I’ve see it used in.
|
||||
|
||||
|
||||
**Kenneth Reitz**, creator of `Requests <https://github.com/psf/requests>`_ (`on paper no less <https://twitter.com/hynek/status/866817877650751488>`_!):
|
||||
|
||||
attrs—classes for humans. I like it.
|
||||
|
||||
|
||||
**Łukasz Langa**, creator of `Black <https://github.com/psf/black>`_, prolific Python core developer, and release manager for Python 3.8 and 3.9:
|
||||
|
||||
I'm increasingly digging your attr.ocity. Good job!
|
||||
|
||||
|
||||
.. -end-
|
||||
|
||||
.. -project-information-
|
||||
|
||||
Getting Help
|
||||
============
|
||||
|
||||
Please use the ``python-attrs`` tag on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_ to get help.
|
||||
|
||||
Answering questions of your fellow developers is also great way to help the project!
|
||||
|
||||
|
||||
Project Information
|
||||
===================
|
||||
|
||||
``attrs`` is released under the `MIT <https://choosealicense.com/licenses/mit/>`_ license,
|
||||
its documentation lives at `Read the Docs <https://www.attrs.org/>`_,
|
||||
the code on `GitHub <https://github.com/python-attrs/attrs>`_,
|
||||
and the latest release on `PyPI <https://pypi.org/project/attrs/>`_.
|
||||
It’s rigorously tested on Python 2.7, 3.4+, and PyPy.
|
||||
|
||||
We collect information on **third-party extensions** in our `wiki <https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs>`_.
|
||||
Feel free to browse and add your own!
|
||||
|
||||
If you'd like to contribute to ``attrs`` you're most welcome and we've written `a little guide <https://www.attrs.org/en/latest/contributing.html>`_ to get you started!
|
||||
|
||||
|
||||
Release Information
|
||||
===================
|
||||
|
||||
19.3.0 (2019-10-15)
|
||||
-------------------
|
||||
|
||||
Changes
|
||||
^^^^^^^
|
||||
|
||||
- Fixed ``auto_attribs`` usage when default values cannot be compared directly with ``==``, such as ``numpy`` arrays.
|
||||
`#585 <https://github.com/python-attrs/attrs/issues/585>`_
|
||||
|
||||
`Full changelog <https://www.attrs.org/en/stable/changelog.html>`_.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
``attrs`` is written and maintained by `Hynek Schlawack <https://hynek.me/>`_.
|
||||
|
||||
The development is kindly supported by `Variomedia AG <https://www.variomedia.de/>`_.
|
||||
|
||||
A full list of contributors can be found in `GitHub's overview <https://github.com/python-attrs/attrs/graphs/contributors>`_.
|
||||
|
||||
It’s the spiritual successor of `characteristic <https://characteristic.readthedocs.io/>`_ and aspires to fix some of it clunkiness and unfortunate decisions.
|
||||
Both were inspired by Twisted’s `FancyEqMixin <https://twistedmatrix.com/documents/current/api/twisted.python.util.FancyEqMixin.html>`_ but both are implemented using class decorators because `subclassing is bad for you <https://www.youtube.com/watch?v=3MNVP9-hglc>`_, m’kay?
|
||||
|
||||
|
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Crossbar.io Technologies 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.
|
@ -0,0 +1,347 @@
|
||||
autobahn/__init__.py,sha256=uCdcBR1rNLDCosowHs3ea1A1lH10lrbC_aPjM5YGjOM,1399
|
||||
autobahn/__main__.py,sha256=vMSMsxDkAWFpIv-53lNBn9k7iFIstouSfLmJcTZPTtc,11114
|
||||
autobahn/_version.py,sha256=nAupaY5VTtY8dIxAKX-Xn8RL_GqiKBfOKcwKZ7Ii_XM,1319
|
||||
autobahn/exception.py,sha256=zJG4u2Y7iQdvBocW6EA0WYCCjB4s50lJw7nTUwwbKDA,1614
|
||||
autobahn/util.py,sha256=Cu68EMPBGYN85_AvrVBY0E2StDiUwHVngCk1U9IfuGs,27599
|
||||
autobahn/asyncio/__init__.py,sha256=EfiMPwA8Ivr-QPWKr1iuM5xpeb7S-n1UJyFeihRUnfA,2055
|
||||
autobahn/asyncio/component.py,sha256=5BNTpptXPv1y4UfDURJ-nxX0BC2RfdT1nizBgo4VaME,16045
|
||||
autobahn/asyncio/rawsocket.py,sha256=TVXD_CaJaWZ9Vfb7TiNiXNWoSZTSyu6-jlhpatw7Xd8,17259
|
||||
autobahn/asyncio/util.py,sha256=GvrzlbVwIRU7v4EIt2AhPyK_p2eQ8TmyUZw7UqsTDdA,3297
|
||||
autobahn/asyncio/wamp.py,sha256=2R7wr7VRYyknKID58ZHPAeTTSgBTrB-uiwLJPl7omDY,11247
|
||||
autobahn/asyncio/websocket.py,sha256=EzHkmuQcPC8IdzbCddtAHh_FfIyOrpZ7BoLd-lpRh8o,12134
|
||||
autobahn/asyncio/xbr.py,sha256=DSnL-qHXyU9O9Tz6QpguGDxN5t8vJDpaEwB_Y2OLNzg,3465
|
||||
autobahn/asyncio/test/README,sha256=LB9qc37yrpi15mmHeOw9sJ7yu9Bzo0xxcXJD2IvNYjM,951
|
||||
autobahn/asyncio/test/test_asyncio_rawsocket.py,sha256=SXo4IwYrka9jf_gl2bg4Gn7p5yfOXOEyj98hoGbSrbM,7713
|
||||
autobahn/asyncio/test/test_asyncio_websocket.py,sha256=9MPJoVTWrYHBoI0dnB4wQYihU9Uu9hDL-GP3fzwXEIk,2401
|
||||
autobahn/nvx/__init__.py,sha256=E2gqYZ596EK-HPfki680pP3KFimqfCCWVhQdzDYtH3c,1426
|
||||
autobahn/nvx/_utf8validator.c,sha256=C6mpS9BVrkwyrfmhqnbdQXn8nMF_fpo8ZYGekaZreUA,17063
|
||||
autobahn/nvx/_utf8validator.py,sha256=ADlNAm_kIUiOkKQnWfywd9d2JOEmJGeyGh0GF4bFcpk,2543
|
||||
autobahn/nvx/test/test_utf8validator.py,sha256=_pAJPTbIW9hogD44BvsKXcGdUPswV811NptiFnITnLU,14213
|
||||
autobahn/rawsocket/__init__.py,sha256=NhWjNyqDKUs_FqdWfLdMN5-ddQ8OrWaYok3_FRZSbJU,1293
|
||||
autobahn/rawsocket/util.py,sha256=7TuuTgPyKcFNQkpPmxy2GYUcQAMagx7dKPIuODifLtk,5931
|
||||
autobahn/rawsocket/test/__init__.py,sha256=NhWjNyqDKUs_FqdWfLdMN5-ddQ8OrWaYok3_FRZSbJU,1293
|
||||
autobahn/rawsocket/test/test_rawsocket_url.py,sha256=orFompUzrWUw7QtgcrIDFss-ZycfYgUnfBsw-Utf6Zg,4982
|
||||
autobahn/test/__init__.py,sha256=_drclixbeIOutvlA9xEKpg1NUa9t6ZVcRFkl8JIToBA,2471
|
||||
autobahn/test/test_rng.py,sha256=J7_nux44GBRcvBHU3INgaglkEUDK_lreE91sLnl9XXQ,3873
|
||||
autobahn/test/test_util.py,sha256=dBT7j-sca67xfI9lCUctjeRtELK_NwIwFDU-0F3h6Mg,1850
|
||||
autobahn/twisted/__init__.py,sha256=af4M9V4yMKhAoAA9cZCNjBLOhccbi_0BSwk8YeZJVE0,3110
|
||||
autobahn/twisted/choosereactor.py,sha256=8-kEWvufaLmlAxvYbHiLxR6CIezuzp_P1a3gdY72fCA,9042
|
||||
autobahn/twisted/component.py,sha256=NJBsvuGn5OVog0KM7QLoFb6oDVmZKuGlHrgzF4uPfqo,15052
|
||||
autobahn/twisted/cryptosign.py,sha256=FCi4fRGDDOJJZQqGyo32MypQzuLdQs-2Moz4SarY7Cs,5856
|
||||
autobahn/twisted/forwarder.py,sha256=HGW35hGYBBHrRTzU9-K-EJth8uzriEgxAhscohfdzok,4695
|
||||
autobahn/twisted/rawsocket.py,sha256=szK49XxxpeoSOBHQqB5uJtPLDBBSTSyfl8W1aLJ4zOQ,22903
|
||||
autobahn/twisted/resource.py,sha256=Uobz7135OXu8ljkj6Kh1aBFydu7uUg7zrKTSQX7YFZA,7208
|
||||
autobahn/twisted/util.py,sha256=mGaVwPAplcnd3ggtOYywpqUzwO9RhDsB3-kqaaM-2FE,5192
|
||||
autobahn/twisted/wamp.py,sha256=iHrowZDebCnQ_G9mBmR2gu-f9nvDLQkNaPRxQlFDgOY,32347
|
||||
autobahn/twisted/websocket.py,sha256=tEZ0sHI9d4cn9iHmyFdne1nKtd-Tw_0bF4VPSUm4vos,30162
|
||||
autobahn/twisted/xbr.py,sha256=vAK3mNAGBJLQ-IBT16M3QL4l4VgoRhOu--PVP9I2va4,3834
|
||||
autobahn/twisted/testing/__init__.py,sha256=JKkTGvbTkmEPNG5Eh83RcA27Tylmu0ZTUQa_Vx5fmhw,10000
|
||||
autobahn/wamp/__init__.py,sha256=PezDFOWF7GIM63pZRXnsD6LgC8fhaaTHgrmFwBvY8vI,2317
|
||||
autobahn/wamp/auth.py,sha256=r5mcqTTxe3lBineC-cz9hrD3qFeNqxilaLs5T-1U2zg,20164
|
||||
autobahn/wamp/component.py,sha256=O5LrREX6CePgRmDUAYg07uj1U-s3e4Paz8hPhgoD1kI,37093
|
||||
autobahn/wamp/cryptobox.py,sha256=lfo_cUTrqMq3imNY1uVp5eoZ_025W75V7wA6m-QlpBs,10673
|
||||
autobahn/wamp/cryptosign.py,sha256=wFUeN99vN4qLJbMVNAkUnzST3Dhap16J7FwvkLMmjXg,21198
|
||||
autobahn/wamp/exception.py,sha256=9teqpFXPJ3Gjlb0S7_irsCpXCLMLpUksiM8EEuAIpyw,10115
|
||||
autobahn/wamp/interfaces.py,sha256=iXad4yNp4ul440jS534MzxWMtV7wW2iupwEw08Xidng,26914
|
||||
autobahn/wamp/message.py,sha256=_oAsii5xTLDPG2AmQqIGu021fGz8TGmrEpX8foeoZs4,215716
|
||||
autobahn/wamp/message_fbs.py,sha256=6NFttrOPlGdtSNBQ-375JezbDOo14kYR-vKrUhbjBFQ,4649
|
||||
autobahn/wamp/protocol.py,sha256=uyEns1_ul27nkpTKFe8_hyhfVquxkPWUsRTeNAeP0ak,85576
|
||||
autobahn/wamp/request.py,sha256=u0dk4eMIlMkKSe2BU2_SKyT_ds5PoqSVIItWjriRZAg,9559
|
||||
autobahn/wamp/role.py,sha256=ojL8hqeyDB1fecD7GH5VGjo94rfg3NWBYtbblFz4CXM,10986
|
||||
autobahn/wamp/serializer.py,sha256=2cSw2jeCZ5FHkaptt5O0ArFhVN8IXTs98qs9T2EnOdY,26307
|
||||
autobahn/wamp/types.py,sha256=YL-qf_owVahxVpDKIbYhBC4Rr6itQq_d4aTxJV3Sv1o,52400
|
||||
autobahn/wamp/uri.py,sha256=-fEI4eYF01S4p4U-2KJoQqIqKFNVs7L1ca0cQ2lv3P4,11613
|
||||
autobahn/wamp/websocket.py,sha256=3yO5qrD3Yx2C0ya7BAWA542XXC3Lv_yIq3KsoAC1oqQ,11165
|
||||
autobahn/wamp/gen/__init__.py,sha256=NhWjNyqDKUs_FqdWfLdMN5-ddQ8OrWaYok3_FRZSbJU,1293
|
||||
autobahn/wamp/gen/schema/auth.bfbs,sha256=5NohfPH8xErVkCRyH00Rvj5vYXOyKt4B44-rvVWiFiM,12792
|
||||
autobahn/wamp/gen/schema/pubsub.bfbs,sha256=SpSvTkNV_dlFIat08VO4-SojyJWRY0qyhszV2oBmDV8,8840
|
||||
autobahn/wamp/gen/schema/roles.bfbs,sha256=Sjyzs0tFLDrbxNUvJov9Tyx_of7z9k2SHYYElsFer_8,8240
|
||||
autobahn/wamp/gen/schema/rpc.bfbs,sha256=XZbU-v1BFD9tBT7Al4GuCWdAJOpVinHxXLwRfvw0Lp8,7664
|
||||
autobahn/wamp/gen/schema/session.bfbs,sha256=Q-9TWiilL6qYGiNOewRoyeNx2BKAdUTBh2w017Ejtz0,15944
|
||||
autobahn/wamp/gen/schema/types.bfbs,sha256=ih3tez-Jdgwx0V0N07jp4ordvJEiS-11SoHRuEQVndE,3736
|
||||
autobahn/wamp/gen/schema/wamp.bfbs,sha256=QENoUkeKlGH2zrDDQOEaschawHAvGNKZvMySQPrdgQk,26856
|
||||
autobahn/wamp/gen/wamp/Map.py,sha256=tAF0s5-5-g7YNOKintFpR1d4lUt2gEgYi0R7wRyDF1k,1208
|
||||
autobahn/wamp/gen/wamp/Void.py,sha256=S9gG7Z1qe-c0bToR6c2OzhSU8ljvCksqiBFB6s9iljs,550
|
||||
autobahn/wamp/gen/wamp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
autobahn/wamp/gen/wamp/proto/Abort.py,sha256=4zEG4LxLduCzuz-bi-vIHNltK1rANazICQiWZE3miUc,1249
|
||||
autobahn/wamp/gen/wamp/proto/AnyMessage.py,sha256=GmCjxk29SVpDJIqklYkizaCnh2XTgMNq1HLx8IujnYQ,605
|
||||
autobahn/wamp/gen/wamp/proto/AuthCraChallenge.py,sha256=SZVi5fJxDH3k6hEBmuH6TD3hLx2V8szcDIIi2P47iA8,2062
|
||||
autobahn/wamp/gen/wamp/proto/AuthCraRequest.py,sha256=-GzXW3MeE3guUaHI6Mnvj6NClIwRFYHToaoP9Aq9jHk,611
|
||||
autobahn/wamp/gen/wamp/proto/AuthCraWelcome.py,sha256=CscxVwes4tfiwJMHiFDjV8mDTWsmuSoEJI8DE-QOJXQ,611
|
||||
autobahn/wamp/gen/wamp/proto/AuthCryptosignChallenge.py,sha256=_s6rx58ftx5Otl8vrjRRRz6xd4jpmhCKczzUzU8TXNQ,1049
|
||||
autobahn/wamp/gen/wamp/proto/AuthCryptosignRequest.py,sha256=TEXT0Hr1XkSl0z5U48CiLl4gEovieIDumcCBoR2PlJU,1409
|
||||
autobahn/wamp/gen/wamp/proto/AuthCryptosignWelcome.py,sha256=39bZjIyQNrGRx2-veF71fnC4zThO00pjrpDI1UQmbtI,653
|
||||
autobahn/wamp/gen/wamp/proto/AuthFactor.py,sha256=Y1zNCZNRMeWV8IvsaNt-uxLI8-yDFZ2Uc0lzKt6mqIg,234
|
||||
autobahn/wamp/gen/wamp/proto/AuthMethod.py,sha256=ZwkdO8-aitDwJLGY2vLbIrbrlg6gnyI5o86SMFXkiK8,222
|
||||
autobahn/wamp/gen/wamp/proto/AuthMode.py,sha256=UWgMyhyNUPeu2xw4aqI-ow_a0cvBOfVQ4L0EdOjnvwg,149
|
||||
autobahn/wamp/gen/wamp/proto/AuthScramChallenge.py,sha256=77CBgc_3tqNUb3yH80Icq003598p17GmN7KWxWzGTwc,2774
|
||||
autobahn/wamp/gen/wamp/proto/AuthScramRequest.py,sha256=aQQye797PXkm1RjldhI0uCW6QvqVHkO4WkkbyuSwzSM,1355
|
||||
autobahn/wamp/gen/wamp/proto/AuthScramWelcome.py,sha256=28xHstHV7V-Tu6-N9AnjmvxJSbnLm58k0sFCblyzAnw,997
|
||||
autobahn/wamp/gen/wamp/proto/AuthTicketChallenge.py,sha256=90DIv5T_ccz_PjDww7mL5Rk3VIV9N4xvsOFcQXP4rhc,641
|
||||
autobahn/wamp/gen/wamp/proto/AuthTicketRequest.py,sha256=ZrFj2sEItg5sYqimyfNMntPtBLfVUjrDuK80X1gjVSc,629
|
||||
autobahn/wamp/gen/wamp/proto/AuthTicketWelcome.py,sha256=AxVrC6XudwUDaDZOUCFvPYoENszNWW53lPnnigDt1QY,629
|
||||
autobahn/wamp/gen/wamp/proto/Authenticate.py,sha256=cPm08hvSQFrWJqOYfu_fTaaXY1j14a0gtRH6W4DSlq4,1443
|
||||
autobahn/wamp/gen/wamp/proto/BrokerFeatures.py,sha256=U1VL0LMW4txZ9f0N27LsVJpzbdsrBjNeTYgKuS3jUJw,6251
|
||||
autobahn/wamp/gen/wamp/proto/Call.py,sha256=VmrWRA5DBfA4gSjXn1-QKz4gnY4t4Wv9b8zZF0sKIM8,4541
|
||||
autobahn/wamp/gen/wamp/proto/CalleeFeatures.py,sha256=wIYgWCA099izQKDEs6EPX8x6nsQqufeM7dIiAfej6UY,4559
|
||||
autobahn/wamp/gen/wamp/proto/CallerFeatures.py,sha256=0BPek4PnoFDEMz4WegcfZFc_705ZEtKks1WhXO0sbnI,2966
|
||||
autobahn/wamp/gen/wamp/proto/Cancel.py,sha256=P0-5iW6et2HXnj9AQDzGsp7gbWHflfJMA4TzUvkZS5E,1197
|
||||
autobahn/wamp/gen/wamp/proto/CancelMode.py,sha256=vh5uzg0R1hO4R2WCQ4TzMjxqwwhp86Tc5mUCoEc_tlM,157
|
||||
autobahn/wamp/gen/wamp/proto/Challenge.py,sha256=49tA5J4B5mKGT1kTodPZxEDn4SH7DuT8WHTzwesLwRc,1373
|
||||
autobahn/wamp/gen/wamp/proto/ChannelBinding.py,sha256=M8qp-JHRwVu_AC_4fWdKor7rmNNRHw1iRETx_rOPvOE,153
|
||||
autobahn/wamp/gen/wamp/proto/ClientRoles.py,sha256=2NUgfetpclNCFOJOl3wg5Q_N6dAy26sa3sVXvilonaY,2679
|
||||
autobahn/wamp/gen/wamp/proto/DealerFeatures.py,sha256=GINWcy2mK4wiQT17mA1zLSc8ljVSNMAa2OcsPbwM0sE,5715
|
||||
autobahn/wamp/gen/wamp/proto/Error.py,sha256=GIwH1ssVtK7nHuL9yl__gFj_lXRe063EwUqggfUdxek,4210
|
||||
autobahn/wamp/gen/wamp/proto/Event.py,sha256=U-NF3jWWrtKrKCdqpVc3ozEKJpC5RbVZaYhUcieZSy4,9087
|
||||
autobahn/wamp/gen/wamp/proto/EventReceived.py,sha256=XZHvzQyjdTzVOx9KPlJ1FFMHzFkMG2QEjyxq0ctBtZA,3720
|
||||
autobahn/wamp/gen/wamp/proto/Goodbye.py,sha256=CuJ6K4g3DhsN31Rxq2gNm2BdCMo2Fsitc8sk2_Z-UVM,1609
|
||||
autobahn/wamp/gen/wamp/proto/Hello.py,sha256=gwApzTi6xiFGW3E39zsZGkSm9WuzR8JpJgy82KBbAN0,4627
|
||||
autobahn/wamp/gen/wamp/proto/HelloNew.py,sha256=7jnNPBeiT8ZMFkrnuCvsawo5CL-0svoG9RT7gTsewaw,5989
|
||||
autobahn/wamp/gen/wamp/proto/Interrupt.py,sha256=Vr4NuGUVzh-F2AZUsCU60w4Aghult6w-AUQnttjtc54,1227
|
||||
autobahn/wamp/gen/wamp/proto/Invocation.py,sha256=Mqcjaex_KNOzWRKUTrkUnyVGMPXs0pomHDFOHdNU6o0,6160
|
||||
autobahn/wamp/gen/wamp/proto/InvocationPolicy.py,sha256=PzKHYeKYTt3vuPUyj260cPSGgXtMZZE0H0JiyMKkeGw,199
|
||||
autobahn/wamp/gen/wamp/proto/Kdf.py,sha256=x9AXogMyCtV9pfBqBXEF6MAQhmQCuhm4koeGnEpfEm8,153
|
||||
autobahn/wamp/gen/wamp/proto/Match.py,sha256=xPyjrehsQXn7qjSIPON2k1-XrigZj2u-R4iNNZSpoqQ,158
|
||||
autobahn/wamp/gen/wamp/proto/Message.py,sha256=LsYjfelutuVJpPH5Wu5w9JyEjuptHb8uZ38bmRVtC5A,1321
|
||||
autobahn/wamp/gen/wamp/proto/MessageType.py,sha256=aluiW50TmyPljbCDBtz6slTC75Qrx2KA28OSXylS_hE,610
|
||||
autobahn/wamp/gen/wamp/proto/Payload.py,sha256=fmB6dDCwB-yVlGjWEYmK6affoF6rxVKcnFeuvDQdIvM,161
|
||||
autobahn/wamp/gen/wamp/proto/Principal.py,sha256=8kLZEH-513EViBkB60XMaou-4meeF3h6_-0S6pp2PxE,1623
|
||||
autobahn/wamp/gen/wamp/proto/Publish.py,sha256=smmlHJN8pXzMl6nkUKLL3kyG3Oby1BIdd0Aw3Inf9So,13155
|
||||
autobahn/wamp/gen/wamp/proto/Published.py,sha256=Ih51BbhQWSSPQdoNjX-0NQYFFUpIlFmH0QXgXdQMy64,1257
|
||||
autobahn/wamp/gen/wamp/proto/PublisherFeatures.py,sha256=uSoS1JazH32tEI082RZH5-8Iqa56Yzx5Z49oOCJWcAo,3124
|
||||
autobahn/wamp/gen/wamp/proto/Register.py,sha256=9cTBlJpuKqdvegS0Bf5BElgDWUKNNqw8Pu_0WWYd_uM,2618
|
||||
autobahn/wamp/gen/wamp/proto/Registered.py,sha256=THhRcMZzj0r-cDXaUT2k15KPEUvoE9H0_NGjP5oMfRg,1271
|
||||
autobahn/wamp/gen/wamp/proto/Result.py,sha256=bReDBODwihT8ZKeZHjRgHTHA-oJtVy6dfC5yXVOJg_o,3885
|
||||
autobahn/wamp/gen/wamp/proto/RouterRoles.py,sha256=9xco2KbV5qgAGmaKq7wsNJIrLgdMc1hFa663hqBscDk,1611
|
||||
autobahn/wamp/gen/wamp/proto/Serializer.py,sha256=64D2_OlFMfiOTJpbY3PHqBeu7qcUVzGmFnOkXHOC6AA,227
|
||||
autobahn/wamp/gen/wamp/proto/Subscribe.py,sha256=OgPOofEt6CRBIrx8oWBQWTAI1ct0SFCZ7Ec-2Mf-ABA,1932
|
||||
autobahn/wamp/gen/wamp/proto/Subscribed.py,sha256=EXs_Q3-TXiA5NIidjLlnVTeSXmm7ddoVAOpx7ppPC7s,1271
|
||||
autobahn/wamp/gen/wamp/proto/SubscriberFeatures.py,sha256=FWbBVXTBsYfNA1AyzkF11EseZOKJ75Xxw5h19wVTnb0,3956
|
||||
autobahn/wamp/gen/wamp/proto/SubscriberReceived.py,sha256=TVZNMQu-jao1Beaw8S9bS7isXD4V67Ne5CePrBzbf_o,5023
|
||||
autobahn/wamp/gen/wamp/proto/Unregister.py,sha256=n3fSOimgjCYVUTTC-I7XerGbaEhI3vCuv8C7TvFoDhI,1271
|
||||
autobahn/wamp/gen/wamp/proto/Unregistered.py,sha256=5oRn_dWavEEo0hLfsIvzge6JEs41drZPICFEYnFhTvU,1649
|
||||
autobahn/wamp/gen/wamp/proto/Unsubscribe.py,sha256=Rv0YhU2DlmhfvVaBfdL3yqcickQCeHEXVRX3mbWAdEU,1281
|
||||
autobahn/wamp/gen/wamp/proto/Unsubscribed.py,sha256=sD6BlUodQMBWsB5XXqTRoHdOdJjPNsWL1JNqQHYvkjQ,1649
|
||||
autobahn/wamp/gen/wamp/proto/Welcome.py,sha256=MMkF8ebOTgnNhG_draW6WwdwhgN022UHPtpGWECRDNY,4669
|
||||
autobahn/wamp/gen/wamp/proto/Yield.py,sha256=bZmFHORjHR3NhOB5UJ4K3rbyUSAzP2KViKRPA3p8A_0,3528
|
||||
autobahn/wamp/gen/wamp/proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
autobahn/wamp/test/__init__.py,sha256=NhWjNyqDKUs_FqdWfLdMN5-ddQ8OrWaYok3_FRZSbJU,1293
|
||||
autobahn/wamp/test/test_auth.py,sha256=mHdNWCu46tY9ziMDMrxMYLaqpWclLRIZ8HynBHy4xQ0,9982
|
||||
autobahn/wamp/test/test_component.py,sha256=U0F5vQcVMRIbsW5M-A_lbVGt7fTGVpMm6jc0R8b272s,7636
|
||||
autobahn/wamp/test/test_component_aio.py,sha256=AhmADJBd4kbPJaHfDgdj9cLHffC3GB0eiqUjn-L32i8,6133
|
||||
autobahn/wamp/test/test_cryptobox.py,sha256=8us4P5Qb382rmAPX9HJlbjSothufX_w-Axi_Glpm2Co,1594
|
||||
autobahn/wamp/test/test_cryptosign.py,sha256=_jAyfjR2gUKvZoDv5J28hYMIoBJeKoX_A4mgkDna_rY,5056
|
||||
autobahn/wamp/test/test_exception.py,sha256=SxaFxSKNqxL0sCXIkpgfX17WaGWIPnhFkmCnMwhD6m4,2277
|
||||
autobahn/wamp/test/test_message.py,sha256=-oj55oc_8DIDLxHoB0UWfux_DiafVDkWPdH7HsIguGk,48434
|
||||
autobahn/wamp/test/test_protocol.py,sha256=mY3UNpX17BrRgd3MsoAeqctIPDdX9SRtfj2jTUm-suc,41982
|
||||
autobahn/wamp/test/test_protocol_peer.py,sha256=hDfL36jafVG1ni3QrUu9j0oKYZd3bFt3cy-dhtXlY8U,4484
|
||||
autobahn/wamp/test/test_runner.py,sha256=fbzE6RSXWnH6KNDYR8OSVC7h0w9HKJC37zgVVh_915s,9280
|
||||
autobahn/wamp/test/test_serializer.py,sha256=0RzcU-AsgVQ-lxTeyUxmsBVE9ggspoj61IRlXDNVuSw,11222
|
||||
autobahn/wamp/test/test_uri_pattern.py,sha256=8RermiOXm_KKNfCF5BsTbE6mXYhUABX0b8V0rjuaKwY,24728
|
||||
autobahn/wamp/test/test_user_handler_errors.py,sha256=mjVdWnRPkxoocmmk4cTsJO_ppnXquOwEnPmepWkMIVY,16020
|
||||
autobahn/wamp/test/test_websocket.py,sha256=gYNEzVKthy8PhWiBGxUrwLNKlxtG_izlIKnA1w9YTWE,1783
|
||||
autobahn/websocket/__init__.py,sha256=psmYUAHzor87GFkLEegMXfi0M_QxtliaTBo0yL278WA,1751
|
||||
autobahn/websocket/compress.py,sha256=oijEK_PGQ0WB1dRmVAn8_8hm3Q8Sb29_9CY34JZe2JQ,4606
|
||||
autobahn/websocket/compress_base.py,sha256=1mx7Chhonb2SjgAbtDCRp2_6wSQYlrNnwn5xrBzmNHU,2146
|
||||
autobahn/websocket/compress_bzip2.py,sha256=uXH6ABvkdHe4heIUUbdSIHAyG3h7j4YLHmyvgKDWyng,18368
|
||||
autobahn/websocket/compress_deflate.py,sha256=_6CIpzYZY95jDstXVGFICvSuDsWfGQO0TnVhVsEDZMg,29930
|
||||
autobahn/websocket/compress_snappy.py,sha256=NR-a90iCb1wwybLvdRxCJSkeHtmcGSZWucD6gltMreA,16979
|
||||
autobahn/websocket/interfaces.py,sha256=5kNhHo7qbZNG0gMmEMwJySxj4quBvU1MBtcG6H0_B4A,30069
|
||||
autobahn/websocket/protocol.py,sha256=Yo5sYF-LdgYDXZ-mAyvBPu2CM2pOaZ9aLFmT6DRRqzY,162502
|
||||
autobahn/websocket/types.py,sha256=Pbw7sNulOg3xKiDcmy8qOHUhfBh4WCNLzC0ZyRJd898,15313
|
||||
autobahn/websocket/utf8validator.py,sha256=zrWgsbNqnOJ5_DolmUJ4q_lyM748sNu6z7goOlQfRTQ,10472
|
||||
autobahn/websocket/util.py,sha256=XVBGXR4ayMYvvX29sHwPhz4meWwHcI-0gmUWy834eHI,6734
|
||||
autobahn/websocket/xormasker.py,sha256=XF9idKxou9DHNFYuRlossm_XLQW2SBydWa43skf2gbI,4546
|
||||
autobahn/websocket/test/__init__.py,sha256=NhWjNyqDKUs_FqdWfLdMN5-ddQ8OrWaYok3_FRZSbJU,1293
|
||||
autobahn/websocket/test/test_protocol.py,sha256=cEm2g69azxh_oNKnBW2rtZFjhoRvt9isoqCshGWlyhM,9523
|
||||
autobahn/websocket/test/test_websocket.py,sha256=oz-V1T31euVBqDgc6JOPRdVheBWpOU4Uy1GZfIvV8Ck,14095
|
||||
autobahn/websocket/test/test_websocket_url.py,sha256=1XU_f6LXYIzIc35M0RpdJ02jNTy_nZ10uqneEGnPieY,5455
|
||||
autobahn/xbr/__init__.py,sha256=KqqlDFiim1xYbwkRdkC_MZRFvbIsmq5l0n7EcUZXFcc,6543
|
||||
autobahn/xbr/_abi.py,sha256=ZbdUEKmwKcB3Q3Ls-O5zsX8MlsC2BPub32piymQ5was,3169
|
||||
autobahn/xbr/_blockchain.py,sha256=K5Yw4p0XzM2GHhtSifOkOdN9MtpZxqFxn8exz2DC9xU,7314
|
||||
autobahn/xbr/_buyer.py,sha256=t_OTeSWVJqvDi8tu4JouhRfKrFHRg7T2x_rngUzZQ3M,25629
|
||||
autobahn/xbr/_interfaces.py,sha256=KH3GryyUnnwmiYuXOQ9tcrSRJYNN-MI_8gPq8cC_FhQ,4572
|
||||
autobahn/xbr/_mnemonic.py,sha256=omcP6F5W3S_j9d636EBuQUtJRol79F8U4sqDuIsac2Q,6112
|
||||
autobahn/xbr/_seller.py,sha256=-cYXjmwg0mCTkSJjtqjGFudUqW8A2vmaZkxqXo2Th2c,31311
|
||||
autobahn/xbr/_util.py,sha256=9KtQBAU42MyqIdKlJJvffYPaPc_SDMxLo3Bv2hN9e0s,7775
|
||||
autobahn/xbr/contracts/ECDSA.json,sha256=EVvH_fkEzyji3VSDgq88_YzJf2d-iAzuBCde_PWN9Bo,102692
|
||||
autobahn/xbr/contracts/ERC20.json,sha256=WE4H06FGvmOUUoJXpafU3OAuu5QOqXTOV6Uz4uFwqgE,485849
|
||||
autobahn/xbr/contracts/ERC20Detailed.json,sha256=EKI-ACQu6D5kUELGLv13aqg6Qg3z5lSgHHWivIpBR_o,54285
|
||||
autobahn/xbr/contracts/IERC20.json,sha256=0H34Yqdivc9wuqCfcUWfYhleEZXNKScn_jhjEFCsqJE,69299
|
||||
autobahn/xbr/contracts/Migrations.json,sha256=-y8tyFGmlIpxiQ8UVnsVXyqog_Z8PstMjkQkEIOkfm8,54004
|
||||
autobahn/xbr/contracts/Roles.json,sha256=VE1FmcWS0sIZhExrlVQSjYgvbMEWrK_iOe_yuB080Qw,90764
|
||||
autobahn/xbr/contracts/SafeMath.json,sha256=ZGXMQAWIl8PbPKCS4fGk6zorp2YU0uWiunSamk7m05M,145860
|
||||
autobahn/xbr/contracts/XBRChannel.json,sha256=lyf2lGIviSAipky82hISfmJtyhYkmhu7YVhRtD8FbFo,785283
|
||||
autobahn/xbr/contracts/XBRMaintained.json,sha256=M_ORh2fC3PrLxls7ppyeAJIjqRcTqDPN3MqIrFdXd6s,97235
|
||||
autobahn/xbr/contracts/XBRNetwork.json,sha256=Kb-yQCubto1yWZeYnxRTA41EofAkeBkGf3yiC2uq8mU,2294558
|
||||
autobahn/xbr/contracts/XBRNetworkProxy.json,sha256=80LV0bjKi4_plCXZD--S7-fq8glRwG1S32qeXx9UZXQ,28872
|
||||
autobahn/xbr/contracts/XBRToken.json,sha256=eBux5eHKi_ZZqAq4-CEOBWDsVUhCzQxa_t6GyDDriuU,68488
|
||||
twisted/plugins/autobahn_endpoints.py,sha256=PwwMP1IYX-2KVAzxm4LYpVc8rpSaPGlcgSKfEFrKiYc,6389
|
||||
twisted/plugins/autobahn_twistd.py,sha256=9mccXpFlai3XpoYHAtxAynedzogaLb6XM7-Qb9IF5yQ,1566
|
||||
autobahn-19.11.1.dist-info/LICENSE,sha256=A4fu_OVwRT2qpgYz8oZ2ADcx7soostCgBxxijjoABO8,1091
|
||||
autobahn-19.11.1.dist-info/METADATA,sha256=TZrnK8k_AAaXCE7n_tSLnKy-AdUk96rc3KnW0yXEKSw,16840
|
||||
autobahn-19.11.1.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110
|
||||
autobahn-19.11.1.dist-info/entry_points.txt,sha256=pOQzqt1oaJ0ZAex4ZEdquHVbriLGGB6DicT_1O1Y96w,50
|
||||
autobahn-19.11.1.dist-info/top_level.txt,sha256=i67H6_FOEnZCuafCKor2-4scMopj2B4VZiqG_K2709g,17
|
||||
autobahn-19.11.1.dist-info/RECORD,,
|
||||
../../../bin/wamp,sha256=f5CNfXC8MlqzFh6qfB3IICZmkBsawPVDZSpwxSPZIWU,272
|
||||
autobahn-19.11.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
autobahn/websocket/test/__pycache__/test_protocol.cpython-37.pyc,,
|
||||
autobahn/websocket/test/__pycache__/test_websocket_url.cpython-37.pyc,,
|
||||
autobahn/websocket/test/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/websocket/test/__pycache__/test_websocket.cpython-37.pyc,,
|
||||
autobahn/websocket/__pycache__/util.cpython-37.pyc,,
|
||||
autobahn/websocket/__pycache__/interfaces.cpython-37.pyc,,
|
||||
autobahn/websocket/__pycache__/compress_base.cpython-37.pyc,,
|
||||
autobahn/websocket/__pycache__/types.cpython-37.pyc,,
|
||||
autobahn/websocket/__pycache__/compress.cpython-37.pyc,,
|
||||
autobahn/websocket/__pycache__/compress_bzip2.cpython-37.pyc,,
|
||||
autobahn/websocket/__pycache__/protocol.cpython-37.pyc,,
|
||||
autobahn/websocket/__pycache__/compress_deflate.cpython-37.pyc,,
|
||||
autobahn/websocket/__pycache__/xormasker.cpython-37.pyc,,
|
||||
autobahn/websocket/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/websocket/__pycache__/utf8validator.cpython-37.pyc,,
|
||||
autobahn/websocket/__pycache__/compress_snappy.cpython-37.pyc,,
|
||||
autobahn/test/__pycache__/test_rng.cpython-37.pyc,,
|
||||
autobahn/test/__pycache__/test_util.cpython-37.pyc,,
|
||||
autobahn/test/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/xbr/__pycache__/_interfaces.cpython-37.pyc,,
|
||||
autobahn/xbr/__pycache__/_util.cpython-37.pyc,,
|
||||
autobahn/xbr/__pycache__/_seller.cpython-37.pyc,,
|
||||
autobahn/xbr/__pycache__/_blockchain.cpython-37.pyc,,
|
||||
autobahn/xbr/__pycache__/_abi.cpython-37.pyc,,
|
||||
autobahn/xbr/__pycache__/_mnemonic.cpython-37.pyc,,
|
||||
autobahn/xbr/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/xbr/__pycache__/_buyer.cpython-37.pyc,,
|
||||
autobahn/rawsocket/test/__pycache__/test_rawsocket_url.cpython-37.pyc,,
|
||||
autobahn/rawsocket/test/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/rawsocket/__pycache__/util.cpython-37.pyc,,
|
||||
autobahn/rawsocket/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/twisted/__pycache__/util.cpython-37.pyc,,
|
||||
autobahn/twisted/__pycache__/forwarder.cpython-37.pyc,,
|
||||
autobahn/twisted/__pycache__/websocket.cpython-37.pyc,,
|
||||
autobahn/twisted/__pycache__/resource.cpython-37.pyc,,
|
||||
autobahn/twisted/__pycache__/rawsocket.cpython-37.pyc,,
|
||||
autobahn/twisted/__pycache__/wamp.cpython-37.pyc,,
|
||||
autobahn/twisted/__pycache__/component.cpython-37.pyc,,
|
||||
autobahn/twisted/__pycache__/xbr.cpython-37.pyc,,
|
||||
autobahn/twisted/__pycache__/choosereactor.cpython-37.pyc,,
|
||||
autobahn/twisted/__pycache__/cryptosign.cpython-37.pyc,,
|
||||
autobahn/twisted/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/twisted/testing/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/__pycache__/util.cpython-37.pyc,,
|
||||
autobahn/__pycache__/_version.cpython-37.pyc,,
|
||||
autobahn/__pycache__/__main__.cpython-37.pyc,,
|
||||
autobahn/__pycache__/exception.cpython-37.pyc,,
|
||||
autobahn/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/nvx/test/__pycache__/test_utf8validator.cpython-37.pyc,,
|
||||
autobahn/nvx/__pycache__/_utf8validator.cpython-37.pyc,,
|
||||
autobahn/nvx/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_message.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_exception.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_runner.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_cryptobox.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_component_aio.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_protocol.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_component.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_auth.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_cryptosign.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_protocol_peer.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_serializer.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_user_handler_errors.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_uri_pattern.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/wamp/test/__pycache__/test_websocket.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/serializer.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/interfaces.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/uri.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/websocket.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/request.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/message.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/message_fbs.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/types.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/component.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/auth.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/role.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/cryptosign.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/exception.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/protocol.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/wamp/__pycache__/cryptobox.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Payload.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Publish.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthScramChallenge.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Principal.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/BrokerFeatures.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Unsubscribe.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Hello.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Register.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Serializer.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/ClientRoles.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/ChannelBinding.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthTicketRequest.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthTicketWelcome.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Event.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/CalleeFeatures.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Result.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Invocation.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Welcome.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Message.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Call.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Published.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Abort.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthScramRequest.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Unregister.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthScramWelcome.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthTicketChallenge.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthMode.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Yield.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/InvocationPolicy.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthMethod.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Unregistered.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Subscribed.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/SubscriberFeatures.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/DealerFeatures.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/RouterRoles.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Registered.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Unsubscribed.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthCraWelcome.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthCraRequest.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Match.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/MessageType.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/EventReceived.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Challenge.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/PublisherFeatures.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthCryptosignChallenge.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/CallerFeatures.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Subscribe.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Interrupt.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/HelloNew.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Authenticate.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthCraChallenge.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Kdf.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AnyMessage.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthCryptosignRequest.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthCryptosignWelcome.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/AuthFactor.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Goodbye.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/CancelMode.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Error.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/SubscriberReceived.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/proto/__pycache__/Cancel.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/__pycache__/Map.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/__pycache__/Void.cpython-37.pyc,,
|
||||
autobahn/wamp/gen/wamp/__pycache__/__init__.cpython-37.pyc,,
|
||||
autobahn/asyncio/test/__pycache__/test_asyncio_websocket.cpython-37.pyc,,
|
||||
autobahn/asyncio/test/__pycache__/test_asyncio_rawsocket.cpython-37.pyc,,
|
||||
autobahn/asyncio/__pycache__/util.cpython-37.pyc,,
|
||||
autobahn/asyncio/__pycache__/websocket.cpython-37.pyc,,
|
||||
autobahn/asyncio/__pycache__/rawsocket.cpython-37.pyc,,
|
||||
autobahn/asyncio/__pycache__/wamp.cpython-37.pyc,,
|
||||
autobahn/asyncio/__pycache__/component.cpython-37.pyc,,
|
||||
autobahn/asyncio/__pycache__/xbr.cpython-37.pyc,,
|
||||
autobahn/asyncio/__pycache__/__init__.cpython-37.pyc,,
|
||||
twisted/plugins/__pycache__/autobahn_endpoints.cpython-37.pyc,,
|
||||
twisted/plugins/__pycache__/autobahn_twistd.cpython-37.pyc,,
|
27
venv/lib/python3.7/site-packages/autobahn/_version.py
Normal file
27
venv/lib/python3.7/site-packages/autobahn/_version.py
Normal file
@ -0,0 +1,27 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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__ = u'19.11.1'
|
505
venv/lib/python3.7/site-packages/autobahn/asyncio/rawsocket.py
Normal file
505
venv/lib/python3.7/site-packages/autobahn/asyncio/rawsocket.py
Normal file
@ -0,0 +1,505 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
# trollious for py2 support - however it has been deprecated
|
||||
import trollius as asyncio
|
||||
import struct
|
||||
import math
|
||||
|
||||
from autobahn.util import public, _LazyHexFormatter
|
||||
from autobahn.wamp.exception import ProtocolError, SerializationError, TransportLost
|
||||
from autobahn.asyncio.util import peer2str, get_serializers
|
||||
import txaio
|
||||
|
||||
__all__ = (
|
||||
'WampRawSocketServerProtocol',
|
||||
'WampRawSocketClientProtocol',
|
||||
'WampRawSocketServerFactory',
|
||||
'WampRawSocketClientFactory'
|
||||
)
|
||||
|
||||
txaio.use_asyncio()
|
||||
|
||||
FRAME_TYPE_DATA = 0
|
||||
FRAME_TYPE_PING = 1
|
||||
FRAME_TYPE_PONG = 2
|
||||
|
||||
MAGIC_BYTE = 0x7F
|
||||
|
||||
|
||||
class PrefixProtocol(asyncio.Protocol):
|
||||
|
||||
prefix_format = '!L'
|
||||
prefix_length = struct.calcsize(prefix_format)
|
||||
max_length = 16 * 1024 * 1024
|
||||
max_length_send = max_length
|
||||
log = txaio.make_logger() # @UndefinedVariable
|
||||
|
||||
def connection_made(self, transport):
|
||||
self.transport = transport
|
||||
peer = transport.get_extra_info('peername')
|
||||
self.peer = peer2str(peer)
|
||||
self.log.debug('RawSocker Asyncio: Connection made with peer {peer}', peer=self.peer)
|
||||
self._buffer = b''
|
||||
self._header = None
|
||||
self._wait_closed = txaio.create_future()
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
if hasattr(self, '_wait_closed'):
|
||||
return self._wait_closed
|
||||
else:
|
||||
f = txaio.create_future()
|
||||
f.set_result(True)
|
||||
return f
|
||||
|
||||
def connection_lost(self, exc):
|
||||
self.log.debug('RawSocker Asyncio: Connection lost')
|
||||
self.transport = None
|
||||
self._wait_closed.set_result(True)
|
||||
self._on_connection_lost(exc)
|
||||
|
||||
def _on_connection_lost(self, exc):
|
||||
pass
|
||||
|
||||
def protocol_error(self, msg):
|
||||
self.log.error(msg)
|
||||
self.transport.close()
|
||||
|
||||
def sendString(self, data):
|
||||
l = len(data)
|
||||
if l > self.max_length_send:
|
||||
raise ValueError('Data too big')
|
||||
header = struct.pack(self.prefix_format, len(data))
|
||||
self.transport.write(header)
|
||||
self.transport.write(data)
|
||||
|
||||
def ping(self, data):
|
||||
raise NotImplementedError()
|
||||
|
||||
def pong(self, data):
|
||||
raise NotImplementedError()
|
||||
|
||||
def data_received(self, data):
|
||||
self._buffer += data
|
||||
pos = 0
|
||||
remaining = len(self._buffer)
|
||||
while remaining >= self.prefix_length:
|
||||
# do not recalculate header if available from previous call
|
||||
if self._header:
|
||||
frame_type, frame_length = self._header
|
||||
else:
|
||||
header = self._buffer[pos:pos + self.prefix_length]
|
||||
frame_type = ord(header[0:1]) & 0b00000111
|
||||
if frame_type > FRAME_TYPE_PONG:
|
||||
self.protocol_error('Invalid frame type')
|
||||
return
|
||||
frame_length = struct.unpack(self.prefix_format, b'\0' + header[1:])[0]
|
||||
if frame_length > self.max_length:
|
||||
self.protocol_error('Frame too big')
|
||||
return
|
||||
|
||||
if remaining - self.prefix_length >= frame_length:
|
||||
self._header = None
|
||||
pos += self.prefix_length
|
||||
remaining -= self.prefix_length
|
||||
data = self._buffer[pos:pos + frame_length]
|
||||
pos += frame_length
|
||||
remaining -= frame_length
|
||||
|
||||
if frame_type == FRAME_TYPE_DATA:
|
||||
self.stringReceived(data)
|
||||
elif frame_type == FRAME_TYPE_PING:
|
||||
self.ping(data)
|
||||
elif frame_type == FRAME_TYPE_PONG:
|
||||
self.pong(data)
|
||||
else:
|
||||
# save heaader
|
||||
self._header = frame_type, frame_length
|
||||
break
|
||||
|
||||
self._buffer = self._buffer[pos:]
|
||||
|
||||
def stringReceived(self, data):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class RawSocketProtocol(PrefixProtocol):
|
||||
|
||||
def __init__(self):
|
||||
max_size = None
|
||||
if max_size:
|
||||
exp = int(math.ceil(math.log(max_size, 2))) - 9
|
||||
if exp > 15:
|
||||
raise ValueError('Maximum length is 16M')
|
||||
self.max_length = 2**(exp + 9)
|
||||
self._length_exp = exp
|
||||
else:
|
||||
self._length_exp = 15
|
||||
self.max_length = 2**24
|
||||
|
||||
def connection_made(self, transport):
|
||||
PrefixProtocol.connection_made(self, transport)
|
||||
self._handshake_done = False
|
||||
|
||||
def _on_handshake_complete(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def parse_handshake(self):
|
||||
buf = bytearray(self._buffer[:4])
|
||||
if buf[0] != MAGIC_BYTE:
|
||||
raise HandshakeError('Invalid magic byte in handshake')
|
||||
return
|
||||
ser = buf[1] & 0x0F
|
||||
lexp = buf[1] >> 4
|
||||
self.max_length_send = 2**(lexp + 9)
|
||||
if buf[2] != 0 or buf[3] != 0:
|
||||
raise HandshakeError('Reserved bytes must be zero')
|
||||
return ser, lexp
|
||||
|
||||
def process_handshake(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def data_received(self, data):
|
||||
self.log.debug('RawSocker Asyncio: data received {data}', data=_LazyHexFormatter(data))
|
||||
if self._handshake_done:
|
||||
return PrefixProtocol.data_received(self, data)
|
||||
else:
|
||||
self._buffer += data
|
||||
if len(self._buffer) >= 4:
|
||||
try:
|
||||
self.process_handshake()
|
||||
except HandshakeError as e:
|
||||
self.protocol_error('Handshake error : {err}'.format(err=e))
|
||||
return
|
||||
self._handshake_done = True
|
||||
self._on_handshake_complete()
|
||||
data = self._buffer[4:]
|
||||
self._buffer = b''
|
||||
if data:
|
||||
PrefixProtocol.data_received(self, data)
|
||||
|
||||
|
||||
ERR_SERIALIZER_UNSUPPORTED = 1
|
||||
|
||||
ERRMAP = {
|
||||
0: "illegal (must not be used)",
|
||||
1: "serializer unsupported",
|
||||
2: "maximum message length unacceptable",
|
||||
3: "use of reserved bits (unsupported feature)",
|
||||
4: "maximum connection count reached"
|
||||
}
|
||||
|
||||
|
||||
class HandshakeError(Exception):
|
||||
def __init__(self, msg, code=0):
|
||||
Exception.__init__(self, msg if not code else msg + ' : %s' % ERRMAP.get(code))
|
||||
|
||||
|
||||
class RawSocketClientProtocol(RawSocketProtocol):
|
||||
|
||||
def check_serializer(self, ser_id):
|
||||
return True
|
||||
|
||||
def process_handshake(self):
|
||||
ser_id, err = self.parse_handshake()
|
||||
if ser_id == 0:
|
||||
raise HandshakeError('Server returned handshake error', err)
|
||||
if self.serializer_id != ser_id:
|
||||
raise HandshakeError('Server returned different serializer {0} then requested {1}'
|
||||
.format(ser_id, self.serializer_id))
|
||||
|
||||
@property
|
||||
def serializer_id(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def connection_made(self, transport):
|
||||
RawSocketProtocol.connection_made(self, transport)
|
||||
# start handshake
|
||||
hs = bytes(bytearray([MAGIC_BYTE,
|
||||
self._length_exp << 4 | self.serializer_id,
|
||||
0, 0]))
|
||||
transport.write(hs)
|
||||
self.log.debug('RawSocket Asyncio: Client handshake sent')
|
||||
|
||||
|
||||
class RawSocketServerProtocol(RawSocketProtocol):
|
||||
|
||||
def supports_serializer(self, ser_id):
|
||||
raise NotImplementedError()
|
||||
|
||||
def process_handshake(self):
|
||||
def send_response(lexp, ser_id):
|
||||
b2 = lexp << 4 | (ser_id & 0x0f)
|
||||
self.transport.write(bytes(bytearray([MAGIC_BYTE, b2, 0, 0])))
|
||||
ser_id, _lexp = self.parse_handshake()
|
||||
if not self.supports_serializer(ser_id):
|
||||
send_response(ERR_SERIALIZER_UNSUPPORTED, 0)
|
||||
raise HandshakeError('Serializer unsupported : {ser_id}'.format(ser_id=ser_id))
|
||||
send_response(self._length_exp, ser_id)
|
||||
|
||||
|
||||
# this is transport independent part of WAMP protocol
|
||||
class WampRawSocketMixinGeneral(object):
|
||||
|
||||
def _on_handshake_complete(self):
|
||||
self.log.debug("WampRawSocketProtocol: Handshake complete")
|
||||
try:
|
||||
self._session = self.factory._factory()
|
||||
self._session.onOpen(self)
|
||||
except Exception as e:
|
||||
# Exceptions raised in onOpen are fatal ..
|
||||
self.log.warn("WampRawSocketProtocol: ApplicationSession constructor / onOpen raised ({err})", err=e)
|
||||
self.abort()
|
||||
else:
|
||||
self.log.info("ApplicationSession started.")
|
||||
|
||||
def stringReceived(self, payload):
|
||||
self.log.debug("WampRawSocketProtocol: RX octets: {octets}", octets=_LazyHexFormatter(payload))
|
||||
try:
|
||||
for msg in self._serializer.unserialize(payload):
|
||||
self.log.debug("WampRawSocketProtocol: RX WAMP message: {msg}", msg=msg)
|
||||
self._session.onMessage(msg)
|
||||
|
||||
except ProtocolError as e:
|
||||
self.log.warn("WampRawSocketProtocol: WAMP Protocol Error ({err}) - aborting connection", err=e)
|
||||
self.abort()
|
||||
|
||||
except Exception as e:
|
||||
self.log.warn("WampRawSocketProtocol: WAMP Internal Error ({err}) - aborting connection", err=e)
|
||||
self.abort()
|
||||
|
||||
def send(self, msg):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.send`
|
||||
"""
|
||||
if self.isOpen():
|
||||
self.log.debug("WampRawSocketProtocol: TX WAMP message: {msg}", msg=msg)
|
||||
try:
|
||||
payload, _ = self._serializer.serialize(msg)
|
||||
except Exception as e:
|
||||
# all exceptions raised from above should be serialization errors ..
|
||||
raise SerializationError("WampRawSocketProtocol: unable to serialize WAMP application payload ({0})"
|
||||
.format(e))
|
||||
else:
|
||||
self.sendString(payload)
|
||||
self.log.debug("WampRawSocketProtocol: TX octets: {octets}", octets=_LazyHexFormatter(payload))
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
def isOpen(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.isOpen`
|
||||
"""
|
||||
return hasattr(self, '_session') and self._session is not None
|
||||
|
||||
|
||||
# this is asyncio dependent part of WAMP protocol
|
||||
class WampRawSocketMixinAsyncio(object):
|
||||
"""
|
||||
Base class for asyncio-based WAMP-over-RawSocket protocols.
|
||||
"""
|
||||
|
||||
def _on_connection_lost(self, exc):
|
||||
try:
|
||||
wasClean = exc is None
|
||||
self._session.onClose(wasClean)
|
||||
except Exception as e:
|
||||
# silently ignore exceptions raised here ..
|
||||
self.log.warn("WampRawSocketProtocol: ApplicationSession.onClose raised ({err})", err=e)
|
||||
self._session = None
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.close`
|
||||
"""
|
||||
if self.isOpen():
|
||||
self.transport.close()
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
def abort(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.abort`
|
||||
"""
|
||||
if self.isOpen():
|
||||
if hasattr(self.transport, 'abort'):
|
||||
# ProcessProtocol lacks abortConnection()
|
||||
self.transport.abort()
|
||||
else:
|
||||
self.transport.close()
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
|
||||
@public
|
||||
class WampRawSocketServerProtocol(WampRawSocketMixinGeneral, WampRawSocketMixinAsyncio, RawSocketServerProtocol):
|
||||
"""
|
||||
asyncio-based WAMP-over-RawSocket server protocol.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransport`
|
||||
"""
|
||||
|
||||
def supports_serializer(self, ser_id):
|
||||
if ser_id in self.factory._serializers:
|
||||
self._serializer = self.factory._serializers[ser_id]()
|
||||
self.log.debug(
|
||||
"WampRawSocketProtocol: client wants to use serializer '{serializer}'",
|
||||
serializer=ser_id,
|
||||
)
|
||||
return True
|
||||
else:
|
||||
self.log.debug(
|
||||
"WampRawSocketProtocol: opening handshake - no suitable serializer found (client requested {serializer}, and we have {serializers}",
|
||||
serializer=ser_id,
|
||||
serializers=self.factory._serializers.keys(),
|
||||
)
|
||||
self.abort()
|
||||
return False
|
||||
|
||||
def get_channel_id(self, channel_id_type=u'tls-unique'):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.get_channel_id`
|
||||
"""
|
||||
return None
|
||||
# return transport_channel_id(self.transport, is_server=True, channel_id_type=channel_id_type)
|
||||
|
||||
|
||||
@public
|
||||
class WampRawSocketClientProtocol(WampRawSocketMixinGeneral, WampRawSocketMixinAsyncio, RawSocketClientProtocol):
|
||||
"""
|
||||
asyncio-based WAMP-over-RawSocket client protocol.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransport`
|
||||
"""
|
||||
|
||||
@property
|
||||
def serializer_id(self):
|
||||
if not hasattr(self, '_serializer'):
|
||||
self._serializer = self.factory._serializer
|
||||
return self._serializer.RAWSOCKET_SERIALIZER_ID
|
||||
|
||||
def get_channel_id(self, channel_id_type=u'tls-unique'):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.get_channel_id`
|
||||
"""
|
||||
return None
|
||||
# return transport_channel_id(self.transport, is_server=False, channel_id_type=channel_id_type)
|
||||
|
||||
|
||||
class WampRawSocketFactory(object):
|
||||
"""
|
||||
Adapter class for asyncio-based WebSocket client and server factories.def dataReceived(self, data):
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
@public
|
||||
def __call__(self):
|
||||
proto = self.protocol()
|
||||
proto.factory = self
|
||||
return proto
|
||||
|
||||
|
||||
@public
|
||||
class WampRawSocketServerFactory(WampRawSocketFactory):
|
||||
"""
|
||||
asyncio-based WAMP-over-RawSocket server protocol factory.
|
||||
"""
|
||||
protocol = WampRawSocketServerProtocol
|
||||
|
||||
def __init__(self, factory, serializers=None):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializers: A list of WAMP serializers to use (or ``None``
|
||||
for all available serializers).
|
||||
:type serializers: list of objects implementing
|
||||
:class:`autobahn.wamp.interfaces.ISerializer`
|
||||
"""
|
||||
if callable(factory):
|
||||
self._factory = factory
|
||||
else:
|
||||
self._factory = lambda: factory
|
||||
|
||||
# when no serializers were requested specifically, then support
|
||||
# all that are available
|
||||
if serializers is None:
|
||||
serializers = get_serializers()
|
||||
|
||||
if not serializers:
|
||||
raise Exception("could not import any WAMP serializers")
|
||||
|
||||
self._serializers = {ser.RAWSOCKET_SERIALIZER_ID: ser for ser in serializers}
|
||||
|
||||
|
||||
@public
|
||||
class WampRawSocketClientFactory(WampRawSocketFactory):
|
||||
"""
|
||||
asyncio-based WAMP-over-RawSocket client factory.
|
||||
"""
|
||||
protocol = WampRawSocketClientProtocol
|
||||
|
||||
def __init__(self, factory, serializer=None):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializer: The WAMP serializer to use (or ``None`` for
|
||||
"best" serializer, chosen as the first serializer available from
|
||||
this list: CBOR, MessagePack, UBJSON, JSON).
|
||||
:type serializer: object implementing :class:`autobahn.wamp.interfaces.ISerializer`
|
||||
"""
|
||||
if callable(factory):
|
||||
self._factory = factory
|
||||
else:
|
||||
self._factory = lambda: factory
|
||||
|
||||
# when no serializer was requested specifically, use the first
|
||||
# one available
|
||||
if serializer is None:
|
||||
serializers = get_serializers()
|
||||
if serializers:
|
||||
serializer = serializers[0]()
|
||||
|
||||
if serializer is None:
|
||||
raise Exception("could not import any WAMP serializer")
|
||||
|
||||
self._serializer = serializer
|
396
venv/lib/python3.7/site-packages/autobahn/asyncio/websocket.py
Normal file
396
venv/lib/python3.7/site-packages/autobahn/asyncio/websocket.py
Normal file
@ -0,0 +1,396 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
from collections import deque
|
||||
|
||||
import txaio
|
||||
txaio.use_asyncio()
|
||||
|
||||
from autobahn.util import public
|
||||
from autobahn.asyncio.util import transport_channel_id, peer2str
|
||||
from autobahn.wamp import websocket
|
||||
from autobahn.websocket import protocol
|
||||
from autobahn.websocket.types import TransportDetails
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
from asyncio import iscoroutine
|
||||
from asyncio import Future
|
||||
except ImportError:
|
||||
# Trollius >= 0.3 was renamed
|
||||
# noinspection PyUnresolvedReferences
|
||||
import trollius as asyncio
|
||||
from trollius import iscoroutine
|
||||
from trollius import Future
|
||||
|
||||
if hasattr(asyncio, 'ensure_future'):
|
||||
ensure_future = asyncio.ensure_future
|
||||
else: # Deprecated since Python 3.4.4
|
||||
ensure_future = getattr(asyncio, 'async')
|
||||
|
||||
__all__ = (
|
||||
'WebSocketServerProtocol',
|
||||
'WebSocketClientProtocol',
|
||||
'WebSocketServerFactory',
|
||||
'WebSocketClientFactory',
|
||||
'WampWebSocketServerProtocol',
|
||||
'WampWebSocketClientProtocol',
|
||||
'WampWebSocketServerFactory',
|
||||
'WampWebSocketClientFactory',
|
||||
)
|
||||
|
||||
|
||||
def yields(value):
|
||||
"""
|
||||
Returns ``True`` iff the value yields.
|
||||
|
||||
.. seealso:: http://stackoverflow.com/questions/20730248/maybedeferred-analog-with-asyncio
|
||||
"""
|
||||
return isinstance(value, Future) or iscoroutine(value)
|
||||
|
||||
|
||||
class WebSocketAdapterProtocol(asyncio.Protocol):
|
||||
"""
|
||||
Adapter class for asyncio-based WebSocket client and server protocols.
|
||||
"""
|
||||
|
||||
def connection_made(self, transport):
|
||||
self.transport = transport
|
||||
|
||||
self.receive_queue = deque()
|
||||
self._consume()
|
||||
|
||||
try:
|
||||
self.peer = peer2str(transport.get_extra_info('peername'))
|
||||
except:
|
||||
self.peer = u"?"
|
||||
|
||||
self._connectionMade()
|
||||
|
||||
def connection_lost(self, exc):
|
||||
self._connectionLost(exc)
|
||||
# according to asyncio docs, connection_lost(None) is called
|
||||
# if something else called transport.close()
|
||||
if exc is not None:
|
||||
self.transport.close()
|
||||
self.transport = None
|
||||
|
||||
def _consume(self):
|
||||
self.waiter = Future(loop=self.factory.loop or txaio.config.loop)
|
||||
|
||||
def process(_):
|
||||
while len(self.receive_queue):
|
||||
data = self.receive_queue.popleft()
|
||||
if self.transport:
|
||||
self._dataReceived(data)
|
||||
self._consume()
|
||||
|
||||
self.waiter.add_done_callback(process)
|
||||
|
||||
def data_received(self, data):
|
||||
self.receive_queue.append(data)
|
||||
if not self.waiter.done():
|
||||
self.waiter.set_result(None)
|
||||
|
||||
def _closeConnection(self, abort=False):
|
||||
if abort and hasattr(self.transport, 'abort'):
|
||||
self.transport.abort()
|
||||
else:
|
||||
self.transport.close()
|
||||
|
||||
def _onOpen(self):
|
||||
res = self.onOpen()
|
||||
if yields(res):
|
||||
ensure_future(res)
|
||||
|
||||
def _onMessageBegin(self, isBinary):
|
||||
res = self.onMessageBegin(isBinary)
|
||||
if yields(res):
|
||||
ensure_future(res)
|
||||
|
||||
def _onMessageFrameBegin(self, length):
|
||||
res = self.onMessageFrameBegin(length)
|
||||
if yields(res):
|
||||
ensure_future(res)
|
||||
|
||||
def _onMessageFrameData(self, payload):
|
||||
res = self.onMessageFrameData(payload)
|
||||
if yields(res):
|
||||
ensure_future(res)
|
||||
|
||||
def _onMessageFrameEnd(self):
|
||||
res = self.onMessageFrameEnd()
|
||||
if yields(res):
|
||||
ensure_future(res)
|
||||
|
||||
def _onMessageFrame(self, payload):
|
||||
res = self.onMessageFrame(payload)
|
||||
if yields(res):
|
||||
ensure_future(res)
|
||||
|
||||
def _onMessageEnd(self):
|
||||
res = self.onMessageEnd()
|
||||
if yields(res):
|
||||
ensure_future(res)
|
||||
|
||||
def _onMessage(self, payload, isBinary):
|
||||
res = self.onMessage(payload, isBinary)
|
||||
if yields(res):
|
||||
ensure_future(res)
|
||||
|
||||
def _onPing(self, payload):
|
||||
res = self.onPing(payload)
|
||||
if yields(res):
|
||||
ensure_future(res)
|
||||
|
||||
def _onPong(self, payload):
|
||||
res = self.onPong(payload)
|
||||
if yields(res):
|
||||
ensure_future(res)
|
||||
|
||||
def _onClose(self, wasClean, code, reason):
|
||||
res = self.onClose(wasClean, code, reason)
|
||||
if yields(res):
|
||||
ensure_future(res)
|
||||
|
||||
def registerProducer(self, producer, streaming):
|
||||
raise Exception("not implemented")
|
||||
|
||||
def unregisterProducer(self):
|
||||
# note that generic websocket/protocol.py code calls
|
||||
# .unregisterProducer whenever we dropConnection -- that's
|
||||
# correct behavior on Twisted so either we'd have to
|
||||
# try/except there, or special-case Twisted, ..or just make
|
||||
# this "not an error"
|
||||
pass
|
||||
|
||||
|
||||
@public
|
||||
class WebSocketServerProtocol(WebSocketAdapterProtocol, protocol.WebSocketServerProtocol):
|
||||
"""
|
||||
Base class for asyncio-based WebSocket server protocols.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.websocket.interfaces.IWebSocketChannel`
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
def get_channel_id(self, channel_id_type=u'tls-unique'):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.get_channel_id`
|
||||
"""
|
||||
return transport_channel_id(self.transport, True, channel_id_type)
|
||||
|
||||
|
||||
@public
|
||||
class WebSocketClientProtocol(WebSocketAdapterProtocol, protocol.WebSocketClientProtocol):
|
||||
"""
|
||||
Base class for asyncio-based WebSocket client protocols.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.websocket.interfaces.IWebSocketChannel`
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
def _onConnect(self, response):
|
||||
res = self.onConnect(response)
|
||||
if yields(res):
|
||||
ensure_future(res)
|
||||
|
||||
def startTLS(self):
|
||||
raise Exception("WSS over explicit proxies not implemented")
|
||||
|
||||
def get_channel_id(self, channel_id_type=u'tls-unique'):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.get_channel_id`
|
||||
"""
|
||||
return transport_channel_id(self.transport, False, channel_id_type)
|
||||
|
||||
def _create_transport_details(self):
|
||||
"""
|
||||
Internal helper.
|
||||
Base class calls this to create a TransportDetails
|
||||
"""
|
||||
is_secure = self.transport.get_extra_info('peercert', None) is not None
|
||||
if is_secure:
|
||||
secure_channel_id = {
|
||||
u'tls-unique': transport_channel_id(self.transport, False, 'tls-unique'),
|
||||
}
|
||||
else:
|
||||
secure_channel_id = {}
|
||||
return TransportDetails(peer=self.peer, is_secure=is_secure, secure_channel_id=secure_channel_id)
|
||||
|
||||
|
||||
class WebSocketAdapterFactory(object):
|
||||
"""
|
||||
Adapter class for asyncio-based WebSocket client and server factories.
|
||||
"""
|
||||
log = txaio.make_logger()
|
||||
|
||||
def __call__(self):
|
||||
proto = self.protocol()
|
||||
proto.factory = self
|
||||
return proto
|
||||
|
||||
|
||||
@public
|
||||
class WebSocketServerFactory(WebSocketAdapterFactory, protocol.WebSocketServerFactory):
|
||||
"""
|
||||
Base class for asyncio-based WebSocket server factories.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`
|
||||
"""
|
||||
|
||||
protocol = WebSocketServerProtocol
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
.. note::
|
||||
In addition to all arguments to the constructor of
|
||||
:meth:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`,
|
||||
you can supply a ``loop`` keyword argument to specify the
|
||||
asyncio event loop to be used.
|
||||
"""
|
||||
loop = kwargs.pop('loop', None)
|
||||
self.loop = loop or asyncio.get_event_loop()
|
||||
|
||||
protocol.WebSocketServerFactory.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
@public
|
||||
class WebSocketClientFactory(WebSocketAdapterFactory, protocol.WebSocketClientFactory):
|
||||
"""
|
||||
Base class for asyncio-based WebSocket client factories.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
|
||||
.. note::
|
||||
In addition to all arguments to the constructor of
|
||||
:meth:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`,
|
||||
you can supply a ``loop`` keyword argument to specify the
|
||||
asyncio event loop to be used.
|
||||
"""
|
||||
loop = kwargs.pop('loop', None)
|
||||
self.loop = loop or asyncio.get_event_loop()
|
||||
|
||||
protocol.WebSocketClientFactory.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
@public
|
||||
class WampWebSocketServerProtocol(websocket.WampWebSocketServerProtocol, WebSocketServerProtocol):
|
||||
"""
|
||||
asyncio-based WAMP-over-WebSocket server protocol.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransport`
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class WampWebSocketServerFactory(websocket.WampWebSocketServerFactory, WebSocketServerFactory):
|
||||
"""
|
||||
asyncio-based WAMP-over-WebSocket server factory.
|
||||
"""
|
||||
|
||||
protocol = WampWebSocketServerProtocol
|
||||
|
||||
def __init__(self, factory, *args, **kwargs):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializers: A list of WAMP serializers to use (or ``None``
|
||||
for all available serializers).
|
||||
:type serializers: list of objects implementing
|
||||
:class:`autobahn.wamp.interfaces.ISerializer`
|
||||
"""
|
||||
|
||||
serializers = kwargs.pop('serializers', None)
|
||||
|
||||
websocket.WampWebSocketServerFactory.__init__(self, factory, serializers)
|
||||
|
||||
kwargs['protocols'] = self._protocols
|
||||
|
||||
# noinspection PyCallByClass
|
||||
WebSocketServerFactory.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
@public
|
||||
class WampWebSocketClientProtocol(websocket.WampWebSocketClientProtocol, WebSocketClientProtocol):
|
||||
"""
|
||||
asyncio-based WAMP-over-WebSocket client protocols.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransport`
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class WampWebSocketClientFactory(websocket.WampWebSocketClientFactory, WebSocketClientFactory):
|
||||
"""
|
||||
asyncio-based WAMP-over-WebSocket client factory.
|
||||
"""
|
||||
|
||||
protocol = WampWebSocketClientProtocol
|
||||
|
||||
def __init__(self, factory, *args, **kwargs):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializer: The WAMP serializer to use (or ``None`` for
|
||||
"best" serializer, chosen as the first serializer available from
|
||||
this list: CBOR, MessagePack, UBJSON, JSON).
|
||||
:type serializer: object implementing :class:`autobahn.wamp.interfaces.ISerializer`
|
||||
"""
|
||||
|
||||
serializers = kwargs.pop('serializers', None)
|
||||
|
||||
websocket.WampWebSocketClientFactory.__init__(self, factory, serializers)
|
||||
|
||||
kwargs['protocols'] = self._protocols
|
||||
|
||||
WebSocketClientFactory.__init__(self, *args, **kwargs)
|
41
venv/lib/python3.7/site-packages/autobahn/exception.py
Normal file
41
venv/lib/python3.7/site-packages/autobahn/exception.py
Normal file
@ -0,0 +1,41 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
from autobahn.util import public
|
||||
|
||||
__all__ = (
|
||||
'PayloadExceededError',
|
||||
)
|
||||
|
||||
|
||||
@public
|
||||
class PayloadExceededError(RuntimeError):
|
||||
"""
|
||||
Exception raised when the serialized and framed (eg WebSocket/RawSocket) WAMP payload
|
||||
exceeds the transport message size limit.
|
||||
"""
|
646
venv/lib/python3.7/site-packages/autobahn/nvx/_utf8validator.c
Normal file
646
venv/lib/python3.7/site-packages/autobahn/nvx/_utf8validator.c
Normal file
@ -0,0 +1,646 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Crossbar.io Technologies 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.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// http://stackoverflow.com/questions/11228855/header-files-for-simd-intrinsics
|
||||
#include <x86intrin.h>
|
||||
|
||||
|
||||
#define UTF8_ACCEPT 0
|
||||
#define UTF8_REJECT 1
|
||||
|
||||
|
||||
typedef struct {
|
||||
size_t current_index;
|
||||
size_t total_index;
|
||||
int state;
|
||||
int impl;
|
||||
} utf8_validator_t;
|
||||
|
||||
|
||||
#define UTF8_VALIDATOR_OPTIMAL 0
|
||||
#define UTF8_VALIDATOR_TABLE_DFA 1
|
||||
#define UTF8_VALIDATOR_UNROLLED_DFA 2
|
||||
#define UTF8_VALIDATOR_SSE2_DFA 3
|
||||
#define UTF8_VALIDATOR_SSE41_DFA 4
|
||||
|
||||
|
||||
int nvx_utf8vld_get_impl (void* utf8vld) {
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
return vld->impl;
|
||||
}
|
||||
|
||||
int nvx_utf8vld_set_impl (void* utf8vld, int impl) {
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
if (impl) {
|
||||
// set requested implementation
|
||||
//
|
||||
#ifndef __SSE4_1__
|
||||
# ifdef __SSE2__
|
||||
if (impl <= UTF8_VALIDATOR_SSE2_DFA) {
|
||||
vld->impl = impl;
|
||||
}
|
||||
# else
|
||||
if (impl <= UTF8_VALIDATOR_UNROLLED_DFA) {
|
||||
vld->impl = impl;
|
||||
}
|
||||
# endif
|
||||
#else
|
||||
if (impl <= UTF8_VALIDATOR_SSE41_DFA) {
|
||||
vld->impl = impl;
|
||||
}
|
||||
#endif
|
||||
|
||||
} else {
|
||||
// set optimal implementation
|
||||
//
|
||||
#ifndef __SSE4_1__
|
||||
# ifdef __SSE2__
|
||||
vld->impl = UTF8_VALIDATOR_SSE2_DFA;
|
||||
# else
|
||||
vld->impl = UTF8_VALIDATOR_UNROLLED_DFA;
|
||||
# endif
|
||||
#else
|
||||
vld->impl = UTF8_VALIDATOR_SSE41_DFA;
|
||||
#endif
|
||||
|
||||
}
|
||||
return vld->impl;
|
||||
}
|
||||
|
||||
|
||||
void nvx_utf8vld_reset (void* utf8vld) {
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
vld->state = 0;
|
||||
vld->current_index = -1;
|
||||
vld->total_index = -1;
|
||||
}
|
||||
|
||||
|
||||
void* nvx_utf8vld_new () {
|
||||
void* p = malloc(sizeof(utf8_validator_t));
|
||||
nvx_utf8vld_reset(p);
|
||||
nvx_utf8vld_set_impl(p, 0);
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
void nvx_utf8vld_free (void* utf8vld) {
|
||||
free (utf8vld);
|
||||
}
|
||||
|
||||
|
||||
// unrolled DFA from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
//
|
||||
static const uint8_t UTF8VALIDATOR_DFA[] __attribute__((aligned(64))) =
|
||||
{
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
|
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
|
||||
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
|
||||
|
||||
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
|
||||
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
|
||||
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
|
||||
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
|
||||
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
|
||||
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1 // s7..s8
|
||||
};
|
||||
|
||||
|
||||
int _nvx_utf8vld_validate_table (void* utf8vld, const uint8_t* data, size_t length) {
|
||||
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
int state = vld->state;
|
||||
|
||||
const uint8_t* end = data + length;
|
||||
|
||||
while (data < end && state != 1) {
|
||||
state = UTF8VALIDATOR_DFA[256 + state * 16 + UTF8VALIDATOR_DFA[*data++]];
|
||||
}
|
||||
|
||||
vld->state = state;
|
||||
|
||||
if (state == 0) {
|
||||
// UTF8 is valid and ends on codepoint
|
||||
return 0;
|
||||
} else {
|
||||
if (state == 1) {
|
||||
// UTF8 is invalid
|
||||
return -1;
|
||||
} else {
|
||||
// UTF8 is valid, but does not end on codepoint (needs more data)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// unrolled DFA from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
//
|
||||
#define DFA_TRANSITION(state, octet) \
|
||||
if (state == 0) { \
|
||||
if (octet >= 0x00 && octet <= 0x7f) { \
|
||||
/* reflective state 0 */ \
|
||||
} else if (octet >= 0xc2 && octet <= 0xdf) { \
|
||||
state = 2; \
|
||||
} else if ((octet >= 0xe1 && octet <= 0xec) || octet == 0xee || octet == 0xef) { \
|
||||
state = 3; \
|
||||
} else if (octet == 0xe0) { \
|
||||
state = 4; \
|
||||
} else if (octet == 0xed) { \
|
||||
state = 5; \
|
||||
} else if (octet == 0xf4) { \
|
||||
state = 8; \
|
||||
} else if (octet == 0xf1 || octet == 0xf2 || octet == 0xf3) { \
|
||||
state = 7; \
|
||||
} else if (octet == 0xf0) { \
|
||||
state = 6; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 2) { \
|
||||
if (octet >= 0x80 && octet <= 0xbf) { \
|
||||
state = 0; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 3) { \
|
||||
if (octet >= 0x80 && octet <= 0xbf) { \
|
||||
state = 2; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 4) { \
|
||||
if (octet >= 0xa0 && octet <= 0xbf) { \
|
||||
state = 2; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 5) { \
|
||||
if (octet >= 0x80 && octet <= 0x9f) { \
|
||||
state = 2; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 6) { \
|
||||
if (octet >= 0x90 && octet <= 0xbf) { \
|
||||
state = 3; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 7) { \
|
||||
if (octet >= 0x80 && octet <= 0xbf) { \
|
||||
state = 3; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 8) { \
|
||||
if (octet >= 0x80 && octet <= 0x8f) { \
|
||||
state = 3; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 1) { \
|
||||
/* refective state 1 */ \
|
||||
} else { \
|
||||
/* should not arrive here */ \
|
||||
}
|
||||
|
||||
|
||||
int _nvx_utf8vld_validate_unrolled (void* utf8vld, const uint8_t* data, size_t length) {
|
||||
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
int state = vld->state;
|
||||
|
||||
const uint8_t* tail_end = data + length;
|
||||
|
||||
while (data < tail_end && state != 1) {
|
||||
|
||||
// get tail octet
|
||||
int octet = *data;
|
||||
|
||||
// do the DFA
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
++data;
|
||||
}
|
||||
|
||||
vld->state = state;
|
||||
|
||||
if (state == 0) {
|
||||
// UTF8 is valid and ends on codepoint
|
||||
return 0;
|
||||
} else {
|
||||
if (state == 1) {
|
||||
// UTF8 is invalid
|
||||
return -1;
|
||||
} else {
|
||||
// UTF8 is valid, but does not end on codepoint (needs more data)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
__m128i _mm_load_si128 (__m128i const* mem_addr)
|
||||
#include "emmintrin.h"
|
||||
Instruction: movdqa
|
||||
CPUID Feature Flag: SSE2
|
||||
|
||||
int _mm_movemask_epi8 (__m128i a)
|
||||
#include "emmintrin.h"
|
||||
Instruction: pmovmskb
|
||||
CPUID Feature Flag: SSE2
|
||||
|
||||
__m128i _mm_srli_si128 (__m128i a, int imm)
|
||||
#include "emmintrin.h"
|
||||
Instruction: psrldq
|
||||
CPUID Feature Flag: SSE2
|
||||
|
||||
int _mm_cvtsi128_si32 (__m128i a)
|
||||
#include "emmintrin.h"
|
||||
Instruction: movd
|
||||
CPUID Feature Flag: SSE2
|
||||
|
||||
int _mm_extract_epi16 (__m128i a, int imm)
|
||||
#include "emmintrin.h"
|
||||
Instruction: pextrw
|
||||
CPUID Feature Flag: SSE2
|
||||
|
||||
int _mm_extract_epi8 (__m128i a, const int imm)
|
||||
#include "smmintrin.h"
|
||||
Instruction: pextrb
|
||||
CPUID Feature Flag: SSE4.1
|
||||
*/
|
||||
|
||||
#ifdef __SSE2__
|
||||
int _nvx_utf8vld_validate_sse2 (void* utf8vld, const uint8_t* data, size_t length) {
|
||||
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
int state = vld->state;
|
||||
|
||||
const uint8_t* tail_end = data + length;
|
||||
|
||||
// process unaligned head (sub 16 octets)
|
||||
//
|
||||
size_t head_len = ((size_t) data) % sizeof(__m128i);
|
||||
if (head_len) {
|
||||
|
||||
const uint8_t* head_end = data + head_len;
|
||||
|
||||
while (data < head_end && state != UTF8_REJECT) {
|
||||
|
||||
// get head octet
|
||||
int octet = *data;
|
||||
|
||||
// do the DFA
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
++data;
|
||||
}
|
||||
}
|
||||
|
||||
// process aligned middle (16 octet chunks)
|
||||
//
|
||||
const __m128i* ptr = ((const __m128i*) data);
|
||||
const __m128i* end = ((const __m128i*) data) + ((length - head_len) / sizeof(__m128i));
|
||||
|
||||
while (ptr < end && state != UTF8_REJECT) {
|
||||
|
||||
__builtin_prefetch(ptr + 1, 0, 3);
|
||||
//__builtin_prefetch(ptr + 4, 0, 3); // 16*4=64: cache-line prefetch
|
||||
|
||||
__m128i xmm1 = _mm_load_si128(ptr);
|
||||
|
||||
if (__builtin_expect(state || _mm_movemask_epi8(xmm1), 0)) {
|
||||
|
||||
// copy to different reg - this allows the prefetching to
|
||||
// do its job in the meantime (I guess ..)
|
||||
|
||||
// SSE2 variant
|
||||
//
|
||||
int octet;
|
||||
|
||||
// octet 0
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 1
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 2
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 3
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 4
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 5
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 6
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 7
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 8
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 9
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 10
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 11
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 12
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 13
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 14
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 15
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
}
|
||||
++ptr;
|
||||
}
|
||||
|
||||
// process unaligned tail (sub 16 octets)
|
||||
//
|
||||
const uint8_t* tail_ptr = (const uint8_t*) ptr;
|
||||
|
||||
while (tail_ptr < tail_end && state != UTF8_REJECT) {
|
||||
|
||||
// get tail octet
|
||||
int octet = *tail_ptr;
|
||||
|
||||
// do the DFA
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
++tail_ptr;
|
||||
}
|
||||
|
||||
vld->state = state;
|
||||
|
||||
if (state == UTF8_ACCEPT) {
|
||||
// UTF8 is valid and ends on codepoint
|
||||
return 0;
|
||||
} else {
|
||||
if (state == UTF8_REJECT) {
|
||||
// UTF8 is invalid
|
||||
return -1;
|
||||
} else {
|
||||
// UTF8 is valid, but does not end on codepoint (needs more data)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __SSE4_1__
|
||||
int _nvx_utf8vld_validate_sse4 (void* utf8vld, const uint8_t* data, size_t length) {
|
||||
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
int state = vld->state;
|
||||
|
||||
const uint8_t* tail_end = data + length;
|
||||
|
||||
// process unaligned head (sub 16 octets)
|
||||
//
|
||||
size_t head_len = ((size_t) data) % sizeof(__m128i);
|
||||
if (head_len) {
|
||||
|
||||
const uint8_t* head_end = data + head_len;
|
||||
|
||||
while (data < head_end && state != UTF8_REJECT) {
|
||||
|
||||
// get head octet
|
||||
int octet = *data;
|
||||
|
||||
// do the DFA
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
++data;
|
||||
}
|
||||
}
|
||||
|
||||
// process aligned middle (16 octet chunks)
|
||||
//
|
||||
const __m128i* ptr = ((const __m128i*) data);
|
||||
const __m128i* end = ((const __m128i*) data) + ((length - head_len) / sizeof(__m128i));
|
||||
|
||||
while (ptr < end && state != UTF8_REJECT) {
|
||||
|
||||
__builtin_prefetch(ptr + 1, 0, 3);
|
||||
//__builtin_prefetch(ptr + 4, 0, 3); // 16*4=64: cache-line prefetch
|
||||
|
||||
__m128i xmm1 = _mm_load_si128(ptr);
|
||||
|
||||
|
||||
if (__builtin_expect(state || _mm_movemask_epi8(xmm1), 0)) {
|
||||
|
||||
// copy to different reg - this allows the prefetching to
|
||||
// do its job in the meantime (I guess ..)
|
||||
|
||||
// SSE4.1 variant
|
||||
//
|
||||
int octet;
|
||||
|
||||
// octet 0
|
||||
octet = _mm_extract_epi8(xmm1, 0);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 1
|
||||
octet = _mm_extract_epi8(xmm1, 1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 2
|
||||
octet = _mm_extract_epi8(xmm1, 2);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 3
|
||||
octet = _mm_extract_epi8(xmm1, 3);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 4
|
||||
octet = _mm_extract_epi8(xmm1, 4);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 5
|
||||
octet = _mm_extract_epi8(xmm1, 5);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 6
|
||||
octet = _mm_extract_epi8(xmm1, 6);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 7
|
||||
octet = _mm_extract_epi8(xmm1, 7);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 8
|
||||
octet = _mm_extract_epi8(xmm1, 8);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 9
|
||||
octet = _mm_extract_epi8(xmm1, 9);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 10
|
||||
octet = _mm_extract_epi8(xmm1, 10);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 11
|
||||
octet = _mm_extract_epi8(xmm1, 11);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 12
|
||||
octet = _mm_extract_epi8(xmm1, 12);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 13
|
||||
octet = _mm_extract_epi8(xmm1, 13);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 14
|
||||
octet = _mm_extract_epi8(xmm1, 14);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 15
|
||||
octet = _mm_extract_epi8(xmm1, 15);
|
||||
DFA_TRANSITION(state, octet);
|
||||
}
|
||||
++ptr;
|
||||
}
|
||||
|
||||
// process unaligned tail (sub 16 octets)
|
||||
//
|
||||
const uint8_t* tail_ptr = (const uint8_t*) ptr;
|
||||
|
||||
while (tail_ptr < tail_end && state != UTF8_REJECT) {
|
||||
|
||||
// get tail octet
|
||||
int octet = *tail_ptr;
|
||||
|
||||
// do the DFA
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
++tail_ptr;
|
||||
}
|
||||
|
||||
vld->state = state;
|
||||
|
||||
if (state == UTF8_ACCEPT) {
|
||||
// UTF8 is valid and ends on codepoint
|
||||
return 0;
|
||||
} else {
|
||||
if (state == UTF8_REJECT) {
|
||||
// UTF8 is invalid
|
||||
return -1;
|
||||
} else {
|
||||
// UTF8 is valid, but does not end on codepoint (needs more data)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
int nvx_utf8vld_validate (void* utf8vld, const uint8_t* data, size_t length) {
|
||||
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
switch (vld->impl) {
|
||||
case UTF8_VALIDATOR_TABLE_DFA:
|
||||
return _nvx_utf8vld_validate_table(utf8vld, data, length);
|
||||
case UTF8_VALIDATOR_UNROLLED_DFA:
|
||||
return _nvx_utf8vld_validate_unrolled(utf8vld, data, length);
|
||||
#ifdef __SSE2__
|
||||
case UTF8_VALIDATOR_SSE2_DFA:
|
||||
return _nvx_utf8vld_validate_table(utf8vld, data, length);
|
||||
#endif
|
||||
#ifdef __SSE4_1__
|
||||
case UTF8_VALIDATOR_SSE41_DFA:
|
||||
return _nvx_utf8vld_validate_table(utf8vld, data, length);
|
||||
#endif
|
||||
default:
|
||||
return _nvx_utf8vld_validate_table(utf8vld, data, length);
|
||||
}
|
||||
}
|
@ -0,0 +1,357 @@
|
||||
# coding=utf-8
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
import six
|
||||
import struct
|
||||
import unittest
|
||||
|
||||
from autobahn.websocket.utf8validator import Utf8Validator as StandardUtf8Validator
|
||||
|
||||
try:
|
||||
from _nvx_utf8validator import lib # noqa
|
||||
from autobahn.nvx import Utf8Validator as NvxUtf8Validator
|
||||
except ImportError:
|
||||
HAS_NVX = False
|
||||
else:
|
||||
HAS_NVX = True
|
||||
|
||||
|
||||
def _create_utf8_test_sequences():
|
||||
"""
|
||||
Create test sequences for UTF-8 decoder tests from
|
||||
http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
|
||||
"""
|
||||
|
||||
UTF8_TEST_SEQUENCES = []
|
||||
|
||||
# 1 Some correct UTF-8 text
|
||||
vss = b'\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5'
|
||||
vs = [b"Some valid UTF-8 sequences", []]
|
||||
vs[1].append((True, b'hello\x24world')) # U+0024
|
||||
vs[1].append((True, b'hello\xC2\xA2world')) # U+00A2
|
||||
vs[1].append((True, b'hello\xE2\x82\xACworld')) # U+20AC
|
||||
vs[1].append((True, b'hello\xF0\xA4\xAD\xA2world')) # U+24B62
|
||||
vs[1].append((True, vss))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# All prefixes of correct UTF-8 text
|
||||
vs = [
|
||||
b"All prefixes of a valid UTF-8 string that contains multi-byte code points",
|
||||
[]]
|
||||
v = StandardUtf8Validator()
|
||||
for i in range(1, len(vss) + 1):
|
||||
v.reset()
|
||||
res = v.validate(vss[:i])
|
||||
vs[1].append((res[0] and res[1], vss[:i]))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 2.1 First possible sequence of a certain length
|
||||
vs = [b"First possible sequence of a certain length", []]
|
||||
vs[1].append((True, b'\x00'))
|
||||
vs[1].append((True, b'\xc2\x80'))
|
||||
vs[1].append((True, b'\xe0\xa0\x80'))
|
||||
vs[1].append((True, b'\xf0\x90\x80\x80'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# the following conform to the UTF-8 integer encoding scheme, but
|
||||
# valid UTF-8 only allows for Unicode code points up to U+10FFFF
|
||||
vs = [b"First possible sequence length 5/6 (invalid codepoints)", []]
|
||||
vs[1].append((False, b'\xf8\x88\x80\x80\x80'))
|
||||
vs[1].append((False, b'\xfc\x84\x80\x80\x80\x80'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 2.2 Last possible sequence of a certain length
|
||||
vs = [b"Last possible sequence of a certain length", []]
|
||||
vs[1].append((True, b'\x7f'))
|
||||
vs[1].append((True, b'\xdf\xbf'))
|
||||
vs[1].append((True, b'\xef\xbf\xbf'))
|
||||
vs[1].append((True, b'\xf4\x8f\xbf\xbf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# the following conform to the UTF-8 integer encoding scheme, but
|
||||
# valid UTF-8 only allows for Unicode code points up to U+10FFFF
|
||||
vs = [b"Last possible sequence length 4/5/6 (invalid codepoints)", []]
|
||||
vs[1].append((False, b'\xf7\xbf\xbf\xbf'))
|
||||
vs[1].append((False, b'\xfb\xbf\xbf\xbf\xbf'))
|
||||
vs[1].append((False, b'\xfd\xbf\xbf\xbf\xbf\xbf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 2.3 Other boundary conditions
|
||||
vs = [b"Other boundary conditions", []]
|
||||
vs[1].append((True, b'\xed\x9f\xbf'))
|
||||
vs[1].append((True, b'\xee\x80\x80'))
|
||||
vs[1].append((True, b'\xef\xbf\xbd'))
|
||||
vs[1].append((True, b'\xf4\x8f\xbf\xbf'))
|
||||
vs[1].append((False, b'\xf4\x90\x80\x80'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 3.1 Unexpected continuation bytes
|
||||
vs = [b"Unexpected continuation bytes", []]
|
||||
vs[1].append((False, b'\x80'))
|
||||
vs[1].append((False, b'\xbf'))
|
||||
vs[1].append((False, b'\x80\xbf'))
|
||||
vs[1].append((False, b'\x80\xbf\x80'))
|
||||
vs[1].append((False, b'\x80\xbf\x80\xbf'))
|
||||
vs[1].append((False, b'\x80\xbf\x80\xbf\x80'))
|
||||
vs[1].append((False, b'\x80\xbf\x80\xbf\x80\xbf'))
|
||||
s = b''
|
||||
|
||||
# 3.2 Lonely start characters
|
||||
vs = [b"Lonely start characters", []]
|
||||
m = [(0xc0, 0xdf), (0xe0, 0xef), (0xf0, 0xf7), (0xf8, 0xfb), (0xfc, 0xfd)]
|
||||
for mm in m:
|
||||
s = b''
|
||||
for i in range(mm[0], mm[1]):
|
||||
s += struct.pack('BB', i, 0x20)
|
||||
# s += chr(i)
|
||||
# s += chr(0x20)
|
||||
vs[1].append((False, s))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 3.3 Sequences with last continuation byte missing
|
||||
vs = [b"Sequences with last continuation byte missing", []]
|
||||
k = [b'\xc0', b'\xe0\x80', b'\xf0\x80\x80', b'\xf8\x80\x80\x80', b'\xfc\x80\x80\x80\x80',
|
||||
b'\xdf', b'\xef\xbf', b'\xf7\xbf\xbf', b'\xfb\xbf\xbf\xbf', b'\xfd\xbf\xbf\xbf\xbf']
|
||||
for kk in k:
|
||||
vs[1].append((False, kk))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 3.4 Concatenation of incomplete sequences
|
||||
vs = [b"Concatenation of incomplete sequences", []]
|
||||
vs[1].append((False, b''.join(k)))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 3.5 Impossible bytes
|
||||
vs = [b"Impossible bytes", []]
|
||||
vs[1].append((False, b'\xfe'))
|
||||
vs[1].append((False, b'\xff'))
|
||||
vs[1].append((False, b'\xfe\xfe\xff\xff'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 4.1 Examples of an overlong ASCII character
|
||||
vs = [b"Examples of an overlong ASCII character", []]
|
||||
vs[1].append((False, b'\xc0\xaf'))
|
||||
vs[1].append((False, b'\xe0\x80\xaf'))
|
||||
vs[1].append((False, b'\xf0\x80\x80\xaf'))
|
||||
vs[1].append((False, b'\xf8\x80\x80\x80\xaf'))
|
||||
vs[1].append((False, b'\xfc\x80\x80\x80\x80\xaf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 4.2 Maximum overlong sequences
|
||||
vs = [b"Maximum overlong sequences", []]
|
||||
vs[1].append((False, b'\xc1\xbf'))
|
||||
vs[1].append((False, b'\xe0\x9f\xbf'))
|
||||
vs[1].append((False, b'\xf0\x8f\xbf\xbf'))
|
||||
vs[1].append((False, b'\xf8\x87\xbf\xbf\xbf'))
|
||||
vs[1].append((False, b'\xfc\x83\xbf\xbf\xbf\xbf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 4.3 Overlong representation of the NUL character
|
||||
vs = [b"Overlong representation of the NUL character", []]
|
||||
vs[1].append((False, b'\xc0\x80'))
|
||||
vs[1].append((False, b'\xe0\x80\x80'))
|
||||
vs[1].append((False, b'\xf0\x80\x80\x80'))
|
||||
vs[1].append((False, b'\xf8\x80\x80\x80\x80'))
|
||||
vs[1].append((False, b'\xfc\x80\x80\x80\x80\x80'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 5.1 Single UTF-16 surrogates
|
||||
vs = [b"Single UTF-16 surrogates", []]
|
||||
vs[1].append((False, b'\xed\xa0\x80'))
|
||||
vs[1].append((False, b'\xed\xad\xbf'))
|
||||
vs[1].append((False, b'\xed\xae\x80'))
|
||||
vs[1].append((False, b'\xed\xaf\xbf'))
|
||||
vs[1].append((False, b'\xed\xb0\x80'))
|
||||
vs[1].append((False, b'\xed\xbe\x80'))
|
||||
vs[1].append((False, b'\xed\xbf\xbf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 5.2 Paired UTF-16 surrogates
|
||||
vs = [b"Paired UTF-16 surrogates", []]
|
||||
vs[1].append((False, b'\xed\xa0\x80\xed\xb0\x80'))
|
||||
vs[1].append((False, b'\xed\xa0\x80\xed\xbf\xbf'))
|
||||
vs[1].append((False, b'\xed\xad\xbf\xed\xb0\x80'))
|
||||
vs[1].append((False, b'\xed\xad\xbf\xed\xbf\xbf'))
|
||||
vs[1].append((False, b'\xed\xae\x80\xed\xb0\x80'))
|
||||
vs[1].append((False, b'\xed\xae\x80\xed\xbf\xbf'))
|
||||
vs[1].append((False, b'\xed\xaf\xbf\xed\xb0\x80'))
|
||||
vs[1].append((False, b'\xed\xaf\xbf\xed\xbf\xbf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 5.3 Other illegal code positions
|
||||
# Those are non-character code points and valid UTF-8 by RFC 3629
|
||||
vs = [b"Non-character code points (valid UTF-8)", []]
|
||||
# https://bug686312.bugzilla.mozilla.org/attachment.cgi?id=561257
|
||||
# non-characters: EF BF [BE-BF]
|
||||
vs[1].append((True, b'\xef\xbf\xbe'))
|
||||
vs[1].append((True, b'\xef\xbf\xbf'))
|
||||
# non-characters: F[0-7] [89AB]F BF [BE-BF]
|
||||
for z1 in [b'\xf0', b'\xf1', b'\xf2', b'\xf3', b'\xf4']:
|
||||
for z2 in [b'\x8f', b'\x9f', b'\xaf', b'\xbf']:
|
||||
# those encode codepoints >U+10FFFF
|
||||
if not (z1 == b'\xf4' and z2 != b'\x8f'):
|
||||
for z3 in [b'\xbe', b'\xbf']:
|
||||
zz = z1 + z2 + b'\xbf' + z3
|
||||
if zz not in [b'\xf0\x8f\xbf\xbe',
|
||||
b'\xf0\x8f\xbf\xbf']: # filter overlong sequences
|
||||
vs[1].append((True, zz))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# Unicode "specials", such as replacement char etc
|
||||
# http://en.wikipedia.org/wiki/Specials_%28Unicode_block%29
|
||||
vs = [b"Unicode specials (i.e. replacement char)", []]
|
||||
vs[1].append((True, b'\xef\xbf\xb9'))
|
||||
vs[1].append((True, b'\xef\xbf\xba'))
|
||||
vs[1].append((True, b'\xef\xbf\xbb'))
|
||||
vs[1].append((True, b'\xef\xbf\xbc'))
|
||||
vs[1].append((True, b'\xef\xbf\xbd')) # replacement char
|
||||
vs[1].append((True, b'\xef\xbf\xbe'))
|
||||
vs[1].append((True, b'\xef\xbf\xbf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
return UTF8_TEST_SEQUENCES
|
||||
|
||||
|
||||
def _create_valid_utf8_test_sequences():
|
||||
"""
|
||||
Generate some exotic, but valid UTF8 test strings.
|
||||
"""
|
||||
VALID_UTF8_TEST_SEQUENCES = []
|
||||
for test in _create_utf8_test_sequences():
|
||||
valids = [x[1] for x in test[1] if x[0]]
|
||||
if len(valids) > 0:
|
||||
VALID_UTF8_TEST_SEQUENCES.append([test[0], valids])
|
||||
return VALID_UTF8_TEST_SEQUENCES
|
||||
|
||||
|
||||
@unittest.skipIf(not HAS_NVX, 'NVX native extensions not present')
|
||||
class TestNvxUtf8Validator(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# These tests verify the UTF-8 decoder/validator on the various test cases from
|
||||
# http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
|
||||
vs = []
|
||||
for k in _create_utf8_test_sequences():
|
||||
vs.extend(k[1])
|
||||
|
||||
# All Unicode code points
|
||||
for i in range(
|
||||
0, 0xffff): # should by 0x10ffff, but non-wide Python build is limited to 16-bits
|
||||
if i < 0xD800 or i > 0xDFFF: # filter surrogate code points, which are disallowed to encode in UTF-8
|
||||
vs.append((True, six.unichr(i).encode("utf-8")))
|
||||
|
||||
# FIXME: UnicodeEncodeError: 'utf-8' codec can't encode character '\ud800'
|
||||
# in position 0: surrogates not allowed
|
||||
if False:
|
||||
# 5.1 Single UTF-16 surrogates
|
||||
for i in range(0xD800, 0xDBFF): # high-surrogate
|
||||
ss = six.unichr(i).encode("utf-8")
|
||||
vs.append((False, ss))
|
||||
for i in range(0xDC00, 0xDFFF): # low-surrogate
|
||||
ss = six.unichr(i).encode("utf-8")
|
||||
vs.append((False, ss))
|
||||
|
||||
# 5.2 Paired UTF-16 surrogates
|
||||
for i in range(0xD800, 0xDBFF): # high-surrogate
|
||||
for j in range(0xDC00, 0xDFFF): # low-surrogate
|
||||
ss1 = six.unichr(i).encode("utf-8")
|
||||
ss2 = six.unichr(j).encode("utf-8")
|
||||
vs.append((False, ss1 + ss2))
|
||||
vs.append((False, ss2 + ss1))
|
||||
|
||||
self._TEST_SEQUENCES = vs
|
||||
|
||||
def test_standard_utf8validator(self):
|
||||
"""
|
||||
Test standard implementation of UTF8 validator.
|
||||
"""
|
||||
validator = StandardUtf8Validator()
|
||||
return self._test_utf8(validator)
|
||||
|
||||
def test_nvx_utf8validator(self):
|
||||
"""
|
||||
Test NVX implementation of UTF8 validator.
|
||||
"""
|
||||
validator = NvxUtf8Validator()
|
||||
return self._test_utf8(validator)
|
||||
|
||||
def test_standard_utf8validator_incremental(self):
|
||||
"""
|
||||
Test standard implementation of UTF8 validator in incremental mode.
|
||||
"""
|
||||
validator = StandardUtf8Validator()
|
||||
return self._test_utf8_incremental(validator)
|
||||
|
||||
# NVX UTF8 validator lack incremental mode implementation
|
||||
@unittest.expectedFailure
|
||||
def test_nvx_utf8validator_incremental(self):
|
||||
"""
|
||||
Test NVX implementation of UTF8 validator in incremental mode.
|
||||
"""
|
||||
validator = NvxUtf8Validator()
|
||||
return self._test_utf8_incremental(validator)
|
||||
|
||||
def _test_utf8(self, validator):
|
||||
for s in self._TEST_SEQUENCES:
|
||||
validator.reset()
|
||||
r = validator.validate(s[1])
|
||||
|
||||
# no UTF-8 decode error _and_ everything consumed
|
||||
res = r[0] and r[1]
|
||||
|
||||
self.assertEqual(res, s[0])
|
||||
|
||||
def _test_utf8_incremental(self, validator, withPositions=True):
|
||||
# These tests verify that the UTF-8 decoder/validator can operate incrementally.
|
||||
if withPositions:
|
||||
# testing validator 4 on incremental detection with positions
|
||||
k = 4
|
||||
else:
|
||||
# testing validator 2 on incremental detection without positions
|
||||
k = 2
|
||||
|
||||
validator.reset()
|
||||
self.assertEqual((True, True, 15, 15)[:k], validator.validate(u'µ@ßöäüàá'.encode('utf8'))[:k])
|
||||
|
||||
validator.reset()
|
||||
self.assertEqual((False, False, 0, 0)[:k], validator.validate(b"\xF5")[:k])
|
||||
|
||||
# the following 3 all fail on eating byte 7 (0xA0)
|
||||
validator.reset()
|
||||
self.assertEqual((True, True, 6, 6)[:k], validator.validate(b"\x65\x64\x69\x74\x65\x64")[:k])
|
||||
self.assertEqual((False, False, 1, 7)[:k], validator.validate(b"\xED\xA0\x80")[:k])
|
||||
|
||||
validator.reset()
|
||||
self.assertEqual((True, True, 4, 4)[:k], validator.validate(b"\x65\x64\x69\x74")[:k])
|
||||
self.assertEqual((False, False, 3, 7)[:k], validator.validate(b"\x65\x64\xED\xA0\x80")[:k])
|
||||
|
||||
validator.reset()
|
||||
self.assertEqual((True, False, 7, 7)[:k], validator.validate(b"\x65\x64\x69\x74\x65\x64\xED")[:k])
|
||||
self.assertEqual((False, False, 0, 7)[:k], validator.validate(b"\xA0\x80")[:k])
|
@ -0,0 +1,126 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from autobahn.rawsocket.util import create_url, parse_url
|
||||
|
||||
|
||||
class TestCreateRsUrl(unittest.TestCase):
|
||||
|
||||
def test_create_url01(self):
|
||||
self.assertEqual(create_url("localhost"), "rs://localhost:80")
|
||||
|
||||
def test_create_url02(self):
|
||||
self.assertEqual(create_url("localhost", port=8090), "rs://localhost:8090")
|
||||
|
||||
def test_create_url03(self):
|
||||
self.assertEqual(create_url("localhost", isSecure=True), "rss://localhost:443")
|
||||
|
||||
def test_create_url04(self):
|
||||
self.assertEqual(create_url("localhost", isSecure=True, port=443), "rss://localhost:443")
|
||||
|
||||
def test_create_url05(self):
|
||||
self.assertEqual(create_url("localhost", isSecure=True, port=80), "rss://localhost:80")
|
||||
|
||||
def test_create_url06(self):
|
||||
self.assertEqual(create_url("unix", port="file.sock"), "rs://unix:file.sock")
|
||||
|
||||
def test_create_url07(self):
|
||||
self.assertEqual(create_url("unix", port="/tmp/file.sock"), "rs://unix:/tmp/file.sock")
|
||||
|
||||
def test_create_url08(self):
|
||||
self.assertEqual(create_url("unix", port="../file.sock"), "rs://unix:../file.sock")
|
||||
|
||||
def test_create_url09(self):
|
||||
self.assertEqual(create_url("unix", isSecure=True, port="file.sock"), "rss://unix:file.sock")
|
||||
|
||||
def test_create_url10(self):
|
||||
self.assertEqual(create_url("unix", isSecure=True, port="/tmp/file.sock"), "rss://unix:/tmp/file.sock")
|
||||
|
||||
def test_create_url11(self):
|
||||
self.assertEqual(create_url("unix", isSecure=True, port="../file.sock"), "rss://unix:../file.sock")
|
||||
|
||||
|
||||
class TestParseWsUrl(unittest.TestCase):
|
||||
|
||||
# parse_url -> (isSecure, host, port)
|
||||
|
||||
def test_parse_url01(self):
|
||||
self.assertEqual(parse_url("rs://localhost"), (False, 'localhost', 80))
|
||||
|
||||
def test_parse_url02(self):
|
||||
self.assertEqual(parse_url("rss://localhost"), (True, 'localhost', 443))
|
||||
|
||||
def test_parse_url03(self):
|
||||
self.assertEqual(parse_url("rs://localhost:9000"), (False, 'localhost', 9000))
|
||||
|
||||
def test_parse_url04(self):
|
||||
self.assertEqual(parse_url("rss://localhost:9000"), (True, 'localhost', 9000))
|
||||
|
||||
def test_parse_url05(self):
|
||||
self.assertRaises(Exception, parse_url, "ws://localhost")
|
||||
|
||||
def test_parse_url06(self):
|
||||
self.assertRaises(Exception, parse_url, "wss://localhost")
|
||||
|
||||
def test_parse_url07(self):
|
||||
self.assertRaises(Exception, parse_url, "ws://localhost:80")
|
||||
|
||||
def test_parse_url08(self):
|
||||
self.assertRaises(Exception, parse_url, "rs://localhost/somepath")
|
||||
|
||||
def test_parse_url09(self):
|
||||
self.assertRaises(Exception, parse_url, "rs://localhost#somefrag")
|
||||
|
||||
def test_parse_url10(self):
|
||||
self.assertRaises(Exception, parse_url, "rs://localhost?foo=bar")
|
||||
|
||||
def test_parse_url11(self):
|
||||
self.assertRaises(Exception, parse_url, "rss://")
|
||||
|
||||
def test_parse_url12(self):
|
||||
self.assertRaises(Exception, parse_url, "rs://")
|
||||
|
||||
def test_parse_url13(self):
|
||||
self.assertEqual(parse_url("rs://unix:file.sock"), (False, 'unix', 'file.sock'))
|
||||
|
||||
def test_parse_url14(self):
|
||||
self.assertEqual(parse_url("rs://unix:/tmp/file.sock"), (False, 'unix', '/tmp/file.sock'))
|
||||
|
||||
def test_parse_url15(self):
|
||||
self.assertEqual(parse_url("rs://unix:../file.sock"), (False, 'unix', '../file.sock'))
|
||||
|
||||
def test_parse_url16(self):
|
||||
self.assertEqual(parse_url("rss://unix:file.sock"), (True, 'unix', 'file.sock'))
|
||||
|
||||
def test_parse_url17(self):
|
||||
self.assertEqual(parse_url("rss://unix:/tmp/file.sock"), (True, 'unix', '/tmp/file.sock'))
|
||||
|
||||
def test_parse_url18(self):
|
||||
self.assertEqual(parse_url("rss://unix:../file.sock"), (True, 'unix', '../file.sock'))
|
111
venv/lib/python3.7/site-packages/autobahn/test/test_rng.py
Normal file
111
venv/lib/python3.7/site-packages/autobahn/test/test_rng.py
Normal file
@ -0,0 +1,111 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import uuid
|
||||
import random
|
||||
from nacl import utils, public
|
||||
|
||||
from autobahn import util
|
||||
|
||||
|
||||
@unittest.skipIf(not sys.platform.startswith('linux'), 'entropy depletion tests only available on Linux')
|
||||
class TestEntropy(unittest.TestCase):
|
||||
|
||||
def test_non_depleting(self):
|
||||
res = {}
|
||||
|
||||
with open('/dev/urandom', 'rb') as rng:
|
||||
for i in range(1000):
|
||||
for j in range(100):
|
||||
|
||||
# "reseed" (seems pointless, but ..)
|
||||
random.seed()
|
||||
|
||||
# random UUIDs
|
||||
v1 = uuid.uuid4() # noqa
|
||||
|
||||
# stdlib random
|
||||
v2 = random.random() # noqa
|
||||
v3 = random.getrandbits(32) # noqa
|
||||
v4 = random.randint(0, 9007199254740992) # noqa
|
||||
v5 = random.normalvariate(10, 100) # noqa
|
||||
v6 = random.choice(range(100)) # noqa
|
||||
|
||||
# PyNaCl
|
||||
v7 = utils.random(public.Box.NONCE_SIZE) # noqa
|
||||
|
||||
# Autobahn utils
|
||||
v8 = util.generate_token(4, 4) # noqa
|
||||
v9 = util.id() # noqa
|
||||
v10 = util.rid() # noqa
|
||||
v11 = util.newid() # noqa
|
||||
|
||||
# direct procfs access to PRNG
|
||||
d = rng.read(1000) # noqa
|
||||
|
||||
# check available entropy
|
||||
with open('/proc/sys/kernel/random/entropy_avail', 'r') as ent:
|
||||
ea = int(ent.read()) // 100
|
||||
if ea not in res:
|
||||
res[ea] = 0
|
||||
res[ea] += 1
|
||||
|
||||
skeys = sorted(res.keys())
|
||||
|
||||
print('\nsystem entropy depletion stats:')
|
||||
for k in skeys:
|
||||
print('{}: {}'.format(k, res[k]))
|
||||
|
||||
self.assertTrue(skeys[0] > 10)
|
||||
|
||||
def test_depleting(self):
|
||||
res = {}
|
||||
|
||||
with open('/dev/random', 'rb') as rng:
|
||||
for i in range(10000):
|
||||
|
||||
# direct procfs access to "real" RNG
|
||||
d = rng.read(1000) # noqa
|
||||
|
||||
# check available entropy
|
||||
with open('/proc/sys/kernel/random/entropy_avail', 'r') as ent:
|
||||
ea = int(ent.read()) // 100
|
||||
if ea not in res:
|
||||
res[ea] = 0
|
||||
res[ea] += 1
|
||||
|
||||
skeys = sorted(res.keys())
|
||||
|
||||
print('\nsystem entropy depletion stats:')
|
||||
for k in skeys:
|
||||
print('{}: {}'.format(k, res[k]))
|
||||
|
||||
self.assertTrue(skeys[0] == 0)
|
48
venv/lib/python3.7/site-packages/autobahn/test/test_util.py
Normal file
48
venv/lib/python3.7/site-packages/autobahn/test/test_util.py
Normal file
@ -0,0 +1,48 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from autobahn.util import IdGenerator
|
||||
|
||||
|
||||
class TestIdGenerator(unittest.TestCase):
|
||||
def test_idgenerator_is_generator(self):
|
||||
"IdGenerator follows the generator protocol"
|
||||
g = IdGenerator()
|
||||
self.assertEqual(1, next(g))
|
||||
self.assertEqual(2, next(g))
|
||||
|
||||
def test_generator_wrap(self):
|
||||
g = IdGenerator()
|
||||
g._next = 2 ** 53 - 1 # cheat a little
|
||||
|
||||
v = next(g)
|
||||
self.assertEqual(v, 2 ** 53)
|
||||
v = next(g)
|
||||
self.assertEqual(v, 1)
|
@ -0,0 +1,89 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import platform
|
||||
|
||||
import twisted
|
||||
|
||||
import autobahn
|
||||
|
||||
# Twisted specific utilities (these should really be in Twisted, but
|
||||
# they aren't, and we use these in example code, so it must be part of
|
||||
# the public API)
|
||||
from autobahn.twisted.util import sleep
|
||||
from autobahn.twisted.choosereactor import install_reactor
|
||||
|
||||
# WebSocket protocol support
|
||||
from autobahn.twisted.websocket import \
|
||||
WebSocketServerProtocol, \
|
||||
WebSocketClientProtocol, \
|
||||
WebSocketServerFactory, \
|
||||
WebSocketClientFactory
|
||||
|
||||
# support for running Twisted stream protocols over WebSocket
|
||||
from autobahn.twisted.websocket import WrappingWebSocketServerFactory, \
|
||||
WrappingWebSocketClientFactory
|
||||
|
||||
# Twisted Web support - FIXME: these imports trigger import of Twisted reactor!
|
||||
# from autobahn.twisted.resource import WebSocketResource, WSGIRootResource
|
||||
|
||||
# WAMP support
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
|
||||
|
||||
__all__ = (
|
||||
# this should really be in Twisted
|
||||
'sleep',
|
||||
'install_reactor',
|
||||
|
||||
# WebSocket
|
||||
'WebSocketServerProtocol',
|
||||
'WebSocketClientProtocol',
|
||||
'WebSocketServerFactory',
|
||||
'WebSocketClientFactory',
|
||||
|
||||
# wrapping stream protocols in WebSocket
|
||||
'WrappingWebSocketServerFactory',
|
||||
'WrappingWebSocketClientFactory',
|
||||
|
||||
# Twisted Web - FIXME: see comment for import above
|
||||
# 'WebSocketResource',
|
||||
|
||||
# this should really be in Twisted - FIXME: see comment for import above
|
||||
# 'WSGIRootResource',
|
||||
|
||||
# WAMP support
|
||||
'ApplicationSession',
|
||||
)
|
||||
|
||||
__ident__ = u'Autobahn/{}-Twisted/{}-{}/{}'.format(autobahn.__version__, twisted.__version__, platform.python_implementation(), '.'.join([str(x) for x in list(sys.version_info[:3])]))
|
||||
"""
|
||||
AutobahnPython library implementation (eg. "Autobahn/0.13.0-Twisted/15.5.0-CPython/3.5.1")
|
||||
"""
|
870
venv/lib/python3.7/site-packages/autobahn/util.py
Normal file
870
venv/lib/python3.7/site-packages/autobahn/util.py
Normal file
@ -0,0 +1,870 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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", fWITHOUT 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 __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import time
|
||||
import struct
|
||||
import sys
|
||||
import re
|
||||
import base64
|
||||
import math
|
||||
import random
|
||||
import binascii
|
||||
from datetime import datetime, timedelta
|
||||
from pprint import pformat
|
||||
from array import array
|
||||
|
||||
import six
|
||||
|
||||
import txaio
|
||||
|
||||
try:
|
||||
_TLS = True
|
||||
from OpenSSL import SSL
|
||||
except ImportError:
|
||||
_TLS = False
|
||||
|
||||
|
||||
__all__ = ("public",
|
||||
"encode_truncate",
|
||||
"xor",
|
||||
"utcnow",
|
||||
"utcstr",
|
||||
"id",
|
||||
"rid",
|
||||
"newid",
|
||||
"rtime",
|
||||
"Stopwatch",
|
||||
"Tracker",
|
||||
"EqualityMixin",
|
||||
"ObservableMixin",
|
||||
"IdGenerator",
|
||||
"generate_token",
|
||||
"generate_activation_code",
|
||||
"generate_serial_number",
|
||||
"generate_user_password")
|
||||
|
||||
|
||||
def public(obj):
|
||||
"""
|
||||
The public user API of Autobahn is marked using this decorator.
|
||||
Everything that is not decorated @public is library internal, can
|
||||
change at any time and should not be used in user program code.
|
||||
"""
|
||||
try:
|
||||
obj._is_public = True
|
||||
except AttributeError:
|
||||
# FIXME: exceptions.AttributeError: 'staticmethod' object has no attribute '_is_public'
|
||||
pass
|
||||
return obj
|
||||
|
||||
|
||||
@public
|
||||
def encode_truncate(text, limit, encoding='utf8', return_encoded=True):
|
||||
"""
|
||||
Given a string, return a truncated version of the string such that
|
||||
the UTF8 encoding of the string is smaller than the given limit.
|
||||
|
||||
This function correctly truncates even in the presence of Unicode code
|
||||
points that encode to multi-byte encodings which must not be truncated
|
||||
in the middle.
|
||||
|
||||
:param text: The (Unicode) string to truncate.
|
||||
:type text: str
|
||||
:param limit: The number of bytes to limit the UTF8 encoding to.
|
||||
:type limit: int
|
||||
:param encoding: Truncate the string in this encoding (default is ``utf-8``).
|
||||
:type encoding: str
|
||||
:param return_encoded: If ``True``, return the string encoded into bytes
|
||||
according to the specified encoding, else return the string as a string.
|
||||
:type return_encoded: bool
|
||||
|
||||
:returns: The truncated string.
|
||||
:rtype: str or bytes
|
||||
"""
|
||||
assert(text is None or type(text) == six.text_type)
|
||||
assert(type(limit) in six.integer_types)
|
||||
assert(limit >= 0)
|
||||
|
||||
if text is None:
|
||||
return
|
||||
|
||||
# encode the given string in the specified encoding
|
||||
s = text.encode(encoding)
|
||||
|
||||
# when the resulting byte string is longer than the given limit ..
|
||||
if len(s) > limit:
|
||||
# .. truncate, and
|
||||
s = s[:limit]
|
||||
|
||||
# decode back, ignoring errors that result from truncation
|
||||
# in the middle of multi-byte encodings
|
||||
text = s.decode(encoding, 'ignore')
|
||||
|
||||
if return_encoded:
|
||||
s = text.encode(encoding)
|
||||
|
||||
if return_encoded:
|
||||
return s
|
||||
else:
|
||||
return text
|
||||
|
||||
|
||||
@public
|
||||
def xor(d1, d2):
|
||||
"""
|
||||
XOR two binary strings of arbitrary (equal) length.
|
||||
|
||||
:param d1: The first binary string.
|
||||
:type d1: binary
|
||||
:param d2: The second binary string.
|
||||
:type d2: binary
|
||||
|
||||
:returns: XOR of the binary strings (``XOR(d1, d2)``)
|
||||
:rtype: bytes
|
||||
"""
|
||||
if type(d1) != six.binary_type:
|
||||
raise Exception("invalid type {} for d1 - must be binary".format(type(d1)))
|
||||
if type(d2) != six.binary_type:
|
||||
raise Exception("invalid type {} for d2 - must be binary".format(type(d2)))
|
||||
if len(d1) != len(d2):
|
||||
raise Exception("cannot XOR binary string of differing length ({} != {})".format(len(d1), len(d2)))
|
||||
|
||||
d1 = array('B', d1)
|
||||
d2 = array('B', d2)
|
||||
|
||||
for i in range(len(d1)):
|
||||
d1[i] ^= d2[i]
|
||||
|
||||
if six.PY3:
|
||||
return d1.tobytes()
|
||||
else:
|
||||
return d1.tostring()
|
||||
|
||||
|
||||
@public
|
||||
def utcstr(ts=None):
|
||||
"""
|
||||
Format UTC timestamp in ISO 8601 format.
|
||||
|
||||
Note: to parse an ISO 8601 formatted string, use the **iso8601**
|
||||
module instead (e.g. ``iso8601.parse_date("2014-05-23T13:03:44.123Z")``).
|
||||
|
||||
:param ts: The timestamp to format.
|
||||
:type ts: instance of :py:class:`datetime.datetime` or ``None``
|
||||
|
||||
:returns: Timestamp formatted in ISO 8601 format.
|
||||
:rtype: str
|
||||
"""
|
||||
assert(ts is None or isinstance(ts, datetime))
|
||||
if ts is None:
|
||||
ts = datetime.utcnow()
|
||||
return u"{0}Z".format(ts.strftime(u"%Y-%m-%dT%H:%M:%S.%f")[:-3])
|
||||
|
||||
|
||||
@public
|
||||
def utcnow():
|
||||
"""
|
||||
Get current time in UTC as ISO 8601 string.
|
||||
|
||||
:returns: Current time as string in ISO 8601 format.
|
||||
:rtype: str
|
||||
"""
|
||||
return utcstr()
|
||||
|
||||
|
||||
class IdGenerator(object):
|
||||
"""
|
||||
ID generator for WAMP request IDs.
|
||||
|
||||
WAMP request IDs are sequential per WAMP session, starting at 1 and
|
||||
wrapping around at 2**53 (both value are inclusive [1, 2**53]).
|
||||
|
||||
The upper bound **2**53** is chosen since it is the maximum integer that can be
|
||||
represented as a IEEE double such that all smaller integers are representable as well.
|
||||
|
||||
Hence, IDs can be safely used with languages that use IEEE double as their
|
||||
main (or only) number type (JavaScript, Lua, etc).
|
||||
|
||||
See https://github.com/wamp-proto/wamp-proto/blob/master/spec/basic.md#ids
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._next = 0 # starts at 1; next() pre-increments
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
Returns next ID.
|
||||
|
||||
:returns: The next ID.
|
||||
:rtype: int
|
||||
"""
|
||||
self._next += 1
|
||||
if self._next > 9007199254740992:
|
||||
self._next = 1
|
||||
return self._next
|
||||
|
||||
# generator protocol
|
||||
def __next__(self):
|
||||
return self.next()
|
||||
|
||||
|
||||
#
|
||||
# Performance comparison of IdGenerator.next(), id() and rid().
|
||||
#
|
||||
# All tests were performed on:
|
||||
#
|
||||
# - Ubuntu 14.04 LTS x86-64
|
||||
# - Intel Core i7 920 @ 3.3GHz
|
||||
#
|
||||
# The tests generated 100 mio. IDs and run-time was measured
|
||||
# as wallclock from Unix "time" command. In each run, a single CPU
|
||||
# core was essentially at 100% load all the time (though the sys/usr
|
||||
# ratio was different).
|
||||
#
|
||||
# PyPy 2.6.1:
|
||||
#
|
||||
# IdGenerator.next() 0.5s
|
||||
# id() 29.4s
|
||||
# rid() 106.1s
|
||||
#
|
||||
# CPython 2.7.10:
|
||||
#
|
||||
# IdGenerator.next() 49.0s
|
||||
# id() 370.5s
|
||||
# rid() 196.4s
|
||||
#
|
||||
|
||||
#
|
||||
# Note on the ID range [0, 2**53]. We once reduced the range to [0, 2**31].
|
||||
# This lead to extremely hard to track down issues due to ID collisions!
|
||||
# Here: https://github.com/crossbario/autobahn-python/issues/419#issue-90483337
|
||||
#
|
||||
|
||||
|
||||
# 8 byte mask with 53 LSBs set (WAMP requires IDs from [0, 2**53]
|
||||
_WAMP_ID_MASK = struct.unpack(">Q", b"\x00\x1f\xff\xff\xff\xff\xff\xff")[0]
|
||||
|
||||
|
||||
def rid():
|
||||
"""
|
||||
Generate a new random integer ID from range **[0, 2**53]**.
|
||||
|
||||
The generated ID is uniformly distributed over the whole range, doesn't have
|
||||
a period (no pseudo-random generator is used) and cryptographically strong.
|
||||
|
||||
The upper bound **2**53** is chosen since it is the maximum integer that can be
|
||||
represented as a IEEE double such that all smaller integers are representable as well.
|
||||
|
||||
Hence, IDs can be safely used with languages that use IEEE double as their
|
||||
main (or only) number type (JavaScript, Lua, etc).
|
||||
|
||||
:returns: A random integer ID.
|
||||
:rtype: int
|
||||
"""
|
||||
return struct.unpack("@Q", os.urandom(8))[0] & _WAMP_ID_MASK
|
||||
|
||||
|
||||
# noinspection PyShadowingBuiltins
|
||||
def id():
|
||||
"""
|
||||
Generate a new random integer ID from range **[0, 2**53]**.
|
||||
|
||||
The generated ID is based on a pseudo-random number generator (Mersenne Twister,
|
||||
which has a period of 2**19937-1). It is NOT cryptographically strong, and
|
||||
hence NOT suitable to generate e.g. secret keys or access tokens.
|
||||
|
||||
The upper bound **2**53** is chosen since it is the maximum integer that can be
|
||||
represented as a IEEE double such that all smaller integers are representable as well.
|
||||
|
||||
Hence, IDs can be safely used with languages that use IEEE double as their
|
||||
main (or only) number type (JavaScript, Lua, etc).
|
||||
|
||||
:returns: A random integer ID.
|
||||
:rtype: int
|
||||
"""
|
||||
return random.randint(0, 9007199254740992)
|
||||
|
||||
|
||||
def newid(length=16):
|
||||
"""
|
||||
Generate a new random string ID.
|
||||
|
||||
The generated ID is uniformly distributed and cryptographically strong. It is
|
||||
hence usable for things like secret keys and access tokens.
|
||||
|
||||
:param length: The length (in chars) of the ID to generate.
|
||||
:type length: int
|
||||
|
||||
:returns: A random string ID.
|
||||
:rtype: str
|
||||
"""
|
||||
l = int(math.ceil(float(length) * 6. / 8.))
|
||||
return base64.b64encode(os.urandom(l))[:length].decode('ascii')
|
||||
|
||||
|
||||
# a standard base36 character set
|
||||
# DEFAULT_TOKEN_CHARS = string.digits + string.ascii_uppercase
|
||||
|
||||
# we take out the following 9 chars (leaving 27), because there
|
||||
# is visual ambiguity: 0/O/D, 1/I, 8/B, 2/Z
|
||||
DEFAULT_TOKEN_CHARS = u'345679ACEFGHJKLMNPQRSTUVWXY'
|
||||
"""
|
||||
Default set of characters to create rtokens from.
|
||||
"""
|
||||
|
||||
DEFAULT_ZBASE32_CHARS = u'13456789abcdefghijkmnopqrstuwxyz'
|
||||
"""
|
||||
Our choice of confusing characters to eliminate is: `0', `l', `v', and `2'. Our
|
||||
reasoning is that `0' is potentially mistaken for `o', that `l' is potentially
|
||||
mistaken for `1' or `i', that `v' is potentially mistaken for `u' or `r'
|
||||
(especially in handwriting) and that `2' is potentially mistaken for `z'
|
||||
(especially in handwriting).
|
||||
|
||||
Note that we choose to focus on typed and written transcription more than on
|
||||
vocal, since humans already have a well-established system of disambiguating
|
||||
spoken alphanumerics, such as the United States military's "Alpha Bravo Charlie
|
||||
Delta" and telephone operators' "Is that 'd' as in 'dog'?".
|
||||
|
||||
* http://philzimmermann.com/docs/human-oriented-base-32-encoding.txt
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
def generate_token(char_groups, chars_per_group, chars=None, sep=None, lower_case=False):
|
||||
"""
|
||||
Generate cryptographically strong tokens, which are strings like `M6X5-YO5W-T5IK`.
|
||||
These can be used e.g. for used-only-once activation tokens or the like.
|
||||
|
||||
The returned token has an entropy of
|
||||
``math.log(len(chars), 2.) * chars_per_group * char_groups``
|
||||
bits.
|
||||
|
||||
With the default charset and 4 characters per group, ``generate_token()`` produces
|
||||
strings with the following entropy:
|
||||
|
||||
================ =================== ========================================
|
||||
character groups entropy (at least) recommended use
|
||||
================ =================== ========================================
|
||||
2 38 bits
|
||||
3 57 bits one-time activation or pairing code
|
||||
4 76 bits secure user password
|
||||
5 95 bits
|
||||
6 114 bits globally unique serial / product code
|
||||
7 133 bits
|
||||
================ =================== ========================================
|
||||
|
||||
Here are some examples:
|
||||
|
||||
* token(3): ``9QXT-UXJW-7R4H``
|
||||
* token(4): ``LPNN-JMET-KWEP-YK45``
|
||||
* token(6): ``NXW9-74LU-6NUH-VLPV-X6AG-QUE3``
|
||||
|
||||
:param char_groups: Number of character groups (or characters if chars_per_group == 1).
|
||||
:type char_groups: int
|
||||
|
||||
:param chars_per_group: Number of characters per character group (or 1 to return a token with no grouping).
|
||||
:type chars_per_group: int
|
||||
|
||||
:param chars: Characters to choose from. Default is 27 character subset
|
||||
of the ISO basic Latin alphabet (see: ``DEFAULT_TOKEN_CHARS``).
|
||||
:type chars: str or None
|
||||
|
||||
:param sep: When separating groups in the token, the separater string.
|
||||
:type sep: str
|
||||
|
||||
:param lower_case: If ``True``, generate token in lower-case.
|
||||
:type lower_case: bool
|
||||
|
||||
:returns: The generated token.
|
||||
:rtype: str
|
||||
"""
|
||||
assert(type(char_groups) in six.integer_types)
|
||||
assert(type(chars_per_group) in six.integer_types)
|
||||
assert(chars is None or type(chars) == six.text_type)
|
||||
chars = chars or DEFAULT_TOKEN_CHARS
|
||||
if lower_case:
|
||||
chars = chars.lower()
|
||||
sep = sep or u'-'
|
||||
rng = random.SystemRandom()
|
||||
token_value = u''.join(rng.choice(chars) for _ in range(char_groups * chars_per_group))
|
||||
if chars_per_group > 1:
|
||||
return sep.join(map(u''.join, zip(*[iter(token_value)] * chars_per_group)))
|
||||
else:
|
||||
return token_value
|
||||
|
||||
|
||||
@public
|
||||
def generate_activation_code():
|
||||
"""
|
||||
Generate a one-time activation code or token of the form ``u'W97F-96MJ-YGJL'``.
|
||||
The generated value is cryptographically strong and has (at least) 57 bits of entropy.
|
||||
|
||||
:returns: The generated activation code.
|
||||
:rtype: str
|
||||
"""
|
||||
return generate_token(char_groups=3, chars_per_group=4, chars=DEFAULT_TOKEN_CHARS, sep=u'-', lower_case=False)
|
||||
|
||||
|
||||
@public
|
||||
def generate_user_password():
|
||||
"""
|
||||
Generate a secure, random user password of the form ``u'kgojzi61dn5dtb6d'``.
|
||||
The generated value is cryptographically strong and has (at least) 76 bits of entropy.
|
||||
|
||||
:returns: The generated password.
|
||||
:rtype: str
|
||||
"""
|
||||
return generate_token(char_groups=16, chars_per_group=1, chars=DEFAULT_ZBASE32_CHARS, sep=u'-', lower_case=True)
|
||||
|
||||
|
||||
@public
|
||||
def generate_serial_number():
|
||||
"""
|
||||
Generate a globally unique serial / product code of the form ``u'YRAC-EL4X-FQQE-AW4T-WNUV-VN6T'``.
|
||||
The generated value is cryptographically strong and has (at least) 114 bits of entropy.
|
||||
|
||||
:returns: The generated serial number / product code.
|
||||
:rtype: str
|
||||
"""
|
||||
return generate_token(char_groups=6, chars_per_group=4, chars=DEFAULT_TOKEN_CHARS, sep=u'-', lower_case=False)
|
||||
|
||||
|
||||
# Select the most precise walltime measurement function available
|
||||
# on the platform
|
||||
#
|
||||
if sys.platform.startswith('win'):
|
||||
# On Windows, this function returns wall-clock seconds elapsed since the
|
||||
# first call to this function, as a floating point number, based on the
|
||||
# Win32 function QueryPerformanceCounter(). The resolution is typically
|
||||
# better than one microsecond
|
||||
if sys.version_info >= (3, 8):
|
||||
_rtime = time.perf_counter
|
||||
else:
|
||||
_rtime = time.clock
|
||||
_ = _rtime() # this starts wallclock
|
||||
else:
|
||||
# On Unix-like platforms, this used the first available from this list:
|
||||
# (1) gettimeofday() -- resolution in microseconds
|
||||
# (2) ftime() -- resolution in milliseconds
|
||||
# (3) time() -- resolution in seconds
|
||||
_rtime = time.time
|
||||
|
||||
|
||||
@public
|
||||
def rtime():
|
||||
"""
|
||||
Precise, fast wallclock time.
|
||||
|
||||
:returns: The current wallclock in seconds. Returned values are only guaranteed
|
||||
to be meaningful relative to each other.
|
||||
:rtype: float
|
||||
"""
|
||||
return _rtime()
|
||||
|
||||
|
||||
class Stopwatch(object):
|
||||
"""
|
||||
Stopwatch based on walltime.
|
||||
|
||||
This can be used to do code timing and uses the most precise walltime measurement
|
||||
available on the platform. This is a very light-weight object,
|
||||
so create/dispose is very cheap.
|
||||
"""
|
||||
|
||||
def __init__(self, start=True):
|
||||
"""
|
||||
|
||||
:param start: If ``True``, immediately start the stopwatch.
|
||||
:type start: bool
|
||||
"""
|
||||
self._elapsed = 0
|
||||
if start:
|
||||
self._started = rtime()
|
||||
self._running = True
|
||||
else:
|
||||
self._started = None
|
||||
self._running = False
|
||||
|
||||
def elapsed(self):
|
||||
"""
|
||||
Return total time elapsed in seconds during which the stopwatch was running.
|
||||
|
||||
:returns: The elapsed time in seconds.
|
||||
:rtype: float
|
||||
"""
|
||||
if self._running:
|
||||
now = rtime()
|
||||
return self._elapsed + (now - self._started)
|
||||
else:
|
||||
return self._elapsed
|
||||
|
||||
def pause(self):
|
||||
"""
|
||||
Pauses the stopwatch and returns total time elapsed in seconds during which
|
||||
the stopwatch was running.
|
||||
|
||||
:returns: The elapsed time in seconds.
|
||||
:rtype: float
|
||||
"""
|
||||
if self._running:
|
||||
now = rtime()
|
||||
self._elapsed += now - self._started
|
||||
self._running = False
|
||||
return self._elapsed
|
||||
else:
|
||||
return self._elapsed
|
||||
|
||||
def resume(self):
|
||||
"""
|
||||
Resumes a paused stopwatch and returns total elapsed time in seconds
|
||||
during which the stopwatch was running.
|
||||
|
||||
:returns: The elapsed time in seconds.
|
||||
:rtype: float
|
||||
"""
|
||||
if not self._running:
|
||||
self._started = rtime()
|
||||
self._running = True
|
||||
return self._elapsed
|
||||
else:
|
||||
now = rtime()
|
||||
return self._elapsed + (now - self._started)
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stops the stopwatch and returns total time elapsed in seconds during which
|
||||
the stopwatch was (previously) running.
|
||||
|
||||
:returns: The elapsed time in seconds.
|
||||
:rtype: float
|
||||
"""
|
||||
elapsed = self.pause()
|
||||
self._elapsed = 0
|
||||
self._started = None
|
||||
self._running = False
|
||||
return elapsed
|
||||
|
||||
|
||||
class Tracker(object):
|
||||
"""
|
||||
A key-based statistics tracker.
|
||||
"""
|
||||
|
||||
def __init__(self, tracker, tracked):
|
||||
"""
|
||||
"""
|
||||
self.tracker = tracker
|
||||
self.tracked = tracked
|
||||
self._timings = {}
|
||||
self._offset = rtime()
|
||||
self._dt_offset = datetime.utcnow()
|
||||
|
||||
def track(self, key):
|
||||
"""
|
||||
Track elapsed for key.
|
||||
|
||||
:param key: Key under which to track the timing.
|
||||
:type key: str
|
||||
"""
|
||||
self._timings[key] = rtime()
|
||||
|
||||
def diff(self, start_key, end_key, formatted=True):
|
||||
"""
|
||||
Get elapsed difference between two previously tracked keys.
|
||||
|
||||
:param start_key: First key for interval (older timestamp).
|
||||
:type start_key: str
|
||||
:param end_key: Second key for interval (younger timestamp).
|
||||
:type end_key: str
|
||||
:param formatted: If ``True``, format computed time period and return string.
|
||||
:type formatted: bool
|
||||
|
||||
:returns: Computed time period in seconds (or formatted string).
|
||||
:rtype: float or str
|
||||
"""
|
||||
if end_key in self._timings and start_key in self._timings:
|
||||
d = self._timings[end_key] - self._timings[start_key]
|
||||
if formatted:
|
||||
if d < 0.00001: # 10us
|
||||
s = "%d ns" % round(d * 1000000000.)
|
||||
elif d < 0.01: # 10ms
|
||||
s = "%d us" % round(d * 1000000.)
|
||||
elif d < 10: # 10s
|
||||
s = "%d ms" % round(d * 1000.)
|
||||
else:
|
||||
s = "%d s" % round(d)
|
||||
return s.rjust(8)
|
||||
else:
|
||||
return d
|
||||
else:
|
||||
if formatted:
|
||||
return "n.a.".rjust(8)
|
||||
else:
|
||||
return None
|
||||
|
||||
def absolute(self, key):
|
||||
"""
|
||||
Return the UTC wall-clock time at which a tracked event occurred.
|
||||
|
||||
:param key: The key
|
||||
:type key: str
|
||||
|
||||
:returns: Timezone-naive datetime.
|
||||
:rtype: instance of :py:class:`datetime.datetime`
|
||||
"""
|
||||
elapsed = self[key]
|
||||
if elapsed is None:
|
||||
raise KeyError("No such key \"%s\"." % elapsed)
|
||||
return self._dt_offset + timedelta(seconds=elapsed)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self._timings:
|
||||
return self._timings[key] - self._offset
|
||||
else:
|
||||
return None
|
||||
|
||||
def __iter__(self):
|
||||
return self._timings.__iter__()
|
||||
|
||||
def __str__(self):
|
||||
return pformat(self._timings)
|
||||
|
||||
|
||||
class EqualityMixin(object):
|
||||
"""
|
||||
Mixing to add equality comparison operators to a class.
|
||||
|
||||
Two objects are identical under this mixin, if and only if:
|
||||
|
||||
1. both object have the same class
|
||||
2. all non-private object attributes are equal
|
||||
"""
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Compare this object to another object for equality.
|
||||
|
||||
:param other: The other object to compare with.
|
||||
:type other: obj
|
||||
|
||||
:returns: ``True`` iff the objects are equal.
|
||||
:rtype: bool
|
||||
"""
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
# we only want the actual message data attributes (not eg _serialize)
|
||||
for k in self.__dict__:
|
||||
if not k.startswith('_'):
|
||||
if not self.__dict__[k] == other.__dict__[k]:
|
||||
return False
|
||||
return True
|
||||
# return (isinstance(other, self.__class__) and self.__dict__ == other.__dict__)
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
Compare this object to another object for inequality.
|
||||
|
||||
:param other: The other object to compare with.
|
||||
:type other: obj
|
||||
|
||||
:returns: ``True`` iff the objects are not equal.
|
||||
:rtype: bool
|
||||
"""
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
def wildcards2patterns(wildcards):
|
||||
"""
|
||||
Compute a list of regular expression patterns from a list of
|
||||
wildcard strings. A wildcard string uses '*' as a wildcard character
|
||||
matching anything.
|
||||
|
||||
:param wildcards: List of wildcard strings to compute regular expression patterns for.
|
||||
:type wildcards: list of str
|
||||
|
||||
:returns: Computed regular expressions.
|
||||
:rtype: list of obj
|
||||
"""
|
||||
# note that we add the ^ and $ so that the *entire* string must
|
||||
# match. Without this, e.g. a prefix will match:
|
||||
# re.match('.*good\\.com', 'good.com.evil.com') # match!
|
||||
# re.match('.*good\\.com$', 'good.com.evil.com') # no match!
|
||||
return [re.compile('^' + wc.replace('.', r'\.').replace('*', '.*') + '$') for wc in wildcards]
|
||||
|
||||
|
||||
class ObservableMixin(object):
|
||||
"""
|
||||
Internal utility for enabling event-listeners on particular objects
|
||||
"""
|
||||
|
||||
# A "helper" style composable class (as opposed to a mix-in) might
|
||||
# be a lot easier to deal with here. Having an __init__ method
|
||||
# with a "mix in" style class can be fragile and error-prone,
|
||||
# especially if it takes arguments. Since we don't use the
|
||||
# "parent" beavior anywhere, I didn't add a .set_parent() (yet?)
|
||||
|
||||
# these are class-level globals; individual instances are
|
||||
# initialized as-needed (e.g. the first .on() call adds a
|
||||
# _listeners dict). Thus, subclasses don't have to call super()
|
||||
# properly etc.
|
||||
_parent = None
|
||||
_valid_events = None
|
||||
_listeners = None
|
||||
|
||||
def set_valid_events(self, valid_events=None):
|
||||
"""
|
||||
:param valid_events: if non-None, .on() or .fire() with an event
|
||||
not listed in valid_events raises an exception.
|
||||
"""
|
||||
self._valid_events = list(valid_events)
|
||||
|
||||
def _check_event(self, event):
|
||||
"""
|
||||
Internal helper. Throws RuntimeError if we have a valid_events
|
||||
list, and the given event isnt' in it. Does nothing otherwise.
|
||||
"""
|
||||
if self._valid_events and event not in self._valid_events:
|
||||
raise RuntimeError(
|
||||
"Invalid event '{event}'. Expected one of: {events}".format(
|
||||
event=event,
|
||||
events=', '.join(self._valid_events),
|
||||
)
|
||||
)
|
||||
|
||||
def on(self, event, handler):
|
||||
"""
|
||||
Add a handler for an event.
|
||||
|
||||
:param event: the name of the event
|
||||
|
||||
:param handler: a callable thats invoked when .fire() is
|
||||
called for this events. Arguments will be whatever are given
|
||||
to .fire()
|
||||
"""
|
||||
# print("adding '{}' to '{}': {}".format(event, hash(self), handler))
|
||||
self._check_event(event)
|
||||
if self._listeners is None:
|
||||
self._listeners = dict()
|
||||
if event not in self._listeners:
|
||||
self._listeners[event] = []
|
||||
self._listeners[event].append(handler)
|
||||
|
||||
def off(self, event=None, handler=None):
|
||||
"""
|
||||
Stop listening for a single event, or all events.
|
||||
|
||||
:param event: if None, remove all listeners. Otherwise, remove
|
||||
listeners for the single named event.
|
||||
|
||||
:param handler: if None, remove all handlers for the named
|
||||
event; otherwise remove just the given handler.
|
||||
"""
|
||||
if event is None:
|
||||
if handler is not None:
|
||||
# maybe this should mean "remove the given handler
|
||||
# from any event at all that contains it"...?
|
||||
raise RuntimeError(
|
||||
"Can't specificy a specific handler without an event"
|
||||
)
|
||||
self._listeners = dict()
|
||||
else:
|
||||
if self._listeners is None:
|
||||
return
|
||||
self._check_event(event)
|
||||
if event in self._listeners:
|
||||
if handler is None:
|
||||
del self._listeners[event]
|
||||
else:
|
||||
self._listeners[event].discard(handler)
|
||||
|
||||
def fire(self, event, *args, **kwargs):
|
||||
"""
|
||||
Fire a particular event.
|
||||
|
||||
:param event: the event to fire. All other args and kwargs are
|
||||
passed on to the handler(s) for the event.
|
||||
|
||||
:return: a Deferred/Future gathering all async results from
|
||||
all handlers and/or parent handlers.
|
||||
"""
|
||||
# print("firing '{}' from '{}'".format(event, hash(self)))
|
||||
if self._listeners is None:
|
||||
return txaio.create_future(result=[])
|
||||
|
||||
self._check_event(event)
|
||||
res = []
|
||||
for handler in self._listeners.get(event, []):
|
||||
future = txaio.as_future(handler, *args, **kwargs)
|
||||
res.append(future)
|
||||
if self._parent is not None:
|
||||
res.append(self._parent.fire(event, *args, **kwargs))
|
||||
return txaio.gather(res, consume_exceptions=False)
|
||||
|
||||
|
||||
class _LazyHexFormatter(object):
|
||||
"""
|
||||
This is used to avoid calling binascii.hexlify() on data given to
|
||||
log.debug() calls unless debug is active (for example). Like::
|
||||
|
||||
self.log.debug(
|
||||
"Some data: {octets}",
|
||||
octets=_LazyHexFormatter(os.urandom(32)),
|
||||
)
|
||||
"""
|
||||
__slots__ = ('obj',)
|
||||
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
|
||||
def __str__(self):
|
||||
return binascii.hexlify(self.obj).decode('ascii')
|
||||
|
||||
|
||||
def _is_tls_error(instance):
|
||||
"""
|
||||
:returns: True if we have TLS support and 'instance' is an
|
||||
instance of :class:`OpenSSL.SSL.Error` otherwise False
|
||||
"""
|
||||
if _TLS:
|
||||
return isinstance(instance, SSL.Error)
|
||||
return False
|
||||
|
||||
|
||||
def _maybe_tls_reason(instance):
|
||||
"""
|
||||
:returns: a TLS error-message, or empty-string if 'instance' is
|
||||
not a TLS error.
|
||||
"""
|
||||
if _is_tls_error(instance):
|
||||
ssl_error = instance.args[0][0]
|
||||
return u"SSL error: {msg} (in {func})".format(
|
||||
func=ssl_error[1],
|
||||
msg=ssl_error[2],
|
||||
)
|
||||
return u""
|
83
venv/lib/python3.7/site-packages/autobahn/wamp/__init__.py
Normal file
83
venv/lib/python3.7/site-packages/autobahn/wamp/__init__.py
Normal file
@ -0,0 +1,83 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
from autobahn.wamp.types import \
|
||||
ComponentConfig, \
|
||||
SessionDetails, \
|
||||
CloseDetails, \
|
||||
RegisterOptions, \
|
||||
CallOptions, \
|
||||
CallDetails, \
|
||||
CallResult, \
|
||||
SubscribeOptions, \
|
||||
PublishOptions, \
|
||||
EventDetails
|
||||
|
||||
from autobahn.wamp.exception import \
|
||||
Error, \
|
||||
SessionNotReady, \
|
||||
SerializationError, \
|
||||
ProtocolError, \
|
||||
TransportLost, \
|
||||
ApplicationError, \
|
||||
InvalidUri
|
||||
|
||||
from autobahn.wamp.interfaces import ISession
|
||||
|
||||
from autobahn.wamp.uri import \
|
||||
error, \
|
||||
register, \
|
||||
subscribe
|
||||
|
||||
|
||||
__all__ = (
|
||||
'ComponentConfig',
|
||||
'SessionDetails',
|
||||
'CloseDetails',
|
||||
'RegisterOptions',
|
||||
'CallOptions',
|
||||
'CallDetails',
|
||||
'CallResult',
|
||||
'SubscribeOptions',
|
||||
'PublishOptions',
|
||||
'EventDetails',
|
||||
|
||||
'Error',
|
||||
'SessionNotReady',
|
||||
'SerializationError',
|
||||
'ProtocolError',
|
||||
'TransportLost',
|
||||
'ApplicationError',
|
||||
'InvalidUri',
|
||||
|
||||
'ISession',
|
||||
|
||||
'error',
|
||||
'register',
|
||||
'subscribe',
|
||||
)
|
314
venv/lib/python3.7/site-packages/autobahn/wamp/exception.py
Normal file
314
venv/lib/python3.7/site-packages/autobahn/wamp/exception.py
Normal file
@ -0,0 +1,314 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
import six
|
||||
|
||||
from autobahn.util import public
|
||||
from autobahn.wamp.uri import error
|
||||
|
||||
__all__ = (
|
||||
'Error',
|
||||
'SessionNotReady',
|
||||
'SerializationError',
|
||||
'ProtocolError',
|
||||
'TransportLost',
|
||||
'ApplicationError',
|
||||
'NotAuthorized',
|
||||
'InvalidUri',
|
||||
)
|
||||
|
||||
|
||||
@public
|
||||
class Error(RuntimeError):
|
||||
"""
|
||||
Base class for all exceptions related to WAMP.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class SessionNotReady(Error):
|
||||
"""
|
||||
The application tried to perform a WAMP interaction, but the
|
||||
session is not yet fully established.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class SerializationError(Error):
|
||||
"""
|
||||
Exception raised when the WAMP serializer could not serialize the
|
||||
application payload (``args`` or ``kwargs`` for ``CALL``, ``PUBLISH``, etc).
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class InvalidUriError(Error):
|
||||
"""
|
||||
Exception raised when an invalid WAMP URI was used.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class ProtocolError(Error):
|
||||
"""
|
||||
Exception raised when WAMP protocol was violated. Protocol errors
|
||||
are fatal and are handled by the WAMP implementation. They are
|
||||
not supposed to be handled at the application level.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class TransportLost(Error):
|
||||
"""
|
||||
Exception raised when the transport underlying the WAMP session
|
||||
was lost or is not connected.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class ApplicationError(Error):
|
||||
"""
|
||||
Base class for all exceptions that can/may be handled
|
||||
at the application level.
|
||||
"""
|
||||
|
||||
INVALID_URI = u"wamp.error.invalid_uri"
|
||||
"""
|
||||
Peer provided an incorrect URI for a URI-based attribute of a WAMP message
|
||||
such as a realm, topic or procedure.
|
||||
"""
|
||||
|
||||
INVALID_PAYLOAD = u"wamp.error.invalid_payload"
|
||||
"""
|
||||
The application payload could not be serialized.
|
||||
"""
|
||||
|
||||
PAYLOAD_SIZE_EXCEEDED = u"wamp.error.payload_size_exceeded"
|
||||
"""
|
||||
The application payload could not be transported becuase the serialized/framed payload
|
||||
exceeds the transport limits.
|
||||
"""
|
||||
|
||||
NO_SUCH_PROCEDURE = u"wamp.error.no_such_procedure"
|
||||
"""
|
||||
A Dealer could not perform a call, since not procedure is currently registered
|
||||
under the given URI.
|
||||
"""
|
||||
|
||||
PROCEDURE_ALREADY_EXISTS = u"wamp.error.procedure_already_exists"
|
||||
"""
|
||||
A procedure could not be registered, since a procedure with the given URI is
|
||||
already registered.
|
||||
"""
|
||||
|
||||
PROCEDURE_EXISTS_INVOCATION_POLICY_CONFLICT = u"wamp.error.procedure_exists_with_different_invocation_policy"
|
||||
"""
|
||||
A procedure could not be registered, since a procedure with the given URI is
|
||||
already registered, and the registration has a conflicting invocation policy.
|
||||
"""
|
||||
|
||||
NO_SUCH_REGISTRATION = u"wamp.error.no_such_registration"
|
||||
"""
|
||||
A Dealer could not perform a unregister, since the given registration is not active.
|
||||
"""
|
||||
|
||||
NO_SUCH_SUBSCRIPTION = u"wamp.error.no_such_subscription"
|
||||
"""
|
||||
A Broker could not perform a unsubscribe, since the given subscription is not active.
|
||||
"""
|
||||
|
||||
NO_SUCH_SESSION = u"wamp.error.no_such_session"
|
||||
"""
|
||||
A router could not perform an operation, since a session ID specified was non-existant.
|
||||
"""
|
||||
|
||||
INVALID_ARGUMENT = u"wamp.error.invalid_argument"
|
||||
"""
|
||||
A call failed, since the given argument types or values are not acceptable to the
|
||||
called procedure - in which case the *Callee* may throw this error. Or a Router
|
||||
performing *payload validation* checked the payload (``args`` / ``kwargs``) of a call,
|
||||
call result, call error or publish, and the payload did not conform.
|
||||
"""
|
||||
|
||||
# FIXME: this currently isn't used neither in Autobahn nor Crossbar. Check!
|
||||
SYSTEM_SHUTDOWN = u"wamp.error.system_shutdown"
|
||||
"""
|
||||
The *Peer* is shutting down completely - used as a ``GOODBYE`` (or ``ABORT``) reason.
|
||||
"""
|
||||
|
||||
# FIXME: this currently isn't used neither in Autobahn nor Crossbar. Check!
|
||||
CLOSE_REALM = u"wamp.error.close_realm"
|
||||
"""
|
||||
The *Peer* want to leave the realm - used as a ``GOODBYE`` reason.
|
||||
"""
|
||||
|
||||
# FIXME: this currently isn't used neither in Autobahn nor Crossbar. Check!
|
||||
GOODBYE_AND_OUT = u"wamp.error.goodbye_and_out"
|
||||
"""
|
||||
A *Peer* acknowledges ending of a session - used as a ``GOOBYE`` reply reason.
|
||||
"""
|
||||
|
||||
NOT_AUTHORIZED = u"wamp.error.not_authorized"
|
||||
"""
|
||||
A call, register, publish or subscribe failed, since the session is not authorized
|
||||
to perform the operation.
|
||||
"""
|
||||
|
||||
AUTHORIZATION_FAILED = u"wamp.error.authorization_failed"
|
||||
"""
|
||||
A Dealer or Broker could not determine if the *Peer* is authorized to perform
|
||||
a join, call, register, publish or subscribe, since the authorization operation
|
||||
*itself* failed. E.g. a custom authorizer did run into an error.
|
||||
"""
|
||||
|
||||
AUTHENTICATION_FAILED = u"wamp.error.authentication_failed"
|
||||
"""
|
||||
Something failed with the authentication itself, that is, authentication could
|
||||
not run to end.
|
||||
"""
|
||||
|
||||
NO_AUTH_METHOD = u"wamp.error.no_auth_method"
|
||||
"""
|
||||
No authentication method the peer offered is available or active.
|
||||
"""
|
||||
|
||||
NO_SUCH_REALM = u"wamp.error.no_such_realm"
|
||||
"""
|
||||
Peer wanted to join a non-existing realm (and the *Router* did not allow to auto-create
|
||||
the realm).
|
||||
"""
|
||||
|
||||
NO_SUCH_ROLE = u"wamp.error.no_such_role"
|
||||
"""
|
||||
A *Peer* was to be authenticated under a Role that does not (or no longer) exists on the Router.
|
||||
For example, the *Peer* was successfully authenticated, but the Role configured does not
|
||||
exists - hence there is some misconfiguration in the Router.
|
||||
"""
|
||||
|
||||
NO_SUCH_PRINCIPAL = u"wamp.error.no_such_principal"
|
||||
"""
|
||||
A *Peer* was authenticated for an authid that does not or longer exists.
|
||||
"""
|
||||
|
||||
CANCELED = u"wamp.error.canceled"
|
||||
"""
|
||||
A Dealer or Callee canceled a call previously issued (WAMP AP).
|
||||
"""
|
||||
|
||||
TIMEOUT = u"wamp.error.timeout"
|
||||
"""
|
||||
A pending (in-flight) call was timed out.
|
||||
"""
|
||||
|
||||
# FIXME: this currently isn't used neither in Autobahn nor Crossbar. Check!
|
||||
NO_ELIGIBLE_CALLEE = u"wamp.error.no_eligible_callee"
|
||||
"""
|
||||
A *Dealer* could not perform a call, since a procedure with the given URI is registered,
|
||||
but *Callee Black- and Whitelisting* and/or *Caller Exclusion* lead to the
|
||||
exclusion of (any) *Callee* providing the procedure (WAMP AP).
|
||||
"""
|
||||
|
||||
ENC_NO_PAYLOAD_CODEC = u"wamp.error.no_payload_codec"
|
||||
"""
|
||||
WAMP message in payload transparency mode received, but no codec set
|
||||
or codec did not decode the payload.
|
||||
"""
|
||||
|
||||
ENC_TRUSTED_URI_MISMATCH = u"wamp.error.encryption.trusted_uri_mismatch"
|
||||
"""
|
||||
WAMP-cryptobox application payload end-to-end encryption error.
|
||||
"""
|
||||
|
||||
ENC_DECRYPT_ERROR = u"wamp.error.encryption.decrypt_error"
|
||||
"""
|
||||
WAMP-cryptobox application payload end-to-end encryption error.
|
||||
"""
|
||||
|
||||
def __init__(self, error, *args, **kwargs):
|
||||
"""
|
||||
|
||||
:param error: The URI of the error that occurred, e.g. ``wamp.error.not_authorized``.
|
||||
:type error: str
|
||||
"""
|
||||
Exception.__init__(self, *args)
|
||||
self.kwargs = kwargs
|
||||
self.error = error
|
||||
self.enc_algo = kwargs.pop('enc_algo', None)
|
||||
self.callee = kwargs.pop('callee', None)
|
||||
self.callee_authid = kwargs.pop('callee_authid', None)
|
||||
self.callee_authrole = kwargs.pop('callee_authrole', None)
|
||||
self.forward_for = kwargs.pop('forward_for', None)
|
||||
|
||||
@public
|
||||
def error_message(self):
|
||||
"""
|
||||
Get the error message of this exception.
|
||||
|
||||
:returns: The error message.
|
||||
:rtype: str
|
||||
"""
|
||||
return u'{0}: {1}'.format(
|
||||
self.error,
|
||||
u' '.join([six.text_type(a) for a in self.args]),
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
if self.kwargs and 'traceback' in self.kwargs:
|
||||
tb = u':\n' + u'\n'.join(self.kwargs.pop('traceback')) + u'\n'
|
||||
self.kwargs['traceback'] = u'...'
|
||||
else:
|
||||
tb = u''
|
||||
return u"ApplicationError(error=<{0}>, args={1}, kwargs={2}, enc_algo={3}, callee={4}, callee_authid={5}, callee_authrole={6}, forward_for={7}){8}".format(
|
||||
self.error, list(self.args), self.kwargs, self.enc_algo, self.callee, self.callee_authid, self.callee_authrole, self.forward_for, tb)
|
||||
|
||||
def __str__(self):
|
||||
if six.PY3:
|
||||
return self.__unicode__()
|
||||
else:
|
||||
return self.__unicode__().encode('utf8')
|
||||
|
||||
|
||||
@error(ApplicationError.NOT_AUTHORIZED)
|
||||
class NotAuthorized(Exception):
|
||||
"""
|
||||
Not authorized to perform the respective action.
|
||||
"""
|
||||
|
||||
|
||||
@error(ApplicationError.INVALID_URI)
|
||||
class InvalidUri(Exception):
|
||||
"""
|
||||
The URI for a topic, procedure or error is not a valid WAMP URI.
|
||||
"""
|
||||
|
||||
|
||||
@error(ApplicationError.INVALID_PAYLOAD)
|
||||
class InvalidPayload(Exception):
|
||||
"""
|
||||
The URI for a topic, procedure or error is not a valid WAMP URI.
|
||||
"""
|
Binary file not shown.
@ -0,0 +1,38 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: wamp
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class Map(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsMap(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Map()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# Map
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Map
|
||||
def Key(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Map
|
||||
def Value(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
def MapStart(builder): builder.StartObject(2)
|
||||
def MapAddKey(builder, key): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(key), 0)
|
||||
def MapAddValue(builder, value): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(value), 0)
|
||||
def MapEnd(builder): return builder.EndObject()
|
@ -0,0 +1,22 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: wamp
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class Void(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsVoid(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Void()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# Void
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
def VoidStart(builder): builder.StartObject(0)
|
||||
def VoidEnd(builder): return builder.EndObject()
|
@ -0,0 +1,11 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class AuthFactor(object):
|
||||
NONE = 0
|
||||
AuthTicketRequest = 1
|
||||
AuthCraRequest = 2
|
||||
AuthScramRequest = 3
|
||||
AuthCryptosignRequest = 4
|
||||
|
@ -0,0 +1,8 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class AuthMode(object):
|
||||
FIRST = 0
|
||||
MULTIFACTOR = 1
|
||||
|
@ -0,0 +1,22 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class AuthTicketChallenge(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthTicketChallenge(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthTicketChallenge()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# AuthTicketChallenge
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
def AuthTicketChallengeStart(builder): builder.StartObject(0)
|
||||
def AuthTicketChallengeEnd(builder): return builder.EndObject()
|
@ -0,0 +1,22 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class AuthTicketRequest(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthTicketRequest(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthTicketRequest()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# AuthTicketRequest
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
def AuthTicketRequestStart(builder): builder.StartObject(0)
|
||||
def AuthTicketRequestEnd(builder): return builder.EndObject()
|
@ -0,0 +1,102 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class CalleeFeatures(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsCalleeFeatures(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = CalleeFeatures()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# CalleeFeatures
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# CalleeFeatures
|
||||
def CallerIdentification(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def CallTrustlevels(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def CallTimeout(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def CallCanceling(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def ProgressiveCallResults(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def RegistrationRevocation(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def PatternBasedRegistration(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def SharedRegistration(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def PayloadTransparency(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def PayloadEncryptionCryptobox(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
def CalleeFeaturesStart(builder): builder.StartObject(10)
|
||||
def CalleeFeaturesAddCallerIdentification(builder, callerIdentification): builder.PrependBoolSlot(0, callerIdentification, 0)
|
||||
def CalleeFeaturesAddCallTrustlevels(builder, callTrustlevels): builder.PrependBoolSlot(1, callTrustlevels, 0)
|
||||
def CalleeFeaturesAddCallTimeout(builder, callTimeout): builder.PrependBoolSlot(2, callTimeout, 0)
|
||||
def CalleeFeaturesAddCallCanceling(builder, callCanceling): builder.PrependBoolSlot(3, callCanceling, 0)
|
||||
def CalleeFeaturesAddProgressiveCallResults(builder, progressiveCallResults): builder.PrependBoolSlot(4, progressiveCallResults, 0)
|
||||
def CalleeFeaturesAddRegistrationRevocation(builder, registrationRevocation): builder.PrependBoolSlot(5, registrationRevocation, 0)
|
||||
def CalleeFeaturesAddPatternBasedRegistration(builder, patternBasedRegistration): builder.PrependBoolSlot(6, patternBasedRegistration, 0)
|
||||
def CalleeFeaturesAddSharedRegistration(builder, sharedRegistration): builder.PrependBoolSlot(7, sharedRegistration, 0)
|
||||
def CalleeFeaturesAddPayloadTransparency(builder, payloadTransparency): builder.PrependBoolSlot(8, payloadTransparency, 0)
|
||||
def CalleeFeaturesAddPayloadEncryptionCryptobox(builder, payloadEncryptionCryptobox): builder.PrependBoolSlot(9, payloadEncryptionCryptobox, 0)
|
||||
def CalleeFeaturesEnd(builder): return builder.EndObject()
|
@ -0,0 +1,70 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class CallerFeatures(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsCallerFeatures(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = CallerFeatures()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# CallerFeatures
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# CallerFeatures
|
||||
def CallerIdentification(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CallerFeatures
|
||||
def CallTimeout(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CallerFeatures
|
||||
def CallCanceling(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CallerFeatures
|
||||
def ProgressiveCallResults(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CallerFeatures
|
||||
def PayloadTransparency(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CallerFeatures
|
||||
def PayloadEncryptionCryptobox(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
def CallerFeaturesStart(builder): builder.StartObject(6)
|
||||
def CallerFeaturesAddCallerIdentification(builder, callerIdentification): builder.PrependBoolSlot(0, callerIdentification, 0)
|
||||
def CallerFeaturesAddCallTimeout(builder, callTimeout): builder.PrependBoolSlot(1, callTimeout, 0)
|
||||
def CallerFeaturesAddCallCanceling(builder, callCanceling): builder.PrependBoolSlot(2, callCanceling, 0)
|
||||
def CallerFeaturesAddProgressiveCallResults(builder, progressiveCallResults): builder.PrependBoolSlot(3, progressiveCallResults, 0)
|
||||
def CallerFeaturesAddPayloadTransparency(builder, payloadTransparency): builder.PrependBoolSlot(4, payloadTransparency, 0)
|
||||
def CallerFeaturesAddPayloadEncryptionCryptobox(builder, payloadEncryptionCryptobox): builder.PrependBoolSlot(5, payloadEncryptionCryptobox, 0)
|
||||
def CallerFeaturesEnd(builder): return builder.EndObject()
|
@ -0,0 +1,9 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class CancelMode(object):
|
||||
SKIP = 0
|
||||
ABORT = 1
|
||||
KILL = 2
|
||||
|
@ -0,0 +1,42 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class Challenge(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsChallenge(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Challenge()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# Challenge
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Challenge
|
||||
def Method(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Challenge
|
||||
def Extra(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
x = self._tab.Indirect(o + self._tab.Pos)
|
||||
from .Map import Map
|
||||
obj = Map()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
def ChallengeStart(builder): builder.StartObject(2)
|
||||
def ChallengeAddMethod(builder, method): builder.PrependUint8Slot(0, method, 0)
|
||||
def ChallengeAddExtra(builder, extra): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(extra), 0)
|
||||
def ChallengeEnd(builder): return builder.EndObject()
|
@ -0,0 +1,8 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class ChannelBinding(object):
|
||||
NONE = 0
|
||||
TLS_UNIQUE = 1
|
||||
|
@ -0,0 +1,126 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class DealerFeatures(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsDealerFeatures(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = DealerFeatures()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# DealerFeatures
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# DealerFeatures
|
||||
def CallerIdentification(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def CallTrustlevels(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def CallTimeout(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def CallCanceling(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def ProgressiveCallResults(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def RegistrationRevocation(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def PatternBasedRegistration(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def SharedRegistration(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def SessionMetaApi(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def RegistrationMetaApi(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def TestamentMetaApi(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def PayloadTransparency(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def PayloadEncryptionCryptobox(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
def DealerFeaturesStart(builder): builder.StartObject(13)
|
||||
def DealerFeaturesAddCallerIdentification(builder, callerIdentification): builder.PrependBoolSlot(0, callerIdentification, 0)
|
||||
def DealerFeaturesAddCallTrustlevels(builder, callTrustlevels): builder.PrependBoolSlot(1, callTrustlevels, 0)
|
||||
def DealerFeaturesAddCallTimeout(builder, callTimeout): builder.PrependBoolSlot(2, callTimeout, 0)
|
||||
def DealerFeaturesAddCallCanceling(builder, callCanceling): builder.PrependBoolSlot(3, callCanceling, 0)
|
||||
def DealerFeaturesAddProgressiveCallResults(builder, progressiveCallResults): builder.PrependBoolSlot(4, progressiveCallResults, 0)
|
||||
def DealerFeaturesAddRegistrationRevocation(builder, registrationRevocation): builder.PrependBoolSlot(5, registrationRevocation, 0)
|
||||
def DealerFeaturesAddPatternBasedRegistration(builder, patternBasedRegistration): builder.PrependBoolSlot(6, patternBasedRegistration, 0)
|
||||
def DealerFeaturesAddSharedRegistration(builder, sharedRegistration): builder.PrependBoolSlot(7, sharedRegistration, 0)
|
||||
def DealerFeaturesAddSessionMetaApi(builder, sessionMetaApi): builder.PrependBoolSlot(8, sessionMetaApi, 0)
|
||||
def DealerFeaturesAddRegistrationMetaApi(builder, registrationMetaApi): builder.PrependBoolSlot(9, registrationMetaApi, 0)
|
||||
def DealerFeaturesAddTestamentMetaApi(builder, testamentMetaApi): builder.PrependBoolSlot(10, testamentMetaApi, 0)
|
||||
def DealerFeaturesAddPayloadTransparency(builder, payloadTransparency): builder.PrependBoolSlot(11, payloadTransparency, 0)
|
||||
def DealerFeaturesAddPayloadEncryptionCryptobox(builder, payloadEncryptionCryptobox): builder.PrependBoolSlot(12, payloadEncryptionCryptobox, 0)
|
||||
def DealerFeaturesEnd(builder): return builder.EndObject()
|
@ -0,0 +1,110 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class Error(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsError(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Error()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# Error
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Error
|
||||
def RequestType(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def Request(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def Error(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Error
|
||||
def Payload(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
a = self._tab.Vector(o)
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def PayloadAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def PayloadLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def EncAlgo(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def EncSerializer(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def EncKey(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
if o != 0:
|
||||
a = self._tab.Vector(o)
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def EncKeyAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def EncKeyLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
def ErrorStart(builder): builder.StartObject(7)
|
||||
def ErrorAddRequestType(builder, requestType): builder.PrependUint16Slot(0, requestType, 0)
|
||||
def ErrorAddRequest(builder, request): builder.PrependUint64Slot(1, request, 0)
|
||||
def ErrorAddError(builder, error): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(error), 0)
|
||||
def ErrorAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0)
|
||||
def ErrorStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def ErrorAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(4, encAlgo, 0)
|
||||
def ErrorAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(5, encSerializer, 0)
|
||||
def ErrorAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(6, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0)
|
||||
def ErrorStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def ErrorEnd(builder): return builder.EndObject()
|
@ -0,0 +1,223 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class Event(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsEvent(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Event()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# Event
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Event
|
||||
def Subscription(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def Publication(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# /// Positional values for application-defined event payload.
|
||||
# Event
|
||||
def Args(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
a = self._tab.Vector(o)
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def ArgsAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def ArgsLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# /// Keyword values for application-defined event payload.
|
||||
# Event
|
||||
def Kwargs(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
a = self._tab.Vector(o)
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def KwargsAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def KwargsLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset.
|
||||
# Event
|
||||
def Payload(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
a = self._tab.Vector(o)
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def PayloadAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def PayloadLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def EncAlgo(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def EncSerializer(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def EncKey(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
if o != 0:
|
||||
a = self._tab.Vector(o)
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def EncKeyAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def EncKeyLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def Publisher(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def PublisherAuthid(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Event
|
||||
def PublisherAuthrole(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Event
|
||||
def Topic(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Event
|
||||
def Retained(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# Event
|
||||
def Acknowledge(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# Event
|
||||
def ForwardFor(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32))
|
||||
if o != 0:
|
||||
x = self._tab.Vector(o)
|
||||
x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4
|
||||
x = self._tab.Indirect(x)
|
||||
from .Principal import Principal
|
||||
obj = Principal()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
# Event
|
||||
def ForwardForLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
def EventStart(builder): builder.StartObject(15)
|
||||
def EventAddSubscription(builder, subscription): builder.PrependUint64Slot(0, subscription, 0)
|
||||
def EventAddPublication(builder, publication): builder.PrependUint64Slot(1, publication, 0)
|
||||
def EventAddArgs(builder, args): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(args), 0)
|
||||
def EventStartArgsVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def EventAddKwargs(builder, kwargs): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(kwargs), 0)
|
||||
def EventStartKwargsVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def EventAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0)
|
||||
def EventStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def EventAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(5, encAlgo, 0)
|
||||
def EventAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(6, encSerializer, 0)
|
||||
def EventAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(7, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0)
|
||||
def EventStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def EventAddPublisher(builder, publisher): builder.PrependUint64Slot(8, publisher, 0)
|
||||
def EventAddPublisherAuthid(builder, publisherAuthid): builder.PrependUOffsetTRelativeSlot(9, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthid), 0)
|
||||
def EventAddPublisherAuthrole(builder, publisherAuthrole): builder.PrependUOffsetTRelativeSlot(10, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthrole), 0)
|
||||
def EventAddTopic(builder, topic): builder.PrependUOffsetTRelativeSlot(11, flatbuffers.number_types.UOffsetTFlags.py_type(topic), 0)
|
||||
def EventAddRetained(builder, retained): builder.PrependBoolSlot(12, retained, 0)
|
||||
def EventAddAcknowledge(builder, acknowledge): builder.PrependBoolSlot(13, acknowledge, 0)
|
||||
def EventAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot(14, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0)
|
||||
def EventStartForwardForVector(builder, numElems): return builder.StartVector(4, numElems, 4)
|
||||
def EventEnd(builder): return builder.EndObject()
|
@ -0,0 +1,94 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class EventReceived(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsEventReceived(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = EventReceived()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# EventReceived
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# EventReceived
|
||||
def Publication(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def Payload(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
a = self._tab.Vector(o)
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def PayloadAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def PayloadLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def EncAlgo(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def EncSerializer(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def EncKey(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
a = self._tab.Vector(o)
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def EncKeyAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def EncKeyLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
def EventReceivedStart(builder): builder.StartObject(5)
|
||||
def EventReceivedAddPublication(builder, publication): builder.PrependUint64Slot(0, publication, 0)
|
||||
def EventReceivedAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0)
|
||||
def EventReceivedStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def EventReceivedAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(2, encAlgo, 0)
|
||||
def EventReceivedAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(3, encSerializer, 0)
|
||||
def EventReceivedAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0)
|
||||
def EventReceivedStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def EventReceivedEnd(builder): return builder.EndObject()
|
@ -0,0 +1,118 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class Hello(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsHello(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Hello()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# Hello
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Hello
|
||||
def Roles(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
x = self._tab.Indirect(o + self._tab.Pos)
|
||||
from .ClientRoles import ClientRoles
|
||||
obj = ClientRoles()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
# Hello
|
||||
def Realm(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Hello
|
||||
def Authmethods(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
a = self._tab.Vector(o)
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
|
||||
return 0
|
||||
|
||||
# Hello
|
||||
def AuthmethodsAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# Hello
|
||||
def AuthmethodsLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Hello
|
||||
def Authid(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Hello
|
||||
def Authrole(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Hello
|
||||
def Authextra(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
x = self._tab.Indirect(o + self._tab.Pos)
|
||||
from .Map import Map
|
||||
obj = Map()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
# Hello
|
||||
def Resumable(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# Hello
|
||||
def ResumeSession(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Hello
|
||||
def ResumeToken(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
def HelloStart(builder): builder.StartObject(9)
|
||||
def HelloAddRoles(builder, roles): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(roles), 0)
|
||||
def HelloAddRealm(builder, realm): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(realm), 0)
|
||||
def HelloAddAuthmethods(builder, authmethods): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(authmethods), 0)
|
||||
def HelloStartAuthmethodsVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def HelloAddAuthid(builder, authid): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(authid), 0)
|
||||
def HelloAddAuthrole(builder, authrole): builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(authrole), 0)
|
||||
def HelloAddAuthextra(builder, authextra): builder.PrependUOffsetTRelativeSlot(5, flatbuffers.number_types.UOffsetTFlags.py_type(authextra), 0)
|
||||
def HelloAddResumable(builder, resumable): builder.PrependBoolSlot(6, resumable, 0)
|
||||
def HelloAddResumeSession(builder, resumeSession): builder.PrependUint64Slot(7, resumeSession, 0)
|
||||
def HelloAddResumeToken(builder, resumeToken): builder.PrependUOffsetTRelativeSlot(8, flatbuffers.number_types.UOffsetTFlags.py_type(resumeToken), 0)
|
||||
def HelloEnd(builder): return builder.EndObject()
|
@ -0,0 +1,38 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class Interrupt(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsInterrupt(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Interrupt()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# Interrupt
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Interrupt
|
||||
def Request(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Interrupt
|
||||
def Mode(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 1
|
||||
|
||||
def InterruptStart(builder): builder.StartObject(2)
|
||||
def InterruptAddRequest(builder, request): builder.PrependUint64Slot(0, request, 0)
|
||||
def InterruptAddMode(builder, mode): builder.PrependUint8Slot(1, mode, 1)
|
||||
def InterruptEnd(builder): return builder.EndObject()
|
@ -0,0 +1,9 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class Kdf(object):
|
||||
NONE = 0
|
||||
PBKDF2 = 1
|
||||
ARGON2 = 2
|
||||
|
@ -0,0 +1,9 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class Match(object):
|
||||
EXACT = 0
|
||||
PREFIX = 1
|
||||
WILDCARD = 2
|
||||
|
@ -0,0 +1,70 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class PublisherFeatures(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsPublisherFeatures(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = PublisherFeatures()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# PublisherFeatures
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# PublisherFeatures
|
||||
def PublisherIdentification(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# PublisherFeatures
|
||||
def PublisherExclusion(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# PublisherFeatures
|
||||
def SubscriberBlackwhiteListing(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# PublisherFeatures
|
||||
def AcknowledgeEventReceived(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# PublisherFeatures
|
||||
def PayloadTransparency(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# PublisherFeatures
|
||||
def PayloadEncryptionCryptobox(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
def PublisherFeaturesStart(builder): builder.StartObject(6)
|
||||
def PublisherFeaturesAddPublisherIdentification(builder, publisherIdentification): builder.PrependBoolSlot(0, publisherIdentification, 0)
|
||||
def PublisherFeaturesAddPublisherExclusion(builder, publisherExclusion): builder.PrependBoolSlot(1, publisherExclusion, 0)
|
||||
def PublisherFeaturesAddSubscriberBlackwhiteListing(builder, subscriberBlackwhiteListing): builder.PrependBoolSlot(2, subscriberBlackwhiteListing, 0)
|
||||
def PublisherFeaturesAddAcknowledgeEventReceived(builder, acknowledgeEventReceived): builder.PrependBoolSlot(3, acknowledgeEventReceived, 0)
|
||||
def PublisherFeaturesAddPayloadTransparency(builder, payloadTransparency): builder.PrependBoolSlot(4, payloadTransparency, 0)
|
||||
def PublisherFeaturesAddPayloadEncryptionCryptobox(builder, payloadEncryptionCryptobox): builder.PrependBoolSlot(5, payloadEncryptionCryptobox, 0)
|
||||
def PublisherFeaturesEnd(builder): return builder.EndObject()
|
@ -0,0 +1,13 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class Serializer(object):
|
||||
TRANSPORT = 0
|
||||
JSON = 1
|
||||
MSGPACK = 2
|
||||
CBOR = 3
|
||||
UBJSON = 4
|
||||
OPAQUE = 5
|
||||
FLATBUFFERS = 6
|
||||
|
@ -0,0 +1,94 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
|
||||
class Yield(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAsYield(cls, buf, offset):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Yield()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
# Yield
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Yield
|
||||
def Request(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Yield
|
||||
def Payload(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
a = self._tab.Vector(o)
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
|
||||
return 0
|
||||
|
||||
# Yield
|
||||
def PayloadAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# Yield
|
||||
def PayloadLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Yield
|
||||
def EncAlgo(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Yield
|
||||
def EncSerializer(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Yield
|
||||
def EncKey(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
a = self._tab.Vector(o)
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
|
||||
return 0
|
||||
|
||||
# Yield
|
||||
def EncKeyAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# Yield
|
||||
def EncKeyLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
def YieldStart(builder): builder.StartObject(5)
|
||||
def YieldAddRequest(builder, request): builder.PrependUint64Slot(0, request, 0)
|
||||
def YieldAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0)
|
||||
def YieldStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def YieldAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(2, encAlgo, 0)
|
||||
def YieldAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(3, encSerializer, 0)
|
||||
def YieldAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0)
|
||||
def YieldStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def YieldEnd(builder): return builder.EndObject()
|
5947
venv/lib/python3.7/site-packages/autobahn/wamp/message.py
Normal file
5947
venv/lib/python3.7/site-packages/autobahn/wamp/message.py
Normal file
File diff suppressed because it is too large
Load Diff
1958
venv/lib/python3.7/site-packages/autobahn/wamp/protocol.py
Normal file
1958
venv/lib/python3.7/site-packages/autobahn/wamp/protocol.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,39 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
from autobahn.wamp import cryptobox
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
@unittest.skipIf(not cryptobox.HAS_CRYPTOBOX, 'no cryptobox support present')
|
||||
class TestCryptoBox(unittest.TestCase):
|
||||
|
||||
def test_create_keyring(self):
|
||||
kr = cryptobox.KeyRing()
|
||||
assert kr
|
@ -0,0 +1,127 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
from mock import Mock
|
||||
|
||||
import txaio
|
||||
|
||||
if os.environ.get('USE_TWISTED', False):
|
||||
txaio.use_twisted()
|
||||
elif os.environ.get('USE_ASYNCIO', False):
|
||||
txaio.use_asyncio()
|
||||
else:
|
||||
raise Exception('no networking framework selected')
|
||||
|
||||
from autobahn.wamp.cryptosign import _makepad, HAS_CRYPTOSIGN
|
||||
from autobahn.wamp import types
|
||||
from autobahn.wamp.auth import create_authenticator
|
||||
|
||||
if HAS_CRYPTOSIGN:
|
||||
from autobahn.wamp.cryptosign import SigningKey
|
||||
from nacl.encoding import HexEncoder
|
||||
|
||||
import tempfile
|
||||
|
||||
import unittest
|
||||
|
||||
keybody = '''-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACAa38i/4dNWFuZN/72QAJbyOwZvkUyML/u2b2B1uW4RbQAAAJj4FLyB+BS8
|
||||
gQAAAAtzc2gtZWQyNTUxOQAAACAa38i/4dNWFuZN/72QAJbyOwZvkUyML/u2b2B1uW4RbQ
|
||||
AAAEBNV9l6aPVVaWYgpthJwM5YJWhRjXKet1PcfHMt4oBFEBrfyL/h01YW5k3/vZAAlvI7
|
||||
Bm+RTIwv+7ZvYHW5bhFtAAAAFXNvbWV1c2VyQGZ1bmt0aGF0LmNvbQ==
|
||||
-----END OPENSSH PRIVATE KEY-----'''
|
||||
|
||||
pubkey = '''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJVp3hjHwIQyEladzd8mFcf0YSXcmyKS3qMLB7VqTQKm someuser@example.com
|
||||
'''
|
||||
|
||||
|
||||
@unittest.skipIf(not HAS_CRYPTOSIGN, 'nacl library not present')
|
||||
class TestAuth(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.key = SigningKey.from_ssh_data(keybody)
|
||||
self.privkey_hex = self.key._key.encode(encoder=HexEncoder)
|
||||
m = hashlib.sha256()
|
||||
m.update("some TLS message".encode())
|
||||
self.channel_id = m.digest()
|
||||
|
||||
def test_valid(self):
|
||||
session = Mock()
|
||||
session._transport.get_channel_id = Mock(return_value=self.channel_id)
|
||||
challenge = types.Challenge(u"ticket", dict(challenge="ff" * 32))
|
||||
signed = yield self.key.sign_challenge(session, challenge)
|
||||
self.assertEqual(
|
||||
u'9b6f41540c9b95b4b7b281c3042fa9c54cef43c842d62ea3fd6030fcb66e70b3e80d49d44c29d1635da9348d02ec93f3ed1ef227dfb59a07b580095c2b82f80f9d16ca518aa0c2b707f2b2a609edeca73bca8dd59817a633f35574ac6fd80d00',
|
||||
signed.result,
|
||||
)
|
||||
|
||||
def test_authenticator(self):
|
||||
authenticator = create_authenticator(
|
||||
u"cryptosign",
|
||||
authid="someone",
|
||||
privkey=self.privkey_hex,
|
||||
)
|
||||
session = Mock()
|
||||
session._transport.get_channel_id = Mock(return_value=self.channel_id)
|
||||
challenge = types.Challenge(u"cryptosign", dict(challenge="ff" * 32))
|
||||
reply = yield authenticator.on_challenge(session, challenge)
|
||||
self.assertEqual(
|
||||
reply.result,
|
||||
u'9b6f41540c9b95b4b7b281c3042fa9c54cef43c842d62ea3fd6030fcb66e70b3e80d49d44c29d1635da9348d02ec93f3ed1ef227dfb59a07b580095c2b82f80f9d16ca518aa0c2b707f2b2a609edeca73bca8dd59817a633f35574ac6fd80d00',
|
||||
)
|
||||
|
||||
|
||||
class TestKey(unittest.TestCase):
|
||||
|
||||
def test_pad(self):
|
||||
self.assertEqual(_makepad(0), '')
|
||||
self.assertEqual(_makepad(2), '\x01\x02')
|
||||
self.assertEqual(_makepad(3), '\x01\x02\x03')
|
||||
|
||||
@unittest.skipIf(not HAS_CRYPTOSIGN, 'nacl library not present')
|
||||
def test_key(self):
|
||||
with tempfile.NamedTemporaryFile('w+t') as fp:
|
||||
fp.write(keybody)
|
||||
fp.seek(0)
|
||||
|
||||
key = SigningKey.from_ssh_key(fp.name)
|
||||
self.assertEqual(key.public_key(), '1adfc8bfe1d35616e64dffbd900096f23b066f914c8c2ffbb66f6075b96e116d')
|
||||
|
||||
@unittest.skipIf(not HAS_CRYPTOSIGN, 'nacl library not present')
|
||||
def test_pubkey(self):
|
||||
with tempfile.NamedTemporaryFile('w+t') as fp:
|
||||
fp.write(pubkey)
|
||||
fp.seek(0)
|
||||
|
||||
key = SigningKey.from_ssh_key(fp.name)
|
||||
self.assertEqual(key.public_key(), '9569de18c7c0843212569dcddf2615c7f46125dc9b2292dea30b07b56a4d02a6')
|
||||
self.assertEqual(key.comment(), 'someuser@example.com')
|
@ -0,0 +1,580 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
from autobahn import wamp
|
||||
from autobahn.wamp.uri import Pattern, RegisterOptions, SubscribeOptions
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class TestUris(unittest.TestCase):
|
||||
|
||||
def test_invalid_uris(self):
|
||||
for u in [u"",
|
||||
u"com.myapp.<product:foo>.update",
|
||||
u"com.myapp.<123:int>.update",
|
||||
u"com.myapp.<:product>.update",
|
||||
u"com.myapp.<product:>.update",
|
||||
u"com.myapp.<int:>.update",
|
||||
]:
|
||||
self.assertRaises(Exception, Pattern, u, Pattern.URI_TARGET_ENDPOINT)
|
||||
|
||||
def test_valid_uris(self):
|
||||
for u in [u"com.myapp.proc1",
|
||||
u"123",
|
||||
u"com.myapp.<product:int>.update",
|
||||
u"com.myapp.<category:string>.<subcategory>.list"
|
||||
u"com.myapp.something..update"
|
||||
]:
|
||||
p = Pattern(u, Pattern.URI_TARGET_ENDPOINT)
|
||||
self.assertIsInstance(p, Pattern)
|
||||
|
||||
def test_parse_uris(self):
|
||||
tests = [
|
||||
(u"com.myapp.<product:int>.update", [
|
||||
(u"com.myapp.0.update", {u'product': 0}),
|
||||
(u"com.myapp.123456.update", {u'product': 123456}),
|
||||
(u"com.myapp.aaa.update", None),
|
||||
(u"com.myapp..update", None),
|
||||
(u"com.myapp.0.delete", None),
|
||||
]
|
||||
),
|
||||
(u"com.myapp.<product:string>.update", [
|
||||
(u"com.myapp.box.update", {u'product': u'box'}),
|
||||
(u"com.myapp.123456.update", {u'product': u'123456'}),
|
||||
(u"com.myapp..update", None),
|
||||
]
|
||||
),
|
||||
(u"com.myapp.<product>.update", [
|
||||
(u"com.myapp.0.update", {u'product': u'0'}),
|
||||
(u"com.myapp.abc.update", {u'product': u'abc'}),
|
||||
(u"com.myapp..update", None),
|
||||
]
|
||||
),
|
||||
(u"com.myapp.<category:string>.<subcategory:string>.list", [
|
||||
(u"com.myapp.cosmetic.shampoo.list", {u'category': u'cosmetic', u'subcategory': u'shampoo'}),
|
||||
(u"com.myapp...list", None),
|
||||
(u"com.myapp.cosmetic..list", None),
|
||||
(u"com.myapp..shampoo.list", None),
|
||||
]
|
||||
)
|
||||
]
|
||||
for test in tests:
|
||||
pat = Pattern(test[0], Pattern.URI_TARGET_ENDPOINT)
|
||||
for ptest in test[1]:
|
||||
uri = ptest[0]
|
||||
kwargs_should = ptest[1]
|
||||
if kwargs_should is not None:
|
||||
args_is, kwargs_is = pat.match(uri)
|
||||
self.assertEqual(kwargs_is, kwargs_should)
|
||||
else:
|
||||
self.assertRaises(Exception, pat.match, uri)
|
||||
|
||||
|
||||
class TestDecorators(unittest.TestCase):
|
||||
|
||||
def test_decorate_endpoint(self):
|
||||
|
||||
@wamp.register(u"com.calculator.square")
|
||||
def square(_):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(square, '_wampuris'))
|
||||
self.assertTrue(type(square._wampuris) == list)
|
||||
self.assertEqual(len(square._wampuris), 1)
|
||||
self.assertIsInstance(square._wampuris[0], Pattern)
|
||||
self.assertTrue(square._wampuris[0].is_endpoint())
|
||||
self.assertFalse(square._wampuris[0].is_handler())
|
||||
self.assertFalse(square._wampuris[0].is_exception())
|
||||
self.assertEqual(square._wampuris[0].uri(), u"com.calculator.square")
|
||||
self.assertEqual(square._wampuris[0]._type, Pattern.URI_TYPE_EXACT)
|
||||
|
||||
@wamp.register(u"com.myapp.product.<product:int>.update")
|
||||
def update_product(product=None, label=None):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(update_product, '_wampuris'))
|
||||
self.assertTrue(type(update_product._wampuris) == list)
|
||||
self.assertEqual(len(update_product._wampuris), 1)
|
||||
self.assertIsInstance(update_product._wampuris[0], Pattern)
|
||||
self.assertTrue(update_product._wampuris[0].is_endpoint())
|
||||
self.assertFalse(update_product._wampuris[0].is_handler())
|
||||
self.assertFalse(update_product._wampuris[0].is_exception())
|
||||
self.assertEqual(update_product._wampuris[0].uri(), u"com.myapp.product.<product:int>.update")
|
||||
self.assertEqual(update_product._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
@wamp.register(u"com.myapp.<category:string>.<cid:int>.update")
|
||||
def update(category=None, cid=None):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(update, '_wampuris'))
|
||||
self.assertTrue(type(update._wampuris) == list)
|
||||
self.assertEqual(len(update._wampuris), 1)
|
||||
self.assertIsInstance(update._wampuris[0], Pattern)
|
||||
self.assertTrue(update._wampuris[0].is_endpoint())
|
||||
self.assertFalse(update._wampuris[0].is_handler())
|
||||
self.assertFalse(update._wampuris[0].is_exception())
|
||||
self.assertEqual(update._wampuris[0].uri(), u"com.myapp.<category:string>.<cid:int>.update")
|
||||
self.assertEqual(update._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
@wamp.register(u"com.myapp.circle.<name:string>",
|
||||
RegisterOptions(match=u"wildcard", details_arg="details"))
|
||||
def circle(name=None, details=None):
|
||||
""" Do nothing. """
|
||||
|
||||
self.assertTrue(hasattr(circle, '_wampuris'))
|
||||
self.assertTrue(type(circle._wampuris) == list)
|
||||
self.assertEqual(len(circle._wampuris), 1)
|
||||
self.assertIsInstance(circle._wampuris[0], Pattern)
|
||||
self.assertIsInstance(circle._wampuris[0].options, RegisterOptions)
|
||||
self.assertEqual(circle._wampuris[0].options.match, u"wildcard")
|
||||
self.assertEqual(circle._wampuris[0].options.details_arg, "details")
|
||||
self.assertTrue(circle._wampuris[0].is_endpoint())
|
||||
self.assertFalse(circle._wampuris[0].is_handler())
|
||||
self.assertFalse(circle._wampuris[0].is_exception())
|
||||
self.assertEqual(circle._wampuris[0].uri(), u"com.myapp.circle.<name:string>")
|
||||
self.assertEqual(circle._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
@wamp.register(u"com.myapp.something..update",
|
||||
RegisterOptions(match=u"wildcard", details_arg="details"))
|
||||
def something(dynamic=None, details=None):
|
||||
""" Do nothing. """
|
||||
self.assertTrue(hasattr(something, '_wampuris'))
|
||||
self.assertTrue(type(something._wampuris) == list)
|
||||
self.assertEqual(len(something._wampuris), 1)
|
||||
self.assertIsInstance(something._wampuris[0], Pattern)
|
||||
self.assertIsInstance(something._wampuris[0].options, RegisterOptions)
|
||||
self.assertEqual(something._wampuris[0].options.match, u"wildcard")
|
||||
self.assertEqual(something._wampuris[0].options.details_arg, "details")
|
||||
self.assertTrue(something._wampuris[0].is_endpoint())
|
||||
self.assertFalse(something._wampuris[0].is_handler())
|
||||
self.assertFalse(something._wampuris[0].is_exception())
|
||||
self.assertEqual(something._wampuris[0].uri(), u"com.myapp.something..update")
|
||||
self.assertEqual(something._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
def test_decorate_handler(self):
|
||||
|
||||
@wamp.subscribe(u"com.myapp.on_shutdown")
|
||||
def on_shutdown():
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(on_shutdown, '_wampuris'))
|
||||
self.assertTrue(type(on_shutdown._wampuris) == list)
|
||||
self.assertEqual(len(on_shutdown._wampuris), 1)
|
||||
self.assertIsInstance(on_shutdown._wampuris[0], Pattern)
|
||||
self.assertFalse(on_shutdown._wampuris[0].is_endpoint())
|
||||
self.assertTrue(on_shutdown._wampuris[0].is_handler())
|
||||
self.assertFalse(on_shutdown._wampuris[0].is_exception())
|
||||
self.assertEqual(on_shutdown._wampuris[0].uri(), u"com.myapp.on_shutdown")
|
||||
self.assertEqual(on_shutdown._wampuris[0]._type, Pattern.URI_TYPE_EXACT)
|
||||
|
||||
@wamp.subscribe(u"com.myapp.product.<product:int>.on_update")
|
||||
def on_product_update(product=None, label=None):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(on_product_update, '_wampuris'))
|
||||
self.assertTrue(type(on_product_update._wampuris) == list)
|
||||
self.assertEqual(len(on_product_update._wampuris), 1)
|
||||
self.assertIsInstance(on_product_update._wampuris[0], Pattern)
|
||||
self.assertFalse(on_product_update._wampuris[0].is_endpoint())
|
||||
self.assertTrue(on_product_update._wampuris[0].is_handler())
|
||||
self.assertFalse(on_product_update._wampuris[0].is_exception())
|
||||
self.assertEqual(on_product_update._wampuris[0].uri(), u"com.myapp.product.<product:int>.on_update")
|
||||
self.assertEqual(on_product_update._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
@wamp.subscribe(u"com.myapp.<category:string>.<cid:int>.on_update")
|
||||
def on_update(category=None, cid=None, label=None):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(on_update, '_wampuris'))
|
||||
self.assertTrue(type(on_update._wampuris) == list)
|
||||
self.assertEqual(len(on_update._wampuris), 1)
|
||||
self.assertIsInstance(on_update._wampuris[0], Pattern)
|
||||
self.assertFalse(on_update._wampuris[0].is_endpoint())
|
||||
self.assertTrue(on_update._wampuris[0].is_handler())
|
||||
self.assertFalse(on_update._wampuris[0].is_exception())
|
||||
self.assertEqual(on_update._wampuris[0].uri(), u"com.myapp.<category:string>.<cid:int>.on_update")
|
||||
self.assertEqual(on_update._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
@wamp.subscribe(u"com.myapp.on.<event:string>",
|
||||
SubscribeOptions(match=u"wildcard", details_arg="details"))
|
||||
def on_event(event=None, details=None):
|
||||
""" Do nothing. """
|
||||
|
||||
self.assertTrue(hasattr(on_event, '_wampuris'))
|
||||
self.assertTrue(type(on_event._wampuris) == list)
|
||||
self.assertEqual(len(on_event._wampuris), 1)
|
||||
self.assertIsInstance(on_event._wampuris[0], Pattern)
|
||||
self.assertIsInstance(on_event._wampuris[0].options, SubscribeOptions)
|
||||
self.assertEqual(on_event._wampuris[0].options.match, u"wildcard")
|
||||
self.assertEqual(on_event._wampuris[0].options.details_arg, "details")
|
||||
self.assertFalse(on_event._wampuris[0].is_endpoint())
|
||||
self.assertTrue(on_event._wampuris[0].is_handler())
|
||||
self.assertFalse(on_event._wampuris[0].is_exception())
|
||||
self.assertEqual(on_event._wampuris[0].uri(), u"com.myapp.on.<event:string>")
|
||||
self.assertEqual(on_event._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
def test_decorate_exception(self):
|
||||
|
||||
@wamp.error(u"com.myapp.error")
|
||||
class AppError(Exception):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(AppError, '_wampuris'))
|
||||
self.assertTrue(type(AppError._wampuris) == list)
|
||||
self.assertEqual(len(AppError._wampuris), 1)
|
||||
self.assertIsInstance(AppError._wampuris[0], Pattern)
|
||||
self.assertFalse(AppError._wampuris[0].is_endpoint())
|
||||
self.assertFalse(AppError._wampuris[0].is_handler())
|
||||
self.assertTrue(AppError._wampuris[0].is_exception())
|
||||
self.assertEqual(AppError._wampuris[0].uri(), u"com.myapp.error")
|
||||
self.assertEqual(AppError._wampuris[0]._type, Pattern.URI_TYPE_EXACT)
|
||||
|
||||
@wamp.error(u"com.myapp.product.<product:int>.product_inactive")
|
||||
class ProductInactiveError(Exception):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(ProductInactiveError, '_wampuris'))
|
||||
self.assertTrue(type(ProductInactiveError._wampuris) == list)
|
||||
self.assertEqual(len(ProductInactiveError._wampuris), 1)
|
||||
self.assertIsInstance(ProductInactiveError._wampuris[0], Pattern)
|
||||
self.assertFalse(ProductInactiveError._wampuris[0].is_endpoint())
|
||||
self.assertFalse(ProductInactiveError._wampuris[0].is_handler())
|
||||
self.assertTrue(ProductInactiveError._wampuris[0].is_exception())
|
||||
self.assertEqual(ProductInactiveError._wampuris[0].uri(), u"com.myapp.product.<product:int>.product_inactive")
|
||||
self.assertEqual(ProductInactiveError._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
@wamp.error(u"com.myapp.<category:string>.<product:int>.inactive")
|
||||
class ObjectInactiveError(Exception):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(ObjectInactiveError, '_wampuris'))
|
||||
self.assertTrue(type(ObjectInactiveError._wampuris) == list)
|
||||
self.assertEqual(len(ObjectInactiveError._wampuris), 1)
|
||||
self.assertIsInstance(ObjectInactiveError._wampuris[0], Pattern)
|
||||
self.assertFalse(ObjectInactiveError._wampuris[0].is_endpoint())
|
||||
self.assertFalse(ObjectInactiveError._wampuris[0].is_handler())
|
||||
self.assertTrue(ObjectInactiveError._wampuris[0].is_exception())
|
||||
self.assertEqual(ObjectInactiveError._wampuris[0].uri(), u"com.myapp.<category:string>.<product:int>.inactive")
|
||||
self.assertEqual(ObjectInactiveError._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
def test_match_decorated_endpoint(self):
|
||||
|
||||
@wamp.register(u"com.calculator.square")
|
||||
def square(x):
|
||||
return x
|
||||
|
||||
args, kwargs = square._wampuris[0].match(u"com.calculator.square")
|
||||
self.assertEqual(square(666, **kwargs), 666)
|
||||
|
||||
@wamp.register(u"com.myapp.product.<product:int>.update")
|
||||
def update_product(product=None, label=None):
|
||||
return product, label
|
||||
|
||||
args, kwargs = update_product._wampuris[0].match(u"com.myapp.product.123456.update")
|
||||
kwargs['label'] = "foobar"
|
||||
self.assertEqual(update_product(**kwargs), (123456, "foobar"))
|
||||
|
||||
@wamp.register(u"com.myapp.<category:string>.<cid:int>.update")
|
||||
def update(category=None, cid=None, label=None):
|
||||
return category, cid, label
|
||||
|
||||
args, kwargs = update._wampuris[0].match(u"com.myapp.product.123456.update")
|
||||
kwargs['label'] = "foobar"
|
||||
self.assertEqual(update(**kwargs), ("product", 123456, "foobar"))
|
||||
|
||||
def test_match_decorated_handler(self):
|
||||
|
||||
@wamp.subscribe(u"com.myapp.on_shutdown")
|
||||
def on_shutdown():
|
||||
pass
|
||||
|
||||
args, kwargs = on_shutdown._wampuris[0].match(u"com.myapp.on_shutdown")
|
||||
self.assertEqual(on_shutdown(**kwargs), None)
|
||||
|
||||
@wamp.subscribe(u"com.myapp.product.<product:int>.on_update")
|
||||
def on_product_update(product=None, label=None):
|
||||
return product, label
|
||||
|
||||
args, kwargs = on_product_update._wampuris[0].match(u"com.myapp.product.123456.on_update")
|
||||
kwargs['label'] = "foobar"
|
||||
self.assertEqual(on_product_update(**kwargs), (123456, "foobar"))
|
||||
|
||||
@wamp.subscribe(u"com.myapp.<category:string>.<cid:int>.on_update")
|
||||
def on_update(category=None, cid=None, label=None):
|
||||
return category, cid, label
|
||||
|
||||
args, kwargs = on_update._wampuris[0].match(u"com.myapp.product.123456.on_update")
|
||||
kwargs['label'] = "foobar"
|
||||
self.assertEqual(on_update(**kwargs), ("product", 123456, "foobar"))
|
||||
|
||||
def test_match_decorated_exception(self):
|
||||
|
||||
@wamp.error(u"com.myapp.error")
|
||||
class AppError(Exception):
|
||||
|
||||
def __init__(self, msg):
|
||||
Exception.__init__(self, msg)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__class__ == other.__class__ and \
|
||||
self.args == other.args
|
||||
|
||||
args, kwargs = AppError._wampuris[0].match(u"com.myapp.error")
|
||||
# noinspection PyArgumentList
|
||||
self.assertEqual(AppError(u"fuck", **kwargs), AppError(u"fuck"))
|
||||
|
||||
@wamp.error(u"com.myapp.product.<product:int>.product_inactive")
|
||||
class ProductInactiveError(Exception):
|
||||
|
||||
def __init__(self, msg, product=None):
|
||||
Exception.__init__(self, msg)
|
||||
self.product = product
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__class__ == other.__class__ and \
|
||||
self.args == other.args and \
|
||||
self.product == other.product
|
||||
|
||||
args, kwargs = ProductInactiveError._wampuris[0].match(u"com.myapp.product.123456.product_inactive")
|
||||
self.assertEqual(ProductInactiveError("fuck", **kwargs), ProductInactiveError("fuck", 123456))
|
||||
|
||||
@wamp.error(u"com.myapp.<category:string>.<product:int>.inactive")
|
||||
class ObjectInactiveError(Exception):
|
||||
|
||||
def __init__(self, msg, category=None, product=None):
|
||||
Exception.__init__(self, msg)
|
||||
self.category = category
|
||||
self.product = product
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__class__ == other.__class__ and \
|
||||
self.args == other.args and \
|
||||
self.category == other.category and \
|
||||
self.product == other.product
|
||||
|
||||
args, kwargs = ObjectInactiveError._wampuris[0].match(u"com.myapp.product.123456.inactive")
|
||||
self.assertEqual(ObjectInactiveError("fuck", **kwargs), ObjectInactiveError("fuck", "product", 123456))
|
||||
|
||||
|
||||
class KwException(Exception):
|
||||
def __init__(self, *args, **kwargs):
|
||||
Exception.__init__(self, *args)
|
||||
self.kwargs = kwargs
|
||||
|
||||
# what if the WAMP error message received
|
||||
# contains args/kwargs that cannot be
|
||||
# consumed by the constructor of the exception
|
||||
# class defined for the WAMP error URI?
|
||||
|
||||
# 1. we can bail out (but we are already signaling an error)
|
||||
# 2. we can require a generic constructor
|
||||
# 3. we can map only unconsumed args/kwargs to generic attributes
|
||||
# 4. we can silently drop unconsumed args/kwargs
|
||||
|
||||
|
||||
class MockSession(object):
|
||||
|
||||
def __init__(self):
|
||||
self._ecls_to_uri_pat = {}
|
||||
self._uri_to_ecls = {}
|
||||
|
||||
def define(self, exception, error=None):
|
||||
if error is None:
|
||||
assert(hasattr(exception, '_wampuris'))
|
||||
self._ecls_to_uri_pat[exception] = exception._wampuris
|
||||
self._uri_to_ecls[exception._wampuris[0].uri()] = exception
|
||||
else:
|
||||
assert(not hasattr(exception, '_wampuris'))
|
||||
self._ecls_to_uri_pat[exception] = [Pattern(error, Pattern.URI_TARGET_HANDLER)]
|
||||
self._uri_to_ecls[error] = exception
|
||||
|
||||
def map_error(self, error, args=None, kwargs=None):
|
||||
|
||||
# FIXME:
|
||||
# 1. map to ecls based on error URI wildcard/prefix
|
||||
# 2. extract additional args/kwargs from error URI
|
||||
|
||||
if error in self._uri_to_ecls:
|
||||
ecls = self._uri_to_ecls[error]
|
||||
try:
|
||||
# the following might fail, eg. TypeError when
|
||||
# signature of exception constructor is incompatible
|
||||
# with args/kwargs or when the exception constructor raises
|
||||
if kwargs:
|
||||
if args:
|
||||
exc = ecls(*args, **kwargs)
|
||||
else:
|
||||
exc = ecls(**kwargs)
|
||||
else:
|
||||
if args:
|
||||
exc = ecls(*args)
|
||||
else:
|
||||
exc = ecls()
|
||||
except Exception:
|
||||
# FIXME: log e
|
||||
exc = KwException(error, *args, **kwargs)
|
||||
else:
|
||||
# this never fails
|
||||
args = args or []
|
||||
kwargs = kwargs or {}
|
||||
exc = KwException(error, *args, **kwargs)
|
||||
return exc
|
||||
|
||||
|
||||
class TestDecoratorsAdvanced(unittest.TestCase):
|
||||
|
||||
def test_decorate_exception_non_exception(self):
|
||||
|
||||
def test():
|
||||
# noinspection PyUnusedLocal
|
||||
@wamp.error(u"com.test.error")
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
self.assertRaises(Exception, test)
|
||||
|
||||
def test_decorate_endpoint_multiple(self):
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@wamp.register(u"com.oldapp.oldproc")
|
||||
@wamp.register(u"com.calculator.square")
|
||||
def square(x):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(square, '_wampuris'))
|
||||
self.assertTrue(type(square._wampuris) == list)
|
||||
self.assertEqual(len(square._wampuris), 2)
|
||||
|
||||
for i in range(2):
|
||||
self.assertIsInstance(square._wampuris[i], Pattern)
|
||||
self.assertTrue(square._wampuris[i].is_endpoint())
|
||||
self.assertFalse(square._wampuris[i].is_handler())
|
||||
self.assertFalse(square._wampuris[i].is_exception())
|
||||
self.assertEqual(square._wampuris[i]._type, Pattern.URI_TYPE_EXACT)
|
||||
|
||||
self.assertEqual(square._wampuris[0].uri(), u"com.calculator.square")
|
||||
self.assertEqual(square._wampuris[1].uri(), u"com.oldapp.oldproc")
|
||||
|
||||
def test_marshal_decorated_exception(self):
|
||||
|
||||
@wamp.error(u"com.myapp.error")
|
||||
class AppError(Exception):
|
||||
pass
|
||||
|
||||
try:
|
||||
raise AppError("fuck")
|
||||
except Exception as e:
|
||||
self.assertEqual(e._wampuris[0].uri(), u"com.myapp.error")
|
||||
|
||||
@wamp.error(u"com.myapp.product.<product:int>.product_inactive")
|
||||
class ProductInactiveError(Exception):
|
||||
|
||||
def __init__(self, msg, product=None):
|
||||
Exception.__init__(self, msg)
|
||||
self.product = product
|
||||
|
||||
try:
|
||||
raise ProductInactiveError("fuck", 123456)
|
||||
except Exception as e:
|
||||
self.assertEqual(e._wampuris[0].uri(), u"com.myapp.product.<product:int>.product_inactive")
|
||||
|
||||
session = MockSession()
|
||||
session.define(AppError)
|
||||
|
||||
def test_define_exception_undecorated(self):
|
||||
|
||||
session = MockSession()
|
||||
|
||||
class AppError(Exception):
|
||||
pass
|
||||
|
||||
# defining an undecorated exception requires
|
||||
# an URI to be provided
|
||||
self.assertRaises(Exception, session.define, AppError)
|
||||
|
||||
session.define(AppError, u"com.myapp.error")
|
||||
|
||||
exc = session.map_error(u"com.myapp.error")
|
||||
self.assertIsInstance(exc, AppError)
|
||||
|
||||
def test_define_exception_decorated(self):
|
||||
|
||||
session = MockSession()
|
||||
|
||||
@wamp.error(u"com.myapp.error")
|
||||
class AppError(Exception):
|
||||
pass
|
||||
|
||||
# when defining a decorated exception
|
||||
# an URI must not be provided
|
||||
self.assertRaises(Exception, session.define, AppError, u"com.myapp.error")
|
||||
|
||||
session.define(AppError)
|
||||
|
||||
exc = session.map_error(u"com.myapp.error")
|
||||
self.assertIsInstance(exc, AppError)
|
||||
|
||||
def test_map_exception_undefined(self):
|
||||
|
||||
session = MockSession()
|
||||
|
||||
exc = session.map_error(u"com.myapp.error")
|
||||
self.assertIsInstance(exc, Exception)
|
||||
|
||||
def test_map_exception_args(self):
|
||||
|
||||
session = MockSession()
|
||||
|
||||
@wamp.error(u"com.myapp.error")
|
||||
class AppError(Exception):
|
||||
pass
|
||||
|
||||
@wamp.error(u"com.myapp.error.product_inactive")
|
||||
class ProductInactiveError(Exception):
|
||||
def __init__(self, product=None):
|
||||
self.product = product
|
||||
|
||||
# define exceptions in mock session
|
||||
session.define(AppError)
|
||||
session.define(ProductInactiveError)
|
||||
|
||||
for test in [
|
||||
# (u"com.myapp.foo.error", [], {}, KwException),
|
||||
(u"com.myapp.error", [], {}, AppError),
|
||||
(u"com.myapp.error", ["you are doing it wrong"], {}, AppError),
|
||||
(u"com.myapp.error", ["you are doing it wrong", 1, 2, 3], {}, AppError),
|
||||
|
||||
(u"com.myapp.error.product_inactive", [], {}, ProductInactiveError),
|
||||
(u"com.myapp.error.product_inactive", [], {"product": 123456}, ProductInactiveError),
|
||||
]:
|
||||
error, args, kwargs, ecls = test
|
||||
exc = session.map_error(error, args, kwargs)
|
||||
|
||||
self.assertIsInstance(exc, ecls)
|
||||
self.assertEqual(list(exc.args), args)
|
1371
venv/lib/python3.7/site-packages/autobahn/wamp/types.py
Normal file
1371
venv/lib/python3.7/site-packages/autobahn/wamp/types.py
Normal file
File diff suppressed because it is too large
Load Diff
296
venv/lib/python3.7/site-packages/autobahn/wamp/websocket.py
Normal file
296
venv/lib/python3.7/site-packages/autobahn/wamp/websocket.py
Normal file
@ -0,0 +1,296 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
import traceback
|
||||
|
||||
from autobahn.websocket import protocol
|
||||
from autobahn.websocket.types import ConnectionDeny
|
||||
from autobahn.wamp.interfaces import ITransport
|
||||
from autobahn.wamp.exception import ProtocolError, SerializationError, TransportLost
|
||||
|
||||
__all__ = ('WampWebSocketServerProtocol',
|
||||
'WampWebSocketClientProtocol',
|
||||
'WampWebSocketServerFactory',
|
||||
'WampWebSocketClientFactory')
|
||||
|
||||
|
||||
class WampWebSocketProtocol(object):
|
||||
"""
|
||||
Base class for WAMP-over-WebSocket transport mixins.
|
||||
"""
|
||||
|
||||
_session = None # default; self.session is set in onOpen
|
||||
|
||||
def _bailout(self, code, reason=None):
|
||||
self.log.debug('Failing WAMP-over-WebSocket transport: code={code}, reason="{reason}"', code=code, reason=reason)
|
||||
self._fail_connection(code, reason)
|
||||
|
||||
def onOpen(self):
|
||||
"""
|
||||
Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onOpen`
|
||||
"""
|
||||
# WebSocket connection established. Now let the user WAMP session factory
|
||||
# create a new WAMP session and fire off session open callback.
|
||||
try:
|
||||
self._session = self.factory._factory()
|
||||
self._session.onOpen(self)
|
||||
except Exception as e:
|
||||
self.log.critical("{tb}", tb=traceback.format_exc())
|
||||
reason = u'WAMP Internal Error ({0})'.format(e)
|
||||
self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_INTERNAL_ERROR, reason=reason)
|
||||
|
||||
def onClose(self, wasClean, code, reason):
|
||||
"""
|
||||
Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onClose`
|
||||
"""
|
||||
# WAMP session might never have been established in the first place .. guard this!
|
||||
self._onclose_reason = reason
|
||||
if self._session is not None:
|
||||
# WebSocket connection lost - fire off the WAMP
|
||||
# session close callback
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
self.log.debug('WAMP-over-WebSocket transport lost: wasClean={wasClean}, code={code}, reason="{reason}"', wasClean=wasClean, code=code, reason=reason)
|
||||
self._session.onClose(wasClean)
|
||||
except Exception:
|
||||
self.log.critical("{tb}", tb=traceback.format_exc())
|
||||
self._session = None
|
||||
|
||||
def onMessage(self, payload, isBinary):
|
||||
"""
|
||||
Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onMessage`
|
||||
"""
|
||||
try:
|
||||
for msg in self._serializer.unserialize(payload, isBinary):
|
||||
self.log.trace(
|
||||
"WAMP RECV: message={message}, session={session}, authid={authid}",
|
||||
authid=self._session._authid,
|
||||
session=self._session._session_id,
|
||||
message=msg,
|
||||
)
|
||||
self._session.onMessage(msg)
|
||||
|
||||
except ProtocolError as e:
|
||||
self.log.critical("{tb}", tb=traceback.format_exc())
|
||||
reason = u'WAMP Protocol Error ({0})'.format(e)
|
||||
self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_PROTOCOL_ERROR, reason=reason)
|
||||
|
||||
except Exception as e:
|
||||
self.log.critical("{tb}", tb=traceback.format_exc())
|
||||
reason = u'WAMP Internal Error ({0})'.format(e)
|
||||
self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_INTERNAL_ERROR, reason=reason)
|
||||
|
||||
def send(self, msg):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.send`
|
||||
"""
|
||||
if self.isOpen():
|
||||
try:
|
||||
self.log.trace(
|
||||
"WAMP SEND: message={message}, session={session}, authid={authid}",
|
||||
authid=self._session._authid,
|
||||
session=self._session._session_id,
|
||||
message=msg,
|
||||
)
|
||||
payload, isBinary = self._serializer.serialize(msg)
|
||||
except Exception as e:
|
||||
self.log.error("WAMP message serialization error: {}".format(e))
|
||||
# all exceptions raised from above should be serialization errors ..
|
||||
raise SerializationError(u"WAMP message serialization error: {0}".format(e))
|
||||
else:
|
||||
self.sendMessage(payload, isBinary)
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
def isOpen(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.isOpen`
|
||||
"""
|
||||
return self._session is not None
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.close`
|
||||
"""
|
||||
if self.isOpen():
|
||||
self.sendClose(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL)
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
def abort(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.abort`
|
||||
"""
|
||||
if self.isOpen():
|
||||
self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_GOING_AWAY)
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
|
||||
ITransport.register(WampWebSocketProtocol)
|
||||
|
||||
|
||||
def parseSubprotocolIdentifier(subprotocol):
|
||||
try:
|
||||
s = subprotocol.split(u'.')
|
||||
if s[0] != u'wamp':
|
||||
raise Exception(u'WAMP WebSocket subprotocol identifier must start with "wamp", not "{}"'.format(s[0]))
|
||||
version = int(s[1])
|
||||
serializerId = u'.'.join(s[2:])
|
||||
return version, serializerId
|
||||
except:
|
||||
return None, None
|
||||
|
||||
|
||||
class WampWebSocketServerProtocol(WampWebSocketProtocol):
|
||||
"""
|
||||
Mixin for WAMP-over-WebSocket server transports.
|
||||
"""
|
||||
|
||||
STRICT_PROTOCOL_NEGOTIATION = True
|
||||
|
||||
def onConnect(self, request):
|
||||
"""
|
||||
Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onConnect`
|
||||
"""
|
||||
headers = {}
|
||||
for subprotocol in request.protocols:
|
||||
version, serializerId = parseSubprotocolIdentifier(subprotocol)
|
||||
if version == 2 and serializerId in self.factory._serializers.keys():
|
||||
self._serializer = self.factory._serializers[serializerId]
|
||||
return subprotocol, headers
|
||||
|
||||
if self.STRICT_PROTOCOL_NEGOTIATION:
|
||||
raise ConnectionDeny(ConnectionDeny.BAD_REQUEST, u'This server only speaks WebSocket subprotocols {}'.format(u', '.join(self.factory.protocols)))
|
||||
else:
|
||||
# assume wamp.2.json
|
||||
self._serializer = self.factory._serializers[u'json']
|
||||
return None, headers
|
||||
|
||||
|
||||
class WampWebSocketClientProtocol(WampWebSocketProtocol):
|
||||
"""
|
||||
Mixin for WAMP-over-WebSocket client transports.
|
||||
"""
|
||||
|
||||
STRICT_PROTOCOL_NEGOTIATION = True
|
||||
|
||||
def onConnect(self, response):
|
||||
"""
|
||||
Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onConnect`
|
||||
"""
|
||||
if response.protocol not in self.factory.protocols:
|
||||
if self.STRICT_PROTOCOL_NEGOTIATION:
|
||||
raise Exception(u'The server does not speak any of the WebSocket subprotocols {} we requested.'.format(u', '.join(self.factory.protocols)))
|
||||
else:
|
||||
# assume wamp.2.json
|
||||
serializerId = u'json'
|
||||
else:
|
||||
version, serializerId = parseSubprotocolIdentifier(response.protocol)
|
||||
|
||||
self._serializer = self.factory._serializers[serializerId]
|
||||
|
||||
|
||||
class WampWebSocketFactory(object):
|
||||
"""
|
||||
Base class for WAMP-over-WebSocket transport factory mixins.
|
||||
"""
|
||||
|
||||
def __init__(self, factory, serializers=None):
|
||||
"""
|
||||
Ctor.
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializers: A list of WAMP serializers to use (or None for default
|
||||
serializers). Serializers must implement
|
||||
:class:`autobahn.wamp.interfaces.ISerializer`.
|
||||
:type serializers: list
|
||||
"""
|
||||
if callable(factory):
|
||||
self._factory = factory
|
||||
else:
|
||||
self._factory = lambda: factory
|
||||
|
||||
if serializers is None:
|
||||
serializers = []
|
||||
|
||||
# try CBOR WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import CBORSerializer
|
||||
serializers.append(CBORSerializer(batched=True))
|
||||
serializers.append(CBORSerializer())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# try MsgPack WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import MsgPackSerializer
|
||||
serializers.append(MsgPackSerializer(batched=True))
|
||||
serializers.append(MsgPackSerializer())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# try UBJSON WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import UBJSONSerializer
|
||||
serializers.append(UBJSONSerializer(batched=True))
|
||||
serializers.append(UBJSONSerializer())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# try JSON WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import JsonSerializer
|
||||
serializers.append(JsonSerializer(batched=True))
|
||||
serializers.append(JsonSerializer())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if not serializers:
|
||||
raise Exception(u'Could not import any WAMP serializer')
|
||||
|
||||
self._serializers = {}
|
||||
for ser in serializers:
|
||||
self._serializers[ser.SERIALIZER_ID] = ser
|
||||
|
||||
self._protocols = [u'wamp.2.{}'.format(ser.SERIALIZER_ID) for ser in serializers]
|
||||
|
||||
|
||||
class WampWebSocketServerFactory(WampWebSocketFactory):
|
||||
"""
|
||||
Mixin for WAMP-over-WebSocket server transport factories.
|
||||
"""
|
||||
|
||||
|
||||
class WampWebSocketClientFactory(WampWebSocketFactory):
|
||||
"""
|
||||
Mixin for WAMP-over-WebSocket client transport factories.
|
||||
"""
|
@ -0,0 +1,448 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Crossbar.io Technologies 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 __future__ import absolute_import
|
||||
|
||||
import bz2
|
||||
|
||||
from autobahn.websocket.compress_base import PerMessageCompressOffer, \
|
||||
PerMessageCompressOfferAccept, \
|
||||
PerMessageCompressResponse, \
|
||||
PerMessageCompressResponseAccept, \
|
||||
PerMessageCompress
|
||||
|
||||
__all__ = (
|
||||
'PerMessageBzip2Mixin',
|
||||
'PerMessageBzip2Offer',
|
||||
'PerMessageBzip2OfferAccept',
|
||||
'PerMessageBzip2Response',
|
||||
'PerMessageBzip2ResponseAccept',
|
||||
'PerMessageBzip2',
|
||||
)
|
||||
|
||||
|
||||
class PerMessageBzip2Mixin(object):
|
||||
"""
|
||||
Mixin class for this extension.
|
||||
"""
|
||||
|
||||
EXTENSION_NAME = "permessage-bzip2"
|
||||
"""
|
||||
Name of this WebSocket extension.
|
||||
"""
|
||||
|
||||
COMPRESS_LEVEL_PERMISSIBLE_VALUES = [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
"""
|
||||
Permissible value for compression level parameter.
|
||||
"""
|
||||
|
||||
|
||||
class PerMessageBzip2Offer(PerMessageCompressOffer, PerMessageBzip2Mixin):
|
||||
"""
|
||||
Set of extension parameters for `permessage-bzip2` WebSocket extension
|
||||
offered by a client to a server.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def parse(cls, params):
|
||||
"""
|
||||
Parses a WebSocket extension offer for `permessage-bzip2` provided by a client to a server.
|
||||
|
||||
:param params: Output from :func:`autobahn.websocket.WebSocketProtocol._parseExtensionsHeader`.
|
||||
:type params: list
|
||||
|
||||
:returns: object -- A new instance of :class:`autobahn.compress.PerMessageBzip2Offer`.
|
||||
"""
|
||||
# extension parameter defaults
|
||||
accept_max_compress_level = False
|
||||
request_max_compress_level = 0
|
||||
|
||||
# verify/parse client ("client-to-server direction") parameters of permessage-bzip2 offer
|
||||
for p in params:
|
||||
|
||||
if len(params[p]) > 1:
|
||||
raise Exception("multiple occurrence of extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
|
||||
|
||||
val = params[p][0]
|
||||
|
||||
if p == 'client_max_compress_level':
|
||||
# noinspection PySimplifyBooleanCheck
|
||||
if val is not True:
|
||||
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
|
||||
else:
|
||||
accept_max_compress_level = True
|
||||
|
||||
elif p == 'server_max_compress_level':
|
||||
try:
|
||||
val = int(val)
|
||||
except:
|
||||
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
|
||||
if val not in PerMessageBzip2Mixin.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
|
||||
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
|
||||
else:
|
||||
request_max_compress_level = val
|
||||
|
||||
else:
|
||||
raise Exception("illegal extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
|
||||
|
||||
offer = cls(accept_max_compress_level,
|
||||
request_max_compress_level)
|
||||
return offer
|
||||
|
||||
def __init__(self,
|
||||
accept_max_compress_level=True,
|
||||
request_max_compress_level=0):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param accept_max_compress_level: Iff true, client accepts "maximum compression level" parameter.
|
||||
:type accept_max_compress_level: bool
|
||||
:param request_max_compress_level: Iff non-zero, client requests given "maximum compression level" - must be 1-9.
|
||||
:type request_max_compress_level: int
|
||||
"""
|
||||
if type(accept_max_compress_level) != bool:
|
||||
raise Exception("invalid type %s for accept_max_compress_level" % type(accept_max_compress_level))
|
||||
|
||||
self.accept_max_compress_level = accept_max_compress_level
|
||||
|
||||
if request_max_compress_level != 0 and request_max_compress_level not in self.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
|
||||
raise Exception("invalid value %s for request_max_compress_level - permissible values %s" % (request_max_compress_level, self.COMPRESS_LEVEL_PERMISSIBLE_VALUES))
|
||||
|
||||
self.request_max_compress_level = request_max_compress_level
|
||||
|
||||
def get_extension_string(self):
|
||||
"""
|
||||
Returns the WebSocket extension configuration string as sent to the server.
|
||||
|
||||
:returns: PMCE configuration string.
|
||||
:rtype: str
|
||||
"""
|
||||
pmce_string = self.EXTENSION_NAME
|
||||
if self.accept_max_compress_level:
|
||||
pmce_string += "; client_max_compress_level"
|
||||
if self.request_max_compress_level != 0:
|
||||
pmce_string += "; server_max_compress_level=%d" % self.request_max_compress_level
|
||||
return pmce_string
|
||||
|
||||
def __json__(self):
|
||||
"""
|
||||
Returns a JSON serializable object representation.
|
||||
|
||||
:returns: JSON serializable representation.
|
||||
:rtype: dict
|
||||
"""
|
||||
return {'extension': self.EXTENSION_NAME,
|
||||
'accept_max_compress_level': self.accept_max_compress_level,
|
||||
'request_max_compress_level': self.request_max_compress_level}
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns Python object representation that can be eval'ed to reconstruct the object.
|
||||
|
||||
:returns: Python string representation.
|
||||
:rtype: str
|
||||
"""
|
||||
return "PerMessageBzip2Offer(accept_max_compress_level = %s, request_max_compress_level = %s)" % (self.accept_max_compress_level, self.request_max_compress_level)
|
||||
|
||||
|
||||
class PerMessageBzip2OfferAccept(PerMessageCompressOfferAccept, PerMessageBzip2Mixin):
|
||||
"""
|
||||
Set of parameters with which to accept an `permessage-bzip2` offer
|
||||
from a client by a server.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
offer,
|
||||
request_max_compress_level=0,
|
||||
compress_level=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param offer: The offer being accepted.
|
||||
:type offer: Instance of :class:`autobahn.compress.PerMessageBzip2Offer`.
|
||||
:param request_max_compress_level: Iff non-zero, server requests given "maximum compression level" - must be 1-9.
|
||||
:type request_max_compress_level: int
|
||||
:param compress_level: Override server ("server-to-client direction") compress level (this must be compatible with offer).
|
||||
:type compress_level: int
|
||||
"""
|
||||
if not isinstance(offer, PerMessageBzip2Offer):
|
||||
raise Exception("invalid type %s for offer" % type(offer))
|
||||
|
||||
self.offer = offer
|
||||
|
||||
if request_max_compress_level != 0 and request_max_compress_level not in self.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
|
||||
raise Exception("invalid value %s for request_max_compress_level - permissible values %s" % (request_max_compress_level, self.COMPRESS_LEVEL_PERMISSIBLE_VALUES))
|
||||
|
||||
if request_max_compress_level != 0 and not offer.accept_max_compress_level:
|
||||
raise Exception("invalid value %s for request_max_compress_level - feature unsupported by client" % request_max_compress_level)
|
||||
|
||||
self.request_max_compress_level = request_max_compress_level
|
||||
|
||||
if compress_level is not None:
|
||||
if compress_level not in self.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
|
||||
raise Exception("invalid value %s for compress_level - permissible values %s" % (compress_level, self.COMPRESS_LEVEL_PERMISSIBLE_VALUES))
|
||||
|
||||
if offer.request_max_compress_level != 0 and compress_level > offer.request_max_compress_level:
|
||||
raise Exception("invalid value %s for compress_level - client requested lower maximum value" % compress_level)
|
||||
|
||||
self.compress_level = compress_level
|
||||
|
||||
def get_extension_string(self):
|
||||
"""
|
||||
Returns the WebSocket extension configuration string as sent to the server.
|
||||
|
||||
:returns: PMCE configuration string.
|
||||
:rtype: str
|
||||
"""
|
||||
pmce_string = self.EXTENSION_NAME
|
||||
if self.offer.request_max_compress_level != 0:
|
||||
pmce_string += "; server_max_compress_level=%d" % self.offer.request_max_compress_level
|
||||
if self.request_max_compress_level != 0:
|
||||
pmce_string += "; client_max_compress_level=%d" % self.request_max_compress_level
|
||||
return pmce_string
|
||||
|
||||
def __json__(self):
|
||||
"""
|
||||
Returns a JSON serializable object representation.
|
||||
|
||||
:returns: JSON serializable representation.
|
||||
:rtype: dict
|
||||
"""
|
||||
return {'extension': self.EXTENSION_NAME,
|
||||
'offer': self.offer.__json__(),
|
||||
'request_max_compress_level': self.request_max_compress_level,
|
||||
'compress_level': self.compress_level}
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns Python object representation that can be eval'ed to reconstruct the object.
|
||||
|
||||
:returns: Python string representation.
|
||||
:rtype: str
|
||||
"""
|
||||
return "PerMessageBzip2Accept(offer = %s, request_max_compress_level = %s, compress_level = %s)" % (self.offer.__repr__(), self.request_max_compress_level, self.compress_level)
|
||||
|
||||
|
||||
class PerMessageBzip2Response(PerMessageCompressResponse, PerMessageBzip2Mixin):
|
||||
"""
|
||||
Set of parameters for `permessage-bzip2` responded by server.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def parse(cls, params):
|
||||
"""
|
||||
Parses a WebSocket extension response for `permessage-bzip2` provided by a server to a client.
|
||||
|
||||
:param params: Output from :func:`autobahn.websocket.WebSocketProtocol._parseExtensionsHeader`.
|
||||
:type params: list
|
||||
|
||||
:returns: A new instance of :class:`autobahn.compress.PerMessageBzip2Response`.
|
||||
:rtype: obj
|
||||
"""
|
||||
client_max_compress_level = 0
|
||||
server_max_compress_level = 0
|
||||
|
||||
for p in params:
|
||||
|
||||
if len(params[p]) > 1:
|
||||
raise Exception("multiple occurrence of extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
|
||||
|
||||
val = params[p][0]
|
||||
|
||||
if p == 'client_max_compress_level':
|
||||
try:
|
||||
val = int(val)
|
||||
except:
|
||||
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
|
||||
if val not in PerMessageBzip2Mixin.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
|
||||
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
|
||||
else:
|
||||
client_max_compress_level = val
|
||||
|
||||
elif p == 'server_max_compress_level':
|
||||
try:
|
||||
val = int(val)
|
||||
except:
|
||||
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
|
||||
if val not in PerMessageBzip2Mixin.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
|
||||
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
|
||||
else:
|
||||
server_max_compress_level = val
|
||||
|
||||
else:
|
||||
raise Exception("illegal extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
|
||||
|
||||
response = cls(client_max_compress_level,
|
||||
server_max_compress_level)
|
||||
return response
|
||||
|
||||
def __init__(self,
|
||||
client_max_compress_level,
|
||||
server_max_compress_level):
|
||||
self.client_max_compress_level = client_max_compress_level
|
||||
self.server_max_compress_level = server_max_compress_level
|
||||
|
||||
def __json__(self):
|
||||
"""
|
||||
Returns a JSON serializable object representation.
|
||||
|
||||
:returns: JSON serializable representation.
|
||||
:rtype: dict
|
||||
"""
|
||||
return {'extension': self.EXTENSION_NAME,
|
||||
'client_max_compress_level': self.client_max_compress_level,
|
||||
'server_max_compress_level': self.server_max_compress_level}
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns Python object representation that can be eval'ed to reconstruct the object.
|
||||
|
||||
:returns: Python string representation.
|
||||
:rtype: str
|
||||
"""
|
||||
return "PerMessageBzip2Response(client_max_compress_level = %s, server_max_compress_level = %s)" % (self.client_max_compress_level, self.server_max_compress_level)
|
||||
|
||||
|
||||
class PerMessageBzip2ResponseAccept(PerMessageCompressResponseAccept, PerMessageBzip2Mixin):
|
||||
"""
|
||||
Set of parameters with which to accept an `permessage-bzip2` response
|
||||
from a server by a client.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
response,
|
||||
compress_level=None):
|
||||
"""
|
||||
|
||||
:param response: The response being accepted.
|
||||
:type response: Instance of :class:`autobahn.compress.PerMessageBzip2Response`.
|
||||
:param compress_level: Override client ("client-to-server direction") compress level (this must be compatible with response).
|
||||
:type compress_level: int
|
||||
"""
|
||||
if not isinstance(response, PerMessageBzip2Response):
|
||||
raise Exception("invalid type %s for response" % type(response))
|
||||
|
||||
self.response = response
|
||||
|
||||
if compress_level is not None:
|
||||
if compress_level not in self.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
|
||||
raise Exception("invalid value %s for compress_level - permissible values %s" % (compress_level, self.COMPRESS_LEVEL_PERMISSIBLE_VALUES))
|
||||
|
||||
if response.client_max_compress_level != 0 and compress_level > response.client_max_compress_level:
|
||||
raise Exception("invalid value %s for compress_level - server requested lower maximum value" % compress_level)
|
||||
|
||||
self.compress_level = compress_level
|
||||
|
||||
def __json__(self):
|
||||
"""
|
||||
Returns a JSON serializable object representation.
|
||||
|
||||
:returns: JSON serializable representation.
|
||||
:rtype: dict
|
||||
"""
|
||||
return {'extension': self.EXTENSION_NAME,
|
||||
'response': self.response.__json__(),
|
||||
'compress_level': self.compress_level}
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns Python object representation that can be eval'ed to reconstruct the object.
|
||||
|
||||
:returns: Python string representation.
|
||||
:rtype: str
|
||||
"""
|
||||
return "PerMessageBzip2ResponseAccept(response = %s, compress_level = %s)" % (self.response.__repr__(), self.compress_level)
|
||||
|
||||
|
||||
class PerMessageBzip2(PerMessageCompress, PerMessageBzip2Mixin):
|
||||
"""
|
||||
`permessage-bzip2` WebSocket extension processor.
|
||||
"""
|
||||
DEFAULT_COMPRESS_LEVEL = 9
|
||||
|
||||
@classmethod
|
||||
def create_from_response_accept(cls, is_server, accept):
|
||||
pmce = cls(is_server,
|
||||
accept.response.server_max_compress_level,
|
||||
accept.compress_level if accept.compress_level is not None else accept.response.client_max_compress_level)
|
||||
return pmce
|
||||
|
||||
@classmethod
|
||||
def create_from_offer_accept(cls, is_server, accept):
|
||||
pmce = cls(is_server,
|
||||
accept.compress_level if accept.compress_level is not None else accept.offer.request_max_compress_level,
|
||||
accept.request_max_compress_level)
|
||||
return pmce
|
||||
|
||||
def __init__(self,
|
||||
is_server,
|
||||
server_max_compress_level,
|
||||
client_max_compress_level):
|
||||
self._isServer = is_server
|
||||
self._compressor = None
|
||||
self._decompressor = None
|
||||
|
||||
self.server_max_compress_level = server_max_compress_level if server_max_compress_level != 0 else self.DEFAULT_COMPRESS_LEVEL
|
||||
self.client_max_compress_level = client_max_compress_level if client_max_compress_level != 0 else self.DEFAULT_COMPRESS_LEVEL
|
||||
|
||||
def __json__(self):
|
||||
return {'extension': self.EXTENSION_NAME,
|
||||
'isServer': self._isServer,
|
||||
'server_max_compress_level': self.server_max_compress_level,
|
||||
'client_max_compress_level': self.client_max_compress_level}
|
||||
|
||||
def __repr__(self):
|
||||
return "PerMessageBzip2(isServer = %s, server_max_compress_level = %s, client_max_compress_level = %s)" % (self._isServer, self.server_max_compress_level, self.client_max_compress_level)
|
||||
|
||||
def start_compress_message(self):
|
||||
if self._isServer:
|
||||
if self._compressor is None:
|
||||
self._compressor = bz2.BZ2Compressor(self.server_max_compress_level)
|
||||
else:
|
||||
if self._compressor is None:
|
||||
self._compressor = bz2.BZ2Compressor(self.client_max_compress_level)
|
||||
|
||||
def compress_message_data(self, data):
|
||||
return self._compressor.compress(data)
|
||||
|
||||
def end_compress_message(self):
|
||||
data = self._compressor.flush()
|
||||
|
||||
# there seems to be no "flush without close stream", and after
|
||||
# full flush, compressor must not be reused
|
||||
self._compressor = None
|
||||
|
||||
return data
|
||||
|
||||
def start_decompress_message(self):
|
||||
if self._decompressor is None:
|
||||
self._decompressor = bz2.BZ2Decompressor()
|
||||
|
||||
def decompress_message_data(self, data):
|
||||
return self._decompressor.decompress(data)
|
||||
|
||||
def end_decompress_message(self):
|
||||
self._decompressor = None
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user