Git Product home page Git Product logo

wampy's Introduction

Travis Python36 Python37 Python37 Python38

image

wampy

[whomp-ee]

For a background as to what WAMP is, please see here.

This is a Python implementation of WAMP using Gevent, but you can also configure wampy to use eventlet, if that is how your application does async. Wampy is is a light-weight alternative to autobahn.

With wampy, you can quickly and easily create your own WAMP clients, whether this is in a web app, a microservice, a script or just in a Python shell. You can integrate wampy into your existing applications without having to re-write or re-design anything.

wampy tries to provide an intuitive API for your WAMP messaging.

See ReadTheDocs for more detailed documentation, but the README here is detailed enough to get going.

wampy features

  • Remote Procedure Calls over websockets
  • Publish and Subscribe over websockets
  • Open Source and Open Thoughts - see internals and then the entire WIKI
  • Use wampy as a microservice/app, or in your Flask or nameko apps, or in scripts.... or just in a Python shell!
  • Client Authentication (Ticket and WCA)
  • Transport Layer Security
  • CLI for easy and rapid development
  • Pytest fixtures to use when testing your projects, including a Crossbar.io fixture that tears down between each test
  • nameko integration with namekowwamp
  • Flask integration with flaskwwamp
  • Compatible with gevent, eventlet and asyncio
  • Alpha features (see below)

QuickStart - Connect and Go!

If you've already got access to a running Router which has other Peers connected, then stay here. If not, jump to the next section. If you're still here...

pip install wampy

...and then open a Python shell.

The example here assumes a Peer connected to a Router on localhost, port 8080, that has registered a remote procedure called get_foobar, and you want to call that procedure.

from wampy.peers import Client

with Client() as client:
    response = client.rpc.get_foobar()

# do something with the response here

The same example here, but the Router is on a remote host.

from wampy.peers import Client

with Client(url="ws://example.com:8080") as client:
    response = client.rpc.get_foobar()

# do something with the response here

The WAMP Session is "context managed", meaning it begins as you enter, and ends as you exit the scope of the client instance.

See ReadTheDocs for much more detail on this.

Running and Calling a wampy Application

Before any messaging can happen you need a Router. Messages are then routed between Clients over an administrative domain on the Router called a Realm.

For the quickest of starts I suggest that you use Crossbar.io and start it up on the default host and port, and with the default realm and roles. See the Crossbar.io docs for the instructions on this or alternatively run with wampy's testing setup.

Note, if you already have Crossbar installed and running you do not need these steps, because the dev requirements also include Crossbar.

$ make dev-install

$ crossbar start --config ./wampy/testing/configs/crossbar.json

wampy RPC

Now open your preferred text editor and we'll write a few lines of Python constructing a simple WAMP service that takes a decimal number and returns the binary representation of it - wowzers!

from wampy.peers.clients import Client
from wampy.roles import callee

class BinaryNumberService(Client):

    @callee
    def get_binary_number(self, number):
        return bin(number)

Save this module somewhere on your Python path and we'll use a wampy command line interface tool to start the service.

$ wampy run path.to.your.module.including.module_name:BinaryNumberService

For example, running one of the wampy example applications against the Router suggested previously:

$ wampy run docs.examples.services:DateService --config ./wampy/testing/configs/crossbar.json

Actually - no need to panic! The BinaryNumberService example already exists in the wampy examples so put that text editor away if you like. Just execute from the command line:

$ wampy run docs.examples.services:BinaryNumberService --config ./wampy/testing/configs/crossbar.json

