JSON-RPC server based on fastapi

Overview

Description

JSON-RPC server based on fastapi:

https://fastapi.tiangolo.com

Motivation

Autogenerated OpenAPI and Swagger (thanks to fastapi) for JSON-RPC!!!

Installation

pip install fastapi-jsonrpc

Documentation

Read FastAPI documentation and see usage examples bellow

Simple usage example

pip install uvicorn

example1.py

import fastapi_jsonrpc as jsonrpc
from pydantic import BaseModel
from fastapi import Body


app = 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(..., example='123'),
) -> str:
    if data == 'error':
        raise MyError(data={'details': 'error'})
    else:
        return data


app.bind_entrypoint(api_v1)


if __name__ == '__main__':
    import uvicorn
    uvicorn.run('example1:app', port=5000, debug=True, access_log=False)

Go to:

http://127.0.0.1:5000/docs

FastAPI dependencies usage example

pip install uvicorn

example2.py

import logging
from contextlib import asynccontextmanager

from pydantic import BaseModel, Field
import fastapi_jsonrpc as jsonrpc
from fastapi import Body, Header, Depends


logger = logging.getLogger(__name__)


# database models

class User:
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        if not isinstance(other, User):
            return False
        return self.name == other.name


class Account:
    def __init__(self, account_id, owner, amount, currency):
        self.account_id = account_id
        self.owner = owner
        self.amount = amount
        self.currency = currency

    def 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 AuthError

    try:
        return get_user_by_token(auth_token)
    except KeyError:
        raise AuthError


def 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 AccountNotFound

    if not account.owned_by(user):
        raise AccountNotFound

    return 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)

Go to:

http://127.0.0.1:5000/docs

./images/fastapi-jsonrpc.png

Development

  • Install poetry

    https://github.com/sdispater/poetry#installation

  • Install dephell

    pip install dephell
  • Install dependencies

    poetry update
  • Regenerate README.rst

    rst_include include -q README.src.rst README.rst
  • Change dependencies

    Edit pyproject.toml

    poetry update
    dephell deps convert
  • Bump version

    poetry version patch
    dephell deps convert
  • Publish to pypi

    poetry publish --build
