diff --git a/README.md b/README.md index 69a136f..83dfb79 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ Here's how to package, test, and ship a new release. (Note that this is [largely ```sh python3 -m build setenv ver X.Y - twine upload -r pypitest dist/lexrpc-$ver.tar.gz + twine upload -r pypitest dist/lexrpc-$ver* ``` 1. Install from test.pypi.org. ```sh @@ -142,7 +142,7 @@ Here's how to package, test, and ship a new release. (Note that this is [largely def ping(input, message=''): return {'message': message} - print(server.call('io.example.ping', message='hello world') + print(server.call('io.example.ping', {}, message='hello world')) ``` 1. 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 changes` on the second line, then copy and paste this version's changelog contents below it. ```sh @@ -152,7 +152,7 @@ Here's how to package, test, and ship a new release. (Note that this is [largely 1. [Click here to draft a new release on GitHub.](https://github.com/snarfed/lexrpc/releases/new) Enter `vX.Y` in the _Tag version_ box. Leave _Release title_ empty. Copy `### Notable changes` and the changelog contents into the description text box. 1. Upload to [pypi.org](https://pypi.org/)! ```sh - twine upload dist/lexrpc-$ver.tar.gz + twine upload dist/lexrpc-$ver* ``` 1. [Wait for the docs to build on Read the Docs](https://readthedocs.org/projects/lexrpc/builds/), then check that they look ok. 1. On the [Versions page](https://readthedocs.org/projects/lexrpc/versions/), check that the new version is active, If it's not, activate it in the _Activate a Version_ section. @@ -160,6 +160,8 @@ Here's how to package, test, and ship a new release. (Note that this is [largely ## Changelog -### 0.1 - unreleased +### 0.1 - 2022-12-13 Initial release! + +Tested interoperability with the `lexicon`, `xprc`, and `xrpc-server` packages in [bluesky-social/atproto](https://github.com/bluesky-social/atproto). Lexicon and XRPC are still very early and under active development; caveat hacker! diff --git a/docs/index.rst b/docs/index.rst index 195f0a1..5c55d11 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,95 +3,95 @@ lexrpc Python implementation of `AT Protocol `__\ ’s `XRPC `__ + -`Lexicon `__, both client and -server. `Docs here. `__ - -- `Getting started <#getting-started>`__ - - - `Client <#client>`__ - - `Server <#server>`__ - - `Flask server <#flask-server>`__ - +`Lexicon `__. lexrpc includes a +simple `XRPC `__ client, server, and +`Flask `__ web server integration. +All three include full `Lexicon `__ +support for validating inputs, outputs, and parameters against their +schemas. + +- `Client <#client>`__ +- `Server <#server>`__ +- `Flask server <#flask-server>`__ +- `Reference `__ - `TODO <#todo>`__ - `Changelog <#changelog>`__ License: This project is placed in the public domain. -Getting started ---------------- - -lexrpc includes a simple `XRPC `__ -client, server, and `Flask `__ web -server integration. All three include full -`Lexicon `__ support for validating -inputs, outputs, and parameters against their schemas. - Client -~~~~~~ +------ The lexrpc client let you `call methods dynamically by their NSIDs `__. To make a call, first instantiate a `Client `__ -object with the server address and method lexicons, then use NSIDs to -make calls, passing input as a dict and parameters as kwargs: +object with the server address and method lexicons, then use method +NSIDs to make calls, passing input as a dict and parameters as kwargs: .. code:: py - from jsonschema import Client + from lexrpc import Client lexicons = [...] client = Client('https://xrpc.example.com', lexicons) - output = client.com.example.my_query({'foo': 'bar'}, a_param=False) + output = client.com.example.my_query({'foo': 'bar'}, param_a=5) Note that ``-`` characters in method NSIDs are converted to ``_``\ s, eg the call above is for the method ``com.example.my-query``. Server -~~~~~~ +------ To implement an XRPC server, use the `Server `__ class. It validates parameters, inputs, and outputs. Use the `method `__ -decorator to register handlers for each NSID, -`Server.call `__ -to call a method from your web framework or anywhere else. +decorator to register method handlers and +`call `__ +to call them, whether from your web framework or anywhere else. .. code:: py - from jsonschema import Server + from lexrpc import Server lexicons = [...] server = Server(lexicons) @server.method('com.example.my-query') def my_query_hander(input, **params): - ... - output = ... + output = {'foo': input['foo'], 'b': params['param_a'] + 1} return output - # In your web framework, use Server.call to run a method + # Extract nsid and decode query parameters from an HTTP request, + # call the method, return the output in an HTTP response + nsid = request.path.removeprefix('/xrpc/') input = request.json() - params = request.query_params() - # decode params + params = server.decode_params(nsid, request.query_params()) output = server.call(input, **params) response.write_json(output) Flask server -~~~~~~~~~~~~ +------------ -First, instantiate a ``Server`` and register method handlers as -described above. Then, attach the server to your Flask app with -`lexrpc.init_flask `__. +To serve XRPC methods in a +`Flask `__ web app, first install +the lexrpc package with the ``flask`` extra, eg +``pip install lexrpc[flask]``. Then, instantiate a +`Server `__ +and register method handlers as described above. Finally, attach the +server to your Flask app with +`flask_server.init_flask `__. .. code:: py from flask import Flask - from lexrpc import init_flask + from lexrpc.flask_server import init_flask + + # instantiate a Server like above + server = ... app = Flask('my-server') - ... init_flask(server, app) This configures the Flask app to serve the methods registered with the @@ -105,20 +105,104 @@ set to ``application/json``. TODO ---- -- validate records/tokens in input/output? or are those only - primitives? -- extensions, https://atproto.com/guides/lexicon#extensibility . is - there anything to do? ah, it’s currently TODO in the spec: - https://atproto.com/specs/xrpc#todos -- “binary blob” support, as in https://atproto.com/specs/xrpc . is it - currently undefined? -- authentication, currently TODO in the spec: - https://atproto.com/specs/xrpc#todos +- support record types, eg via type “ref” and ref field pointing to the + nsid `example + here `__, + ref points to + `app.bsky.actor.ref `__. + ref isn’t documented yet though, and these lexicons also use a + ``defs`` field, which isn’t really documented either. `they plan to + update the docs and specs + soon. `__ +- `extensions `__. is + there anything to do? ah, `they’re currently TODO in the + spec `__. +- `“binary blob” support. `__ currently + undefined ish? is it based on the ``encoding`` field? +- `authentication, currently TODO in the + spec `__ + +Release instructions +-------------------- + +Here’s how to package, test, and ship a new release. (Note that this is +`largely duplicated in the oauth-dropins readme +too `__.) + +1. Run the unit tests. + ``sh source local/bin/activate.csh python3 -m unittest discover`` + +2. Bump the version number in ``setup.py`` and ``docs/conf.py``. + ``git grep`` the old version number to make sure it only appears in + the changelog. Change the current changelog entry in ``README.md`` + for this new version from *unreleased* to the current date. + +3. Build the docs. If you added any new modules, add them to the + appropriate file(s) in ``docs/source/``. Then run + ``./docs/build.sh``. Check that the generated HTML looks fine by + opening ``docs/_build/html/index.html`` and looking around. + +4. ``git commit -am 'release vX.Y'`` + +5. Upload to `test.pypi.org `__ for testing. + ``sh python3 -m build setenv ver X.Y twine upload -r pypitest dist/lexrpc-$ver*`` + +6. Install from test.pypi.org. + ``sh cd /tmp python3 -m venv local source local/bin/activate.csh pip3 uninstall lexrpc # make sure we force pip to use the uploaded version pip3 install --upgrade pip pip3 install -i https://test.pypi.org/simple --extra-index-url https://pypi.org/simple lexrpc==$ver deactivate`` + +7. Smoke test that the code trivially loads and runs. + ``sh source local/bin/activate.csh python3 # run test code below deactivate`` + Test code to paste into the interpreter: \`py from lexrpc import + Server + + server = Server([{ ‘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’) def ping(input, message=’‘): + return {’message’: message} + + print(server.call(‘io.example.ping’, {}, message=‘hello world’)) + \``\` + +8. 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 changes`` on the + second line, then copy and paste this version’s changelog contents + below it. + ``sh git tag -a v$ver --cleanup=verbatim git push && git push --tags`` + +9. `Click here to draft a new release on + GitHub. `__ Enter + ``vX.Y`` in the *Tag version* box. Leave *Release title* empty. Copy + ``### Notable changes`` and the changelog contents into the + description text box. + +10. Upload to `pypi.org `__! + ``sh twine upload dist/lexrpc-$ver*`` + +11. `Wait for the docs to build on Read the + Docs `__, then + check that they look ok. + +12. On the `Versions + page `__, check + that the new version is active, If it’s not, activate it in the + *Activate a Version* section. Changelog --------- -0.1 - unreleased +0.1 - 2022-12-13 ~~~~~~~~~~~~~~~~ Initial release! + +Tested interoperability with the ``lexicon``, ``xprc``, and +``xrpc-server`` packages in +`bluesky-social/atproto `__. +Lexicon and XRPC are still very early and under active development; +caveat hacker!