Денис Катаев
tinkoff.ru
и WSDL друганя его
XML-RPC
JSON-RPC
и многообразие HTTP методов
уже немного БЕСИТ
Поверх HTTP/2
.proto файлы
RPC protocol
vs Thrift
template = "Итак, мы переводим {sum} {name}"
slots = {"sum": "1000", "name": "Азамату"}
prefix.wav + azamat.wav + 1000_rub.wavsyntax = "proto3";text to speech
syntax = "proto3";
message TemplateInput {
    string template           = 1;
    map<string, string> slots = 2;
    string backup_phrase      = 3;
};t = TemplateInput(
    template='Итак, мы переводим {sum} {name}', 
    slots={'sum': '1000', 'name': 'Азамату'}
)
b = t.SerializeToString()
b'\n.\xd0\x98\xd1\x82\xd0\xb0\xd0\xba, \xd0\xbc\xd1\x8b \xd0\xbf\xd0\xb5\xd1\x80\xd0\xb5\xd0\xb2\xd0\xbe\xd0\xb4\xd0\xb8\xd0\xbc {sum} {name}\x12\x16\n\x04name\x12\x0e\xd0\x90\xd0\xb7\xd0\xb0\xd0\xbc\xd0\xb0\xd1\x82\xd1\x83\x12\x0b\n\x03sum\x12\x041000't = TemplateInput(
    template='Итак, мы переводим {sum} {name}', 
    slots={'sum': '1000', 'name': 'Азамату'}
)
b = t.SerializeToString()t = TemplateInput(
    template='Итак, мы переводим {sum} {name}', 
    slots={'sum': '1000', 'name': 'Азамату'}
)
b = t.SerializeToString()
enum AudioEncoding {
    ENCODING_UNSPECIFIED = 0;
    LINEAR16 = 1;
    reserved "FLAC";
    reserved 2;
}Сахар
message Foo {
    string test = 1;
}
message Bar {
    string test = 1;
}
message Result {
    string type = 1;
    oneof object {
        Foo foo = 2;
        Bar bar = 3;
    }
}Массивы
message Voice {
    repeated string language_codes = 1;
    string name = 2;
}message TemplateInput {
    string template = 1;
    map<string, string> slots = 2;
    string backup_phrase = 3;
};
package tinkoff.tts;
import "google/protobuf/duration.proto";🤢 .gitmodules 🤢
FROM znly/protoc as proto
FROM python:3.7 as build_proto
COPY --from=proto /protobuf/google/ /protobuf/google/
RUN pip install --no-cache grpcio-tools
COPY ./tts.proto /source/tts.proto
FROM python:3.7-alpine
COPY --from=build_proto \
   /source/output/tts_pb2.py \
   /your_project/tts_pb2.pycopy compiled data from docker for local develop
protobuf:
    command: sh -c "cp -r /your_project/tts_pb2.py /mnt"
    build:
      context: protobuf
      dockerfile: Dockerfile
    volumes:
      - ./:/mnt
_TEMPLATEINPUT = _descriptor.Descriptor(
  name='TemplateInput',
  full_name='tinkoff.cloud.tts.v1.TemplateInput',
  filename=None,
  file=DESCRIPTOR,
  containing_type=None,
  fields=[
    _descriptor.FieldDescriptor(
      name='template', full_name='tinkoff.TemplateInput.template', index=0,
      number=1, type=9, cpp_type=9, label=1,
      has_default_value=False, default_value=_b("").decode('utf-8'),
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR),
    _descriptor.FieldDescriptor(
      name='slots', full_name='tinkoff.TemplateInput.slots', index=1,
      number=2, type=11, cpp_type=10, label=3,
      has_default_value=False, default_value=[],
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR),
    _descriptor.FieldDescriptor(
      name='backup_phrase', full_name='tinkoff.TemplateInput.backup_phrase', index=2,
      number=3, type=9, cpp_type=9, label=1,
      has_default_value=False, default_value=_b("").decode('utf-8'),
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR),
  ],
  extensions=[
  ],
  nested_types=[_TEMPLATEINPUT_SLOTSENTRY, ],
  enum_types=[
  ],
  serialized_options=None,
  is_extendable=False,
  syntax='proto3',
  extension_ranges=[],
  oneofs=[
  ],
  serialized_start=366,
  serialized_end=531,
)
from tts_pb2 import (
    TemplateInput,
    LINEAR16
)
from tts_pb2 import TemplateInput
tmpl = TemplateInput(
    template='some str',
    slots={}
)from tts_pb2 import TemplateInput
tmpl = TemplateInput()
# 👇 не работает!!!
tmpl.slots = {} # 👈 не работает!!!
# Только изменение
tmpl.slots.update(test=1)from tts_pb2 import TemplateInput
tmpl = TemplateInput()
tmpl.no_field = 1 # raises AttributeError
tmpl.template = 1234 # raises TypeErrorfrom tts_pb2 import TemplateInput
tmpl.IsInitialized()
tmpl.__str__()
tmpl.CopyFrom(other_mgs)
tmpl.Clear()from tts_pb2 import TemplateInput
tmpl = TemplateInput(template='test')
blob: bytes = tmpl.SerializeToString()
tmpl = TemplateInput()
tmpl.ParseFromString(data: bytes)С++ over Cython на борту
syntax = "proto3";
package tinkoff.tts;
import "google/api/annotations.proto";
service TextToSpeech {
    rpc Synthesize (SynthesizeRequest) 
        returns (SynthesizeResponse);
    }
    rpc StreamingSynthesize (SynthesizeRequest) 
        returns (stream StreamingSpeechResponse);
}
message TemplateInput {
    string template = 1;
    map<string, string> slots = 2;
    string backup_phrase = 3;
};
message SynthesizeSpeechRequest {
    SynthesisInput input = 1;
    VoiceSelectionParams voice = 2;
    AudioConfig audio_config = 3;
    TemplateInput template_input = 4;
}
*_pb2_grpc.py
from tts_pb2_grpc import TextToSpeechServicer
class Servicer(TextToSpeechServicer):
    def Synthesize(self, request, context):
        pass
    def StreamingSynthesize(self, request, context):
        pass
