Metadata-Version: 2.1
Name: wampify
Version: 0.0.6
Summary: Web Application Messaging Protocol Framework
Home-page: https://github.com/aturkenov/wampify
Author: Aidar Turkenov
Author-email: a.k.turken0v@gmail.com
License: UNKNOWN
Project-URL: Bug Tracker, https://github.com/aturkenov/wampify/issues
Project-URL: Discussions, https://github.com/aturkenov/wampify/discussions
Platform: UNKNOWN
Classifier: Typing :: Typed
Classifier: Topic :: Internet
Classifier: Topic :: Communications
Classifier: Framework :: AsyncIO
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.6
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: autobahn
Requires-Dist: pydantic

# Wampify - High-Level WAMP framework

![license](https://img.shields.io/github/license/aturkenov/wampify)
![package-version](https://img.shields.io/pypi/v/wampify)
![pyversions](https://img.shields.io/pypi/pyversions/wampify)

Provides an opportunity for better interaction with Web Application Messaging Protocol. It offers a variety of tools (background tasks, scheduling, signals, middlewares, etc...) that enable one to create a platform of independent applications. Built on top of [autobahn library](https://autobahn.readthedocs.io/en/latest/index.html).

## Features:
- [High performance](https://github.com/aturkenov/wampify/tree/main/example/benchmark)
- [Remote Procedure Calls (RPC)](https://github.com/aturkenov/wampify#remote-procedure-call-rpc) and [Publish & Subscribe (PubSub)](https://github.com/aturkenov/wampify#publish--subscribe-pubsub)
- [Payload validation](https://pydantic-docs.helpmanual.io/usage/validation_decorator/) based on python [pydantic library](https://pydantic-docs.helpmanual.io)
- [Signals (WAMP session joined, WAMP session leaved, etc...)](https://github.com/aturkenov/wampify#signals)
- [Middlewares](https://github.com/aturkenov/wampify#custom-middlewares)
- [Background tasks](https://github.com/aturkenov/wampify#backgroud-tasks)
- [Scheduling](https://github.com/aturkenov/wampify#scheduling) based on python [schedule library]()
- Source code is well documented

# Introduction

[Web Application Messaging Protocol (WAMP)](https://wamp-proto.org/intro.html) is an open standard WebSocket subprotocol that provides two messaging patterns in one Web native protocol:

- [routed Remote Procedure Calls (RPC)](https://github.com/aturkenov/wampify#remote-procedure-call-rpc)
- [Publish & Subscribe (PubSub)](https://github.com/aturkenov/wampify#publish--subscribe-pubsub)

The [WebSocket protocol](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) brings bi-directional (soft) real-time and wire traffic efficient connections to the browser. Today (2022) WebSocket is universally supported in browsers, network equipment, servers and client languages.

Using WAMP you can build distributed systems which are loosely coupled and communicate in (soft) real-time.

`INFO!` Full source code in [example/basic/ directory](https://github.com/aturkenov/wampify/tree/main/example/basic) (`a.py` is server side and `b.py` is client side). More examples [here](https://github.com/aturkenov/wampify/tree/main/example).

Before install and configure your [WAMP router](https://wamp-proto.org/implementations.html#routers). I'm recomending to use [Crossbar router](https://crossbar.io). More about crossbar configuration [here](https://crossbar.io/docs/Getting-Started/#crossbar-configuration).

```bash
pip install crossbar
crossbar init
```

```bash
pip install wampify
```

Initialize Wampify application, pass `preuri` (URI prefix), WAMP router URL, WAMP session realm and WAMP session authentication (by default anonymous).

```python
from wampify import Wampify

wampify = Wampify(
    debug=True,
    preuri='com.example',
    router={
        'url': 'ws://127.0.0.1:8080/private'
    },
    wamps={
        'realm': 'example',
        'authid': 'application',
        'authmethods': ['anonymous'],
        'authrole': 'private',
        'show_registered': True,
        'show_subscribed': True
    }
)

@wampify.subscribe
async def hello(name: str = 'Anonymous'):
    print(f'{name} you are welcome!')

if __name__ == '__main__':
    wampify.run()
```

`IMPORTANT!` Don't forget to disable debug mode in production. (default `debug=False`)

Finally, run router `crossbar start` and application `python application.py` in different terminal sessions.

## Remote Procedure Calls (RPC)

A Caller issues calls to remote procedures by providing the procedure URI and any arguments for the call. The Callee will execute the procedure using the supplied arguments to the call and return the result of the call to the Caller.

Callees register procedures they provide with Dealers. Callers initiate procedure calls first to Dealers. Dealers route calls incoming from Callers to Callees implementing the procedure called, and route call results back from Callees to Callers.

By default Wampify validates input payload if type annotations are defined and takes procedure name as URI segment

It will executes when someone call `com.example.pow`

```python
@wampify.register
async def pow(x: float = 1):
    return x ** 2
```

But you can disable payload validation

```python
@wampify.register(settings={ 'validate_payload': False })
```

Change to another URI

```python
@wampify.register('math.square')
```

## Publish & Subscribe (PubSub)

A Publishers publishes events to topics by providing the topic URI and any payload for the event. Subscribers of the topic will receive the event together with the event payload.

Subscribers subscribe to topics they are interested in with Brokers. Publishers initiate publication first at Brokers. Brokers route events incoming from Publishers to Subscribers that are subscribed to respective topics.

It will executes when someone publish something to `com.example.hello`

```python
@wampify.subscribe
async def hello(name: str = 'Anonymous'):
    print(f'{name} you are welcome!')
```

# Advanced

## Story

Like Request object Story represents request details: caller or publisher details (if not disclosed), sent time and event type.

All required resources (as WAMP Session, Background tasks, ...) bind to Story instance.

How to get current Story instance?
Just `import * from wampify.story` module and call `get_current_story()` in your procedure.

### Use WAMP session in procedure

```python
...
from wampify.story import *

@wampify.subscribe
async def hello(name: str = 'Anonymous'):
    story = get_current_story()
    story._wamps_.call(
        'com.another_application.client.counter.increment'
    )
    print(f'{name} you are welcome!')

...
```

This example increment clients counter in another application (or microservice) via calling remote procedure, when someone publishes event to `com.example.hello` topic.

## Background Tasks

It's useful for operations that need to happen after a request, but that client doesn't really have to be waiting for operation to complete before receiving response. Also cpu intensive operations can be executed in separated thread.

Mount wampify background task module by passing instance of Wampify, then define required procedure and pass it as `story._background_tasks_.add($REQUIRED_PROCEDURE)`.

```python
from wampify import Wampify, background_task
from wampify.story import *

wampify = Wampify(...)

background_task.mount(wampify)

async def task():
    print('im here')

@wampify.register
async def asap():
    story = get_current_story()
    story._background_tasks_.add(task)
    print('background task pushed to queue')

if __name__ == '__main__':
    wampify.run()
```

More examples [here](https://github.com/aturkenov/wampify/tree/main/example/background_task/).

## Scheduling

Run anything periodically using a friendly syntax.

Before install dependencies. More about library [here](https://schedule.readthedocs.io/en/stable/).

```bash
pip install schedule
```

Mount wampify scheduling module by passing instance of Wampify, then define required procedure and pass it as `wampify.schedule.every($INTERVAL).$TIME_UNIT.do($REQUIRED_PROCEDURE)`

```python
from wampify import Wampify, scheduling

wampify = Wampify(...)

scheduling.mount(wampify)

async def send_message_every_day(): ...

wampify.schedule.every().day.do(send_message_every_day)

if __name__ == '__main__':
    wampify.run()
```

More examples [here](https://github.com/aturkenov/wampify/tree/main/example/scheduling/).

## Signals (Events)

Signals allow certain senders to notify listeners. For example subscribe to wamp session `joined` or `leaved` events via `@wamps_signals.on` decorator.

```python
from wampify import Wampify
from wampify.signals import wamps_signals

wampify = Wampify(...)

@wamps_signals.on
async def joined(): ...

@wamps_signals.on
async def leaved(): ...

if __name__ == '__main__':
    wampify.run()
```

More examples [here](https://github.com/aturkenov/wampify/tree/main/example/signal/)

## Middlewares

A "middleware" is a behavior that works with every request before it is processed by any specific procedure. And also with every response before returning it.

For example `TimeoutMiddleware`. it raises `TimedOut` error if procedure runtime overflow `n` seconds

```python
from wampify import Wampify
from wampify.middleware.timeout import TimeoutMiddleware

wampify = Wampify(
    debug=False,
    preuri='com.example',
    router={ 'url': 'ws://127.0.0.1:8765/private' },
    wamps={
        'realm': 'example',
        'show_registered': True,
        'show_subscribed': True
    },
    middlewares={
        'timeout': { 'duration': 60 }
    }
)

wampify.add_middleware(TimeoutMiddleware)

if __name__ == '__main__':
    wampify.run()
```

More examples [here](https://github.com/aturkenov/wampify/tree/main/example/middleware/)

## How to connect SQLAlchemy?

```python
from wampify import Wampify
from wampify.signals import entrypoint_signals

from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import sessionmaker

wampify = Wampify(...)

engine = create_async_engine('postgresql+asyncpg://scott:tiger@localhost/test', echo=True)

AlchemySession = sessionmaker(engine, AsyncSession)

@entrypoint_signals.on
async def opened(story):
    story.alchemy = AlchemySession()
    print('SQLAlchemy Async Session initialized')

@entrypoint_signals.on
async def raised(story, e):
    await story.alchemy.rollback()
    await story.alchemy.close()
    print('SQLAlchemy Async Session rollback')

@entrypoint_signals.on
async def closed(story):
    await story.alchemy.commit()
    await story.alchemy.close()
    print('SQLAlchemy Async Session closed')

@wampify.subscribe
async def hello(name: str = 'Anonymous'):
    story = get_current_story()
    STMT = "select * from test"
    print(await story.alchemy.execute(STMT))

...
```

# TODO:

- Benchmarking
- Unit tests
- Better payload validation for subscriptions and pattern matching
- Subscription white/black listing
- Wampify serializer
- Background tasks must have wamp session
- Progressive calls
- In Memory Cache
- Uvloop support
- Documentation
- Clean Arch
- API Schema generation

# Contribution

https://stackoverflow.com/users/13774052/aidar-turkenov

https://stackoverflow.com/questions/tagged/wamp

https://stackoverflow.com/questions/tagged/wampify

https://github.com/aturkenov/wampify/discussions

https://github.com/aturkenov/wampify/issues

a.k.turken0v@gmail.com

