├── README.md ├── config.env ├── docker-compose.yml ├── grpc ├── Dockerfile ├── app.py ├── client.py ├── messages │ ├── user_pb2.py │ └── user_pb2_grpc.py ├── protobuf │ └── user.proto └── services │ └── user.py └── restful ├── Dockerfile ├── application ├── app.py ├── controllers │ └── user.py ├── databaseConf.cfg ├── models │ ├── db.py │ └── user.py ├── resources │ └── user.py └── schemas │ ├── marshmallow.py │ └── user.py └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | # grpc-flask-tutorial 2 | This repo contain a tutorial guide to use flask and grpc side by side by side,, and also showing how you can share the codes among the different apps in docker container 3 | 4 | ## How to run the app 5 | You have to install docker and docker-compose to run this app, if you already have installed then you just have to run following commands 6 | 7 | ``` docker-compose build ``` and wait docker to build everything once done 8 | 9 | ``` docker-compose up -d ``` to run docker in background 10 | 11 | ``` docker-compose ps ``` Will show you the running instance of docker ( Everything should be up and running ) 12 | 13 | ``` Name Command State Ports 14 | -------------------------------------------------------------------------------------------- 15 | grpc-flask-tutorial_db_1 /docker-entrypoint.sh mysqld Up 0.0.0.0:5016->3306/tcp 16 | grpc-flask-tutorial_grpc_1 python ./app.py Up 0.0.0.0:50051->50051/tcp 17 | grpc-flask-tutorial_web_1 python application/app.py Up 0.0.0.0:3001->3001/tcp 18 | 19 | ``` 20 | ## How to test rest-api 21 | Just curl your localhost with port 3001 22 | 23 | ### POST request 24 | ```curl -d '[{"name":"ABC", "birth":"2019-01-24", "gender": "MALE"}]' -H "Content-Type: application/json" -X POST http://localhost:3001/users``` 25 | 26 | ### GET request 27 | ```curl localhost:3001/user/1``` 28 | ```curl localhost:3001/users``` 29 | 30 | ## How to test grpc-api 31 | If you have client setup outside the docker then you call the grpc function using localhost:50051 and it should give you the data. But here we will test is little differently ( Not recommended in productions ), I am doing this because I already have small written client script. 32 | 33 | You have to go inside the running grpc docker instance 34 | 35 | ``` docker exec -it grpc-flask-tutorial_grpc_1 sh ``` 36 | 37 | You will see new bash shell, Now we have to lunch python terminal so just type 38 | 39 | ```python ``` 40 | 41 | You will see python terminal, now you need to import client file and the get_user and get_users function 42 | 43 | ``` from client import get_user, get_users ``` 44 | 45 | Once imported then, just call function 46 | 47 | ```get_user(1)``` you will see result , if you have data or not found error 48 | 49 | ```get_users()``` it will give you all data 50 | 51 | 52 | -------------------------------------------------------------------------------- /config.env: -------------------------------------------------------------------------------- 1 | dev=databaseConf.cfg -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | build: 5 | context: ./restful 6 | image: restful-api 7 | networks: 8 | - frontend 9 | - backend 10 | ports: 11 | - "3001:3001" 12 | env_file: ./config.env 13 | 14 | grpc: 15 | build: 16 | context: ./grpc 17 | image: grpc-api 18 | networks: 19 | - frontend 20 | - backend 21 | ports: 22 | - "50051:50051" 23 | env_file: ./config.env 24 | 25 | db: 26 | image: percona:5.7 27 | restart: always 28 | environment: 29 | MYSQL_ROOT_PASSWORD: test 30 | MYSQL_USER: user-api 31 | MYSQL_PASSWORD: whateveryoulike 32 | MYSQL_DATABASE: userDB 33 | ports: 34 | - "5016:3306" 35 | networks: 36 | - backend 37 | volumes: 38 | - db-data:/var/lib/mysql/ 39 | - ./docker/mysql/conf.d:/etc/mysql/conf.d 40 | 41 | volumes: 42 | db-data: 43 | networks: 44 | backend: 45 | frontend: 46 | 47 | -------------------------------------------------------------------------------- /grpc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM restful-api:latest as build 2 | WORKDIR /usr/src/app/grpc 3 | RUN apk add --update --no-cache libstdc++ libc6-compat openssh-client git gcc cython linux-headers make musl-dev python3-dev g++ 4 | ENV GRPC_PYTHON_VERSION 1.15.0 5 | RUN pip install grpcio==${GRPC_PYTHON_VERSION} grpcio-tools==${GRPC_PYTHON_VERSION} 6 | 7 | 8 | FROM python:3.6-alpine 9 | COPY --from=build /usr/local/lib/python3.6/site-packages/ /usr/local/lib/python3.6/site-packages/ 10 | COPY --from=build /usr/src/app/restful /usr/src/app/restful 11 | RUN apk add --update --no-cache libstdc++ libc6-compat 12 | RUN mkdir -p /usr/src/app/grpc 13 | WORKDIR /usr/src/app/grpc 14 | COPY . /usr/src/app/grpc 15 | ENV PYTHONPATH /usr/src/app/grpc:/usr/src/app/grpc:/usr/src/app/grpc/messages:/usr/src/app/restful 16 | EXPOSE 50051 17 | 18 | CMD [ "python", "./app.py" ] -------------------------------------------------------------------------------- /grpc/app.py: -------------------------------------------------------------------------------- 1 | from concurrent import futures 2 | import time 3 | import grpc 4 | import messages.user_pb2_grpc as user_service 5 | from services.user import UserService 6 | import logging 7 | _ONE_DAY_IN_SECONDS = 60 * 60 * 24 8 | 9 | def grpc_server(): 10 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 11 | 12 | # Register user service 13 | user_serve = UserService() 14 | user_service.add_UserServicer_to_server(user_serve, server) 15 | 16 | logging.info('GRPC running') 17 | # This is just for testing onyl ( Not recommended in production ) 18 | server.add_insecure_port('[::]:50051') 19 | server.start() 20 | try: 21 | while True: 22 | time.sleep(_ONE_DAY_IN_SECONDS) 23 | except KeyboardInterrupt: 24 | logging.debug('GRPC stop') 25 | server.stop(0) 26 | if __name__ == '__main__': 27 | grpc_server() 28 | -------------------------------------------------------------------------------- /grpc/client.py: -------------------------------------------------------------------------------- 1 | import grpc 2 | import messages.user_pb2_grpc as user_service 3 | import messages.user_pb2 as user_messages 4 | 5 | # FYI : This is junst an example how to use the existing GRPC services 6 | 7 | def get_user(id): 8 | channel = grpc.insecure_channel("127.0.0.1:50051") 9 | client = user_service.UserStub(channel) 10 | response = client.GetUser(user_messages.UserRequest(id=id)) 11 | if response: 12 | print( "ID: {}".format(response.id)) 13 | print( "Name: {}".format(response.name)) 14 | print( "Gender: {}".format(response.gender)) 15 | print( "DOB: {}".format(response.birth.ToDatetime())) 16 | 17 | def get_users(name=[]): 18 | channel = grpc.insecure_channel("127.0.0.1:50051") 19 | client = user_service.UserStub(channel) 20 | responses = client.GetUsers(user_messages.UsersRequest(name=name)) 21 | for response in responses: 22 | print( "ID: {}".format(response.id)) 23 | print( "Name: {}".format(response.name)) 24 | print( "Gender: {}".format(response.gender)) 25 | print( "DOB: {}".format(response.birth.ToDatetime())) 26 | -------------------------------------------------------------------------------- /grpc/messages/user_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: user.proto 3 | 4 | import sys 5 | _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) 6 | from google.protobuf.internal import enum_type_wrapper 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import message as _message 9 | from google.protobuf import reflection as _reflection 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf import descriptor_pb2 12 | # @@protoc_insertion_point(imports) 13 | 14 | _sym_db = _symbol_database.Default() 15 | 16 | 17 | from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 18 | 19 | 20 | DESCRIPTOR = _descriptor.FileDescriptor( 21 | name='user.proto', 22 | package='userpb', 23 | syntax='proto3', 24 | serialized_pb=_b('\n\nuser.proto\x12\x06userpb\x1a\x1fgoogle/protobuf/timestamp.proto\"\x19\n\x0bUserRequest\x12\n\n\x02id\x18\x01 \x01(\x03\"\x1c\n\x0cUsersRequest\x12\x0c\n\x04name\x18\x01 \x03(\t\"s\n\x0cUserResponse\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x1e\n\x06gender\x18\x03 \x01(\x0e\x32\x0e.userpb.Gender\x12)\n\x05\x62irth\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp*+\n\x06Gender\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x08\n\x04MALE\x10\x01\x12\n\n\x06\x46\x45MALE\x10\x02\x32z\n\x04User\x12\x36\n\x07GetUser\x12\x13.userpb.UserRequest\x1a\x14.userpb.UserResponse\"\x00\x12:\n\x08GetUsers\x12\x14.userpb.UsersRequest\x1a\x14.userpb.UserResponse\"\x00\x30\x01\x62\x06proto3') 25 | , 26 | dependencies=[google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,]) 27 | 28 | _GENDER = _descriptor.EnumDescriptor( 29 | name='Gender', 30 | full_name='userpb.Gender', 31 | filename=None, 32 | file=DESCRIPTOR, 33 | values=[ 34 | _descriptor.EnumValueDescriptor( 35 | name='UNKNOWN', index=0, number=0, 36 | options=None, 37 | type=None), 38 | _descriptor.EnumValueDescriptor( 39 | name='MALE', index=1, number=1, 40 | options=None, 41 | type=None), 42 | _descriptor.EnumValueDescriptor( 43 | name='FEMALE', index=2, number=2, 44 | options=None, 45 | type=None), 46 | ], 47 | containing_type=None, 48 | options=None, 49 | serialized_start=229, 50 | serialized_end=272, 51 | ) 52 | _sym_db.RegisterEnumDescriptor(_GENDER) 53 | 54 | Gender = enum_type_wrapper.EnumTypeWrapper(_GENDER) 55 | UNKNOWN = 0 56 | MALE = 1 57 | FEMALE = 2 58 | 59 | 60 | 61 | _USERREQUEST = _descriptor.Descriptor( 62 | name='UserRequest', 63 | full_name='userpb.UserRequest', 64 | filename=None, 65 | file=DESCRIPTOR, 66 | containing_type=None, 67 | fields=[ 68 | _descriptor.FieldDescriptor( 69 | name='id', full_name='userpb.UserRequest.id', index=0, 70 | number=1, type=3, cpp_type=2, label=1, 71 | has_default_value=False, default_value=0, 72 | message_type=None, enum_type=None, containing_type=None, 73 | is_extension=False, extension_scope=None, 74 | options=None), 75 | ], 76 | extensions=[ 77 | ], 78 | nested_types=[], 79 | enum_types=[ 80 | ], 81 | options=None, 82 | is_extendable=False, 83 | syntax='proto3', 84 | extension_ranges=[], 85 | oneofs=[ 86 | ], 87 | serialized_start=55, 88 | serialized_end=80, 89 | ) 90 | 91 | 92 | _USERSREQUEST = _descriptor.Descriptor( 93 | name='UsersRequest', 94 | full_name='userpb.UsersRequest', 95 | filename=None, 96 | file=DESCRIPTOR, 97 | containing_type=None, 98 | fields=[ 99 | _descriptor.FieldDescriptor( 100 | name='name', full_name='userpb.UsersRequest.name', index=0, 101 | number=1, type=9, cpp_type=9, label=3, 102 | has_default_value=False, default_value=[], 103 | message_type=None, enum_type=None, containing_type=None, 104 | is_extension=False, extension_scope=None, 105 | options=None), 106 | ], 107 | extensions=[ 108 | ], 109 | nested_types=[], 110 | enum_types=[ 111 | ], 112 | options=None, 113 | is_extendable=False, 114 | syntax='proto3', 115 | extension_ranges=[], 116 | oneofs=[ 117 | ], 118 | serialized_start=82, 119 | serialized_end=110, 120 | ) 121 | 122 | 123 | _USERRESPONSE = _descriptor.Descriptor( 124 | name='UserResponse', 125 | full_name='userpb.UserResponse', 126 | filename=None, 127 | file=DESCRIPTOR, 128 | containing_type=None, 129 | fields=[ 130 | _descriptor.FieldDescriptor( 131 | name='id', full_name='userpb.UserResponse.id', index=0, 132 | number=1, type=3, cpp_type=2, label=1, 133 | has_default_value=False, default_value=0, 134 | message_type=None, enum_type=None, containing_type=None, 135 | is_extension=False, extension_scope=None, 136 | options=None), 137 | _descriptor.FieldDescriptor( 138 | name='name', full_name='userpb.UserResponse.name', index=1, 139 | number=2, type=9, cpp_type=9, label=1, 140 | has_default_value=False, default_value=_b("").decode('utf-8'), 141 | message_type=None, enum_type=None, containing_type=None, 142 | is_extension=False, extension_scope=None, 143 | options=None), 144 | _descriptor.FieldDescriptor( 145 | name='gender', full_name='userpb.UserResponse.gender', index=2, 146 | number=3, type=14, cpp_type=8, label=1, 147 | has_default_value=False, default_value=0, 148 | message_type=None, enum_type=None, containing_type=None, 149 | is_extension=False, extension_scope=None, 150 | options=None), 151 | _descriptor.FieldDescriptor( 152 | name='birth', full_name='userpb.UserResponse.birth', index=3, 153 | number=4, type=11, cpp_type=10, label=1, 154 | has_default_value=False, default_value=None, 155 | message_type=None, enum_type=None, containing_type=None, 156 | is_extension=False, extension_scope=None, 157 | options=None), 158 | ], 159 | extensions=[ 160 | ], 161 | nested_types=[], 162 | enum_types=[ 163 | ], 164 | options=None, 165 | is_extendable=False, 166 | syntax='proto3', 167 | extension_ranges=[], 168 | oneofs=[ 169 | ], 170 | serialized_start=112, 171 | serialized_end=227, 172 | ) 173 | 174 | _USERRESPONSE.fields_by_name['gender'].enum_type = _GENDER 175 | _USERRESPONSE.fields_by_name['birth'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP 176 | DESCRIPTOR.message_types_by_name['UserRequest'] = _USERREQUEST 177 | DESCRIPTOR.message_types_by_name['UsersRequest'] = _USERSREQUEST 178 | DESCRIPTOR.message_types_by_name['UserResponse'] = _USERRESPONSE 179 | DESCRIPTOR.enum_types_by_name['Gender'] = _GENDER 180 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 181 | 182 | UserRequest = _reflection.GeneratedProtocolMessageType('UserRequest', (_message.Message,), dict( 183 | DESCRIPTOR = _USERREQUEST, 184 | __module__ = 'user_pb2' 185 | # @@protoc_insertion_point(class_scope:userpb.UserRequest) 186 | )) 187 | _sym_db.RegisterMessage(UserRequest) 188 | 189 | UsersRequest = _reflection.GeneratedProtocolMessageType('UsersRequest', (_message.Message,), dict( 190 | DESCRIPTOR = _USERSREQUEST, 191 | __module__ = 'user_pb2' 192 | # @@protoc_insertion_point(class_scope:userpb.UsersRequest) 193 | )) 194 | _sym_db.RegisterMessage(UsersRequest) 195 | 196 | UserResponse = _reflection.GeneratedProtocolMessageType('UserResponse', (_message.Message,), dict( 197 | DESCRIPTOR = _USERRESPONSE, 198 | __module__ = 'user_pb2' 199 | # @@protoc_insertion_point(class_scope:userpb.UserResponse) 200 | )) 201 | _sym_db.RegisterMessage(UserResponse) 202 | 203 | 204 | 205 | _USER = _descriptor.ServiceDescriptor( 206 | name='User', 207 | full_name='userpb.User', 208 | file=DESCRIPTOR, 209 | index=0, 210 | options=None, 211 | serialized_start=274, 212 | serialized_end=396, 213 | methods=[ 214 | _descriptor.MethodDescriptor( 215 | name='GetUser', 216 | full_name='userpb.User.GetUser', 217 | index=0, 218 | containing_service=None, 219 | input_type=_USERREQUEST, 220 | output_type=_USERRESPONSE, 221 | options=None, 222 | ), 223 | _descriptor.MethodDescriptor( 224 | name='GetUsers', 225 | full_name='userpb.User.GetUsers', 226 | index=1, 227 | containing_service=None, 228 | input_type=_USERSREQUEST, 229 | output_type=_USERRESPONSE, 230 | options=None, 231 | ), 232 | ]) 233 | _sym_db.RegisterServiceDescriptor(_USER) 234 | 235 | DESCRIPTOR.services_by_name['User'] = _USER 236 | 237 | try: 238 | # THESE ELEMENTS WILL BE DEPRECATED. 239 | # Please use the generated *_pb2_grpc.py files instead. 240 | import grpc 241 | from grpc.beta import implementations as beta_implementations 242 | from grpc.beta import interfaces as beta_interfaces 243 | from grpc.framework.common import cardinality 244 | from grpc.framework.interfaces.face import utilities as face_utilities 245 | 246 | 247 | class UserStub(object): 248 | # missing associated documentation comment in .proto file 249 | pass 250 | 251 | def __init__(self, channel): 252 | """Constructor. 253 | 254 | Args: 255 | channel: A grpc.Channel. 256 | """ 257 | self.GetUser = channel.unary_unary( 258 | '/userpb.User/GetUser', 259 | request_serializer=UserRequest.SerializeToString, 260 | response_deserializer=UserResponse.FromString, 261 | ) 262 | self.GetUsers = channel.unary_stream( 263 | '/userpb.User/GetUsers', 264 | request_serializer=UsersRequest.SerializeToString, 265 | response_deserializer=UserResponse.FromString, 266 | ) 267 | 268 | 269 | class UserServicer(object): 270 | # missing associated documentation comment in .proto file 271 | pass 272 | 273 | def GetUser(self, request, context): 274 | # missing associated documentation comment in .proto file 275 | pass 276 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 277 | context.set_details('Method not implemented!') 278 | raise NotImplementedError('Method not implemented!') 279 | 280 | def GetUsers(self, request, context): 281 | # missing associated documentation comment in .proto file 282 | pass 283 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 284 | context.set_details('Method not implemented!') 285 | raise NotImplementedError('Method not implemented!') 286 | 287 | 288 | def add_UserServicer_to_server(servicer, server): 289 | rpc_method_handlers = { 290 | 'GetUser': grpc.unary_unary_rpc_method_handler( 291 | servicer.GetUser, 292 | request_deserializer=UserRequest.FromString, 293 | response_serializer=UserResponse.SerializeToString, 294 | ), 295 | 'GetUsers': grpc.unary_stream_rpc_method_handler( 296 | servicer.GetUsers, 297 | request_deserializer=UsersRequest.FromString, 298 | response_serializer=UserResponse.SerializeToString, 299 | ), 300 | } 301 | generic_handler = grpc.method_handlers_generic_handler( 302 | 'userpb.User', rpc_method_handlers) 303 | server.add_generic_rpc_handlers((generic_handler,)) 304 | 305 | 306 | class BetaUserServicer(object): 307 | """The Beta API is deprecated for 0.15.0 and later. 308 | 309 | It is recommended to use the GA API (classes and functions in this 310 | file not marked beta) for all further purposes. This class was generated 311 | only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" 312 | # missing associated documentation comment in .proto file 313 | pass 314 | def GetUser(self, request, context): 315 | # missing associated documentation comment in .proto file 316 | pass 317 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 318 | def GetUsers(self, request, context): 319 | # missing associated documentation comment in .proto file 320 | pass 321 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 322 | 323 | 324 | class BetaUserStub(object): 325 | """The Beta API is deprecated for 0.15.0 and later. 326 | 327 | It is recommended to use the GA API (classes and functions in this 328 | file not marked beta) for all further purposes. This class was generated 329 | only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" 330 | # missing associated documentation comment in .proto file 331 | pass 332 | def GetUser(self, request, timeout, metadata=None, with_call=False, protocol_options=None): 333 | # missing associated documentation comment in .proto file 334 | pass 335 | raise NotImplementedError() 336 | GetUser.future = None 337 | def GetUsers(self, request, timeout, metadata=None, with_call=False, protocol_options=None): 338 | # missing associated documentation comment in .proto file 339 | pass 340 | raise NotImplementedError() 341 | 342 | 343 | def beta_create_User_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): 344 | """The Beta API is deprecated for 0.15.0 and later. 345 | 346 | It is recommended to use the GA API (classes and functions in this 347 | file not marked beta) for all further purposes. This function was 348 | generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" 349 | request_deserializers = { 350 | ('userpb.User', 'GetUser'): UserRequest.FromString, 351 | ('userpb.User', 'GetUsers'): UsersRequest.FromString, 352 | } 353 | response_serializers = { 354 | ('userpb.User', 'GetUser'): UserResponse.SerializeToString, 355 | ('userpb.User', 'GetUsers'): UserResponse.SerializeToString, 356 | } 357 | method_implementations = { 358 | ('userpb.User', 'GetUser'): face_utilities.unary_unary_inline(servicer.GetUser), 359 | ('userpb.User', 'GetUsers'): face_utilities.unary_stream_inline(servicer.GetUsers), 360 | } 361 | server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) 362 | return beta_implementations.server(method_implementations, options=server_options) 363 | 364 | 365 | def beta_create_User_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): 366 | """The Beta API is deprecated for 0.15.0 and later. 367 | 368 | It is recommended to use the GA API (classes and functions in this 369 | file not marked beta) for all further purposes. This function was 370 | generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" 371 | request_serializers = { 372 | ('userpb.User', 'GetUser'): UserRequest.SerializeToString, 373 | ('userpb.User', 'GetUsers'): UsersRequest.SerializeToString, 374 | } 375 | response_deserializers = { 376 | ('userpb.User', 'GetUser'): UserResponse.FromString, 377 | ('userpb.User', 'GetUsers'): UserResponse.FromString, 378 | } 379 | cardinalities = { 380 | 'GetUser': cardinality.Cardinality.UNARY_UNARY, 381 | 'GetUsers': cardinality.Cardinality.UNARY_STREAM, 382 | } 383 | stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) 384 | return beta_implementations.dynamic_stub(channel, 'userpb.User', cardinalities, options=stub_options) 385 | except ImportError: 386 | pass 387 | # @@protoc_insertion_point(module_scope) 388 | -------------------------------------------------------------------------------- /grpc/messages/user_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | import user_pb2 as user__pb2 5 | 6 | 7 | class UserStub(object): 8 | # missing associated documentation comment in .proto file 9 | pass 10 | 11 | def __init__(self, channel): 12 | """Constructor. 13 | 14 | Args: 15 | channel: A grpc.Channel. 16 | """ 17 | self.GetUser = channel.unary_unary( 18 | '/userpb.User/GetUser', 19 | request_serializer=user__pb2.UserRequest.SerializeToString, 20 | response_deserializer=user__pb2.UserResponse.FromString, 21 | ) 22 | self.GetUsers = channel.unary_stream( 23 | '/userpb.User/GetUsers', 24 | request_serializer=user__pb2.UsersRequest.SerializeToString, 25 | response_deserializer=user__pb2.UserResponse.FromString, 26 | ) 27 | 28 | 29 | class UserServicer(object): 30 | # missing associated documentation comment in .proto file 31 | pass 32 | 33 | def GetUser(self, request, context): 34 | # missing associated documentation comment in .proto file 35 | pass 36 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 37 | context.set_details('Method not implemented!') 38 | raise NotImplementedError('Method not implemented!') 39 | 40 | def GetUsers(self, request, context): 41 | # missing associated documentation comment in .proto file 42 | pass 43 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 44 | context.set_details('Method not implemented!') 45 | raise NotImplementedError('Method not implemented!') 46 | 47 | 48 | def add_UserServicer_to_server(servicer, server): 49 | rpc_method_handlers = { 50 | 'GetUser': grpc.unary_unary_rpc_method_handler( 51 | servicer.GetUser, 52 | request_deserializer=user__pb2.UserRequest.FromString, 53 | response_serializer=user__pb2.UserResponse.SerializeToString, 54 | ), 55 | 'GetUsers': grpc.unary_stream_rpc_method_handler( 56 | servicer.GetUsers, 57 | request_deserializer=user__pb2.UsersRequest.FromString, 58 | response_serializer=user__pb2.UserResponse.SerializeToString, 59 | ), 60 | } 61 | generic_handler = grpc.method_handlers_generic_handler( 62 | 'userpb.User', rpc_method_handlers) 63 | server.add_generic_rpc_handlers((generic_handler,)) 64 | -------------------------------------------------------------------------------- /grpc/protobuf/user.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package userpb; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | enum Gender { 8 | UNKNOWN = 0; 9 | MALE = 1; 10 | FEMALE = 2; 11 | } 12 | 13 | message UserRequest { 14 | int64 id = 1; 15 | } 16 | 17 | message UsersRequest { 18 | repeated string name = 1; 19 | } 20 | 21 | message UserResponse { 22 | int64 id = 1; 23 | string name = 2; 24 | Gender gender = 3; 25 | google.protobuf.Timestamp birth = 4; 26 | } 27 | 28 | service User { 29 | rpc GetUser(UserRequest) returns (UserResponse) {} ; 30 | rpc GetUsers(UsersRequest) returns (stream UserResponse) {}; 31 | } 32 | -------------------------------------------------------------------------------- /grpc/services/user.py: -------------------------------------------------------------------------------- 1 | import grpc 2 | import user_pb2_grpc as user_service 3 | import user_pb2 as user_messages 4 | import logging 5 | from application.app import app 6 | from application.controllers.user import UserController 7 | import time 8 | from datetime import datetime 9 | from google.protobuf.timestamp_pb2 import Timestamp 10 | 11 | class UserService(user_service.UserServicer): 12 | 13 | def GetUser(self, request, context): 14 | if request.id: 15 | with app.app_context(): 16 | user = UserController().getUserByID(id = request.id) 17 | logging.debug(user) 18 | if user: 19 | return user_messages.UserResponse( \ 20 | id = user.id, \ 21 | name = user.name, \ 22 | birth = self.convertIntoTimestamp(user.birth), \ 23 | gender = user.gender.upper()) 24 | msg = 'User ID not found' 25 | context.set_details(msg) 26 | context.set_code(grpc.StatusCode.NOT_FOUND) 27 | return user_messages.UserResponse() 28 | msg = 'Must pass ID' 29 | context.set_details(msg) 30 | context.set_code(grpc.StatusCode.INVALID_ARGUMENT) 31 | return user_messages.UserResponse() 32 | 33 | def GetUsers(self, request, context): 34 | with app.app_context(): 35 | if request and request.name: 36 | for name in request.name: 37 | for user in UserController().getUsersByName(name = name): 38 | yield user_messages.UserResponse( \ 39 | id = user.id, \ 40 | name = user.name, \ 41 | birth = self.convertIntoTimestamp(user.birth), \ 42 | gender = user.gender.upper()) 43 | else: 44 | for user in UserController().getUsers(): 45 | yield user_messages.UserResponse( \ 46 | id = user.id, \ 47 | name = user.name, \ 48 | birth = self.convertIntoTimestamp(user.birth), \ 49 | gender = user.gender.upper()) 50 | 51 | def convertIntoTimestamp(self, old_date): 52 | ts = Timestamp() 53 | new_date = datetime.combine(old_date, datetime.min.time()) 54 | ts.FromDatetime(new_date) 55 | return ts 56 | 57 | -------------------------------------------------------------------------------- /restful/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6-alpine as build 2 | WORKDIR /usr/src/app/restful 3 | COPY requirements.txt /usr/src/app/restful 4 | RUN python -m pip install --upgrade pip 5 | RUN pip install -r requirements.txt 6 | 7 | FROM python:3.6-alpine 8 | COPY --from=build /usr/local/lib/python3.6/site-packages/ /usr/local/lib/python3.6/site-packages/ 9 | RUN mkdir -p /usr/src/app/restful 10 | WORKDIR /usr/src/app/restful 11 | COPY . /usr/src/app/restful 12 | ENV PYTHONPATH /usr/src/app/restful 13 | CMD ["python","application/app.py"] -------------------------------------------------------------------------------- /restful/application/app.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from flask import Flask, jsonify 4 | from flask_restful import Resource, Api 5 | from application.models.db import db 6 | from application.schemas.marshmallow import ma 7 | 8 | 9 | # ***** Import Resource Classes ***** # 10 | from application.resources.user import User, Users 11 | 12 | dir = os.path.dirname(__file__) 13 | def create_app(config_name): 14 | 15 | app = Flask(__name__) 16 | app.config.from_envvar(config_name) 17 | db.init_app(app) 18 | #Not suggested in production environment 19 | with app.app_context(): 20 | db.create_all() 21 | ma.init_app(app) 22 | 23 | api = Api(app) 24 | 25 | # ***** Error Handling and Logging ***** # 26 | logging.basicConfig(filename = 'debug.log', level = logging.DEBUG) 27 | 28 | # ***** Define Endpoints ***** # 29 | api.add_resource(Users, '/users') 30 | api.add_resource(User, '/user/', '/user') 31 | 32 | @app.after_request 33 | def after_request(response): 34 | response.headers.set('Access-Control-Expose-Headers', 'Link') 35 | response.headers.set('Access-Control-Allow-Origin', '*') 36 | response.headers.set('Access-Control-Allow-Headers', 'Content-Type,Authorization') 37 | response.headers.set('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE') 38 | return response 39 | 40 | return app 41 | 42 | app = create_app('dev') 43 | 44 | if __name__ == '__main__': 45 | app.run(host="0.0.0.0", port="3001") -------------------------------------------------------------------------------- /restful/application/controllers/user.py: -------------------------------------------------------------------------------- 1 | from application.models.user import UserModel 2 | from application.models.db import db 3 | 4 | class UserController(object): 5 | def __init__(self): 6 | self.model = UserModel 7 | 8 | def getUserByID(self, id): 9 | return self.model.query.filter_by(id=id).one_or_none() 10 | 11 | 12 | def getUsers(self): 13 | return self.model.query.all() 14 | 15 | def getUsersByName(self, name): 16 | if name: 17 | return self.model.query.filter_by(name=name).all() 18 | return None 19 | 20 | def updateUser(self): 21 | db.session.commit() 22 | 23 | def deleteUser(self, id): 24 | user = self.getUserByID(id) 25 | if user: 26 | db.session.delete(user) 27 | db.session.commit() 28 | 29 | def addUser(self, users): 30 | db.session.add_all(users) 31 | db.session.commit() -------------------------------------------------------------------------------- /restful/application/databaseConf.cfg: -------------------------------------------------------------------------------- 1 | SQLALCHEMY_TRACK_MODIFICATIONS=False 2 | SQLALCHEMY_DATABASE_URI='mysql+pymysql://user-api:whateveryoulike@db/userDB' 3 | -------------------------------------------------------------------------------- /restful/application/models/db.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | db = SQLAlchemy() -------------------------------------------------------------------------------- /restful/application/models/user.py: -------------------------------------------------------------------------------- 1 | from application.models.db import db 2 | from sqlalchemy import Enum 3 | import datetime 4 | import sys 5 | 6 | class UserModel(db.Model): 7 | __tablename__ = 'tbl_user' 8 | 9 | id = db.Column(db.Integer(), primary_key = True) 10 | name = db.Column(db.String(60), nullable = False) 11 | birth = db.Column(db.Date, nullable = False) 12 | gender = db.Column(db.Enum("MALE", "FEMALE", native_enum = False), nullable = False) 13 | 14 | -------------------------------------------------------------------------------- /restful/application/resources/user.py: -------------------------------------------------------------------------------- 1 | from flask_restful import Resource 2 | from application.schemas.user import UserSchema 3 | from application.controllers.user import UserController 4 | from webargs.flaskparser import use_args, use_kwargs 5 | 6 | class User(Resource): 7 | def get(self, id): 8 | user = UserController().getUserByID(id) 9 | if user: 10 | return UserSchema().dump(user).data 11 | return 'User not found', 404 12 | 13 | @use_args(UserSchema()) 14 | def put(self, args): 15 | UserController().updateUser() 16 | return UserSchema().dump(args).data, 200 17 | 18 | def delete(self, id): 19 | UserController().deleteUser(id) 20 | return 'User removed', 200 21 | 22 | class Users(Resource): 23 | 24 | @use_args(UserSchema(many = True)) 25 | def post(self, args): 26 | UserController().addUser(args) 27 | return UserSchema(many = True).dump(args).data, 201 28 | 29 | def get(self): 30 | users = UserController().getUsers() 31 | return UserSchema(many = True).dump(users).data, 201 -------------------------------------------------------------------------------- /restful/application/schemas/marshmallow.py: -------------------------------------------------------------------------------- 1 | from flask_marshmallow import Marshmallow 2 | ma = Marshmallow() -------------------------------------------------------------------------------- /restful/application/schemas/user.py: -------------------------------------------------------------------------------- 1 | 2 | from marshmallow_sqlalchemy import ModelSchema 3 | 4 | from application.models.db import db 5 | from application.models.user import UserModel 6 | 7 | class UserSchema(ModelSchema): 8 | class Meta: 9 | model = UserModel 10 | strict = True 11 | sqla_session = db.session -------------------------------------------------------------------------------- /restful/requirements.txt: -------------------------------------------------------------------------------- 1 | aniso8601==1.3.0 2 | click==6.7 3 | Flask==1.0 4 | flask-marshmallow==0.8.0 5 | Flask-MySQL==1.4.0 6 | Flask-RESTful==0.3.6 7 | Flask-SQLAlchemy==2.3.2 8 | itsdangerous==0.24 9 | marshmallow==2.15.1 10 | marshmallow-sqlalchemy==0.13.2 11 | PyMySQL==0.7.11 12 | python-dateutil==2.6.1 13 | pytz==2017.3 14 | six==1.11.0 15 | SQLAlchemy==1.1.15 16 | webargs==5.1.3 17 | Werkzeug==0.15.3 18 | boto3==1.5.22 19 | --------------------------------------------------------------------------------