Skip to content

Commit

Permalink
Merge pull request #23 from wesky93/viridianforge/retrieve-fields
Browse files Browse the repository at this point in the history
Viridianforge/retrieve fields
  • Loading branch information
ViridianForge authored Sep 3, 2023
2 parents c18e499 + b1439ff commit 633e714
Show file tree
Hide file tree
Showing 22 changed files with 456 additions and 25 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
pip install -e .
- name: Lint with flake8
run: |
flake8 . --count --show-source --statistics
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ grpcio>=1.49.1
grpcio-reflection>=1.49.1
google-api-core>=2.9.0
cryptography>=39.0.1
protobuf<=4.23.0
protobuf<=4.23.0
50 changes: 50 additions & 0 deletions src/examples/client_tester_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from grpc_requests.client import Client

"""
helloworld_reflection
An example of how grpc_requests works against a reflection enabled grpc server.
In order for this example to work, the example helloworld server must be
running.
"""

host = 'localhost'
port = '50051'
endpoint = f"{host}:{port}"

client = Client.get_by_endpoint(endpoint)

service = 'client_tester.ClientTester'

# Unary-Unary Example

unary_unary_method = 'TestUnaryUnary'
unary_unary_request = {}

response = client.unary_unary(service, unary_unary_method, unary_unary_request)
print(response)

# Unary-Stream Example

unary_stream_method = 'TestUnaryStream'
unary_stream_request = {}

responses = client.unary_stream(service, unary_stream_method, unary_stream_request)
print(responses)

# Stream-Unary Example

stream_unary_method = 'TestStreamUnary'
stream_unary_request = [{}, {}, {}]

response = client.stream_unary(service, stream_unary_method, stream_unary_request)
print(response)

# Stream-Stream Example

stream_stream_method = 'TestStreamStream'
stream_stream_request = [{}, {}, {}]

