User guide

The following sections explain how to use the most common functions of a WAMP client. The more advanced options have been documented in the API reference.

For practical examples, see the examples directory.

Calling remote procedures

To call a remote procedure, use the call() method:

result = await ctx.wamp.call('procedurename', arg1, arg2, arg3='foo')

To receive progressive results from the call, you can give a callback as the on_progress option:

def progress(status):
    print('operation status: {}'.format(status))

result = await ctx.wamp.call('procedurename', arg1, arg2, arg3='foo',
                             options=dict(on_progress=progress))

To set a time limit for how long to wait for the call to complete, use the timeout option:

# Wait 10 seconds until giving up
result = await ctx.wamp.call('procedurename', arg1, arg2, arg3='foo', options=dict(timeout=10))

Note

This will not stop the remote handler from finishing; it will just make the client stop waiting and discard the results of the call.

Registering procedure handlers

To register a procedure on the router, create a callable that takes a CallContext as the first argument and use the call() method to register it:

async def procedure_handler(ctx: CallContext, *args, **kwargs):
    ...

await ctx.wamp.register(procedure_handler, 'my_remote_procedure')

The handler can be either an asynchronous function or a regular function, but the latter will obviously have fewer use cases due to the lack of await.

To send progressive results, you can call the progress callback on the CallContext object. For this to work, the caller must have used the on_progress option when making the call. Otherwise progress will be None.

For example:

async def procedure_handler(ctx: CallContext, *args, **kwargs):
    for i in range(1, 11):
        await asyncio.sleep(1)
        if ctx.progress:
            ctx.progress('{}% complete'.format(i * 10))

    return 'Done'

await ctx.wamp.register(procedure_handler, 'my_remote_procedure')

Publishing messages

To publish a message on the router, call publish() with the topic as the first argument and then add any positional and keyword arguments you want to include in the message:

await ctx.wamp.publish('some_topic', 'hello', 'world', another='argument')

By default, publications are not acknowledged by the router. This means that a published message could be silently discarded if, for example, the publisher does not have proper permissions to publish it. To avoid this, use the acknowledge option:

await ctx.wamp.publish('some_topic', 'hello', 'world', another='argument',
                       options=dict(acknowledge=True))

Subscribing to topics

You can use the subscribe() method to receive published messages from the router:

async def subscriber(ctx: EventContext, *args, **kwargs):
    print('new message: args={}, kwargs={}'.format(args, kwargs))

await ctx.wamp.subscribe(subscriber, 'some_topic')

Just like procedure handlers, subscription handlers can be either an asynchronous or regular functions.

Mapping WAMP exceptions to Python exceptions

Exceptions transmitted over WAMP are identified by a specific URI. WAMP errors can be mapped to Python exceptions by linking a specific URI to a specific exception class by means of either exception(), map_exception() or map_exception().

When you map an exception, you can raise it in your procedure or subscription handlers and it will be automatically translated using the given error URI so that the recipients will be able to properly map it on their end as well. Likewise, when a matching error is received from the router, the appropriate exception class is instantiated and raised in the calling code.

Any unmapped exceptions manifest themselves as ApplicationError exceptions.

Using registries to structure your application

While it may at first seem convenient to register every procedure and subscription handler using register() and subscribe(), it does not scale very well when your handlers are distributed over several packages and modules.

The WAMPRegistry class provides an alternative to this. Each registry object stores registered procedure handlers, subscription handlers and mapped exceptions, and can apply defaults on each of these. Each registry can have a separate namespace prefix so you don’t have to repeat it in every single procedure name, topic or mapped error.

Suppose you want to register two procedures and one subscriber, all under the foo prefix and you want to apply the invoke='roundrobin' setting to all procedures:

from asphalt.wamp import WAMPRegistry

registry = WAMPRegistry('foo', procedure_defaults={'invoke': 'roundrobin'})


@registry.procedure
def multiply(ctx, factor1, factor2):
    return factor1 * factor2


@registry.procedure
def divide(ctx, numerator, denominator):
    return numerator / denominator


@registry.subscriber
def message_received(ctx, message):
    print('new message: %s' % message)

To use the registry, pass it to the WAMP component as an option:

class ApplicationComponent(ContainerComponent):
    async def start(ctx):
        ctx.add_component('wamp', registry=registry)
        await super.start(ctx)

This will register the foo.multiply, foo.divide procedures and a subscriptions for the foo.message_received topic.

Say your procedures and/or subscribers are spread over several modules and you want a different namespace for every module, you could have a separate registry in every module and then combine them into a single registry using add_from():

from asphalt.wamp import WAMPRegistry

from myapp.services import accounting, deliveries, production  # these are modules

registry = WAMPRegistry()
registry.add_from(accounting.registry, 'accounting')
registry.add_from(deliveries.registry, 'deliveries')
registry.add_from(production.registry, 'production')

You can set the prefix either in the call to add_from() or when creating the registry of each subsection. Note that if you do both, you end up with two prefixes!