Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d5c12c532 | ||
|
|
706bd5a475 | ||
|
|
52beeb0d73 | ||
|
|
7e2dc595db |
36
CHANGELOG.md
Normal file
36
CHANGELOG.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# 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.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.0...HEAD
|
||||||
|
[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 = "*"
|
jinja2 = "*"
|
||||||
grpclib = "*"
|
grpclib = "*"
|
||||||
stringcase = "*"
|
stringcase = "*"
|
||||||
|
black = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.7"
|
python_version = "3.7"
|
||||||
@@ -23,3 +24,6 @@ python_version = "3.7"
|
|||||||
plugin = "protoc --plugin=protoc-gen-custom=betterproto/plugin.py --custom_out=output"
|
plugin = "protoc --plugin=protoc-gen-custom=betterproto/plugin.py --custom_out=output"
|
||||||
generate = "python betterproto/tests/generate.py"
|
generate = "python betterproto/tests/generate.py"
|
||||||
test = "pytest ./betterproto/tests"
|
test = "pytest ./betterproto/tests"
|
||||||
|
|
||||||
|
[pipenv]
|
||||||
|
allow_prereleases = true
|
||||||
|
|||||||
64
Pipfile.lock
generated
64
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "28c38cd6c4eafb0b9ac9a64cf623145868fdee163111d3b941b34d23011db6ca"
|
"sha256": "c7b72ed87dc3d70566c53d7ec8a636c8d4854aa30aa97a9116c0734cd5266f33"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@@ -16,12 +16,41 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
"grpclib": {
|
"appdirs": {
|
||||||
"hashes": [
|
"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",
|
"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": {
|
"h2": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -154,6 +183,13 @@
|
|||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.2.0"
|
"version": "==1.2.0"
|
||||||
|
},
|
||||||
|
"toml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
||||||
|
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
|
||||||
|
],
|
||||||
|
"version": "==0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
@@ -180,11 +216,11 @@
|
|||||||
},
|
},
|
||||||
"flake8": {
|
"flake8": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548",
|
"sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb",
|
||||||
"sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"
|
"sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.7.8"
|
"version": "==3.7.9"
|
||||||
},
|
},
|
||||||
"importlib-metadata": {
|
"importlib-metadata": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -287,11 +323,11 @@
|
|||||||
},
|
},
|
||||||
"pytest": {
|
"pytest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7e4800063ccfc306a53c461442526c5571e1462f61583506ce97e4da6a1d88c8",
|
"sha256:27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6",
|
||||||
"sha256:ca563435f4941d0cb34767301c27bc65c510cb82e90b9ecf9cb52dc2c63caaa0"
|
"sha256:58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==5.2.1"
|
"version": "==5.2.2"
|
||||||
},
|
},
|
||||||
"rope": {
|
"rope": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -336,11 +372,11 @@
|
|||||||
},
|
},
|
||||||
"typing-extensions": {
|
"typing-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95",
|
"sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2",
|
||||||
"sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87",
|
"sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d",
|
||||||
"sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed"
|
"sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575"
|
||||||
],
|
],
|
||||||
"version": "==3.7.4"
|
"version": "==3.7.4.1"
|
||||||
},
|
},
|
||||||
"wcwidth": {
|
"wcwidth": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ You can do stuff like:
|
|||||||
```py
|
```py
|
||||||
>>> t = Test().from_dict({"maybe": True, "ts": "2019-01-01T12:00:00Z", "duration": "1.200s"})
|
>>> t = Test().from_dict({"maybe": True, "ts": "2019-01-01T12:00:00Z", "duration": "1.200s"})
|
||||||
>>> t
|
>>> 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
|
>>> t.ts - t.duration
|
||||||
datetime.datetime(2019, 1, 1, 11, 59, 58, 800000, tzinfo=datetime.timezone.utc)
|
datetime.datetime(2019, 1, 1, 11, 59, 58, 800000, tzinfo=datetime.timezone.utc)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from typing import (
|
|||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
Union,
|
||||||
get_type_hints,
|
get_type_hints,
|
||||||
|
TYPE_CHECKING,
|
||||||
)
|
)
|
||||||
|
|
||||||
import grpclib.client
|
import grpclib.client
|
||||||
@@ -29,6 +30,9 @@ import stringcase
|
|||||||
|
|
||||||
from .casing import safe_snake_case
|
from .casing import safe_snake_case
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from grpclib._protocols import IProtoMessage
|
||||||
|
|
||||||
# Proto 3 data types
|
# Proto 3 data types
|
||||||
TYPE_ENUM = "enum"
|
TYPE_ENUM = "enum"
|
||||||
TYPE_BOOL = "bool"
|
TYPE_BOOL = "bool"
|
||||||
@@ -420,6 +424,7 @@ class Message(ABC):
|
|||||||
register the message fields which get used by the serializers and parsers
|
register the message fields which get used by the serializers and parsers
|
||||||
to go between Python, binary and JSON protobuf message representations.
|
to go between Python, binary and JSON protobuf message representations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_serialized_on_wire: bool
|
_serialized_on_wire: bool
|
||||||
_unknown_fields: bytes
|
_unknown_fields: bytes
|
||||||
_group_map: Dict[str, dict]
|
_group_map: Dict[str, dict]
|
||||||
@@ -705,7 +710,7 @@ class Message(ABC):
|
|||||||
for field in dataclasses.fields(self):
|
for field in dataclasses.fields(self):
|
||||||
meta = FieldMetadata.get(field)
|
meta = FieldMetadata.get(field)
|
||||||
v = getattr(self, field.name)
|
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 meta.proto_type == "message":
|
||||||
if isinstance(v, datetime):
|
if isinstance(v, datetime):
|
||||||
if v != DATETIME_ZERO:
|
if v != DATETIME_ZERO:
|
||||||
@@ -741,7 +746,7 @@ class Message(ABC):
|
|||||||
else:
|
else:
|
||||||
output[cased_name] = b64encode(v).decode("utf8")
|
output[cased_name] = b64encode(v).decode("utf8")
|
||||||
elif meta.proto_type == TYPE_ENUM:
|
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):
|
if isinstance(v, list):
|
||||||
output[cased_name] = [enum_values[e].name for e in v]
|
output[cased_name] = [enum_values[e].name for e in v]
|
||||||
else:
|
else:
|
||||||
@@ -902,6 +907,7 @@ class _WrappedMessage(Message):
|
|||||||
Google protobuf wrapper types base class. JSON representation is just the
|
Google protobuf wrapper types base class. JSON representation is just the
|
||||||
value itself.
|
value itself.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
value: Any
|
value: Any
|
||||||
|
|
||||||
def to_dict(self, casing: Casing = Casing.CAMEL) -> Any:
|
def to_dict(self, casing: Casing = Casing.CAMEL) -> Any:
|
||||||
@@ -982,11 +988,11 @@ class ServiceStub(ABC):
|
|||||||
self.channel = channel
|
self.channel = channel
|
||||||
|
|
||||||
async def _unary_unary(
|
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:
|
) -> T:
|
||||||
"""Make a unary request and return the response."""
|
"""Make a unary request and return the response."""
|
||||||
async with self.channel.request(
|
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:
|
) as stream:
|
||||||
await stream.send_message(request, end=True)
|
await stream.send_message(request, end=True)
|
||||||
response = await stream.recv_message()
|
response = await stream.recv_message()
|
||||||
@@ -994,11 +1000,11 @@ class ServiceStub(ABC):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
async def _unary_stream(
|
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]:
|
) -> AsyncGenerator[T, None]:
|
||||||
"""Make a unary request and return the stream response iterator."""
|
"""Make a unary request and return the stream response iterator."""
|
||||||
async with self.channel.request(
|
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:
|
) as stream:
|
||||||
await stream.send_message(request, end=True)
|
await stream.send_message(request, end=True)
|
||||||
async for message in stream:
|
async for message in stream:
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ import textwrap
|
|||||||
from typing import Any, List, Tuple
|
from typing import Any, List, Tuple
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import jinja2
|
import black
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print(
|
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)
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
import jinja2
|
||||||
import stringcase
|
import stringcase
|
||||||
|
|
||||||
from google.protobuf.compiler import plugin_pb2 as plugin
|
from google.protobuf.compiler import plugin_pb2 as plugin
|
||||||
@@ -398,8 +399,11 @@ def generate_code(request, response):
|
|||||||
# print(filename, file=sys.stderr)
|
# print(filename, file=sys.stderr)
|
||||||
f.name = filename.replace(".", os.path.sep) + ".py"
|
f.name = filename.replace(".", os.path.sep) + ".py"
|
||||||
|
|
||||||
# f.content = json.dumps(output, indent=2)
|
# Render and then format the output file.
|
||||||
f.content = template.render(description=output).rstrip("\n") + "\n"
|
f.content = black.format_str(
|
||||||
|
template.render(description=output),
|
||||||
|
mode=black.FileMode(target_versions=set([black.TargetVersion.PY37])),
|
||||||
|
)
|
||||||
|
|
||||||
inits = set([""])
|
inits = set([""])
|
||||||
for f in response.file:
|
for f in response.file:
|
||||||
|
|||||||
@@ -81,17 +81,15 @@ class {{ service.py_name }}Stub(betterproto.ServiceStub):
|
|||||||
{% if method.server_streaming %}
|
{% if method.server_streaming %}
|
||||||
async for response in self._unary_stream(
|
async for response in self._unary_stream(
|
||||||
"{{ method.route }}",
|
"{{ method.route }}",
|
||||||
{{ method.input }},
|
|
||||||
{{ method.output }},
|
|
||||||
request,
|
request,
|
||||||
|
{{ method.output }},
|
||||||
):
|
):
|
||||||
yield response
|
yield response
|
||||||
{% else %}
|
{% else %}
|
||||||
return await self._unary_unary(
|
return await self._unary_unary(
|
||||||
"{{ method.route }}",
|
"{{ method.route }}",
|
||||||
{{ method.input }},
|
|
||||||
{{ method.output }},
|
|
||||||
request,
|
request,
|
||||||
|
{{ method.output }},
|
||||||
)
|
)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
4
setup.py
4
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="betterproto",
|
name="betterproto",
|
||||||
version="1.1.0",
|
version="1.2.0",
|
||||||
description="A better Protobuf / gRPC generator & library",
|
description="A better Protobuf / gRPC generator & library",
|
||||||
long_description=open("README.md", "r").read(),
|
long_description=open("README.md", "r").read(),
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
@@ -19,6 +19,6 @@ setup(
|
|||||||
package_data={"betterproto": ["py.typed", "templates/template.py"]},
|
package_data={"betterproto": ["py.typed", "templates/template.py"]},
|
||||||
python_requires=">=3.7",
|
python_requires=">=3.7",
|
||||||
install_requires=["grpclib", "stringcase"],
|
install_requires=["grpclib", "stringcase"],
|
||||||
extras_require={"compiler": ["jinja2", "protobuf"]},
|
extras_require={"compiler": ["black", "jinja2", "protobuf"]},
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user