Issues
  • В swagger в Example Value выставляется неверное значение версии jsonrpc

    В swagger в Example Value выставляется неверное значение версии jsonrpc

    В swagger в примере запросе поле "jsonrpc" выставляется равным 2, как число. Необходимо, чтобы выставлялось "2.0", как строка.

    image image

    Есть предположение что, если тут значение example='"2.0"', то тогда swagger автоматом не преобразовывал бы это значение в численное.

    Версия библиотеки fastapi-jsonrpc==2.0.2, pydantic==1.7.3

    opened by DianaArapova 6
  • Allow using custom request class

    Allow using custom request class

    Hey

    My team and I came across a use case where we needed to allow some extra fields in the first level of the Json-RPC payload request.

    So I added an option to use a custom JsonRpcRequest class, inheriting from the initial fastapi-jsonrpc's JsonRpcRequest class.

    The usage is the following:

    
    import fastapi_jsonrpc as jsonrpc
    
    from fastapi.middleware.cors import CORSMiddleware
    from fastapi import Body
    from fastapi_jsonrpc import JsonRpcRequest
    
    app = jsonrpc.API()
    
    app.add_middleware(CORSMiddleware, allow_origins=["*"])
    
    
    class CustomJsonRpcRequest(JsonRpcRequest):
        extravalue: str
    
    api_v1 = jsonrpc.Entrypoint("/api", request_class=CustomJsonRpcRequest)
    
    
    @api_v1.method()
    def echo(
        data: str = Body(..., example="123"),
    ) -> dict:
        return {"from": "echo", "data": data}
    
    
    app.bind_entrypoint(api_v1)
    

    This example allows receiving this payload:

    {
      id: 0,
      jsonrpc: "2.0",
      method: "echo",
      params: {data: "testdata"},
      extravalue: "test"
    }
    

    I hope you'll find this feature useful 👍.

    opened by Smlep 4
  • Logging access

    Logging access

    Because client makes post on only one route it's hard to tell what is request

    Maybe it's not about this library, but it would be really useful to have this feature out of the box.

    opened by denisSurkov 3
  • Loosen version requirements on FastApi

    Loosen version requirements on FastApi

    This lib is (most likely) needlessly restricted to 0.55.x rather than 0.55+ and this gets rid of that restriction making it compatible with 0.56, 57, 58 etc

    This PR closes #9

    May I add this is blocking adoption for anyone not using specifically version 0.55 of fastapi on poetry (and potentially more) as there is no simple way to override version solving issues. For now I have vendored it and await for a version bump.

    Thanks for the lib!

    opened by arlyon 3
  • Possible to have a function with json return type or no return type at all act as a route?

    Possible to have a function with json return type or no return type at all act as a route?

    So I want to use you jsonrpc implementation for its numerous benefits, but when I try to combine it with some functions that return JSON strings I cannot seem to get the response configuration right. When I run it with json return type I get an error, when I try to use a class implementing BaseModel and containing the json element, then it runs the server but responses error the server out. If you want to see the code, its basically:

    from pydantic import json
    @api_v1.method()
    def consistency(past_root:str) -> json:
    

    which I would be fine removing the json return type, but the value it responds with to the client should absolutely be this json string. I feel that I am missing something, being a beginner at fastapi and your library as well. The code in question can be found here.

    opened by csajedi 2
  • fix(openapi): no more conflicts in openapi components which leads to missing models in openapi.json or openapi generation error in rare cases

    fix(openapi): no more conflicts in openapi components which leads to missing models in openapi.json or openapi generation error in rare cases

    pydantic.schema.get_long_model_name use qualname to solve conflicts, if we do not respect this value get_model_name_map can replace some models due same qualname and this is root of problem

    opened by spumer 2
  • License

    License

    Is this project Open Source? If you don't add a license, it's not Open Source even if the code is public. By default, everything is copyrighted (all right reserved) even if not explicitly written. That's why there are licenses that circumvent that.

    opened by jcubic 2
  • Refactored clone_dependant

    Refactored clone_dependant

    Old version: In case if Dependant model will change you have to add new lines to function New version: Everything will be copied automatically

    Fixing #4

    opened by denisSurkov 1
  • Add log_handled_exceptions_resp option

    Add log_handled_exceptions_resp option

    Hey 👋

    We have a use case in which it would be nice to easily be able to log all response handled exceptions, which are errors inheriting from BaseError.

    So I implemented an option for this.

    opened by Smlep 1
  • Add bind_entrypoints option to reproduce FastApi's router feature

    Add bind_entrypoints option to reproduce FastApi's router feature

    closes #18

    Fastapi offers a router functionality detailed here. It can be really useful to split the methods into many files.

    This PR adds an option to bind_entrypoint so that multiple Entrypoint with the same path can be merged.

    Here is an example of how I would use this:

    # main.py
    from items import items_router
    from products import products_router
    
    
    app = jsonrpc.API()
    
    app.bind_entrypoint(items_router)
    
    app.bind_entrypoint(products_router, add_to_existing_path=True)
    
    
    # items.py
    import fastapi_jsonrpc as jsonrpc
    
    items_router = jsonrpc.Entrypoint("/api")
    
    
    @items_router.method()
    def list_items() -> dict:
        return {"from": "list_items"}
    
    # products.py
    import fastapi_jsonrpc as jsonrpc
    
    products_router = jsonrpc.Entrypoint("/api")
    
    
    @products_router.method()
    def list_products() -> dict:
        return {"from": "list_products"}
    

    With this example, both list_items and list_products methods can be accessed from /api.

    I have also added some tests for this feature.

    opened by Smlep 0
  • tests: Support fastapi 0.64.0

    tests: Support fastapi 0.64.0

    closes #25

    This fixes the issues related to the updates of openapi doc generation in fastapi 0.64.0.

    This allows us to bump fastapi to 0.66.0

    opened by Smlep 0
  • Support Fastapi 0.64: missing data examples in openapi schema

    Support Fastapi 0.64: missing data examples in openapi schema

    openapi tests failed after fastapi 0.64 released: https://github.com/tiangolo/fastapi/releases/tag/0.64.0

    bug help wanted 
    opened by spumer 0
  • Allow using routers to split the code

    Allow using routers to split the code

    Hi,

    Fastapi offers a router functionality detailed here. It can be really useful to split the methods into many files.

    It would be nice to be able to do the same things with the JSONRPC methods. Currently, since Entrypoint inherits from fastapi's APIRouter, trying to bind another Entrypoint with the same path won't allow access to the methods from the second Entrypoint.

    There should be a way to bind 2 Entrypoint with the same path to one app, allowing both of their methods to be considered.

    Really great library btw, good work!

    opened by Smlep 3
  • Websocket support

    Websocket support

    Nice library! I was wondering if there are any plans to also support websockets (which is a nice extra fastapi provides), like these libraries do?:

    https://jsonrpcclient.readthedocs.io/en/latest/ https://github.com/codemation/easyrpc

    opened by pjotterplotter 1
  • Missing description of Params

    Missing description of Params

    Add docs for Params usage

    opened by denisSurkov 0
  • clone_dependant() fakes cloning

    clone_dependant() fakes cloning

    The problem: clone_dependant() seems to copy the dependant, but it's not. When you try to append something to cloned dependant's fields you edit the original fields too.

    cloned_dependant = clone_dependant(original_dependant)
    
    original_dependant.body_params.append("some value that should be in copied")
    assert cloned_dependant.body_params == ["some path"]  # fails
    
    opened by denisSurkov 0
Releases(v2.1.2)
  • v2.1.2(Jul 27, 2021)

A Jupyter server based on FastAPI (Experimental)

