# 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 |
# 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 |
#!/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()) |
#!/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()) |
#!/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')() | |||||
) |
#!/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')() | |||||
) |
#!/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')() | |||||
) |
#!/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')() | |||||
) |
#!/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')() | |||||
) |
#!/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()) |
pip |
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. |
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! | |||||
[console_scripts] | |||||
automat-visualize = automat._visualize:tool | |||||
automat |
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. |
[console_scripts] | |||||
django-admin = django.core.management:execute_from_command_line | |||||
# 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__", | |||||
] |
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) |
# 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__) |
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 | |||||
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 | |||||
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,, |
Wheel-Version: 1.0 | |||||
Generator: bdist_wheel (0.24.0) | |||||
Root-Is-Purelib: true | |||||
Tag: py2-none-any | |||||
Tag: py3-none-any | |||||
{"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"} |
pip |
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,, |
Wheel-Version: 1.0 | |||||
Generator: bdist_wheel (0.32.3) | |||||
Root-Is-Purelib: true | |||||
Tag: py2-none-any | |||||
Tag: py3-none-any | |||||
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 |
twisted |
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,, |
Wheel-Version: 1.0 | |||||
Generator: bdist_wheel (0.33.6) | |||||
Root-Is-Purelib: true | |||||
Tag: py2-none-any | |||||
Tag: py3-none-any | |||||
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. |
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>`_. | |||||
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 |
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), | |||||
) |
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 |
# 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 |
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) ;) |
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() |
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 |
""" | |||||
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 |
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]: ... |
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: ... |
""" | |||||
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_ |
pip |
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. |
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? | |||||
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. |
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,, |
############################################################################### | |||||
# | |||||
# 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' |
############################################################################### | |||||
# | |||||
# 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 |
############################################################################### | |||||
# | |||||
# 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) |
############################################################################### | |||||
# | |||||
# 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. | |||||
""" |
/////////////////////////////////////////////////////////////////////////////// | |||||
// | |||||
// 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); | |||||
} | |||||
} |
# 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]) |
############################################################################### | |||||
# | |||||
# 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')) |
############################################################################### | |||||
# | |||||
# 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) |
############################################################################### | |||||
# | |||||
# 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) |
############################################################################### | |||||
# | |||||
# 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") | |||||
""" |
############################################################################### | |||||
# | |||||
# 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"" |
############################################################################### | |||||
# | |||||
# 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', | |||||
) |
############################################################################### | |||||
# | |||||
# 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. | |||||
""" |
# 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() |
# 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() |
# automatically generated by the FlatBuffers compiler, do not modify | |||||
# namespace: proto | |||||
class AuthFactor(object): | |||||
NONE = 0 | |||||
AuthTicketRequest = 1 | |||||
AuthCraRequest = 2 | |||||
AuthScramRequest = 3 | |||||
AuthCryptosignRequest = 4 | |||||
# automatically generated by the FlatBuffers compiler, do not modify | |||||
# namespace: proto | |||||
class AuthMode(object): | |||||
FIRST = 0 | |||||
MULTIFACTOR = 1 | |||||
# 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() |
# 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() |
# 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() |
# 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() |
# automatically generated by the FlatBuffers compiler, do not modify | |||||
# namespace: proto | |||||
class CancelMode(object): | |||||
SKIP = 0 | |||||
ABORT = 1 | |||||
KILL = 2 | |||||
# 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() |
# automatically generated by the FlatBuffers compiler, do not modify | |||||
# namespace: proto | |||||
class ChannelBinding(object): | |||||
NONE = 0 | |||||
TLS_UNIQUE = 1 | |||||
# 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() |
# 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() |
# 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() |
# 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() |
# 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() |
# 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() |
# automatically generated by the FlatBuffers compiler, do not modify | |||||
# namespace: proto | |||||
class Kdf(object): | |||||
NONE = 0 | |||||
PBKDF2 = 1 | |||||
ARGON2 = 2 | |||||
# automatically generated by the FlatBuffers compiler, do not modify | |||||
# namespace: proto | |||||
class Match(object): | |||||
EXACT = 0 | |||||
PREFIX = 1 | |||||
WILDCARD = 2 | |||||
# 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() |
# 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 | |||||
# 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() |
############################################################################### | |||||
# | |||||
# 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 |
############################################################################### | |||||
# | |||||
# 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') |
############################################################################### | |||||
# | |||||
# 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) |
############################################################################### | |||||
# | |||||
# 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. | |||||
""" |