Compare commits
8 Commits
v2.0.0b3
...
285-semant
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee4265492d | ||
|
|
32faddf322 | ||
|
|
89d1f47fac | ||
|
|
c424b6f8db | ||
|
|
421fdba309 | ||
|
|
fb2793e0b6 | ||
|
|
ad8b91766a | ||
|
|
a33126544b |
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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).
|
||||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -6,6 +6,7 @@
|
|||||||
.pytest_cache
|
.pytest_cache
|
||||||
.python-version
|
.python-version
|
||||||
build/
|
build/
|
||||||
|
node_modules/
|
||||||
tests/output_*
|
tests/output_*
|
||||||
**/__pycache__
|
**/__pycache__
|
||||||
dist
|
dist
|
||||||
|
|||||||
39
README.md
39
README.md
@@ -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)!
|
||||||
|
|||||||
@@ -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
52
package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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]
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user