Skip to content

Commit

Permalink
Merge pull request #31 from JoshYuJump/main
Browse files Browse the repository at this point in the history
Reduced the client code size (breaking changes from 1.x)
  • Loading branch information
JoshYuJump authored May 12, 2022
2 parents 9d8e408 + 0dcb270 commit 014c04d
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ indent_style = space
tab_width = 2

[*.py]
max_line_length = 100
max_line_length = 99
2 changes: 1 addition & 1 deletion .style.yapf
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[style]
based_on_style = facebook
COLUMN_LIMIT = 100
COLUMN_LIMIT = 99
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
<p align="center">
<img src="https://raw.githubusercontent.com/bali-framework/bali/master/docs/img/bali.png" alt='bali framework' />
</p>

<p align="center">
<b>bali-cli</b> is
CLI tools to simplify gRPC services and clients
</p>

# bali-cli

<img src="https://img.shields.io/pypi/v/bali-cli" />

CLI tools to simplify gRPC services and clients


Expand All @@ -16,7 +28,7 @@ CLI tools to simplify gRPC services and clients
## Related Projects

[![bali](https://github-readme-stats.vercel.app/api/pin/?username=JoshYuJump&repo=bali)](https://github.com/JoshYuJump/bali)
[![bali](https://github-readme-stats.vercel.app/api/pin/?username=bali-framework&repo=bali)](https://github.com/bali-framework/bali)


## CONTRIBUTE
Expand Down
2 changes: 1 addition & 1 deletion cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.1.3'
__version__ = '2.0.0'
13 changes: 12 additions & 1 deletion cli/biz.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,18 @@ def compile_client_file(proto_path: Path, service_name: str):
methods.append([i.strip() for i in _rpc.groups()])

template = jinja2_env.get_template("client.jinja2")
content = template.render(service=service, methods=methods, filename=proto_path.stem)

# service is like `UserService`
# service abbreviation is like `User`
service_abbr = service.replace('Service', '')

content = template.render(
service=service,
service_abbr=service_abbr,
service_cls=f'{service_abbr}Client',
methods=methods,
filename=proto_path.stem,
)
with (proto_path.parent / f"{service_name}_client.py").open(mode="w") as f:
f.write(content)

Expand Down
76 changes: 24 additions & 52 deletions cli/templates/client.jinja2
Original file line number Diff line number Diff line change
@@ -1,69 +1,41 @@
import logging
# Generated by the bali-cli 2.x. DO NOT EDIT!

from grpc import insecure_channel
from google.protobuf.empty_pb2 import Empty
import logging

from . import {{ filename }}_pb2, {{ filename }}_pb2_grpc, {{ filename }}_schema
from . import (
{{ filename }}_pb2 as pb2,
{{ filename }}_pb2_grpc as pb2_grpc,
{{ filename }}_schema as schemas,
)
from .._config import GRPC_ADDRESS, GRPC_LOGGER, GRPC_CACHE
from .._utils import MessageToDict, ParseDict, make_cache_key
from .._utils import ClientMixin

try:
from .._config import GRPC_CHANNEL_OPTIONS
except ImportError:
GRPC_CHANNEL_OPTIONS = None

__all__ = ["{{ service | replace("Service", "") }}Client"]
__all__ = ["{{ service_cls }}"]

logger = logging.getLogger(GRPC_LOGGER)


class {{ service | replace("Service", "") }}Client:
class {{ service_cls }}(ClientMixin):
URL = f"{GRPC_ADDRESS['host']}:{GRPC_ADDRESS['port']}"
s = schemas = {{ filename }}_schema
{% for method in methods %}
def {{ method[0] | decamelize }}(self{% if method[1] == "google.protobuf.Empty" %}{% else %}, schema: {{ filename }}_schema.{{ method[1] }}{% endif %}, *, fail_silently=False, cache_timeout=0, refresh=False) -> {{ filename }}_schema.{{ method[2] }}:

service_name, rpc_name = "{{ service }}", "{{ method[0] }}"
if GRPC_CACHE:
cache_key = make_cache_key(service_name, rpc_name, {% if method[1] != "google.protobuf.Empty" %}schema{% endif %})
if refresh:
GRPC_CACHE.delete(cache_key)
logger.info("gRPC client of %s.%s cache cleared: %s", service_name, rpc_name, cache_key)
# check cache
if cache_timeout:
cached = GRPC_CACHE.get(cache_key)
if cached is not None:
logger.info("gRPC client of %s.%s received(hit cache): %s", service_name, rpc_name, cached)
return cached
CHANNEL_OPTIONS = GRPC_CHANNEL_OPTIONS
pb2 = pb2
pb2_grpc = pb2_grpc

with insecure_channel(self.URL, options=GRPC_CHANNEL_OPTIONS) as channel:
stub = {{ filename }}_pb2_grpc.{{ service }}Stub(channel)
service_name, rpc_name = "{{ service }}", "{{ method[0] }}"
{% if method[1] == "google.protobuf.Empty" %}
logger.info("gRPC client of %s.%s send nothing", service_name, rpc_name)
request = Empty()
{% else %}
payload = schema.dict()
logger.info("gRPC client of %s.%s send: %s", service_name, rpc_name, payload)
request = ParseDict(payload, {{ filename }}_pb2.{{ method[1] }}())
{% endif %}
try:
response = stub.{{ method[0] }}(request)
except Exception as e:
logger.error("gRPC client of %s.%s exception: %s", service_name, rpc_name, e)
if not fail_silently:
raise
logger.warning(repr(e))
reply = {}
else:
reply = MessageToDict(response, True, True)

logger.info("gRPC client of %s.%s received: %s", service_name, rpc_name, reply)
result = {{ filename }}_schema.{{ method[2] }}(**reply)
# write cache
if GRPC_CACHE and cache_timeout:
GRPC_CACHE.set(cache_key, result, timeout=cache_timeout)

return {{ filename }}_schema.{{ method[2] }}(**reply)
logger = logger
cache = GRPC_CACHE

s = schemas
schemas = schemas
{% for method in methods %}
def {{ method[0] | decamelize }}(self{% if method[1] == "google.protobuf.Empty" %}{% else %}, schema: schemas.{{ method[1] }}{% endif %}, *, fail_silently=False, cache_timeout=0, refresh=False) -> schemas.{{ method[2] }}:
service, rpc_name, request_protobuf = "{{ service }}", "{{ method[0] }}", "{{ method[1] }}"
response_schema_cls = schemas.{{ method[2] }}
schema = {% if method[1] == "google.protobuf.Empty" %}None{% else %}schema{% endif %}
cache_options = {'timeout': cache_timeout, 'refresh': refresh}
return self.request(service, rpc_name, request_protobuf, schema, response_schema_cls, fail_silently, cache_options)
{% endfor %}
82 changes: 82 additions & 0 deletions cli/templates/utils.jinja2
Original file line number Diff line number Diff line change
@@ -1,8 +1,89 @@
# Generated by the bali-cli. DO NOT EDIT!
# Please updated the version when utils changed.

# bali-cli utils `v20220512`

import hashlib
from datetime import datetime, date
from urllib.parse import quote

from grpc import insecure_channel
from google.protobuf import json_format
from google.protobuf.empty_pb2 import Empty


# All RPC clients inherited from the `ClientMixin`
class ClientMixin:
def check_cache(self, service, rpc_name, options, *args, **kwargs):
cache_timeout = options.get('timeout')
refresh = options.get('refresh')
if not self.cache:
return None, None

cache_key = make_cache_key(service, rpc_name, *args, **kwargs)
if refresh:
self.cache.delete(cache_key)
self.logger.info("gRPC client of %s.%s cache cleared: %s", service, rpc_name, cache_key)
# check cache
if cache_timeout:
cached = self.cache.get(cache_key)
if cached is not None:
self.logger.info("gRPC client of %s.%s received(hit cache): %s", service, rpc_name, cached)
return cached, cache_key

return None, None

def set_cache(self, cache_key, result, cache_timeout):
if self.cache and cache_timeout:
self.cache.set(cache_key, result, timeout=cache_timeout)

def request(self, service, rpc_name, request_protobuf, schema, response_schema_cls, fail_silently, cache_options):
"""rpc request entry

:param service: Service name like `UserService`
:param rpc_name: Service name like `GetUser`
:param request_protobuf:
:param schema: RPC request schema objects, it's None when message type is Empty
:param response_schema_cls:
:param fail_silently: failed silently
:param cache_options: cache options include `timeout` and `refresh`
:return:
"""
if schema:
cached, cache_key = self.check_cache(service, rpc_name, cache_options, schema)
else:
cached, cache_key = self.check_cache(service, rpc_name, cache_options)
if cached is not None:
return cached

stub_cls = getattr(self.pb2_grpc, f'{service}Stub')
request_data = ParseDict(schema.dict(), getattr(self.pb2, request_protobuf)()) if schema else Empty()

with insecure_channel(self.URL, options=self.CHANNEL_OPTIONS) as channel:
stub = stub_cls(channel)
try:
self.logger.info("gRPC client of %s.%s send: %s. <%s>", service, rpc_name, request_data, type(request_data))
response = getattr(stub, rpc_name)(request_data)
except Exception as ex:
self.logger.error("gRPC client of %s.%s exception: %s", service, rpc_name, ex)
if not fail_silently:
raise
self.logger.warning(repr(ex))
reply = {}
else:
reply = MessageToDict(
response,
including_default_value_fields=True,
preserving_proto_field_name=True,
)

self.logger.info("gRPC client of %s.%s received: %s", service, rpc_name, reply)

result = response_schema_cls(**reply)

cache_timeout = cache_options.get('timeout')
self.set_cache(cache_key, result, cache_timeout)
return result


class ProtobufParser(json_format._Parser): # noqa
Expand Down Expand Up @@ -101,6 +182,7 @@ def ParseDict( # noqa
parser.ConvertMessage(js_dict, message)
return message


def make_cache_key(service, method, *args, **kwargs):
"""Make cache key

Expand Down
8 changes: 8 additions & 0 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-r requirements.txt

coverage==6.2
pytest==6.1.2
pytest-asyncio>=0.15.0
pytest-cov==2.12.0
twine==3.2.0
wheel==0.35.1

0 comments on commit 014c04d

Please sign in to comment.