Home

Awesome

<p align="center"> <img src="https://raw.githubusercontent.com/bali-framework/bali/master/docs/img/bali.png" alt='bali framework' /> </p> <p align="center"> <em>🏝 Simplify Cloud Native Microservices development base on FastAPI and gRPC.</em> </p> <p align="center"> <a href="https://pepy.tech/project/bali-core"> <img src="https://pepy.tech/badge/bali-core" /> </a> <a href="https://pypi.org/project/bali-core/"> <img src="https://img.shields.io/pypi/v/bali-core" /> </a> <a href="https://github.com/bali-framework/bali/actions/workflows/python-ci.yml"> <img src="https://github.com/bali-framework/bali/actions/workflows/python-ci.yml/badge.svg" /> </a> </p>

Documentation: https://bali-framework.github.io/bali/


Bali

Bali is a framework integrate FastAPI and gRPC. If you want to provide both HTTP and RPC, it can improve development efficiency.

It gives you the following features:

Who's using bali framework

<a href="https://www.360shuke.com/"> <img width="200" src="https://raw.githubusercontent.com/bali-framework/bali/master/docs/img/cases/qfin.png" /> </a> <a href="https://www.xinfei.cn/"> <img width="200" src="https://raw.githubusercontent.com/bali-framework/bali/master/docs/img/cases/xinfei.png" /> </a>

Requirements

1. Python 3.7+
2. FastAPI 0.63+
3. grpcio>=1.32,<1.50

Install

pip install bali-core # Bali framework 
pip install bali-cli # Bali command line tool 

Project structure layout

Application

Create Application

app = Bali() # Initialized App

Launch


# With bali-cli 
bali run http
bali run rpc
bali run event

python main.py run --http  # launch HTTP in development mode 
python main.py run --rpc  # launch RPC 
python main.py run --event  # launch Event 

More usage of Application: example

Database

connect

from bali import db

# connect to database when app started
# db is a sqla-wrapper instance
db.connect('DATABASE_URI')  
  

Declarative mode with sqla-wrapper


class User(db.Model):
    __tablename__ "users"
    id = db.Column(db.Integer, primary_key=True)
    ...

db.create_all()

db.add(User(...))
db.commit()

todos = db.query(User).all()

More convenient usage, ref to SQLA-Wrapper

Declare models inherit from convenient base models

BaseModel

# using BaseModel
class User(db.BaseModel):
    __tablename__ "users"
    id = db.Column(db.Integer, primary_key=True)
    ...
# BaseModel's source code 

class BaseModel(db.Model):
    __abstract__ = True

    created_time = Column(DateTime(timezone=True), default=datetime.utcnow)
    updated_time = Column(
        DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow
    )
    is_active = Column(Boolean(), default=True)

Transaction

SQLA-wrapper default model behavior is auto commit, auto commit will be disabled with db.transaction context.

with db.transaction():
    item = Item.create(name='test1')

Operators

Operators provided get_filters_expr to transform filters (dict) to SQLAlchemy expressions.

from bali.db.operators import get_filters_expr
from models import User

users = User.query().filter(*get_filters_expr(User, **filters)).all()

Schema

model_to_schema

# generate pydantic schema from models
# `User` is a db.Model or db.BaseModel instance 
from bali.schemas import model_to_schema
UserSchema = model_to_schema(User)

Resource

<i>New in version 2.0.</i>

Resource’s design borrows several key concepts from the REST architectural style.

Inspired by ViewSet in Django REST Framework.

Actions' name according Standard methods in Google API design guide

Generic HTTP/RPC Actions

Generic HTTP/RPC support actions:

ActionRouteMethodRPCDescription
get/{id}GETGet{Resource}Get an existing resource matching the given id
list/GETList{Resource}Get all the resources
create/POSTCreate{Resource}Create a new resource
update/{id}PATCHUpdate{Resource}Update an existing resource matching the given id
delete/{id}DELETEDelete{Resource}Delete an existing resource matching the given id

Generic Actions examples:


# 1. import `Resource` base class
from bali.resources import Resource


# 2. implementation actions inherited from Resource

class GreeterResource(Resource):

    schema = Greeter

    @action()
    def get(self, pk=None):
        return [g for g in GREETERS if g.get('id') == pk][0]

    @action()
    def list(self, schema_in: ListRequest):
        return GREETERS[:schema_in.limit]

    @action()
    def create(self, schema_in: schema):
        return {'id': schema_in.id, 'content': schema_in.content}

    @action()
    def update(self, schema_in: schema, pk=None):
        return {'id': pk, 'content': schema_in.content}

    @action()
    def delete(self, pk=None):
        return {'id': pk, 'result': True}  # using `id` instand of `result`

Custom HTTP/RPC Actions

Custom actions also decorated by @action, but detail signature is required.

@action(detail=False)
def custom_action(self):
    pass

detail has no default value.

True means action to single resource, url path is '/{resources}/{id}'.

False means action set of resources, url path is '/{resources}'.

Override HTTP Actions

If the default HTTP action template is not satisfied your request, you can override HTTP actions.

# Get the origin router 
router = GreeterResource.as_router()

# Override the actions using the FastAPI normal way
@router.get("/")
def root():
    return {"message": "Hello World"}

More usage of Resource: GreeterResource

ModelResource

<i>New in version 2.1.</i>

class UserResource(ModelResource):
    model = User
    schema = UserSchema
    filters = [
        {'username': str},
        {'age': Optional[str]},
    ]  # yapf: disable
    permission_classes = [IsAuthenticated]

Service Mixin

# import 
from bali.mixins import ServiceMixin

class Hello(hello_pb2_grpc.HelloServiceServicer, ServiceMixin):
    pass

Cache

Cache API

from bali import cache

# Usage example (API)

# Read cache 
cache.get(key)

# Set cache 
cache.set(key, value, timeout=10)

cache memoize

# Import the cache_memoize from bali core 
from bali import cache_memoize

# Attach decorator to cacheable function with a timeout of 100 seconds.
@cache_memoize(100)
def expensive_function(start, end):
    return random.randint(start, end)

Utils

dateparser

dateparser docs

MessageToDict/ParseDict

Optimized MessageToDict/ParseDict from google.protobuf.js_format

from bali.utils import MessageToDict, ParseDict

Tests

gRPC service tests

from bali.tests import GRPCTestBase
from service.demo import demo_service, demo_pb2, demo_pb2_grpc


class TestDemoRPC(GRPCTestBase):
    server_class = demo_service.DemoService  # Provided service 

    pb2 = demo_pb2  # Provided pb2
    pb2_grpc = demo_pb2_grpc  # Provided pb2 grpc

    def setup_method(self):  # Pytest setup 
        pass

    def teardown_method(self):  # Pytest teardown
        pass

    def test_demo(self):
        pass

Related Projects

bali-cli cookiecutter-bali