from grpc import ServicerContext
from tts_pb2 import (SynthesizeRequest, 
                     SynthesizeResponse,
                     StreamingSynthesizeResponse)
from tts_pb2_grpc import TextToSpeechServicer
class Servicer(TextToSpeechServicer):
    def Synthesize(
            self, request: SynthesizeRequest, 
            context: ServicerContext
    ) -> SynthesizeResponse:
        return SynthesizeResponse()
    def StreamingSynthesize(
            self, request: SynthesizeRequest, 
            context: ServicerContext
    ) -> StreamingSynthesizeResponse:
        yield StreamingSynthesizeResponse()
from concurrent import futures
import grpc
from tts_pb2_grpc import (
    add_TextToSpeechServicer_to_server)
from code import Servicer
servicer = Servicer()
pool = futures.ThreadPoolExecutor()
server = grpc.server(pool)
add_TextToSpeechServicer_to_server(
    servicer, server)
server.add_insecure_port(address)import signal
import time
import sys
server.run()
def stop(timeout=None):
    server.stop(timeout)
    pool.shutdown(wait=bool(timeout))
    sys.exit()
signal.signal(signal.SIGTERM, lambda *args: stop(15))
signal.signal(signal.SIGINT, lambda *args: stop())
while True:
    time.sleep(_ONE_DAY_IN_SECONDS)add_TextToSpeechServicer_to_server(
    servicer, server)
add_SpeechToTextServicer_to_server(
    servicer, server)
import gprc
from tts_pb2_grpc import TextToSpeechStub
endpoint = 'localhost:50051'
channel = grpc.insecure_channel(endpoint)
stub = TextToSpeechStub(channel)from tts_pb2 import (
    SynthesizeRequest, SynthesizeResponse
)
request = SynthesizeRequest()
response = stub.Synthesize(request)
assert isinstance(response, SynthesizeResponse)headers
jwt
from hypothesis_protobuf import modules_to_strategies
import tts_pb2
strategies = modules_to_strategies(tts_pb2)
@given(instant_message=strategies)
def test_instant_message_processor(instant_message):
    assert process_message(instant_message)stubs
pip install pytest-grpc
    def _end_unary_response_blocking(state, call, with_call, deadline):
        if state.code is grpc.StatusCode.OK:
            if with_call:
                rendezvous = _Rendezvous(state, call, None, deadline)
                return state.response, rendezvous
            else:
                return state.response
        else:
>           raise _Rendezvous(state, None, None, deadline)
E           grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
E           	status = StatusCode.UNKNOWN
E           	details = "Exception calling application: Some error"
E           	debug_error_string = "{"created":"@1552312084.410564000","description":"Error received from peer","file":"src/core/lib/surface/call.cc","file_line":1039,"grpc_message":"Exception calling application: Some error","grpc_status":2}"
E           >
по желанию
grpc_stub = <stub.test_pb2_grpc.EchoServiceStub object at #>
    def test_example(grpc_stub):
        request = EchoRequest()
>       response = grpc_stub.error_handler(request)
test_example.py:35:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../pytest_grpc/plugin.py:79: in fake_handler
    return real_method(request, context)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def error_handler(self, request: EchoRequest, context):
>       raise RuntimeError('Some error')
E       RuntimeError: Some error
src/servicer.py:10: RuntimeErrorimport pytest
from stub.test_pb2 import EchoRequest
@pytest.fixture(scope='module')
def grpc_add_to_server():
    from stub.test_pb2_grpc import (
        add_EchoServiceServicer_to_server
    )
    return add_EchoServiceServicer_to_server@pytest.fixture(scope='module')
def grpc_servicer():
    from servicer import Servicer
    return Servicer()
@pytest.fixture(scope='module')
def grpc_stub_cls(grpc_channel):
    from stub.test_pb2_grpc import EchoServiceStub
    return EchoServiceStubdef test_some(grpc_stub):
    request = EchoRequest()
    response = grpc_stub.handler(request)
    assert response.name == f'test-{request.name}'
def test_example(grpc_stub):
    request = EchoRequest()
    response = grpc_stub.error_handler(request)
    assert response.name == f'test-{request.name}'# с тредами и ближе к проду
$ py.test
# без транспорта, вызовы напрямую
# но чище трейсы
$ py.test --grpc-fake-server 
Хорошая альтернатива