Building Services¶
This tutorial will create a simple blog gRPC Service.
Environment setup¶
Create a new virtual environment for our project:
python3 -m venv env
source env/bin/activate
Install our packages:
pip install django
pip install djangorestframework # we need the serialization
pip install djangogrpcframework
pip install grpcio
pip install grpcio-tools
Project setup¶
Let’s create a new project to work with:
django-admin startproject tutorial
cd tutorial
Now we can create an app that we’ll use to create a simple gRPC Service:
python manage.py startapp blog
We’ll need to add our new blog
app and the django_grpc_framework
app to
INSTALLED_APPS
. Let’s edit the tutorial/settings.py
file:
INSTALLED_APPS = [
...
'django_grpc_framework',
'blog',
]
Create a model¶
Now we’re going to create a simple Post
model that is used to store blog
posts. Edit the blog/models.py
file:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['created']
We also need to create a migration for our post model, and sync the database:
python manage.py makemigrations blog
python manage.py migrate
Defining a service¶
Our first step is to define the gRPC service and messages, create a directory
tutorial/protos
that sits next to tutorial/manage.py
, create another
directory protos/blog_proto
and create the protos/blog_proto/post.proto
file:
syntax = "proto3";
package blog_proto;
import "google/protobuf/empty.proto";
service PostController {
rpc List(PostListRequest) returns (stream Post) {}
rpc Create(Post) returns (Post) {}
rpc Retrieve(PostRetrieveRequest) returns (Post) {}
rpc Update(Post) returns (Post) {}
rpc Destroy(Post) returns (google.protobuf.Empty) {}
}
message Post {
int32 id = 1;
string title = 2;
string content = 3;
}
message PostListRequest {
}
message PostRetrieveRequest {
int32 id = 1;
}
For a model-backed service, you could also just run the model proto generator:
python manage.py generateproto --model blog.models.Post --fields=id,title,content --file protos/blog_proto/post.proto
Then edit it as needed, here the package name can’t be automatically inferred
by the proto generator, change package post
to package blog_proto
.
Next we need to generate gRPC code, from the tutorial
directory, run:
python -m grpc_tools.protoc --proto_path=./protos --python_out=./ --grpc_python_out=./ ./protos/blog_proto/post.proto
Create a Serializer class¶
Before we implement our gRPC service, we need to provide a way of serializing
and deserializing the post instances into protocol buffer messages. We can
do this by declaring serializers, create a file in the blog
directory
named serializers.py
and add the following:
from django_grpc_framework import proto_serializerss
from blog.models import Post
from blog_proto import post_pb2
class PostProtoSerializer(proto_serializers.ModelProtoSerializer):
class Meta:
model = Post
proto_class = post_pb2.Post
fields = ['id', 'title', 'content']
Write a service¶
With our serializer class, we’ll write a regular grpc service, create a file
in the blog
directory named services.py
and add the following:
import grpc
from google.protobuf import empty_pb2
from django_grpc_framework.services import Service
from blog.models import Post
from blog.serializers import PostProtoSerializer
class PostService(Service):
def List(self, request, context):
posts = Post.objects.all()
serializer = PostProtoSerializer(posts, many=True)
for msg in serializer.message:
yield msg
def Create(self, request, context):
serializer = PostProtoSerializer(message=request)
serializer.is_valid(raise_exception=True)
serializer.save()
return serializer.message
def get_object(self, pk):
try:
return Post.objects.get(pk=pk)
except Post.DoesNotExist:
self.context.abort(grpc.StatusCode.NOT_FOUND, 'Post:%s not found!' % pk)
def Retrieve(self, request, context):
post = self.get_object(request.id)
serializer = PostProtoSerializer(post)
return serializer.message
def Update(self, request, context):
post = self.get_object(request.id)
serializer = PostProtoSerializer(post, message=request)
serializer.is_valid(raise_exception=True)
serializer.save()
return serializer.message
def Destroy(self, request, context):
post = self.get_object(request.id)
post.delete()
return empty_pb2.Empty()
Finally we need to wire there services up, create blog/handlers.py
file:
from blog._services import PostService
from blog_proto import post_pb2_grpc
def grpc_handlers(server):
post_pb2_grpc.add_PostControllerServicer_to_server(PostService.as_servicer(), server)
Also we need to wire up the root handlers conf, in tutorial/urls.py
file, include our blog app’s grpc handlers:
from blog.handlers import grpc_handlers as blog_grpc_handlers
urlpatterns = []
def grpc_handlers(server):
blog_grpc_handlers(server)
Calling our service¶
Now we can start up a gRPC server so that clients can actually use our service:
python manage.py grpcrunserver --dev
In another terminal window, we can test the server:
import grpc
from blog_proto import post_pb2, post_pb2_grpc
with grpc.insecure_channel('localhost:50051') as channel:
stub = post_pb2_grpc.PostControllerStub(channel)
print('----- Create -----')
response = stub.Create(post_pb2.Post(title='t1', content='c1'))
print(response, end='')
print('----- List -----')
for post in stub.List(post_pb2.PostListRequest()):
print(post, end='')
print('----- Retrieve -----')
response = stub.Retrieve(post_pb2.PostRetrieveRequest(id=response.id))
print(response, end='')
print('----- Update -----')
response = stub.Update(post_pb2.Post(id=response.id, title='t2', content='c2'))
print(response, end='')
print('----- Delete -----')
stub.Destroy(post_pb2.Post(id=response.id))