8 Commits

Author SHA1 Message Date
kalzoo
ee4265492d docs: Add semantic release information and issue template 2021-11-16 22:42:31 -08:00
kalzoo
32faddf322 chore: Add Semantic Release GitHub action 2021-11-16 22:42:03 -08:00
kalzoo
89d1f47fac Install Semantic Release 2021-11-16 22:30:16 -08:00
lazytype
c424b6f8db Include AsyncIterator import for both clients and servers (#264)
Co-authored-by: Robin Lambertz <github@roblab.la>
2021-11-05 14:22:15 +00:00
James Hilton-Balfe
421fdba309 Allow parsing of messages from ByteStrings #266 2021-10-26 00:34:33 +01:00
Robin Lambertz
fb2793e0b6 Allow parsing messages from byteslike
Byteslike objects (like memoryview) do not have a decode function defined.
Instead, a string may be created from them by passing them to the str
constructor along with an encoding.
2021-08-25 12:53:02 +02:00
PIGNOSE
ad8b91766a Add benchmarking cases for nested, repeat and deserialize (#241) 2021-06-21 23:38:22 +02:00
Bekhzod Tillakhanov
a33126544b Fix readme docs 'Async gRPC Support' (#249) 2021-06-21 23:29:59 +02:00
10 changed files with 4219 additions and 14 deletions

9
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,9 @@
## Description
<!-- Thanks for contributing to betterproto! Add a thorough explanation of what your changes do below this line: -->
## Checklist
- [ ] This PR targets the `rc` branch (**not** `master`).
- [ ] [If this should release a new version to PyPI when merged] The title of the PR follows the [Angular Conventional Commit](https://www.conventionalcommits.org/) syntax (`feat:` or `fix:`, with `BREAKING CHANGE:` in the commit message body if appropriate), and clearly describes the fix or feature.
- [ ] Documentation is updated (`README.md` and docstrings).

View File

@@ -16,12 +16,18 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with:
persist-credentials: false
- name: Set up Python 3.8 - name: Set up Python 3.8
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: 3.8 python-version: 3.8
- name: Install poetry - name: Install poetry
run: python -m pip install poetry run: python -m pip install poetry
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build package - name: Build package
run: poetry build run: poetry build
- name: Publish package to PyPI - name: Publish package to PyPI

1
.gitignore vendored
View File

@@ -6,6 +6,7 @@
.pytest_cache .pytest_cache
.python-version .python-version
build/ build/
node_modules/
tests/output_* tests/output_*
**/__pycache__ **/__pycache__
dist dist

View File

@@ -160,6 +160,12 @@ service Echo {
} }
``` ```
Generate echo proto file:
```
python -m grpc_tools.protoc -I . --python_betterproto_out=. echo.proto
```
A client can be implemented as follows: A client can be implemented as follows:
```python ```python
import asyncio import asyncio
@@ -199,28 +205,29 @@ To use them, simply subclass the base class in the generated files and override
service methods: service methods:
```python ```python
from echo import EchoBase import asyncio
from echo import EchoBase, EchoResponse, EchoStreamResponse
from grpclib.server import Server from grpclib.server import Server
from typing import AsyncIterator from typing import AsyncIterator
class EchoService(EchoBase): class EchoService(EchoBase):
async def echo(self, value: str, extra_times: int) -> "EchoResponse": async def echo(self, value: str, extra_times: int) -> "EchoResponse":
return value return EchoResponse([value for _ in range(extra_times)])
async def echo_stream( async def echo_stream(self, value: str, extra_times: int) -> AsyncIterator["EchoStreamResponse"]:
self, value: str, extra_times: int
) -> AsyncIterator["EchoStreamResponse"]:
for _ in range(extra_times): for _ in range(extra_times):
yield value yield EchoStreamResponse(value)
async def start_server(): async def main():
HOST = "127.0.0.1"
PORT = 1337
server = Server([EchoService()]) server = Server([EchoService()])
await server.start(HOST, PORT) await server.start("127.0.0.1", 50051)
await server.serve_forever() await server.wait_closed()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
``` ```
### JSON ### JSON
@@ -491,6 +498,16 @@ protoc \
- [x] Automate running tests - [x] Automate running tests
- [ ] Cleanup! - [ ] Cleanup!
## Release
New versions are versioned and released using [Semantic Release](https://github.com/semantic-release/semantic-release). When new commits
using the Angular Conventional Commits syntax land on `master` or `rc`, those commits are used to determine what new version to release.
All Pull Requests must target the `rc` branch; when merged into `rc` they will publish new release candidate (`rc`) versions to PyPI
automatically. When maintainers want to publish a new full release, they simply merge `rc` into `master`. This flow ensures that features
and fixes are published quickly and continuously rather than awaiting a manual release process.
## Community ## Community
Join us on [Slack](https://join.slack.com/t/betterproto/shared_invite/zt-f0n0uolx-iN8gBNrkPxtKHTLpG3o1OQ)! Join us on [Slack](https://join.slack.com/t/betterproto/shared_invite/zt-f0n0uolx-iN8gBNrkPxtKHTLpG3o1OQ)!

View File

@@ -1,6 +1,8 @@
import betterproto import betterproto
from dataclasses import dataclass from dataclasses import dataclass
from typing import List
@dataclass @dataclass
class TestMessage(betterproto.Message): class TestMessage(betterproto.Message):
@@ -9,6 +11,29 @@ class TestMessage(betterproto.Message):
baz: float = betterproto.float_field(2) baz: float = betterproto.float_field(2)
@dataclass
class TestNestedChildMessage(betterproto.Message):
str_key: str = betterproto.string_field(0)
bytes_key: bytes = betterproto.bytes_field(1)
bool_key: bool = betterproto.bool_field(2)
float_key: float = betterproto.float_field(3)
int_key: int = betterproto.uint64_field(4)
@dataclass
class TestNestedMessage(betterproto.Message):
foo: TestNestedChildMessage = betterproto.message_field(0)
bar: TestNestedChildMessage = betterproto.message_field(1)
baz: TestNestedChildMessage = betterproto.message_field(2)
@dataclass
class TestRepeatedMessage(betterproto.Message):
foo_repeat: List[str] = betterproto.string_field(0)
bar_repeat: List[int] = betterproto.int64_field(1)
baz_repeat: List[bool] = betterproto.bool_field(2)
class BenchMessage: class BenchMessage:
"""Test creation and usage a proto message.""" """Test creation and usage a proto message."""
@@ -16,6 +41,30 @@ class BenchMessage:
self.cls = TestMessage self.cls = TestMessage
self.instance = TestMessage() self.instance = TestMessage()
self.instance_filled = TestMessage(0, "test", 0.0) self.instance_filled = TestMessage(0, "test", 0.0)
self.instance_filled_bytes = bytes(self.instance_filled)
self.instance_filled_nested = TestNestedMessage(
TestNestedChildMessage("foo", bytearray(b"test1"), True, 0.1234, 500),
TestNestedChildMessage("bar", bytearray(b"test2"), True, 3.1415, -302),
TestNestedChildMessage("baz", bytearray(b"test3"), False, 1e5, 300),
)
self.instance_filled_nested_bytes = bytes(self.instance_filled_nested)
self.instance_filled_repeated = TestRepeatedMessage(
[
"test1",
"test2",
"test3",
"test4",
"test5",
"test6",
"test7",
"test8",
"test9",
"test10",
],
[2, -100, 0, 500000, 600, -425678, 1000000000, -300, 1, -694214214466],
[True, False, False, False, True, True, False, True, False, False],
)
self.instance_filled_repeated_bytes = bytes(self.instance_filled_repeated)
def time_overhead(self): def time_overhead(self):
"""Overhead in class definition.""" """Overhead in class definition."""
@@ -50,6 +99,26 @@ class BenchMessage:
"""Time serializing a message to wire.""" """Time serializing a message to wire."""
bytes(self.instance_filled) bytes(self.instance_filled)
def time_deserialize(self):
"""Time deserialize a message."""
TestMessage().parse(self.instance_filled_bytes)
def time_serialize_nested(self):
"""Time serializing a nested message to wire."""
bytes(self.instance_filled_nested)
def time_deserialize_nested(self):
"""Time deserialize a nested message."""
TestNestedMessage().parse(self.instance_filled_nested_bytes)
def time_serialize_repeated(self):
"""Time serializing a repeated message to wire."""
bytes(self.instance_filled_repeated)
def time_deserialize_repeated(self):
"""Time deserialize a repeated message."""
TestRepeatedMessage().parse(self.instance_filled_repeated_bytes)
class MemSuite: class MemSuite:
def setup(self): def setup(self):

52
package.json Normal file
View File

@@ -0,0 +1,52 @@
{
"name": "python-betterproto-semantic-release",
"version": "1.0.0",
"description": "Encapsulate dependencies needed to use semantic-release",
"dependencies": {
"@semantic-release/exec": "^5.0.0",
"@semantic-release/git": "^9.0.0",
"@semantic-release/gitlab": "^6.0.4",
"conventional-changelog-eslint": "^3.0.8",
"semantic-release": "^17.1.1"
},
"release": {
"branches": [
"master",
{
"name": "rc",
"prerelease": true
}
],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "angular"
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "angular"
}
],
[
"@semantic-release/exec",
{
"prepareCmd": "poetry version ${nextRelease.version}"
}
],
"@semantic-release/github",
[
"@semantic-release/git",
{
"assets": [
"pyproject.toml"
],
"message": "Release v${nextRelease.version} [skip ci]"
}
]
],
"repositoryUrl": "ssh://git@github.com/danielgtaylor/python-betterproto.git"
}
}

View File

@@ -866,7 +866,7 @@ class Message(ABC):
value = struct.unpack(fmt, value)[0] value = struct.unpack(fmt, value)[0]
elif wire_type == WIRE_LEN_DELIM: elif wire_type == WIRE_LEN_DELIM:
if meta.proto_type == TYPE_STRING: if meta.proto_type == TYPE_STRING:
value = value.decode("utf-8") value = str(value, "utf-8")
elif meta.proto_type == TYPE_MESSAGE: elif meta.proto_type == TYPE_MESSAGE:
cls = self._betterproto.cls_by_field[field_name] cls = self._betterproto.cls_by_field[field_name]

View File

@@ -1,5 +1,5 @@
from abc import ABC from abc import ABC
from collections import AsyncIterable from collections.abc import AsyncIterable
from typing import Callable, Any, Dict from typing import Callable, Any, Dict
import grpclib import grpclib

View File

@@ -653,7 +653,9 @@ class ServiceMethodCompiler(ProtoContentBase):
self.output_file.typing_imports.add("AsyncIterable") self.output_file.typing_imports.add("AsyncIterable")
self.output_file.typing_imports.add("Iterable") self.output_file.typing_imports.add("Iterable")
self.output_file.typing_imports.add("Union") self.output_file.typing_imports.add("Union")
if self.server_streaming:
# Required by both client and server
if self.client_streaming or self.server_streaming:
self.output_file.typing_imports.add("AsyncIterator") self.output_file.typing_imports.add("AsyncIterator")
super().__post_init__() # check for unset fields super().__post_init__() # check for unset fields

4049
yarn.lock Normal file

File diff suppressed because it is too large Load Diff