Package: lexrpc
lexrpcPython implementation ofAT Protocol'sXRPC+Lexicon. lexrpc includes a simpleXRPCclient, server, andFlaskweb server integration. All three include fullLexiconsupport for validating inputs, outputs, and parameters against their schemas.Install fromPyPIwithpip install lexrpcorpip install lexrpc[flask].License: This project is placed in the public domain. You may also use it under theCC0 License.ClientServerFlask serverReference docsTODORelease instructionsChangelogClientThe lexrpc client let youcall methods dynamically by their NSIDs. To make a call, first instantiate aClient, then use NSIDs to make calls, passing input as a dict and parameters as kwargs. Here's an example of logging into theofficial Bluesky PDSand fetching the user's timeline:fromlexrpcimportClientclient=Client()session=client.com.atproto.server.createSession({'identifier':'snarfed.bsky.social','password':'hunter2',})print('Logged in as',session['did'])timeline=client.app.bsky.feed.getTimeline(limit=10)print('First 10 posts:',json.dumps(timeline,indent=2))By default,Clientconnects to the officialbsky.socialPDS and uses theofficial lexiconsforapp.bskyandcom.atproto. You can connect to a different PDS or use custom lexicons by passing them to theClientconstructor:lexicons=[{"lexicon":1,"id":"com.example.my-procedure","defs":...},...]client=Client('my.server.com',lexicons=lexicons)output=client.com.example.my_procedure({'foo':'bar'},baz=5)Note that-characters in method NSIDs are converted to_s, eg the call above is for the methodcom.example.my-procedure.To call a method with non-JSON (eg binary) input, passbytesto the call instead of adict, and pass the content type withheaders={'Content-Type': '...'}.Event stream methodswith typesubscriptionare generators thatyield(header, payload) tuples sent by the server. They take parameters as kwargs, but no positionalinput.forheader,msginclient.com.example.count(start=1,end=10):print(header['t'])print(msg['num'])ServerTo implement an XRPC server, use theServerclass. It validates parameters, inputs, and outputs. Use themethoddecorator to register method handlers andcallto call them, whether from your web framework or anywhere else.fromlexrpcimportServerserver=Server()@server.method('com.example.my-query')defmy_query(input,num=None):output={'foo':input['foo'],'b':num+1}returnoutput# Extract nsid and decode query parameters from an HTTP request,# call the method, return the output in an HTTP responsensid=request.path.removeprefix('/xrpc/')input=request.json()params=server.decode_params(nsid,request.query_params())output=server.call(nsid,input,**params)response.write_json(output)You can also register a method handler withServer.register:server.register('com.example.my-query', my_query_handler)As withClient, you can use custom lexicons by passing them to theServerconstructor:lexicons = [
  {
    "lexicon": 1,
    "id": "com.example.myQuery",
    "defs": ...
  },
  ...
]
server = Server(lexicons=lexicons)Event stream methodswith typesubscriptionshould be generators thatyieldframes to send to the client.Each frameis a(header dict, payload dict)tuple that will be DAG-CBOR encoded and sent to the websocket client. Subscription methods take parameters as kwargs, but no positionalinput.@server.method('com.example.count')
def count(start=None, end=None):
    for num in range(start, end):
        yield {'num': num}Flask serverTo serve XRPC methods in aFlaskweb app, first install the lexrpc package with theflaskextra, egpip install lexrpc[flask]. Then, instantiate aServerand register method handlers as described above. Finally, attach the server to your Flask app withflask_server.init_flask.fromflaskimportFlaskfromlexrpc.flask_serverimportinit_flask# instantiate a Server like aboveserver=...app=Flask('my-server')init_flask(server,app)This configures the Flask app to serve the methods registered with the lexrpc serveras per the spec. Each method is served at the path/xrpc/[NSID], procedures via POSTs and queries via GETs. Parameters are decoded from query parameters, input is taken from the JSON HTTP request body, and output is returned in the JSON HTTP response body. TheContent-Typeresponse header is set toapplication/json.TODOsupport record types, eg via typerefandreffield pointing to the nsid.example here.check outatproto@63b9873bb1699b6bce54e7a8d3db2fcbd2cfc5ab!extensions. is there anything to do? ah,they're currently TODO in the spec.authenticationRelease instructionsHere's how to package, test, and ship a new release.Run the unit tests.sourcelocal/bin/activate.csh
