Usage

To use gRPCAlchemy in a project:

from grpcalchemy.orm import Message
from grpcalchemy import Server, Context, grpcmethod

class HelloMessage(Message):
    __filename__ = 'hello'
    text: str

class HelloService(Server):
    @grpcmethod
    def Hello(self, request: HelloMessage, context: Context) -> HelloMessage:
        return HelloMessage(text=f'Hello {request.text}')

if __name__ == '__main__':
    HelloService.run()

Defining our Message

Any message which is used in RPC method must have a explicit schema. We can use py files generated by proto files by the grpc_tools straightly. However defining the schemas by our ORM can help to iron out bugs involving incorrect types or missing fields, and also allow us to define utility methods on our message in the same way that traditional ORMs do.

In our Tutorial Application we need to send several different types of message. We will need to have a collection of users, so that we may link posts to an individual. We also need to send our different types of posts (eg: text, image and link) in the RPC method. To aid navigation of our Tutorial Application, posts may have tags associated with them, so that the list of posts shown to the user may be limited to posts that have been assigned a specific tag. Finally, it would be nice if comments could be added to posts. We’ll start with users, as the other document models are slightly more involved.

Users

Just as if we were using a RPC Message with an ORM, we need to define which fields a User may have, and what types of data they might have:

from grpcalchemy.orm import Message
class User(Message):
    email: str
    first_name: str
    last_name: str

Posts, Comments and Tags

Now we’ll think about how to define the rest of the information. To associate the comments with individual posts, We’d also need a link message to provide the many-to-many relationship between posts and tags.

Posts

We can think of Post as a base class, and TextPost, ImagePost and LinkPost as subclasses of Post.

from grpcalchemy.orm import Message
class Post(Message):
    title: str
    author: str

class TextPost(Post):
    content: str

class ImagePost(Post):
    image_path: str

class LinkPost(Post):
    link_url: str

We are storing a reference to the author of the posts using a ReferenceField object. These are equal to use other message types in RPC message.

Tags

Now that we have our Post models figured out, how will we attach tags to them? RPC message allows us to define lists of items natively. So, for both efficiency and simplicity’s sake, we’ll define the tags as strings directly within the post. Let’s take a look at the code of our modified Post class:

from typing import List
from grpcalchemy.orm import Message, Repeated
class Post(Message):
    title: str
    author: User
    tags: Repeated[str]

The ListField object that is used to define a Post’s tags takes a field object as its first argument — this means that you can have lists of any type of field (including lists).

Note

We don’t need to modify the specialized post types as they all inherit from Post.

Comments

A comment is typically associated with one post.utility methods, in exactly the same way we do with regular documents:

from grpcalchemy.orm import Message
class Comment(Message):
    content: str
    name: str

We can then define a list of comment documents in our post message:

from typing import List
from grpcalchemy.orm import Message, Repeated
class Post(Message):
    title: str
    author: User
    tags: Repeated[str]
    comments: Repeated[Comment]

Defining our gRPC Method

grpcmethod is a decorator indicating gRPC methods.

The valid gRPC Method must be with explicit type hint to define the type of request and return value.

Using Iterator to define Stream gRPC Method:

from typing import Iterator

class HelloService(Server):

    @grpcmethod
    def UnaryUnary(self, request: HelloMessage, context: Context) -> HelloMessage:
        return HelloMessage(text=f'Hello {request.text}')

    @grpcmethod
    def UnaryStream(self, request: HelloMessage, context: Context) -> Iterator[HelloMessage]:
        yield HelloMessage(text=f'Hello {request.text}')

    @grpcmethod
    def StreamUnary(self, request: Iterator[HelloMessage], context: Context) -> HelloMessage:
        for r in request:
            pass
        return HelloMessage(text=f'Hello {r.text}')

    @grpcmethod
    def StreamStream(self, request: Iterator[HelloMessage], context: Context) -> Iterator[HelloMessage]:
        for r in request:
            yield HelloMessage(text=f'Hello {r.text}')

The above code is equal to an RPC service with a method:

syntax = "proto3";

service HelloService {
    rpc StreamStream (stream HelloMessage) returns (stream HelloMessage) {}

    rpc StreamUnary (stream HelloMessage) returns (HelloMessage) {}

    rpc UnaryStream (HelloMessage) returns (stream HelloMessage) {}

    rpc UnaryUnary (HelloMessage) returns (HelloMessage) {}
}

Using Blueprint to Build Your Large Application

gRPCAlchemy uses a concept of blueprints for making gRPC services and supporting common patterns within an application or across applications. Blueprint can greatly simplify how large applications work.

from typing import List, Type
from grpcalchemy.orm import Message
from grpcalchemy import Server, Context, Blueprint

class MyService(Server):
    @classmethod
    def get_blueprints(cls) -> List[Type[Blueprint]]:
        return [HelloService]

class HelloMessage(Message):
    __filename__ = 'hello'
    text: str

class HelloService(Blueprint):
    @grpcmethod
    def Hello(self, request: HelloMessage, context: Context) -> HelloMessage:
        return HelloMessage(text=f'Hello {request.text}')


if __name__ == '__main__':
    MyService.run()

Configuration

You can define your custom config by inherit from DefaultConfig which defined a list of configuration available in gRPCAlchemy and their default values.

Note

DefaultConfig is defined by configalchemy - https://configalchemy.readthedocs.io

from grpcalchemy import DefaultConfig

from hello import HelloService

class MyConfig(DefaultConfig):
    ...

config = MyConfig()

HelloService.run(config=config)

Middleware

Middleware is a framework of hooks into gRPCAlchemy’s request/response processing.

Costume middleware can implement by overriding Blueprint.before_request, Blueprint.after_request, Server.process_request and Server.process_response.