responses = client.stream_stream(service, stream_stream_method, stream_stream_request)
print(responses)
1 change: 1 addition & 0 deletions src/examples/helloworld_proto/helloworld.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ service Greeter {

// Message Definition
message HelloRequest {
//The name of the individual to say hello to. This is a required field.
string name = 1;
}

Expand Down
8 changes: 4 additions & 4 deletions src/examples/helloworld_reflection.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
unary_unary_request = {"name": "sinsky"}

response = client.unary_unary(service, unary_unary_method, unary_unary_request)
assert type(response) == dict
assert isinstance(response, dict)
assert response == {"message": "Hello, sinsky!"}

# Unary-Stream Example
Expand All @@ -34,7 +34,7 @@
unary_stream_request = {"name": "".join(name_list)}

responses = client.unary_stream(service, unary_stream_method, unary_stream_request)
assert all(type(response) == dict for response in responses)
assert all(isinstance(response, dict) for response in responses)
for response, name in zip(responses, name_list):
assert response == {"message": f"Hello, {name}!"}

Expand All @@ -44,7 +44,7 @@
stream_unary_request = [{"name": name} for name in name_list]

response = client.stream_unary(service, stream_unary_method, stream_unary_request)
assert type(response) == dict
assert isinstance(response, dict)
assert response == {'message': f"Hello, {name_string}!"}

# Stream-Stream Example
Expand All @@ -53,6 +53,6 @@
stream_stream_request = [{"name": name} for name in name_list]

responses = client.stream_stream(service, stream_stream_method, stream_stream_request)
assert all(type(response) == dict for response in responses)
assert all(isinstance(response, dict) for response in responses)
for response, name in zip(responses, name_list):
assert response == {"message": f"Hello, {name}!"}
5 changes: 4 additions & 1 deletion src/grpc_requests/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from google.protobuf.json_format import MessageToDict, ParseDict
from grpc_reflection.v1alpha import reflection_pb2, reflection_pb2_grpc

from .utils import load_data
from .utils import describe_request, load_data

if sys.version_info >= (3, 8):
from typing import TypedDict # pylint: disable=no-name-in-module
Expand Down Expand Up @@ -287,6 +287,9 @@ def stream_stream(self, service, method, requests, raw_output=False, **kwargs):
def get_service_descriptor(self, service):
return self._desc_pool.FindServiceByName(service)

def describe_method_request(self, service, method):
return describe_request(self.get_method_descriptor(service, method))

def get_method_descriptor(self, service, method):
svc_desc = self.get_service_descriptor(service)
return svc_desc.FindMethodByName(method)
Expand Down
34 changes: 34 additions & 0 deletions src/grpc_requests/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,41 @@
from pathlib import Path
from google.protobuf.descriptor import MethodDescriptor

# String descriptions of protobuf field types
FIELD_TYPES = [
'DOUBLE',
'FLOAT',
'INT64',
'UINT64',
'INT32',
'FIXED64',
'FIXED32',
'BOOL',
'STRING',
'GROUP',
'MESSAGE',
'BYTES',
'UINT32',
'ENUM',
'SFIXED32',
'SFIXED64',
'SINT32',
'SINT64'
]

def load_data(_path):
with open(Path(_path).expanduser(), 'rb') as f:
data = f.read()
return data

def describe_request(method_descriptor: MethodDescriptor) -> dict:
"""
Provide a dictionary that describes the fields of a Method request
with a string description of their types.
:param method_descriptor: MethodDescriptor
:return: dict - a mapping of field names to their types
"""
description = {}
for field in method_descriptor.input_type.fields:
description[field.name] = FIELD_TYPES[field.type-1]
return description
15 changes: 14 additions & 1 deletion src/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,30 @@
import pytest
import time

from .test_servers.helloworld_server import HelloWorldServer
from test_servers.helloworld.helloworld_server import HelloWorldServer
from test_servers.client_tester.client_tester_server import ClientTesterServer


def helloworld_server_starter():
server = HelloWorldServer('50051')
server.serve()

def client_tester_server_starter():
server = ClientTesterServer('50052')
server.serve()

@pytest.fixture(scope="session", autouse=True)
def helloworld_server():
helloworld_server_process = multiprocessing.Process(target=helloworld_server_starter)
helloworld_server_process.start()
time.sleep(1)
yield
helloworld_server_process.terminate()

@pytest.fixture(scope="session", autouse=True)
def client_tester_server():
client_tester_server_process = multiprocessing.Process(target=client_tester_server_starter)
client_tester_server_process.start()
time.sleep(1)
yield
client_tester_server_process.terminate()
36 changes: 29 additions & 7 deletions src/tests/reflection_client_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import pytest

from ..grpc_requests.client import Client
from grpc_requests.client import Client
from google.protobuf.json_format import ParseError

"""
Expand All @@ -18,16 +18,38 @@ def helloworld_reflection_client():
except: # noqa: E722
pytest.fail("Could not connect to local HelloWorld server")

@pytest.fixture(scope="module")
def client_tester_reflection_client():
try:
client = Client.get_by_endpoint('localhost:50051')
yield client
except: # noqa: E722
pytest.fail("Could not connect to local Test server")


def test_unary_unary(helloworld_reflection_client):
response = helloworld_reflection_client.request('helloworld.Greeter', 'SayHello', {"name": "sinsky"})
assert type(response) == dict
assert isinstance(response, dict)
assert response == {"message": "Hello, sinsky!"}

def test_describe_method_request(client_tester_reflection_client):
request_description = \
client_tester_reflection_client.describe_method_request('client_tester.ClientTester', 'TestUnaryUnary')
expected_request_description = {
'factor': 'INT32',
'readings': 'FLOAT',
'uuid': 'UINT64',
'sample_flag': 'BOOL',
'request_name': 'STRING',
'extra_data': 'BYTES'
}
assert (
request_description == expected_request_description
), f"Expected: {expected_request_description}, Actual: {request_description}"

def test_empty_body_request(helloworld_reflection_client):
response = helloworld_reflection_client.request('helloworld.Greeter', 'SayHello', {})
logger.warning(f"Response: {response}")
assert type(response) == dict
assert isinstance(response, dict)

def test_nonexistent_service(helloworld_reflection_client):
with pytest.raises(ValueError):
Expand All @@ -48,7 +70,7 @@ def test_unary_stream(helloworld_reflection_client):
'SayHelloGroup',
{"name": "".join(name_list)}
)
assert all(type(response) == dict for response in responses)
assert all(isinstance(response, dict) for response in responses)
for response, name in zip(responses, name_list):
assert response == {"message": f"Hello, {name}!"}

Expand All @@ -59,7 +81,7 @@ def test_stream_unary(helloworld_reflection_client):
'HelloEveryone',
[{"name": name} for name in name_list]
)
assert type(response) == dict
assert isinstance(response, dict)
assert response == {'message': f'Hello, {" ".join(name_list)}!'}

def test_stream_stream(helloworld_reflection_client):
Expand All @@ -69,7 +91,7 @@ def test_stream_stream(helloworld_reflection_client):
'SayHelloOneByOne',
[{"name": name} for name in name_list]
)
assert all(type(response) == dict for response in responses)
assert all(isinstance(response, dict) for response in responses)
for response, name in zip(responses, name_list):
assert response == {"message": f"Hello, {name}!"}

Expand Down
2 changes: 1 addition & 1 deletion src/tests/service_client_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import pytest

from ..grpc_requests.client import Client, ServiceClient
from grpc_requests.client import Client, ServiceClient

"""
Test cases for ServiceClient
Expand Down
14 changes: 7 additions & 7 deletions src/tests/stub_client_test.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import logging
import pytest

from ..grpc_requests.client import StubClient
from .test_protos.helloworld_pb2 import _GREETER
from grpc_requests.client import StubClient
from test_servers.helloworld.helloworld_pb2 import _GREETER
from google.protobuf.json_format import ParseError

"""
Expand All @@ -22,13 +22,13 @@ def helloworld_stub_client():

def test_unary_unary(helloworld_stub_client):
response = helloworld_stub_client.unary_unary('helloworld.Greeter', 'SayHello', {"name": "sinsky"})
assert type(response) == dict
assert isinstance(response, dict)
assert response == {"message": "Hello, sinsky!"}

def test_empty_body_request(helloworld_stub_client):
response = helloworld_stub_client.unary_unary('helloworld.Greeter', 'SayHello', {})
logger.warning(f"Response: {response}")
assert type(response) == dict
assert isinstance(response, dict)

def test_nonexistent_service(helloworld_stub_client):
with pytest.raises(ValueError):
Expand All @@ -49,7 +49,7 @@ def test_unary_stream(helloworld_stub_client):
'SayHelloGroup',
{"name": "".join(name_list)}
)
assert all(type(response) == dict for response in responses)
assert all(isinstance(response, dict) for response in responses)
for response, name in zip(responses, name_list):
assert response == {"message": f"Hello, {name}!"}

Expand All @@ -60,7 +60,7 @@ def test_stream_unary(helloworld_stub_client):
'HelloEveryone',
[{"name": name} for name in name_list]
)
assert type(response) == dict
assert isinstance(response, dict)
assert response == {'message': f'Hello, {" ".join(name_list)}!'}

def test_stream_stream(helloworld_stub_client):
Expand All @@ -70,6 +70,6 @@ def test_stream_stream(helloworld_stub_client):
'SayHelloOneByOne',
[{"name": name} for name in name_list]
)
assert all(type(response) == dict for response in responses)
assert all(isinstance(response, dict) for response in responses)
for response, name in zip(responses, name_list):
assert response == {"message": f"Hello, {name}!"}
File renamed without changes.
24 changes: 24 additions & 0 deletions src/tests/test_servers/client_tester/client_tester.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
syntax = "proto3";

package client_tester;

service ClientTester {
rpc TestUnaryUnary (TestRequest) returns (TestResponse) {}
rpc TestUnaryStream (TestRequest) returns (stream TestResponse) {}
rpc TestStreamUnary (stream TestRequest) returns (TestResponse) {}
rpc TestStreamStream (stream TestRequest) returns (stream TestResponse) {}
}

message TestRequest {
int32 factor = 1;
repeated float readings = 2;
uint64 uuid = 3;
bool sample_flag = 4;
string request_name = 5;
repeated bytes extra_data = 6;
}

message TestResponse {
double average = 1;
string feedback = 2;
}
30 changes: 30 additions & 0 deletions src/tests/test_servers/client_tester/client_tester_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 633e714

Please sign in to comment.