python3-munittestdiscoverBump the version number inpyproject.tomlanddocs/conf.py.git grepthe old version number to make sure it only appears in the changelog. Change the current changelog entry inREADME.mdfor this new version fromunreleasedto the current date.Build the docs. If you added any new modules, add them to the appropriate file(s) indocs/source/. Then run./docs/build.sh. Check that the generated HTML looks fine by openingdocs/_build/html/index.htmland looking around.git commit -am 'release vX.Y'Upload totest.pypi.orgfor testing.python3-mbuild
setenvverX.Y
twineupload-rpypitestdist/lexrpc-$ver*Install from test.pypi.org.cd/tmp
python3-mvenvlocalsourcelocal/bin/activate.csh
pip3uninstalllexrpc# make sure we force pip to use the uploaded versionpip3install--upgradepip
pip3install-ihttps://test.pypi.org/simple--extra-index-urlhttps://pypi.org/simplelexrpc==$verdeactivateSmoke test that the code trivially loads and runs.sourcelocal/bin/activate.csh
python3# run test code belowdeactivateTest code to paste into the interpreter:fromlexrpcimportServerserver=Server(lexicons=[{'lexicon':1,'id':'io.example.ping','defs':{'main':{'type':'query','description':'Ping the server','parameters':{'message':{'type':'string'}},'output':{'encoding':'application/json','schema':{'type':'object','required':['message'],'properties':{'message':{'type':'string'}},},},},},}])@server.method('io.example.ping')defping(input,message=''):return{'message':message}print(server.call('io.example.ping',{},message='hello world'))Tag the release in git. In the tag message editor, delete the generated comments at bottom, leave the first line blank (to omit the release "title" in github), put### Notable changeson the second line, then copy and paste this version's changelog contents below it.gittag-av$ver--cleanup=verbatim
gitpush&&gitpush--tagsClick here to draft a new release on GitHub.EntervX.Yin theTag versionbox. LeaveRelease titleempty. Copy### Notable changesand the changelog contents into the description text box.Upload topypi.org!twineuploaddist/lexrpc-$ver.tar.gzdist/lexrpc-$ver-py3-none-any.whlWait for the docs to build on Read the Docs, then check that they look ok.On theVersions page, check that the new version is active, If it's not, activate it in theActivate a Versionsection.Changelog0.5 - 2023-12-10Client:Support binary request data automatically based on input type, egdictvsbytes.Add newheaderskwarg tocalland auto-generated lexicon method calls, useful for providing an explicitContent-Typewhen sending binary data.Bug fix: don't infinite loop ifrefreshSessionfails.Other minor authentication bug fixes.0.4 - 2023-10-28Bundlethe official lexiconsforapp.bskyandcom.atproto, use them by default.Base:Expose lexicons indefsattribute.Client:Add minimal auth support withaccess_tokenandrefresh_tokenconstructor kwargs andsessionattribute. If you use aClientto callcom.atproto.server.createSessionorcom.atproto.server.refreshSession, the returned tokens will be automatically stored and used in future requests.Bug fix: handle trailing slash on server address, eghttp://ser.ver/vshttp://ser.ver.Default server address to officialhttps://bsky.socialPDS.Add defaultUser-Agent: lexrpc (https://lexrpc.readthedocs.io/)request header.Server:Add newRedirectclass. Handlers can raise this to indicate that the web server should serve an HTTP redirect.Whether this is official supported by the XRPC spec is still TBD.flask_server:Return HTTP 405 error on HTTP requests to subscription (websocket) XRPCs.Support the newRedirectexception.Add theerrorfield to the JSON response bodies for most error responses.0.3 - 2023-08-29Add array type support.Add support for non-JSON input and output encodings.Addsubscriptionmethod type support over websockets.Addheaderskwarg toClientconstructor.Add newServer.registermethod for manually registering handlers.Bug fix for server@methoddecorator.0.2 - 2023-03-13Bluesky's Lexicon design and schema handling is still actively changing, so this is an interim release. It generally supports the current lexicon design, but not full schema validation yet. I'm not yet trying to fast follow the changes too closely; as they settle down and stabilize, I'll put more effort into matching and fully implementing them. Stay tuned!Breaking changes:Fully migrate tonew lexicon format. Original format is no longer supported.0.1 - 2022-12-13Initial release!Tested interoperability with thelexicon,xprc, andxrpc-serverpackages inbluesky-social/atproto. Lexicon and XRPC themselves are still very early and under active development; caveat hacker!