Package: wampify
Wampify - High-Level WAMP frameworkProvides 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 ofautobahn library.Features:High performanceRemote Procedure Calls (RPC)andPublish & Subscribe (PubSub)Payload validationbased on pythonpydantic librarySignals (WAMP session joined, WAMP session leaved, etc...)MiddlewaresBackground tasksSchedulingbased on pythonschedule librarySource code is well documentedIntroductionWeb Application Messaging Protocol (WAMP)is an open standard WebSocket subprotocol that provides two messaging patterns in one Web native protocol:routed Remote Procedure Calls (RPC)Publish & Subscribe (PubSub)TheWebSocket protocolbrings 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 inexample/basic/ directory(a.pyis server side andb.pyis client side). More exampleshere.Before install and configure yourWAMP router. I'm recomending to useCrossbar router. More about crossbar configurationhere.pipinstallcrossbar
crossbarinitpipinstallwampifyInitialize Wampify application, passpreuri(URI prefix), WAMP router URL, WAMP session realm and WAMP session authentication (by default anonymous).fromwampifyimportWampifywampify=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.subscribeasyncdefhello(name:str='Anonymous'):print(f'{name}you are welcome!')if__name__=='__main__':wampify.run()IMPORTANT!Don't forget to disable debug mode in production. (defaultdebug=False)Finally, run routercrossbar startand applicationpython application.pyin 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 segmentIt will executes when someone callcom.example.pow@wampify.registerasyncdefpow(x:float=1):returnx**2But you can disable payload validation@wampify.register(settings={'validate_payload':False})Change to another URI@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 tocom.example.hello@wampify.subscribeasyncdefhello(name:str='Anonymous'):print(f'{name}you are welcome!')AdvancedStoryLike 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?
Justimport * from wampify.storymodule and callget_current_story()in your procedure.Use WAMP session in procedure...fromwampify.storyimport*@wampify.subscribeasyncdefhello(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 tocom.example.hellotopic.Background TasksIt'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 asstory._background_tasks_.add($REQUIRED_PROCEDURE).fromwampifyimportWampify,background_taskfromwampify.storyimport*wampify=Wampify(...)background_task.mount(wampify)asyncdeftask():print('im here')@wampify.registerasyncdefasap():story=get_current_story()story._background_tasks_.add(task)print('background task pushed to queue')if__name__=='__main__':wampify.run()More exampleshere.SchedulingRun anything periodically using a friendly syntax.Before install dependencies. More about libraryhere.pipinstallscheduleMount wampify scheduling module by passing instance of Wampify, then define required procedure and pass it aswampify.schedule.every($INTERVAL).$TIME_UNIT.do($REQUIRED_PROCEDURE)fromwampifyimportWampify,schedulingwampify=Wampify(...)scheduling.mount(wampify)asyncdefsend_message_every_day():...wampify.schedule.every().day.do(send_message_every_day)if__name__=='__main__':wampify.run()More exampleshere.Signals (Events)Signals allow certain senders to notify listeners. For example subscribe to wamp sessionjoinedorleavedevents via@wamps_signals.ondecorator.fromwampifyimportWampifyfromwampify.signalsimportwamps_signalswampify=Wampify(...)@wamps_signals.onasyncdefjoined():...@wamps_signals.onasyncdefleaved():...if__name__=='__main__':wampify.run()More exampleshereMiddlewaresA "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 exampleTimeoutMiddleware. it raisesTimedOuterror if procedure runtime overflownsecondsfromwampifyimportWampifyfromwampify.middleware.timeoutimportTimeoutMiddlewarewampify=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 exampleshereHow to connect SQLAlchemy?fromwampifyimportWampifyfromwampify.signalsimportentrypoint_signalsfromsqlalchemy.ext.asyncioimportcreate_async_enginefromsqlalchemy.ext.asyncioimportAsyncSessionfromsqlalchemy.ormimportsessionmakerwampify=Wampify(...)engine=create_async_engine('postgresql+asyncpg://scott:tiger@localhost/test',echo=True)AlchemySession=sessionmaker(engine,AsyncSession)@entrypoint_signals.onasyncdefopened(story):story.alchemy=AlchemySession()print('SQLAlchemy Async Session initialized')@entrypoint_signals.onasyncdefraised(story,e):awaitstory.alchemy.rollback()awaitstory.alchemy.close()print('SQLAlchemy Async Session rollback')@entrypoint_signals.onasyncdefclosed(story):awaitstory.alchemy.commit()awaitstory.alchemy.close()print('SQLAlchemy Async Session closed')@wampify.subscribeasyncdefhello(name:str='Anonymous'):story=get_current_story()STMT="select * from test"print(awaitstory.alchemy.execute(STMT))...TODO:BenchmarkingUnit testsBetter payload validation for subscriptions and pattern matchingSubscription white/black listingWampify serializerBackground tasks must have wamp sessionProgressive callsIn Memory CacheUvloop supportDocumentationClean ArchAPI Schema generationContributionhttps://stackoverflow.com/users/13774052/aidar-turkenovhttps://stackoverflow.com/questions/tagged/wamphttps://stackoverflow.com/questions/tagged/wampifyhttps://github.com/aturkenov/wampify/discussionshttps://github.com/aturkenov/wampify/issuesa.k.turken0v@gmail.com