# pyease-grpc
[![Build](https://github.com/dipu-bd/pyease-grpc/actions/workflows/commit.yml/badge.svg)](https://github.com/dipu-bd/pyease-grpc/actions/workflows/commit.yml)
[![Release](https://github.com/dipu-bd/pyease-grpc/actions/workflows/release.yml/badge.svg)](https://github.com/dipu-bd/pyease-grpc/actions/workflows/release.yml)
[![PyPI version](https://img.shields.io/pypi/v/pyease-grpc.svg?logo=python)](https://pypi.org/project/pyease-grpc)
[![Python version](https://img.shields.io/pypi/pyversions/pyease-grpc.svg)](https://pypi.org/project/pyease-grpc)
[![GitHub License](https://img.shields.io/github/license/dipu-bd/pyease-grpc)](https://github.com/dipu-bd/pyease-grpc/blob/master/LICENSE)
[![Downloads](https://pepy.tech/badge/pyease-grpc/month)](https://pepy.tech/project/pyease-grpc)
Easy to use gRPC-web client in python
### Installation
Install the package using:
```
$ pip install pyease-grpc
```
Run the following to check if it has been installed correctly:
```
$ pyease-grpc --version
```
## Tutorial
> Before you start, you need to have a basic understanding of [how gRPC works](https://grpc.io/docs/what-is-grpc/introduction/).
This package provides a `requests` like interface to make calls to native gRPC and gRPC-Web servers.
### Example Server
An example server and client can be found in the `example` folder.
```
> cd example
> docker compose up
```
It uses two ports:
- Native gRPC server: `localhost:50050`
- gRPC-Web server using envoy: `http://localhost:8080`
You can test the native serve with the client:
```
$ python example/server/client.py
Calling SayHello:
reply: "Hello, world!"
Calling LotsOfReplies:
reply: "Hello, world no. 0!"
reply: "Hello, world no. 1!"
reply: "Hello, world no. 2!"
reply: "Hello, world no. 3!"
reply: "Hello, world no. 4!"
Calling LotsOfGreetings:
reply: "Hello, A, B, C!"
Calling BidiHello:
reply: "Hello, A!"
reply: "Hello, B!"
reply: "Hello, C!"
```
### Loading the Protobuf
The proto file is located at `example/server/abc.proto`
```proto
// file: example/server/abc.proto
syntax = "proto3";
package pyease.sample.v1;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloResponse);
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string reply = 1;
}
```
You can directly load this file using `pyease_grpc` without generating any stubs:
```py
from pyease_grpc import Protobuf
protobuf = Protobuf.from_file("example/server/abc.proto")
```
Internally, it converts the proto file into `FileDescriptorSet` message.
It is recommended to use the `FileDescriptorSet` json to load the `Protobuf` faster.
To generate the `FileDescriptorSet` json from a proto file:
```
$ pyease-grpc -I example/server example/server/abc.proto --output abc_fds.json
```
Now you can use this descriptor file directly to create a `Protobuf` instance.
```py
protobuf = Protobuf.restore_file('abc_fds.json')
```
### Getting response from gRPC-Web
For **Unary RPC** request:
```py
from pyease_grpc import RpcSession, RpcUri
session = RpcSession.from_file("example/server/abc.proto")
response = session.request(
RpcUri(
base_url="http://localhost:8080",
package="pyease.sample.v1",
service="Greeter",
method="SayHello",
),
{
"name": "world"
},
)
response.raise_for_status()
print(response.single['reply'])
```
For a **Server-side Streaming RPC** request:
```py
from pyease_grpc import RpcSession, RpcUri
session = RpcSession.from_file("example/server/abc.proto")
response = session.request(
RpcUri(
base_url="http://localhost:8080",
package="pyease.sample.v1",
service="Greeter",
method="LotsOfReplies",
),
{
"name": "world",
},
)
response.raise_for_status()
for payload in response.iter_payloads():
print(payload["reply"])
```
> gRPC-Web currently supports 2 RPC modes: Unary RPCs, Server-side Streaming RPCs.
> Client-side and Bi-directional streaming is not currently supported.
### Using the native gRPC protocol
You can also directly call a method using the native gRPC protocol.
For **Unary RPC** request:
```py
from pyease_grpc import RpcSession, RpcUri
session = RpcSession.from_file("example/server/abc.proto")
response = session.call(
RpcUri(
base_url="localhost:50050",
package="pyease.sample.v1",
service="Greeter",
method="SayHello",
),
{
"name": "world",
}
)
print(response.single["reply"])
print(response.payloads)
```
For a **Server-side Streaming RPC** request:
```py
from pyease_grpc import RpcSession, RpcUri
session = RpcSession.from_file("example/server/abc.proto")
response = session.call(
RpcUri(
base_url="localhost:50050",
package="pyease.sample.v1",
service="Greeter",
method="LotsOfReplies",
),
{
"name": "world",
},
)
for payload in response.iter_payloads():
print(payload["reply"])
print(response.payloads)
```
For a **Client-Side Streaming RPC** request:
```py
from pyease_grpc import RpcSession, RpcUri
session = RpcSession.from_file("example/server/abc.proto")
response = session.call(
RpcUri(
base_url="localhost:50050",
package="pyease.sample.v1",
service="Greeter",
method="LotsOfGreetings",
),
iter(
[
{"name": "A"},
{"name": "B"},
{"name": "C"},
]
),
)
print(response.single["reply"])
print(response.payloads)
```
For a **Bidirectional Streaming RPC** request:
```py
from pyease_grpc import RpcSession, RpcUri
session = RpcSession.from_file("example/server/abc.proto")
response = session.call(
RpcUri(
base_url="localhost:50050",
package="pyease.sample.v1",
service="Greeter",
method="BidiHello",
),
iter(
[
{"name": "A"},
{"name": "B"},
{"name": "C"},
]
),
)
for payload in response.iter_payloads():
print(payload["reply"])
print(response.payloads)
```
### Error Handling
Errors are raised as soon as they appear.
List of errors that can appear during `request`:
- `ValueError`: If the requested method, service or package is not found
- `requests.exceptions.InvalidHeader`: If the header of expected length is not found
- `requests.exceptions.ContentDecodingError`: If the data of expected length is not found
- `NotImplementedError`: If compression is enabled in the response headers
- `grpc.RpcError`: If the grpc-status is non-zero
List of errors that can appear during `call`:
- `ValueError`: If the requested method, service or package is not found
- `grpc.RpcError`: If the grpc-status is non-zero
To get the `grpc-status` and `grpc-message`, you can add a try-catch to your call. e.g.:
```py
import grpc
from pyease_grpc import RpcSession, RpcUri
session = RpcSession.from_file("example/server/abc.proto")
rpc_uri = RpcUri(
base_url="localhost:50050",
package="pyease.sample.v1",
service="Greeter",
method="SayHello",
)
try:
response = session.call(rpc_uri, {"name": "error"})
print(response.single["reply"])
except grpc.RpcError as e:
print('grpc status', e.code())
print('grpc message', e.details())
```
Raw data
{
"_id": null,
"home_page": "https://github.com/dipu-bd/pyease-grpc",
"name": "pyease-grpc",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "grpc, protobuf, grpc-web, requests",
"author": "Sudipto Chandra",
"author_email": "dipu.sudipta@gmail.com",
"download_url": null,
"platform": null,
"description": "# pyease-grpc\n\n[![Build](https://github.com/dipu-bd/pyease-grpc/actions/workflows/commit.yml/badge.svg)](https://github.com/dipu-bd/pyease-grpc/actions/workflows/commit.yml)\n[![Release](https://github.com/dipu-bd/pyease-grpc/actions/workflows/release.yml/badge.svg)](https://github.com/dipu-bd/pyease-grpc/actions/workflows/release.yml)\n[![PyPI version](https://img.shields.io/pypi/v/pyease-grpc.svg?logo=python)](https://pypi.org/project/pyease-grpc)\n[![Python version](https://img.shields.io/pypi/pyversions/pyease-grpc.svg)](https://pypi.org/project/pyease-grpc)\n[![GitHub License](https://img.shields.io/github/license/dipu-bd/pyease-grpc)](https://github.com/dipu-bd/pyease-grpc/blob/master/LICENSE)\n[![Downloads](https://pepy.tech/badge/pyease-grpc/month)](https://pepy.tech/project/pyease-grpc)\n\nEasy to use gRPC-web client in python\n\n### Installation\n\nInstall the package using:\n\n```\n$ pip install pyease-grpc\n```\n\nRun the following to check if it has been installed correctly:\n\n```\n$ pyease-grpc --version\n```\n\n## Tutorial\n\n> Before you start, you need to have a basic understanding of [how gRPC works](https://grpc.io/docs/what-is-grpc/introduction/).\n\nThis package provides a `requests` like interface to make calls to native gRPC and gRPC-Web servers.\n\n### Example Server\n\nAn example server and client can be found in the `example` folder.\n\n```\n> cd example\n> docker compose up\n```\n\nIt uses two ports:\n\n- Native gRPC server: `localhost:50050`\n- gRPC-Web server using envoy: `http://localhost:8080`\n\nYou can test the native serve with the client:\n\n```\n$ python example/server/client.py\nCalling SayHello:\nreply: \"Hello, world!\"\n\nCalling LotsOfReplies:\nreply: \"Hello, world no. 0!\"\nreply: \"Hello, world no. 1!\"\nreply: \"Hello, world no. 2!\"\nreply: \"Hello, world no. 3!\"\nreply: \"Hello, world no. 4!\"\n\nCalling LotsOfGreetings:\nreply: \"Hello, A, B, C!\"\n\nCalling BidiHello:\nreply: \"Hello, A!\"\nreply: \"Hello, B!\"\nreply: \"Hello, C!\"\n```\n\n### Loading the Protobuf\n\nThe proto file is located at `example/server/abc.proto`\n\n```proto\n// file: example/server/abc.proto\nsyntax = \"proto3\";\n\npackage pyease.sample.v1;\n\nservice Greeter {\n rpc SayHello (HelloRequest) returns (HelloResponse);\n rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);\n rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);\n rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);\n}\n\nmessage HelloRequest {\n string name = 1;\n}\n\nmessage HelloResponse {\n string reply = 1;\n}\n```\n\nYou can directly load this file using `pyease_grpc` without generating any stubs:\n\n```py\nfrom pyease_grpc import Protobuf\n\nprotobuf = Protobuf.from_file(\"example/server/abc.proto\")\n```\n\nInternally, it converts the proto file into `FileDescriptorSet` message.\n\nIt is recommended to use the `FileDescriptorSet` json to load the `Protobuf` faster.\n\nTo generate the `FileDescriptorSet` json from a proto file:\n\n```\n$ pyease-grpc -I example/server example/server/abc.proto --output abc_fds.json\n```\n\nNow you can use this descriptor file directly to create a `Protobuf` instance.\n\n```py\nprotobuf = Protobuf.restore_file('abc_fds.json')\n```\n\n### Getting response from gRPC-Web\n\nFor **Unary RPC** request:\n\n```py\nfrom pyease_grpc import RpcSession, RpcUri\n\nsession = RpcSession.from_file(\"example/server/abc.proto\")\nresponse = session.request(\n RpcUri(\n base_url=\"http://localhost:8080\",\n package=\"pyease.sample.v1\",\n service=\"Greeter\",\n method=\"SayHello\",\n ),\n {\n \"name\": \"world\"\n },\n)\nresponse.raise_for_status()\n\nprint(response.single['reply'])\n```\n\nFor a **Server-side Streaming RPC** request:\n\n```py\nfrom pyease_grpc import RpcSession, RpcUri\n\nsession = RpcSession.from_file(\"example/server/abc.proto\")\nresponse = session.request(\n RpcUri(\n base_url=\"http://localhost:8080\",\n package=\"pyease.sample.v1\",\n service=\"Greeter\",\n method=\"LotsOfReplies\",\n ),\n {\n \"name\": \"world\",\n },\n)\nresponse.raise_for_status()\n\nfor payload in response.iter_payloads():\n print(payload[\"reply\"])\n```\n\n> gRPC-Web currently supports 2 RPC modes: Unary RPCs, Server-side Streaming RPCs.\n> Client-side and Bi-directional streaming is not currently supported.\n\n### Using the native gRPC protocol\n\nYou can also directly call a method using the native gRPC protocol.\n\nFor **Unary RPC** request:\n\n```py\nfrom pyease_grpc import RpcSession, RpcUri\n\nsession = RpcSession.from_file(\"example/server/abc.proto\")\nresponse = session.call(\n RpcUri(\n base_url=\"localhost:50050\",\n package=\"pyease.sample.v1\",\n service=\"Greeter\",\n method=\"SayHello\",\n ),\n {\n \"name\": \"world\",\n }\n)\n\nprint(response.single[\"reply\"])\nprint(response.payloads)\n```\n\nFor a **Server-side Streaming RPC** request:\n\n```py\nfrom pyease_grpc import RpcSession, RpcUri\n\nsession = RpcSession.from_file(\"example/server/abc.proto\")\nresponse = session.call(\n RpcUri(\n base_url=\"localhost:50050\",\n package=\"pyease.sample.v1\",\n service=\"Greeter\",\n method=\"LotsOfReplies\",\n ),\n {\n \"name\": \"world\",\n },\n)\n\nfor payload in response.iter_payloads():\n print(payload[\"reply\"])\nprint(response.payloads)\n```\n\nFor a **Client-Side Streaming RPC** request:\n\n```py\nfrom pyease_grpc import RpcSession, RpcUri\n\nsession = RpcSession.from_file(\"example/server/abc.proto\")\nresponse = session.call(\n RpcUri(\n base_url=\"localhost:50050\",\n package=\"pyease.sample.v1\",\n service=\"Greeter\",\n method=\"LotsOfGreetings\",\n ),\n iter(\n [\n {\"name\": \"A\"},\n {\"name\": \"B\"},\n {\"name\": \"C\"},\n ]\n ),\n)\n\nprint(response.single[\"reply\"])\nprint(response.payloads)\n```\n\nFor a **Bidirectional Streaming RPC** request:\n\n```py\nfrom pyease_grpc import RpcSession, RpcUri\n\nsession = RpcSession.from_file(\"example/server/abc.proto\")\nresponse = session.call(\n RpcUri(\n base_url=\"localhost:50050\",\n package=\"pyease.sample.v1\",\n service=\"Greeter\",\n method=\"BidiHello\",\n ),\n iter(\n [\n {\"name\": \"A\"},\n {\"name\": \"B\"},\n {\"name\": \"C\"},\n ]\n ),\n)\n\nfor payload in response.iter_payloads():\n print(payload[\"reply\"])\nprint(response.payloads)\n```\n\n### Error Handling\n\nErrors are raised as soon as they appear.\n\nList of errors that can appear during `request`:\n\n- `ValueError`: If the requested method, service or package is not found\n- `requests.exceptions.InvalidHeader`: If the header of expected length is not found\n- `requests.exceptions.ContentDecodingError`: If the data of expected length is not found\n- `NotImplementedError`: If compression is enabled in the response headers\n- `grpc.RpcError`: If the grpc-status is non-zero\n\nList of errors that can appear during `call`:\n\n- `ValueError`: If the requested method, service or package is not found\n- `grpc.RpcError`: If the grpc-status is non-zero\n\nTo get the `grpc-status` and `grpc-message`, you can add a try-catch to your call. e.g.:\n\n```py\nimport grpc\nfrom pyease_grpc import RpcSession, RpcUri\n\nsession = RpcSession.from_file(\"example/server/abc.proto\")\nrpc_uri = RpcUri(\n base_url=\"localhost:50050\",\n package=\"pyease.sample.v1\",\n service=\"Greeter\",\n method=\"SayHello\",\n)\ntry:\n response = session.call(rpc_uri, {\"name\": \"error\"})\n print(response.single[\"reply\"])\nexcept grpc.RpcError as e:\n print('grpc status', e.code())\n print('grpc message', e.details())\n```\n",
"bugtrack_url": null,
"license": null,
"summary": "Easy gRPC-web client in python",
"version": "1.7.0",
"project_urls": {
"Homepage": "https://github.com/dipu-bd/pyease-grpc"
},
"split_keywords": [
"grpc",
" protobuf",
" grpc-web",
" requests"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "7026f1437aedd58397cc3b572cedeb10e2f565fd508120a4b47dbcaacea586d9",
"md5": "7b68967ad340f526af8d6f8493b01c23",
"sha256": "279047e03f21f4b65ebe251d527504a8f64d84ccfa028b50bba0e7315321f05e"
},
"downloads": -1,
"filename": "pyease_grpc-1.7.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7b68967ad340f526af8d6f8493b01c23",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 16056,
"upload_time": "2024-08-12T18:31:23",
"upload_time_iso_8601": "2024-08-12T18:31:23.046928Z",
"url": "https://files.pythonhosted.org/packages/70/26/f1437aedd58397cc3b572cedeb10e2f565fd508120a4b47dbcaacea586d9/pyease_grpc-1.7.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-08-12 18:31:23",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "dipu-bd",
"github_project": "pyease-grpc",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "requests",
"specs": [
[
">=",
"2.25.0"
]
]
},
{
"name": "protobuf",
"specs": [
[
">=",
"3.19.0"
]
]
},
{
"name": "grpcio",
"specs": []
}
],
"lcname": "pyease-grpc"
}