Now, open a Python console in a new terminal, allowing the BinaryNumberService to run uninterupted in your original terminal (but once you're done with it Ctrl-C is required).

In [1]: from wampy.peers.clients import Client

In [2]: with Client(url="ws://localhost:8080") as client:
            result = client.rpc.get_binary_number(number=100)

In [3]: result
Out[3]: u'0b1100100'

wampy RPC for Crossbar.io

The RPC pattern above was inspired by the nameko project, but this pattern may not feel intuitive for those familiar with Crossbar.io, the primary Router used by wampy.

For this reason there also exists the CallProxy object which implements the call API by more loosely wrapping wampy's Call Message. In this pattern, applications and their endpoints are identified by dot delimented strings rather than a single API name, e.g.

"com.example.endpoint"

Just like the rpc API, the call API is directly available on every wampy client. Lets look at the two examples side by side.

>>> client.rpc.get_foo_bar(eggs, foo=bar, spam=ham)
>>> client.call("get_foo_bar", eggs, foo=bar, spam=ham)

Noted these are very similar and achieve the same, but the intention here is for the call API to behave more like a classic Crossbar.io application and the rpc to be used in namekowwamp.

The call API however does allow calls of the form...

>>> client.call("com.myapp.foo.bar", eggs, foo=bar, spam=ham) 

...which you will not be able to do with the rpc API.

Publishing and Subscribing is equally as simple

To demonstrate, first of all you need a Subscriber. You can either create one yourself in a Python module (as a subclass of a wampy Client) or use the example Client already for you in docs.examples.services.

Here we use the said example service, but all a Subscriber is is a wampy Client with a method decorated by subscribe. Take a look and see for yourself in the examples.

Let's start up that example service.

$ wampy run docs.examples.services:SubscribingService --config ./wampy/testing/configs/crossbar.json

Now we have a service running that subscribes to the topic "foo".

In another terminal, with a wampy virtualenv, you can create a Publisher - which is no different to any other wampy Client.

In [1]: from wampy.peers import Client

In [2]: with Client() as client:
            result = client.publish(topic="foo", message="spam")

Hopefully you'll see any message you send printed to the screen where the example service is running. You'll also see the meta data that wampy chooses to send.

Please note. wampy believes in explicit kwargs and not bare args, so you can only publish keyword arguments. Bare arguments don't tell readers enough about the call, so even though WAMP supports them, wampy does not.

It doesn't matter what the kwargs are they will be published, but you might find a call like this is not supported by subscribers of other WAMP implementations (sorry) e.g.

In [1]: from wampy.peers import Client

In [2]: with Client() as client:
            client.publish(
                topic="foo",
                ham="spam",
                birds={'foo_bird': 1, 'bar_bird': 2},
                message="hello world",
            )

Notice topic is always first, followed by kwargs. Happy to explore how implementations like autobahn can be supported here.

See ReadTheDocs for more detailed documentation.

Have Fun With Wampy

You can simply import a wampy client into a Python shell and start creating WAMP apps. Open a few shells and start clients running! Or start an example app and open a shell and start calling it. Don't forget to start Crossbar first though!

$ make install

$ crossbar start --config ./wampy/testing/configs/crossbar.json

Extensions

Wampy is a "simple" WAMP client and so it can easily be integrated with other frameworks. The current extensions are:

Extensions for other Python Frameworks are encouraged!

Async Networking

The default backend for async networking is gevent, but you can switch this to eventlet if that is what your applications already use.

$ export WAMPY_ASYNC_NAME=eventlet

Swapping back is easy.

$ export WAMPY_ASYNC_NAME=gevent

Gevent is officially supported but eventlet no longer is, sorry. There were issues with Eventlet and Python 3.7 that I cannot currently work around.

Async.io would require a complete re-write, and if you're already using the standard library and want to use wampy that is not a problem - just roll with the default gevent - as the two event loops can run side by side.

Why does wampy support both eventlet and gevent? Because wampy is not a framework like Flask or nameko, and wampy tries to make as few assumptions about the process it is running in as possible. Wampy is intended to be integrated into existing Python apps as an easy way to send and receive WAMP messages, and if your app is already committed to a paritcular async architecture, then wampy may not be usable unless he can switch between them freely. And do remember: both options are compatible with the core asyncio library, so don't be put off if your app uses this.

Alpha Features

WebSocket Client -> Server Pings

Disabled by default, but by setting the environment variable DEFAULT_HEARTBEAT_SECONDS you can tell wampy to start Pinging the Router/Broker, i.e. Crossbar.

$ export DEFAULT_HEARTBEAT_SECONDS=5

There is also HEARTBEAT_TIMEOUT_SECONDS (defaults to 2 seconds) which on missed will incrmeent a missed Pong counter. That's it for now; WIP.

WAMP Call TimeOuts

WAMP advacned protocol describes an RPC timeout which wampy implements but Crossbar as yet does not. See crossbario/crossbar#299. wampy does pass your preferred value to the Router/Broker in the Call Message, but the actual timeout is implemented by wampy, simply cutting the request off at the head. Sadly this does mean the server still may return a value for you and your app will have to handle this. We send the Cancel Message too, but there are issues here as well: Work In Progress.

Running the tests

$ pip install --editable .[dev]
$ py.test ./test -v

Build the docs

$ pip install -r rtd_requirements.txt
$ sphinx-build -E -b html ./docs/ ./docs/_build/

If you like this project, then Thank You, and you're welcome to get involved.

Contributing

Firstly. thank you everyone who does. And everyone is welcome to. And thanks for reading the CONTRIBUTING guidelines. And for adding yourselves to the CONTRIBUTORS list on your PR - you should! Many thanks. It's also great to hear how everyone uses wampy, so please do share how with me on your PR in comments.

Then, please read about the internals.

Finally.... get coding!!

Thanks!

TroubleShooting

Crossbar.io is used by the test runner and has many dependencies.

Mac OS

snappy/snappymodule.cc:31:10: fatal error: 'snappy-c.h' file not found #include <snappy-c.h>

Fix by brew install snappy

wampy's People

Contributors

abulte avatar chadrik avatar dmugtasimov avatar edstafford avatar fnordian avatar foxmask avatar jpinsonault avatar jwg4 avatar kilroy42 avatar noisyboiler avatar titilambert avatar wodcz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

wampy's Issues

Cannot receive result from RPC to autobahn callee

I start an autobahn callee and leave it running. I start the wampy caller and it results with the error below. However, i can see that the callee receives the call and replies to it. The problem is that the result cannot be retrieved using the wampy client.

Happens on all releases that we tried (0.9.14, 0.9.17) and latest master (commit 1cc127c)

Error:

Traceback (most recent call last):
  File "/home/goran/workspace/tmp/wampy_bug_caller.py", line 7, in <module>
    result = client.call("ping")
  File "/home/goran/.local/share/virtualenvs/tmp-yHIhTlRE/lib/python3.5/site-packages/wampy/roles/caller.py", line 40, in __call__
    return response.value
  File "/home/goran/.local/share/virtualenvs/tmp-yHIhTlRE/lib/python3.5/site-packages/wampy/messages/result.py", line 46, in value
    return self.yield_kwargs['message']
TypeError: 'NoneType' object is not subscriptable

Autobahn callee:

from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner

class Component(ApplicationSession):
    async def onJoin(self, details):

        def ping():
            print("Replying with 'pong' to {}".format(details.realm))
            return 'pong'

        reg = await self.register(ping, 'ping')
        print("Registered 'ping' with id {}".format(reg.id))


if __name__ == '__main__':
    ROUTER_URL = 'ws://localhost:8080/ws'
    REALM = 'crossbardemo'
    runner = ApplicationRunner(ROUTER_URL, REALM)
    runner.run(Component)

Wampy caller:

from wampy.peers import Client

ROUTER_URL = 'ws://localhost:8080/ws'
REALM = 'crossbardemo'

with Client(url=ROUTER_URL, realm=REALM) as client:
    result = client.call("ping")
    print(result)

I managed to make the code work by implementing this code in wampy/messages/result.py:

    @property
    def value(self):
        if self.yield_kwargs:
            return self.yield_kwargs['message']
        return self.yield_args[0]

Doc : how to setup the URL in the config.json file of crossbar

Hi,

I think it could be useful to add something in the doc about how to setup the URL in the config.json file of crossbar. Because when starting from scratch, there is no URL parms.

So When running the app we get the error

wampy.errors.WampyError: The ``url`` value is required by Wampy. Please add to your configuration file. Thanks.

And I had to digg in the source to find where to wampy expected to see the URL parm to be able to add it in the config.json file of the crossbar router.

Python 3.7 Support

Biggest issue at the moment is usage of the reserved word async. Will update with further findings.

How to connect to a router, which is not on the same host ?!

In all your examples, you make similars connexions like that :

c=Client(router=Crossbar())

But, this works only when the client is on the same host as the router (because it seems it feeds it with the local crossbar config).
I don't have found a way to connect from a different machine ?!
Is it possible with this wampy ?

MonkeyPatchWarning

When importing from wampy (tried from wampy.peers import Client and import wampy) both when trying to run a file and when in the Python REPL, I get the following error (I broke it into several lines to make it a bit more readable):

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/wampy/__init__.py:28: 
MonkeyPatchWarning: Monkey-patching ssl after ssl has already been imported may lead to errors, 
including RecursionError on Python 3.6. It may also silently lead to incorrect behaviour on Python 3.7. 
Please monkey-patch earlier. See https://github.com/gevent/gevent/issues/1016. 
Modules that had direct imports (NOT patched):
['eventlet.green.ssl (/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/eventlet/green/ssl.py)']. 
Subclasses (NOT patched): ["<class 'eventlet.green.ssl.GreenSSLContext'>"].
  gevent.monkey.patch_all()

Python v3.7.1

unale to run the example

Hi,

First i have installed lastest version off crossbar into a virtual pypy3 vritualenvironement.
Then I have installed wampy using pip inside the virtualenvironement.
Thn i have the following crossbar config https://github.com/noisyboiler/wampy/blob/master/wampy/testing/configs/crossbar.json
and the following example https://github.com/noisyboiler/wampy/blob/master/docs/examples/services.py
runing the router => crossbar start --config crossbar.json
And try to launch the service using the run command.
It fail

then i have cloned the repo and followed your instruction
pip install --editable .[dev]
crossbar, and many other lib downgraded to very old version

  Found existing installation: autobahn 18.9.2
    Uninstalling autobahn-18.9.2:
      Successfully uninstalled autobahn-18.9.2
  Found existing installation: crossbar 18.9.2
    Uninstalling crossbar-18.9.2:
      Successfully uninstalled crossbar-18.9.2
  Found existing installation: wampy 0.9.17
    Uninstalling wampy-0.9.17:
      Successfully uninstalled wampy-0.9.17
  Running setup.py develop for wampy
Successfully installed autobahn-0.17.2 colorlog-3.1.4 coverage-4.5.1 crossbar-0.15.0 enum-compat-0.0.2 eventlet-0.20.1 flake8-3.5.0 gevent-websocket-0.10.1 mccabe-0.6.1 mock-1.3.0 pbr-4.3.0 py-1.7.0 pycodestyle-2.3.1 pyflakes-1.6.0 pytest-3.1.3 pytest-capturelog-0.7 shutilwhich-1.1.0 wampy

Running again the router & all the stuff as specified in the doc,
And again the same probleme

wampy run docs.examples.services:BinaryNumberService --config ./wampy/testing/configs/crossbar.json
Traceback (most recent call last):
  File "/home/erc/.virtualenvs/pypy3/bin/wampy", line 11, in <module>
    load_entry_point('wampy', 'console_scripts', 'wampy')()
  File "/home/rep/icham/golden/crossbar/wampy/wampy/cli/main.py", line 27, in main
    args.main(args)
  File "/home/rep/icham/golden/crossbar/wampy/wampy/cli/run.py", line 104, in main
    run(app, config_path)
  File "/home/rep/icham/golden/crossbar/wampy/wampy/cli/run.py", line 70, in run
    router = Crossbar(config_path)
  File "/home/rep/icham/golden/crossbar/wampy/wampy/peers/routers.py", line 43, in __init__
    with open(config_path) as data_file:
FileNotFoundError: [Errno 2] No such file or directory: './crossbar/config.json'
ls -al wampy/testing/configs/crossbar.json
-rw-rw-r-- 1 erc erc 1324 Oct 13 19:11 wampy/testing/configs/crossbar.json

Help With Coverage

Most wampy tests are end-to-end and do actually hit the entire internal stack. but the coverage report does not seem to notice all these internal function calls. So..., for confidence, wampy does need some more "unit" tests.

coverage should be >=95%

I support having internal, "private" methods, starting with underscores that are not unit tested, as long as the callers are unit tested; many functions within wampy could be underscored.

Example for Subscribing to a Topic

Hey there. I'm trying to add support to an existing (multithreaded) app for the Poloniex WAMP API. After a horrible day trying to butcher the Autobahn client into place I happened across Wampy. It looks like it could be a better fit for my (non-twisted, non-asyncio) needs but I'm struggling to get it up and running.

Could give a simple example of connecting to a WAMP service and subscribing to a topic?

Example: Ticket authentication

Hello,
for beginning, thank you for this package.

I've just implemented wampy to our stack, and I have some code that I think would be useful for readme or examples.
In our stack, Crossbar router runs in separate docker container, and is configured to use ticket authentication (which is not natively supported in wampy).

First, I created a new TicketAuthenticate class - message that will be sent after ticket challenge.
It receives ticket in constructor.

class TicketAuthenticate(object):
    WAMP_CODE = 5
    name = "authenticate"

    def __init__(self, ticket, kwargs_dict=None):
        super(TicketAuthenticate, self).__init__()

        self.ticket = ticket
        self.kwargs_dict = kwargs_dict or {}

    @property
    def message(self):
        return [
            self.WAMP_CODE, self.ticket, self.kwargs_dict
        ]

Then, I extended MessageHandler to respond with this message in handle_challenge. It receives ticket in constructor and passes it to TicketAuthenticate once requested.

from wampy.message_handler import MessageHandler, logger

class TicketMessageHandler(MessageHandler):

    ticket = None

    def __init__(self, ticket) -> None:
        super().__init__()
        self.ticket = ticket

    def handle_challenge(self, message_obj):
        logger.debug("client has been Challenged")
        message = TicketAuthenticate(self.ticket)
        self.session.send_message(message)

And finally usage example

        roles = {
            'roles': {
                'subscriber': {},
                'publisher': {},
                'callee': {
                    'shared_registration': True,
                },
                'caller': {},
            },
            'authmethods': ['ticket'],
            'authid': settings.WAMP_BACKEND_PRINCIPAL
        }
        self.client = Client(
            url=f"ws://{settings.WAMP_BACKEND_HOST}:{settings.WAMP_BACKEND_PORT}",
            realm=settings.WAMP_BACKEND_REALM,
            roles=roles,
            message_handler=TicketMessageHandler(settings.WAMP_BACKEND_TICKET)
        )
        self.client.start()

I'm not a Python developer, but I feel that this code is not the best approach - that's why I'm creating this issue - it's not suitable for a pull request.
It could save some time to anyone though, maybe it would be an inspiration for adding a support for ticket auth to wampy.

Please let me know whether you want this issue to be closed without discussion (just kept for reference), or you'd like to discuss this topic.

Thanks!

client can't connect to crossbar universal transport

hi,
Crossbar v18.11.1 is shiped with a config template using universal transport.
I mean when you do crossbar init , a folder is created with a basic config & the certificats.
When wampy client try to connect crossbar try to reconise a websocket client
in basis of the first byte => what he call "the magic Raw Socket byte"

see here => https://crossbar.io/docs/Transport-Endpoints/ (search on this page for Universal Listening Endpoints)

2018-11-12T18:34:05+0100 [Router      21379 crossbar.router.unisocket.UniSocketServerProtocol] switching to HTTP on Request-URI //, mapping part 
2018-11-12T18:34:05+0100 [Router      21379 crossbar.router.unisocket.UniSocketServerProtocol] no mapping found for request URI //, trying to map part 
2018-11-12T18:34:05+0100 [Router      21379 crossbar.router.unisocket.UniSocketServerProtocol] client wants to talk HTTP/Web, but we have no factory configured for that

wampy requires too old gevent package

Got segmentation fault on a fresh wampy installation due to #python-greenlet/greenlet#178 (comment)

$ mkdir denv
$ virtualenv --python=python3 denv
created virtual environment CPython3.8.5.final.0-64 in 301ms
  creator CPython3Posix(dest=/home/homo/git/range/crossbar-learn/denv, clear=False, global=False)
  seeder FromAppData(download=False, wheel=latest, six=latest, progress=latest, appdirs=latest, lockfile=latest, pip=latest, distro=latest, html5lib=latest, setuptools=latest, CacheControl=latest, ipaddr=latest, certifi=latest, msgpack=latest, retrying=latest, distlib=latest, colorama=latest, pkg_resources=latest, packaging=latest, webencodings=latest, requests=latest, urllib3=latest, contextlib2=latest, pytoml=latest, chardet=latest, idna=latest, pyparsing=latest, pep517=latest, via=copy, app_data_dir=/home/homo/.local/share/virtualenv/seed-app-data/v1.0.1.debian)
  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
$ source denv/bin/activate
(denv) $ pip install wampy
Collecting wampy
  Using cached wampy-1.0.0-py2.py3-none-any.whl (75 kB)
Collecting attrs==19.2.0
  Using cached attrs-19.2.0-py2.py3-none-any.whl (40 kB)
Collecting gevent==20.4.0
  Using cached gevent-20.4.0-cp38-cp38-manylinux2010_x86_64.whl (5.9 MB)
Requirement already satisfied: six>=1.11.0 in ./denv/lib/python3.8/site-packages (from wampy) (1.14.0)
Collecting simplejson>=3.11.1
  Using cached simplejson-3.17.2-cp38-cp38-manylinux2010_x86_64.whl (137 kB)
Collecting eventlet>=0.24.1
  Using cached eventlet-0.30.1-py2.py3-none-any.whl (224 kB)
Collecting greenlet>=0.4.14; platform_python_implementation == "CPython"
  Using cached greenlet-1.0.0-cp38-cp38-manylinux2010_x86_64.whl (165 kB)
Collecting dnspython<2.0.0,>=1.15.0
  Using cached dnspython-1.16.0-py2.py3-none-any.whl (188 kB)
Installing collected packages: attrs, greenlet, gevent, simplejson, dnspython, eventlet, wampy
Successfully installed attrs-19.2.0 dnspython-1.16.0 eventlet-0.30.1 gevent-20.4.0 greenlet-1.0.0 simplejson-3.17.2 wampy-1.0.0
(denv) $ python wampy_clients.py 
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
gevent monkey-patched your environment
2021-02-25 19:45:19,131 INFO:logging configured
2021-02-25 19:45:19,131 INFO:wampy starting up with event loop: gevent
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
2021-02-25 19:45:19,236 INFO:socket connected
2021-02-25 19:45:19,241 INFO:handshake complete: 101 : {'status_info': ['HTTP/1.1', '101', 'Switching Protocols'], 'status': 101, 'server': 'crossbar', 'upgrade': 'websocket', 'connection': 'upgrade', 'sec-websocket-protocol': 'wamp.2.json', 'sec-websocket-accept': 'mnz8fnoibb36pfcrvbbsuijodcu='}
Segmentation fault (core dumped)

Error when transferring big, really big strings

Trying to transfer 178363 bytes length json RESULT message. wampy client throws UnicodeDecodeError.

At the same time Autbahn's component implementation works as expected.

Traceback (most recent call last):
  File "src/gevent/greenlet.py", line 854, in gevent._greenlet.Greenlet.run
  File ".../lib/python3.7/site-packages/wampy/session.py", line 165, in connection_handler
    message = frame.payload
  File "../lib/python3.7/site-packages/wampy/transports/websocket/frames.py", line 109, in payload
    payload_str = self.raw_bytes[6:].decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb9 in position 2: invalid start byte

wampy run command not working

Hi,

The run command return the following error.
FileNotFoundError: [Errno 2] No such file or directory: './crossbar/config.json'

As workaround i use the following code:
apprunner.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os, sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
from wampy.backends import async_adapter

class AppRunner(object):

    def __init__(self):
        self.apps = []

    def add_app(self, app):
        self.apps.append(app)
        
    def run(self):
        for app in self.apps:
            print("starting up app: %s" % app.__class__.__name__)
            app.start()
            print("{} is now running and connected.".format(app.__class__.__name__))
        print('all services started!')
        try:
            while True:
                self.wait()
        except KeyboardInterrupt:
            print('stopping all!!')
            self.stop()
    def stop(self):
        for app in self.apps:
            print("stopping app: %s" % app.__class__.__name__)
            app.stop()
            
    def wait(self):
        async_adapter.sleep(0)


def run(apps, url):
    print("starting up services...")
    runner = AppRunner()
    for app in apps:
        app = app(url=url)
        runner.add_app(app)     
    runner.run()

And here the service:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os, sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
import datetime
from wampy.peers.clients import Client
from wampy.roles.callee import callee
from apprunner import run
import time
class DateService(Client):
    """ 
    slow date service
    """  
    @callee
    def get_slow(self):
        time.sleep(15)
        return datetime.date.today().isoformat()
        
def main():
    url = 'ws://192.168.11.10:8080/ws'
    apps = [DateService]
    run(apps, url)
   
if __name__ == '__main__':
    main()

ujson vs simplejson

Hi, I don't know if you've considered this already, but I started using "ujson" instead of "json" a little while back, seems to be completely stable / reliable, and around 4x faster than "simplejson". If your app does a lot of WAMP type work, it can make a noticeable difference ..

https://github.com/esnme/ultrajson

wampy 'run' command broken

The listener started with wampy 'run' command is not listening to his theme

(venv) $ wampy run wampy_clients:Listener
gevent monkey-patched your environment
2021-03-01 18:44:59,249 INFO:logging configured
2021-03-01 18:44:59,249 INFO:wampy starting up with event loop: gevent
2021-03-01 18:44:59,354 INFO:starting CLI with args: Namespace(application=['wampy_clients:Listener'], config='./crossbar/config.json', main=<function main at 0x7fc6a57adc10>)
2021-03-01 18:44:59,354 WARNING:defaulting to IPV 4 because neither was specified.
starting up services...
2021-03-01 18:44:59,363 INFO:socket connected
2021-03-01 18:44:59,367 INFO:handshake complete: 101 : {'status_info': ['HTTP/1.1', '101', 'Switching Protocols'], 'status': 101, 'server': 'crossbar', 'upgrade': 'websocket', 'connection': 'upgrade', 'sec-websocket-protocol': 'wamp.2.json', 'sec-websocket-accept': 'lqv92pxvjug33nw61rrjonylois='}

And nothing more. Meanwhile there are messages in his theme in server log

Wrong arg for socket.connect()

Connection error on fresh wampy installation

$ mkdir denv
$ virtualenv --python=python3 denv
created virtual environment CPython3.8.5.final.0-64 in 3333ms
  creator CPython3Posix(dest=/home/homo/git/range/crossbar-learn/denv, clear=False, global=False)
  seeder FromAppData(download=False, wheel=latest, six=latest, progress=latest, appdirs=latest, lockfile=latest, pip=latest, distro=latest, html5lib=latest, setuptools=latest, CacheControl=latest, ipaddr=latest, certifi=latest, msgpack=latest, retrying=latest, distlib=latest, colorama=latest, pkg_resources=latest, packaging=latest, webencodings=latest, requests=latest, urllib3=latest, contextlib2=latest, pytoml=latest, chardet=latest, idna=latest, pyparsing=latest, pep517=latest, via=copy, app_data_dir=/home/homo/.local/share/virtualenv/seed-app-data/v1.0.1.debian)
  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
$ source denv/bin/activate
(denv) $ pip install wampy
Collecting wampy
  Using cached wampy-1.0.0-py2.py3-none-any.whl (75 kB)
Requirement already satisfied: six>=1.11.0 in ./denv/lib/python3.8/site-packages (from wampy) (1.14.0)
Collecting simplejson>=3.11.1
  Using cached simplejson-3.17.2-cp38-cp38-manylinux2010_x86_64.whl (137 kB)
Collecting eventlet>=0.24.1
  Using cached eventlet-0.30.1-py2.py3-none-any.whl (224 kB)
Collecting gevent==20.4.0
  Using cached gevent-20.4.0-cp38-cp38-manylinux2010_x86_64.whl (5.9 MB)
Collecting attrs==19.2.0
  Using cached attrs-19.2.0-py2.py3-none-any.whl (40 kB)
Collecting dnspython<2.0.0,>=1.15.0
  Using cached dnspython-1.16.0-py2.py3-none-any.whl (188 kB)
Collecting greenlet>=0.3
  Using cached greenlet-1.0.0-cp38-cp38-manylinux2010_x86_64.whl (165 kB)
Installing collected packages: simplejson, dnspython, greenlet, eventlet, gevent, attrs, wampy
Successfully installed attrs-19.2.0 dnspython-1.16.0 eventlet-0.30.1 gevent-20.4.0 greenlet-1.0.0 simplejson-3.17.2 wampy-1.0.0
(denv) $ pip install gevent -U
Collecting gevent
  Using cached gevent-21.1.2-cp38-cp38-manylinux2010_x86_64.whl (6.3 MB)
Collecting zope.interface
  Using cached zope.interface-5.2.0-cp38-cp38-manylinux2010_x86_64.whl (244 kB)
Collecting zope.event
  Using cached zope.event-4.5.0-py2.py3-none-any.whl (6.8 kB)
Requirement already satisfied, skipping upgrade: setuptools in ./denv/lib/python3.8/site-packages (from gevent) (44.0.0)
Requirement already satisfied, skipping upgrade: greenlet<2.0,>=0.4.17; platform_python_implementation == "CPython" in ./denv/lib/python3.8/site-packages (from gevent) (1.0.0)
ERROR: wampy 1.0.0 has requirement gevent==20.4.0, but you'll have gevent 21.1.2 which is incompatible.
Installing collected packages: zope.interface, zope.event, gevent
  Attempting uninstall: gevent
    Found existing installation: gevent 20.4.0
    Uninstalling gevent-20.4.0:
      Successfully uninstalled gevent-20.4.0
Successfully installed gevent-21.1.2 zope.event-4.5.0 zope.interface-5.2.0
(denv) $ python wampy_clients.py 
gevent monkey-patched your environment
2021-03-01 18:23:26,165 INFO:logging configured
2021-03-01 18:23:26,165 INFO:wampy starting up with event loop: gevent
Traceback (most recent call last):
  File "wampy_clients.py", line 34, in <module>
    ticker = WampyTicker(url="ws://localhost:8080")
  File "/home/homo/git/range/crossbar-learn/denv/lib/python3.8/site-packages/wampy/peers/clients.py", line 95, in __init__
    self._session = Session(
  File "/home/homo/git/range/crossbar-learn/denv/lib/python3.8/site-packages/wampy/session.py", line 92, in __init__
    self.connection = self.transport.connect(upgrade=True)
  File "/home/homo/git/range/crossbar-learn/denv/lib/python3.8/site-packages/wampy/transports/websocket/connection.py", line 59, in connect
    self._connect()
  File "/home/homo/git/range/crossbar-learn/denv/lib/python3.8/site-packages/wampy/transports/websocket/connection.py", line 150, in _connect
    _socket.connect((self.host.encode(), self.port))
  File "/home/homo/git/range/crossbar-learn/denv/lib/python3.8/site-packages/gevent/_socketcommon.py", line 602, in connect
    address = _resolve_addr(self._sock, address)
  File "/home/homo/git/range/crossbar-learn/denv/lib/python3.8/site-packages/gevent/_socketcommon.py", line 428, in _resolve_addr
    if __socket__.inet_pton(sock.family, address[0]):
TypeError: inet_pton() argument 2 must be str, not bytes

Dependency issue: No module named tenacity

I tried to install wampy in a clean python environment (docker python:latest). There is no difference between python 2 and 3.

pip install wampy
python -c "import wampy"

lead to this error:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/local/lib/python2.7/site-packages/wampy/__init__.py", line 18, in <module>
    from wampy.peers.clients import Client  # noqa
  File "/usr/local/lib/python2.7/site-packages/wampy/peers/__init__.py", line 5, in <module>
    from . clients import Client  # noqa
  File "/usr/local/lib/python2.7/site-packages/wampy/peers/clients.py", line 18, in <module>
    from wampy.peers.routers import Router
  File "/usr/local/lib/python2.7/site-packages/wampy/peers/routers.py", line 13, in <module>
    from tenacity import retry, stop_after_attempt, wait_fixed
ImportError: No module named tenacity

Install tenacity seem to fix the issue:

pip install tenacity
python -c "import wampy"

More permissive license

The GPL3 license attached to wampy requires that any code that utilizes this library for WAMP transport to also be released under GPL3.

For example, this would mean any and all server microservices code I create using wampy must also be released as be open-source -- which I do not wish to do.

I strongly believe this choice of the GPL3 license will greatly hinder community adoption and code contribution back to this project.

I suggest changing it to a much more permissive MIT/BSD style license.

edit: pertinent discussion here: http://davidherron.com/blog/2014-04-26/whats-best-open-source-license-nodejs-modules

Issue with UTF-8 caracters as parameters with python 3

Hello !

I have an issue when I'm trying to call a remote function with a UTF-8 caracter.
Here my example

Server

from wampy.peers.clients import Client
from wampy.roles.callee import callee

class BinaryNumberService(Client):

    @callee
    def get_binary_number(self, text):
        print(text)


toto  = BinaryNumberService()

toto.start()

try:
    import time
    time.sleep(9999)
except:
    print('stopping')
toto.stop()

Client

from wampy.peers.clients import Client

with Client(url="ws://localhost:8080") as client:
            result = client.rpc.get_binary_number(text='100éfa')

And the error:

python client.py 
Traceback (most recent call last):
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/wampy/transports/websocket/frames.py", line 239, in __init__
    self.payload = json.loads(str(self.body.decode()))
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xea in position 1: invalid continuation byte

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/env3.6/lib/python3.6/site-packages/eventlet-0.21.0-py3.6.egg/eventlet/hubs/poll.py", line 114, in wait
    listener.cb(fileno)
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/env3.6/lib/python3.6/site-packages/eventlet-0.21.0-py3.6.egg/eventlet/greenthread.py", line 218, in main
    result = function(*args, **kwargs)
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/wampy/session.py", line 159, in connection_handler
    frame = connection.receive()
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/wampy/transports/websocket/connection.py", line 83, in receive
    frame = ServerFrame(received_bytes)
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/wampy/transports/websocket/frames.py", line 242, in __init__
    'Failed to load JSON object from: "%s"', self.body
wampy.errors.WebsocktProtocolError: ('Failed to load JSON object from: "%s"', bytearray(b"\x03\xeaWAMP Protocol Error (invalid serialization of WAMP message (Expecting \',\' delimiter: line 1 column 56 (char 55)))"))
Removing descriptor: 3
Traceback (most recent call last):
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/wampy/session.py", line 115, in recv_message
    message = self._wait_for_message(timeout)
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/wampy/session.py", line 184, in _wait_for_message
    eventlet.sleep()
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/env3.6/lib/python3.6/site-packages/eventlet-0.21.0-py3.6.egg/eventlet/greenthread.py", line 35, in sleep
    hub.switch()
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/env3.6/lib/python3.6/site-packages/eventlet-0.21.0-py3.6.egg/eventlet/hubs/hub.py", line 295, in switch
    return self.greenlet.switch()
eventlet.timeout.Timeout: 5 seconds

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "client.py", line 6, in <module>
    result = client.rpc.get_binary_number(text='100éfa')
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/wampy/roles/caller.py", line 63, in wrapper
    response = self.client.make_rpc(message)
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/wampy/peers/clients.py", line 211, in make_rpc
    response = self.session.recv_message()
  File "/home/tcohen/perso/gits/github.com/noisyboiler/wampy/wampy/session.py", line 118, in recv_message
    "no message returned (timed-out in {})".format(timeout)
wampy.errors.WampProtocolError: no message returned (timed-out in 5)

flask example

Hi,

On the description i see wampy can be used in a webapp.
I have read the doc, and there are multiple example for standalone application,
but no example of using wampy inside a web application.
Could you post or add on the docs or on the example folder a simple flask example ?
I have a flask aplication exposing an http service,
and i want to extend it by publishing toward a crossbar router some event.

Double slash in path

When I run

from wampy.peers import Client

client = Client()
client.start()

I get

2018-10-06 15:25:21,280 DEBUG connection headers: ['GET //8080 HTTP/1.1', 'Host: localhost:8080', 'Upgrade: websocket', 'Connection: Upgrade', 'Sec-WebSocket-Key: pTTOyGC/QjClWaGYIlFaYw==', 'Origin: ws://localhost:8080', 'Sec-WebSocket-Version: 13', 'Sec-WebSocket-Protocol: wamp.2.json']

Should not it be GET / instead of GET //8080?

For

from wampy.peers.clients import Client
from wampy.peers.routers import Crossbar

with WampyApp(router=Crossbar(url='ws://127.0.0.1:8080/ws', config_path=config_path),
              roles=roles, message_handler=TicketMessageHandler('ticket')) as client:
    pass

I get

2018-10-06T15:26:13+0300 [Router      10075 crossbar.router.protocol.WampWebSocketServerProtocol] received HTTP request:

b'GET //ws HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: 9Zby3RqBSLWA3Ovh2NLDJA==\r\nOrigin: ws://127.0.0.1:8080\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Protocol: wamp.2.json\r\n\r\n'

on Crossbar side. Should it be GET /ws instead of GET //ws?

Please, let me know if you can't grasp the issue from the above description. I will prepare a full fledged example.

ping handling

Hi!

Creating and starting a long running client leads to disconnects due to unhandled pings from the router.

with wampy.Client(url="ws://localhost:8080/ws") as client:
    client.publish(topic="test", foo="bar")
    time.sleep(10)
    client.publish(topic="test", foo="bar")
 Traceback (most recent call last):
  File "/home/marcus/local/python/python3env/lib/python3.6/site-packages/wampy/transports/websocket/frames.py", line 253, in __init__
    self.payload = json.loads(self.body.decode('utf-8'))
  File "/usr/lib64/python3.6/json/__init__.py", line 354, in loads
    return _default_decoder.decode(s)
  File "/usr/lib64/python3.6/json/decoder.py", line 342, in decode
    raise JSONDecodeError("Extra data", s, end)
json.decoder.JSONDecodeError: Extra data: line 1 column 2 (char 1)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/marcus/local/python/python3env/lib/python3.6/site-packages/eventlet/hubs/poll.py", line 114, in wait
    listener.cb(fileno)
  File "/home/marcus/local/python/python3env/lib/python3.6/site-packages/eventlet/greenthread.py", line 218, in main
    result = function(*args, **kwargs)
  File "/home/marcus/local/python/python3env/lib/python3.6/site-packages/wampy/session.py", line 158, in connection_handler
    frame = connection.receive()
  File "/home/marcus/local/python/python3env/lib/python3.6/site-packages/wampy/transports/websocket/connection.py", line 86, in receive
    frame = ServerFrame(received_bytes)
  File "/home/marcus/local/python/python3env/lib/python3.6/site-packages/wampy/transports/websocket/frames.py", line 256, in __init__
    'Failed to load JSON object from: "%s"', self.body
wampy.errors.WebsocktProtocolError: ('Failed to load JSON object from: "%s"', bytearray(b'6xLI'))
Removing descriptor: 3

Are there plans to add ping-handling?

feature request: expose session timeout to Client & caller

Hi,

I try to proxy do a wamp2 proxy to a slow rest api (response time > 10 seconde),
I get constant timeout, because the timeout is hardcoded to 5 secondes in session.recv_message .

Could you add a Client scope timeout, and a caller, suscriber timeout?

class BinaryNumberService(Client):

    @callee(timeout=15)
    def get_binary_number(self, number):
        return bin(number)

not sure if we can change the client timeout when we subclassing the Client class?

and

with Client(timeout=15) as client:
    response = client.rpc.get_foobar()

Travis Builds are Broken

This is a pain.

wampy uses eventlet under the hood for non-blocking IO. Travis recently move to 2.7.13 from 2.7.10. There is a bug that requires eventlet < 0.21.0 after making this move.
The issue is here: eventlet/eventlet#401

But wampy requires eventlet >= 0.21.0 to avoid an SSL recursion bug in 3.6
The issue is here: eventlet/eventlet#371

Options that I see are:

  • abandon Python 2 entirely
  • abandon Travis
  • wait for one of the bugs to be fixed
  • accept broken builds
  • ask for hellp

I am now asking for help.....

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.