Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/smagafurov/fastapi-jsonrpc
JSON-RPC server based on fastapi
https://github.com/smagafurov/fastapi-jsonrpc
asgi fastapi json-rpc json-rpc-server openapi pydantic starlette swagger
Last synced: 3 months ago
JSON representation
JSON-RPC server based on fastapi
- Host: GitHub
- URL: https://github.com/smagafurov/fastapi-jsonrpc
- Owner: smagafurov
- License: mit
- Created: 2019-08-09T17:56:48.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2024-09-24T19:52:48.000Z (4 months ago)
- Last Synced: 2024-09-24T20:07:51.607Z (4 months ago)
- Topics: asgi, fastapi, json-rpc, json-rpc-server, openapi, pydantic, starlette, swagger
- Language: Python
- Size: 546 KB
- Stars: 287
- Watchers: 15
- Forks: 28
- Open Issues: 8
-
Metadata Files:
- Readme: README.rst
- License: LICENSE
Awesome Lists containing this project
- awesome-fastapi - JSON-RPC Server - JSON-RPC server based on FastAPI. (Projects / Open Source Projects)
- best-of-web-python - GitHub - 17% open · ⏱️ 12.03.2024): (RPC Utilities)
- awesome-fastapi - JSON-RPC Server - JSON-RPC server based on FastAPI. (Projects / Open Source Projects)
README
|tests|
.. |tests| image:: https://github.com/smagafurov/fastapi-jsonrpc/actions/workflows/tests.yml/badge.svg
:target: https://github.com/smagafurov/fastapi-jsonrpc/actions/workflows/tests.ymlDescription
===========JSON-RPC server based on fastapi:
https://fastapi.tiangolo.com
OpenRPC supported too.
Motivation
^^^^^^^^^^Autogenerated **OpenAPI** and **Swagger** (thanks to fastapi) for JSON-RPC!!!
Installation
============.. code-block:: bash
pip install fastapi-jsonrpc
Documentation
=============Read FastAPI documentation and see usage examples bellow
Simple usage example
====================.. code-block:: bash
pip install uvicorn
example1.py
.. code-block:: python
import fastapi_jsonrpc as jsonrpc
from pydantic import BaseModel
from fastapi import Bodyapp = jsonrpc.API()
api_v1 = jsonrpc.Entrypoint('/api/v1/jsonrpc')
class MyError(jsonrpc.BaseError):
CODE = 5000
MESSAGE = 'My error'class DataModel(BaseModel):
details: str@api_v1.method(errors=[MyError])
def echo(
data: str = Body(..., examples=['123']),
) -> str:
if data == 'error':
raise MyError(data={'details': 'error'})
else:
return dataapp.bind_entrypoint(api_v1)
if __name__ == '__main__':
import uvicorn
uvicorn.run('example1:app', port=5000, debug=True, access_log=False)OpenRPC:
http://127.0.0.1:5000/openrpc.json
Swagger:
http://127.0.0.1:5000/docs
FastAPI dependencies usage example
==================================.. code-block:: bash
pip install uvicorn
example2.py
.. code-block:: python
import logging
from contextlib import asynccontextmanagerfrom pydantic import BaseModel, Field
import fastapi_jsonrpc as jsonrpc
from fastapi import Body, Header, Dependslogger = logging.getLogger(__name__)
# database models
class User:
def __init__(self, name):
self.name = namedef __eq__(self, other):
if not isinstance(other, User):
return False
return self.name == other.nameclass Account:
def __init__(self, account_id, owner, amount, currency):
self.account_id = account_id
self.owner = owner
self.amount = amount
self.currency = currencydef owned_by(self, user: User):
return self.owner == user# fake database
users = {
'1': User('user1'),
'2': User('user2'),
}accounts = {
'1.1': Account('1.1', users['1'], 100, 'USD'),
'1.2': Account('1.2', users['1'], 200, 'EUR'),
'2.1': Account('2.1', users['2'], 300, 'USD'),
}def get_user_by_token(auth_token) -> User:
return users[auth_token]def get_account_by_id(account_id) -> Account:
return accounts[account_id]# schemas
class Balance(BaseModel):
"""Account balance"""
amount: int = Field(..., example=100)
currency: str = Field(..., example='USD')# errors
class AuthError(jsonrpc.BaseError):
CODE = 7000
MESSAGE = 'Auth error'class AccountNotFound(jsonrpc.BaseError):
CODE = 6000
MESSAGE = 'Account not found'class NotEnoughMoney(jsonrpc.BaseError):
CODE = 6001
MESSAGE = 'Not enough money'class DataModel(BaseModel):
balance: Balance# dependencies
def get_auth_user(
# this will become the header-parameter of json-rpc method that uses this dependency
auth_token: str = Header(
None,
alias='user-auth-token',
),
) -> User:
if not auth_token:
raise AuthErrortry:
return get_user_by_token(auth_token)
except KeyError:
raise AuthErrordef get_account(
# this will become the parameter of the json-rpc method that uses this dependency
account_id: str = Body(..., example='1.1'),
user: User = Depends(get_auth_user),
) -> Account:
try:
account = get_account_by_id(account_id)
except KeyError:
raise AccountNotFoundif not account.owned_by(user):
raise AccountNotFoundreturn account
# JSON-RPC middlewares
@asynccontextmanager
async def logging_middleware(ctx: jsonrpc.JsonRpcContext):
logger.info('Request: %r', ctx.raw_request)
try:
yield
finally:
logger.info('Response: %r', ctx.raw_response)# JSON-RPC entrypoint
common_errors = [AccountNotFound, AuthError]
common_errors.extend(jsonrpc.Entrypoint.default_errors)api_v1 = jsonrpc.Entrypoint(
# Swagger shows for entrypoint common parameters gathered by dependencies and common_dependencies:
# - json-rpc-parameter 'account_id'
# - header parameter 'user-auth-token'
'/api/v1/jsonrpc',
errors=common_errors,
middlewares=[logging_middleware],
# this dependencies called once for whole json-rpc batch request
dependencies=[Depends(get_auth_user)],
# this dependencies called separately for every json-rpc request in batch request
common_dependencies=[Depends(get_account)],
)# JSON-RPC methods of this entrypoint
# this json-rpc method has one json-rpc-parameter 'account_id' and one header parameter 'user-auth-token'
@api_v1.method()
def get_balance(
account: Account = Depends(get_account),
) -> Balance:
return Balance(
amount=account.amount,
currency=account.currency,
)# this json-rpc method has two json-rpc-parameters 'account_id', 'amount' and one header parameter 'user-auth-token'
@api_v1.method(errors=[NotEnoughMoney])
def withdraw(
account: Account = Depends(get_account),
amount: int = Body(..., gt=0, example=10),
) -> Balance:
if account.amount - amount < 0:
raise NotEnoughMoney(data={'balance': get_balance(account)})
account.amount -= amount
return get_balance(account)# JSON-RPC API
app = jsonrpc.API()
app.bind_entrypoint(api_v1)if __name__ == '__main__':
import uvicorn
uvicorn.run('example2:app', port=5000, debug=True, access_log=False)OpenRPC:
http://127.0.0.1:5000/openrpc.json
Swagger:
http://127.0.0.1:5000/docs
.. image:: ./images/fastapi-jsonrpc.png
Development
===========* Install poetry
https://github.com/sdispater/poetry#installation
* Install dependencies
.. code-block:: bash
poetry update
* Regenerate README.rst
.. code-block:: bash
rst_include include -q README.src.rst README.rst
* Change dependencies
Edit ``pyproject.toml``
.. code-block:: bash
poetry update
* Bump version
.. code-block:: bash
poetry version patch
poetry version minor
poetry version major* Publish to pypi
.. code-block:: bash
poetry publish --build