Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
feea790116 | ||
|
33f74f6a45 | ||
|
3d5c12c532 | ||
|
706bd5a475 | ||
|
52beeb0d73 | ||
|
7e2dc595db |
41
CHANGELOG.md
Normal file
41
CHANGELOG.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.2.1] - 2019-10-29
|
||||
|
||||
- Fix comment indentation bug in rendered gRPC methods.
|
||||
|
||||
## [1.2.0] - 2019-10-28
|
||||
|
||||
- Generated code output auto-formatting via [Black](https://github.com/psf/black)
|
||||
- Simplified gRPC helper functions
|
||||
|
||||
## [1.1.0] - 2019-10-27
|
||||
|
||||
- Better JSON casing support
|
||||
- Handle field names which clash with Python reserved words
|
||||
- Better handling of default values from type introspection
|
||||
- Support for Google Duration & Timestamp types
|
||||
- Support for Google wrapper types
|
||||
- Documentation updates
|
||||
|
||||
## [1.0.1] - 2019-10-22
|
||||
|
||||
- README to the PyPI details page
|
||||
|
||||
## [1.0.0] - 2019-10-22
|
||||
|
||||
- Initial release
|
||||
|
||||
[unreleased]: https://github.com/danielgtaylor/python-betterproto/compare/v1.2.1...HEAD
|
||||
[1.2.1]: https://github.com/danielgtaylor/python-betterproto/compare/v1.2.0...v1.2.1
|
||||
[1.2.0]: https://github.com/danielgtaylor/python-betterproto/compare/v1.1.0...v1.2.0
|
||||
[1.1.0]: https://github.com/danielgtaylor/python-betterproto/compare/v1.0.1...v1.1.0
|
||||
[1.0.1]: https://github.com/danielgtaylor/python-betterproto/compare/v1.0.0...v1.0.1
|
||||
[1.0.0]: https://github.com/danielgtaylor/python-betterproto/releases/tag/v1.0.0
|
4
Pipfile
4
Pipfile
@@ -15,6 +15,7 @@ protobuf = "*"
|
||||
jinja2 = "*"
|
||||
grpclib = "*"
|
||||
stringcase = "*"
|
||||
black = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
@@ -23,3 +24,6 @@ python_version = "3.7"
|
||||
plugin = "protoc --plugin=protoc-gen-custom=betterproto/plugin.py --custom_out=output"
|
||||
generate = "python betterproto/tests/generate.py"
|
||||
test = "pytest ./betterproto/tests"
|
||||
|
||||
[pipenv]
|
||||
allow_prereleases = true
|
||||
|
64
Pipfile.lock
generated
64
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "28c38cd6c4eafb0b9ac9a64cf623145868fdee163111d3b941b34d23011db6ca"
|
||||
"sha256": "c7b72ed87dc3d70566c53d7ec8a636c8d4854aa30aa97a9116c0734cd5266f33"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@@ -16,12 +16,41 @@
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"grpclib": {
|
||||
"appdirs": {
|
||||
"hashes": [
|
||||
"sha256:d19e2ea87cb073e5b0825dfee15336fd2b1c09278d271816e04c90faddc107ea"
|
||||
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
|
||||
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
|
||||
],
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
||||
],
|
||||
"version": "==19.3.0"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf",
|
||||
"sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.3.0"
|
||||
"version": "==19.3b0"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
||||
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
||||
],
|
||||
"version": "==7.0"
|
||||
},
|
||||
"grpclib": {
|
||||
"hashes": [
|
||||
"sha256:2d63cee35f764e40a7ea196f27354d2f4ab936401c40b14128bbb4fec06f51d4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.3.1rc2"
|
||||
},
|
||||
"h2": {
|
||||
"hashes": [
|
||||
@@ -154,6 +183,13 @@
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
||||
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
|
||||
],
|
||||
"version": "==0.10.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
@@ -180,11 +216,11 @@
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548",
|
||||
"sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"
|
||||
"sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb",
|
||||
"sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.7.8"
|
||||
"version": "==3.7.9"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
@@ -287,11 +323,11 @@
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:7e4800063ccfc306a53c461442526c5571e1462f61583506ce97e4da6a1d88c8",
|
||||
"sha256:ca563435f4941d0cb34767301c27bc65c510cb82e90b9ecf9cb52dc2c63caaa0"
|
||||
"sha256:27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6",
|
||||
"sha256:58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.2.1"
|
||||
"version": "==5.2.2"
|
||||
},
|
||||
"rope": {
|
||||
"hashes": [
|
||||
@@ -336,11 +372,11 @@
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95",
|
||||
"sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87",
|
||||
"sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed"
|
||||
"sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2",
|
||||
"sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d",
|
||||
"sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575"
|
||||
],
|
||||
"version": "==3.7.4"
|
||||
"version": "==3.7.4.1"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
|
@@ -281,7 +281,7 @@ You can do stuff like:
|
||||
```py
|
||||
>>> t = Test().from_dict({"maybe": True, "ts": "2019-01-01T12:00:00Z", "duration": "1.200s"})
|
||||
>>> t
|
||||
st(maybe=True, ts=datetime.datetime(2019, 1, 1, 12, 0, tzinfo=datetime.timezone.utc), duration=datetime.timedelta(seconds=1, microseconds=200000))
|
||||
Test(maybe=True, ts=datetime.datetime(2019, 1, 1, 12, 0, tzinfo=datetime.timezone.utc), duration=datetime.timedelta(seconds=1, microseconds=200000))
|
||||
|
||||
>>> t.ts - t.duration
|
||||
datetime.datetime(2019, 1, 1, 11, 59, 58, 800000, tzinfo=datetime.timezone.utc)
|
||||
|
@@ -21,6 +21,7 @@ from typing import (
|
||||
TypeVar,
|
||||
Union,
|
||||
get_type_hints,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
import grpclib.client
|
||||
@@ -29,6 +30,9 @@ import stringcase
|
||||
|
||||
from .casing import safe_snake_case
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from grpclib._protocols import IProtoMessage
|
||||
|
||||
# Proto 3 data types
|
||||
TYPE_ENUM = "enum"
|
||||
TYPE_BOOL = "bool"
|
||||
@@ -420,6 +424,7 @@ class Message(ABC):
|
||||
register the message fields which get used by the serializers and parsers
|
||||
to go between Python, binary and JSON protobuf message representations.
|
||||
"""
|
||||
|
||||
_serialized_on_wire: bool
|
||||
_unknown_fields: bytes
|
||||
_group_map: Dict[str, dict]
|
||||
@@ -705,7 +710,7 @@ class Message(ABC):
|
||||
for field in dataclasses.fields(self):
|
||||
meta = FieldMetadata.get(field)
|
||||
v = getattr(self, field.name)
|
||||
cased_name = casing(field.name).rstrip("_") # type: ignore
|
||||
cased_name = casing(field.name).rstrip("_") # type: ignore
|
||||
if meta.proto_type == "message":
|
||||
if isinstance(v, datetime):
|
||||
if v != DATETIME_ZERO:
|
||||
@@ -741,7 +746,7 @@ class Message(ABC):
|
||||
else:
|
||||
output[cased_name] = b64encode(v).decode("utf8")
|
||||
elif meta.proto_type == TYPE_ENUM:
|
||||
enum_values = list(self._cls_for(field)) # type: ignore
|
||||
enum_values = list(self._cls_for(field)) # type: ignore
|
||||
if isinstance(v, list):
|
||||
output[cased_name] = [enum_values[e].name for e in v]
|
||||
else:
|
||||
@@ -902,6 +907,7 @@ class _WrappedMessage(Message):
|
||||
Google protobuf wrapper types base class. JSON representation is just the
|
||||
value itself.
|
||||
"""
|
||||
|
||||
value: Any
|
||||
|
||||
def to_dict(self, casing: Casing = Casing.CAMEL) -> Any:
|
||||
@@ -982,11 +988,11 @@ class ServiceStub(ABC):
|
||||
self.channel = channel
|
||||
|
||||
async def _unary_unary(
|
||||
self, route: str, request_type: Type, response_type: Type[T], request: Any
|
||||
self, route: str, request: "IProtoMessage", response_type: Type[T]
|
||||
) -> T:
|
||||
"""Make a unary request and return the response."""
|
||||
async with self.channel.request(
|
||||
route, grpclib.const.Cardinality.UNARY_UNARY, request_type, response_type
|
||||
route, grpclib.const.Cardinality.UNARY_UNARY, type(request), response_type
|
||||
) as stream:
|
||||
await stream.send_message(request, end=True)
|
||||
response = await stream.recv_message()
|
||||
@@ -994,11 +1000,11 @@ class ServiceStub(ABC):
|
||||
return response
|
||||
|
||||
async def _unary_stream(
|
||||
self, route: str, request_type: Type, response_type: Type[T], request: Any
|
||||
self, route: str, request: "IProtoMessage", response_type: Type[T]
|
||||
) -> AsyncGenerator[T, None]:
|
||||
"""Make a unary request and return the stream response iterator."""
|
||||
async with self.channel.request(
|
||||
route, grpclib.const.Cardinality.UNARY_STREAM, request_type, response_type
|
||||
route, grpclib.const.Cardinality.UNARY_STREAM, type(request), response_type
|
||||
) as stream:
|
||||
await stream.send_message(request, end=True)
|
||||
async for message in stream:
|
||||
|
@@ -9,13 +9,14 @@ import textwrap
|
||||
from typing import Any, List, Tuple
|
||||
|
||||
try:
|
||||
import jinja2
|
||||
import black
|
||||
except ImportError:
|
||||
print(
|
||||
"Unable to import `jinja2`. Did you install the compiler feature with `pip install betterproto[compiler]`?"
|
||||
"Unable to import `black` formatter. Did you install the compiler feature with `pip install betterproto[compiler]`?"
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
import jinja2
|
||||
import stringcase
|
||||
|
||||
from google.protobuf.compiler import plugin_pb2 as plugin
|
||||
@@ -141,25 +142,26 @@ def traverse(proto_file):
|
||||
)
|
||||
|
||||
|
||||
def get_comment(proto_file, path: List[int]) -> str:
|
||||
def get_comment(proto_file, path: List[int], indent: int = 4) -> str:
|
||||
pad = " " * indent
|
||||
for sci in proto_file.source_code_info.location:
|
||||
# print(list(sci.path), path, file=sys.stderr)
|
||||
if list(sci.path) == path and sci.leading_comments:
|
||||
lines = textwrap.wrap(
|
||||
sci.leading_comments.strip().replace("\n", ""), width=75
|
||||
sci.leading_comments.strip().replace("\n", ""), width=79 - indent
|
||||
)
|
||||
|
||||
if path[-2] == 2 and path[-4] != 6:
|
||||
# This is a field
|
||||
return " # " + "\n # ".join(lines)
|
||||
return f"{pad}# " + f"\n{pad}# ".join(lines)
|
||||
else:
|
||||
# This is a message, enum, service, or method
|
||||
if len(lines) == 1 and len(lines[0]) < 70:
|
||||
if len(lines) == 1 and len(lines[0]) < 79 - indent - 6:
|
||||
lines[0] = lines[0].strip('"')
|
||||
return f' """{lines[0]}"""'
|
||||
return f'{pad}"""{lines[0]}"""'
|
||||
else:
|
||||
joined = "\n ".join(lines)
|
||||
return f' """\n {joined}\n """'
|
||||
joined = f"\n{pad}".join(lines)
|
||||
return f'{pad}"""\n{pad}{joined}\n{pad}"""'
|
||||
|
||||
return ""
|
||||
|
||||
@@ -370,7 +372,7 @@ def generate_code(request, response):
|
||||
{
|
||||
"name": method.name,
|
||||
"py_name": stringcase.snakecase(method.name),
|
||||
"comment": get_comment(proto_file, [6, i, 2, j]),
|
||||
"comment": get_comment(proto_file, [6, i, 2, j], indent=8),
|
||||
"route": f"/{package}.{service.name}/{method.name}",
|
||||
"input": get_ref_type(
|
||||
package, output["imports"], method.input_type
|
||||
@@ -398,8 +400,11 @@ def generate_code(request, response):
|
||||
# print(filename, file=sys.stderr)
|
||||
f.name = filename.replace(".", os.path.sep) + ".py"
|
||||
|
||||
# f.content = json.dumps(output, indent=2)
|
||||
f.content = template.render(description=output).rstrip("\n") + "\n"
|
||||
# Render and then format the output file.
|
||||
f.content = black.format_str(
|
||||
template.render(description=output),
|
||||
mode=black.FileMode(target_versions=set([black.TargetVersion.PY37])),
|
||||
)
|
||||
|
||||
inits = set([""])
|
||||
for f in response.file:
|
||||
|
@@ -15,8 +15,8 @@ import betterproto
|
||||
{% if description.services %}
|
||||
import grpclib
|
||||
{% endif %}
|
||||
{% for i in description.imports %}
|
||||
|
||||
{% for i in description.imports %}
|
||||
{{ i }}
|
||||
{% endfor %}
|
||||
|
||||
@@ -81,17 +81,15 @@ class {{ service.py_name }}Stub(betterproto.ServiceStub):
|
||||
{% if method.server_streaming %}
|
||||
async for response in self._unary_stream(
|
||||
"{{ method.route }}",
|
||||
{{ method.input }},
|
||||
{{ method.output }},
|
||||
request,
|
||||
{{ method.output }},
|
||||
):
|
||||
yield response
|
||||
{% else %}
|
||||
return await self._unary_unary(
|
||||
"{{ method.route }}",
|
||||
{{ method.input }},
|
||||
{{ method.output }},
|
||||
request,
|
||||
{{ method.output }},
|
||||
)
|
||||
{% endif %}
|
||||
|
||||
|
4
setup.py
4
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="betterproto",
|
||||
version="1.1.0",
|
||||
version="1.2.1",
|
||||
description="A better Protobuf / gRPC generator & library",
|
||||
long_description=open("README.md", "r").read(),
|
||||
long_description_content_type="text/markdown",
|
||||
@@ -19,6 +19,6 @@ setup(
|
||||
package_data={"betterproto": ["py.typed", "templates/template.py"]},
|
||||
python_requires=">=3.7",
|
||||
install_requires=["grpclib", "stringcase"],
|
||||
extras_require={"compiler": ["jinja2", "protobuf"]},
|
||||
extras_require={"compiler": ["black", "jinja2", "protobuf"]},
|
||||
zip_safe=False,
|
||||
)
|
||||
|
Reference in New Issue
Block a user