@@ -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 |
@@ -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 |
@@ -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()) |
@@ -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()) |
@@ -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')() | |||
) |
@@ -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')() | |||
) |
@@ -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')() | |||
) |
@@ -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')() | |||
) |
@@ -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')() | |||
) |
@@ -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. |
@@ -0,0 +1,3 @@ | |||
[console_scripts] | |||
django-admin = django.core.management:execute_from_command_line | |||
@@ -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__", | |||
] |
@@ -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) |
@@ -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 |
@@ -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 |
@@ -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), | |||
) |
@@ -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 |
@@ -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 |
@@ -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) ;) |
@@ -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() |
@@ -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 |
@@ -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 |
@@ -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]: ... |
@@ -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: ... |
@@ -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. |
@@ -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,, |
@@ -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' |
@@ -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 |
@@ -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) |
@@ -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. | |||
""" |
@@ -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')) |
@@ -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) |
@@ -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") | |||
""" |
@@ -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"" |
@@ -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', | |||
) |
@@ -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. | |||
""" |
@@ -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() |
@@ -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) |
@@ -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. | |||
""" |