jupyverse is experimental and should not be used in place of jupyter-server, which is the official Jupyter server.

Jupyter Server 81 Nov 16, 2021
OpenAPI generated FastAPI server

OpenAPI generated FastAPI server This Python package is automatically generated by the OpenAPI Generator project: API version: 1.0.0 Build package: or

microbo 1 Oct 31, 2021
The template for building scalable web APIs based on FastAPI, Tortoise ORM and other.

FastAPI and Tortoise ORM. Powerful but simple template for web APIs w/ FastAPI (as web framework) and Tortoise-ORM (for working via database without h

prostomarkeloff 73 Nov 18, 2021
a lightweight web framework based on fastapi

start-fastapi Version 2021, based on FastAPI, an easy-to-use web app developed upon Starlette Framework Version 2020 中文文档 Requirements python 3.6+ (fo

HiKari 65 Nov 30, 2021
Example app using FastAPI and JWT

FastAPI-Auth Example app using FastAPI and JWT virtualenv -p python3 venv source venv/bin/activate pip3 install -r requirements.txt mv config.yaml.exa

Sander 19 Mar 28, 2021
FastAPI Learning Example,对应中文视频学习教程:https://space.bilibili.com/396891097

视频教学地址 中文学习教程 1、本教程每一个案例都可以独立跑,前提是安装好依赖包。 2、本教程并未按照官方教程顺序,而是按照实际使用顺序编排。 Video Teaching Address FastAPI Learning Example 1.Each case in this tutorial c

null 298 Nov 24, 2021
🤪 FastAPI + Vue构建的Mall项目后台管理

Mall项目后台管理 前段时间学习Vue写了一个移动端项目 https://www.charmcode.cn/app/mall/home 然后教程到此就结束了, 我就总感觉少点什么,计划自己着手写一套后台管理。 相关项目 移动端Mall项目源码(Vue构建): https://github.com/

王小右 99 Nov 10, 2021
Backend, modern REST API for obtaining match and odds data crawled from multiple sites. Using FastAPI, MongoDB as database, Motor as async MongoDB client, Scrapy as crawler and Docker.

Introduction Apiestas is a project composed of a backend powered by the awesome framework FastAPI and a crawler powered by Scrapy. This project has fo

Fran Lozano 45 Nov 19, 2021
Backend Skeleton using FastAPI and Sqlalchemy ORM

Backend API Skeleton Based on @tiangolo's full stack postgres template, with some things added, some things removed, and some things changed. This is

David Montague 13 Nov 8, 2021
FastAPI on Google Cloud Run

cloudrun-fastapi Boilerplate for running FastAPI on Google Cloud Run with Google Cloud Build for deployment. For all documentation visit the docs fold

Anthony Corletti 71 Nov 11, 2021
FastAPI + Django experiment

django-fastapi-example This is an experiment to demonstrate one potential way of running FastAPI with Django. It won't be actively maintained. If you'

Jordan Eremieff 60 Nov 17, 2021
Auth for use with FastAPI

FastAPI Auth Pluggable auth for use with FastAPI Supports OAuth2 Password Flow Uses JWT access and refresh tokens 100% mypy and test coverage Supports

David Montague 65 Nov 30, 2021
FastAPI Boilerplate

FastAPI Boilerplate Features SQlAlchemy session Custom user class Top-level dependency Dependencies for specific permissions Celery SQLAlchemy for asy

Hide 69 Nov 21, 2021
Minimal example utilizing fastapi and celery with RabbitMQ for task queue, Redis for celery backend and flower for monitoring the celery tasks.

FastAPI with Celery Minimal example utilizing FastAPI and Celery with RabbitMQ for task queue, Redis for Celery backend and flower for monitoring the

Grega Vrbančič 255 Nov 24, 2021
A simple docker-compose app for orchestrating a fastapi application, a celery queue with rabbitmq(broker) and redis(backend)

fastapi - celery - rabbitmq - redis -> Docker A simple docker-compose app for orchestrating a fastapi application, a celery queue with rabbitmq(broker

Kartheekasasanka Kaipa 37 Nov 26, 2021
fastapi-crud-sync

Developing and Testing an API with FastAPI and Pytest Syncronous Example Want to use this project? Build the images and run the containers: $ docker-c

null 41 Nov 26, 2021
FastAPI Skeleton App to serve machine learning models production-ready.

FastAPI Model Server Skeleton Serving machine learning models production-ready, fast, easy and secure powered by the great FastAPI by Sebastián Ramíre

null 207 Nov 26, 2021
row level security for FastAPI framework

Row Level Permissions for FastAPI While trying out the excellent FastApi framework there was one peace missing for me: an easy, declarative way to def

Holger Frey 201 Nov 29, 2021
FastAPI framework plugins

Plugins for FastAPI framework, high performance, easy to learn, fast to code, ready for production fastapi-plugins FastAPI framework plugins Cache Mem

RES 178 Nov 27, 2021