Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce9f492f50 | ||
|
|
93a6334015 | ||
|
|
36a14026d8 | ||
|
|
04a2fcd3eb | ||
|
|
582a12577c | ||
|
|
3616190451 | ||
|
|
9b990ee1bd | ||
|
|
72a77b0d65 | ||
|
|
b2b36c8575 | ||
|
|
203105f048 | ||
|
|
fe11f74227 | ||
|
|
dc7a3e9bdf | ||
|
|
f2e8afc609 | ||
|
|
dbd438e682 | ||
|
|
dce1c89fbe | ||
|
|
c78851b1b8 | ||
|
|
4554d91f89 | ||
|
|
c0170f4d80 | ||
|
|
559b8833d8 | ||
|
|
7ccef16579 | ||
|
|
d8785b4622 | ||
|
|
45e7a30300 | ||
|
|
d7559c22f8 | ||
|
|
f9c351a98d | ||
|
|
feea790116 | ||
|
|
33f74f6a45 | ||
|
|
3d5c12c532 | ||
|
|
706bd5a475 | ||
|
|
52beeb0d73 | ||
|
|
7e2dc595db |
37
.github/workflows/ci.yml
vendored
37
.github/workflows/ci.yml
vendored
@@ -3,7 +3,34 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [ '3.6', '3.7' ]
|
||||
|
||||
name: Python ${{ matrix.python-version }} test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- uses: dschep/install-pipenv-action@v1
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt install protobuf-compiler libprotobuf-dev
|
||||
pipenv install --dev --python ${pythonLocation}/python
|
||||
- name: Run tests
|
||||
run: |
|
||||
cp .env.default .env
|
||||
pipenv run pip install -e .
|
||||
pipenv run generate
|
||||
pipenv run test
|
||||
|
||||
build-release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -15,13 +42,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt install protobuf-compiler libprotobuf-dev
|
||||
pipenv install --dev
|
||||
- name: Run tests
|
||||
run: |
|
||||
cp .env.default .env
|
||||
pipenv run pip install -e .
|
||||
pipenv run generate
|
||||
pipenv run test
|
||||
pipenv install --dev --python ${pythonLocation}/python
|
||||
- name: Build package
|
||||
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
|
||||
run: pipenv run python setup.py sdist
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,6 +2,8 @@
|
||||
.vscode/settings.json
|
||||
.mypy_cache
|
||||
.pytest_cache
|
||||
.python-version
|
||||
build/
|
||||
betterproto/tests/*.bin
|
||||
betterproto/tests/*_pb2.py
|
||||
betterproto/tests/*.py
|
||||
|
||||
54
CHANGELOG.md
Normal file
54
CHANGELOG.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# 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).
|
||||
|
||||
## [1.2.3] - 2020-04-15
|
||||
|
||||
- Exclude empty lists from `to_dict` by default [#16](https://github.com/danielgtaylor/python-betterproto/pull/16)
|
||||
- Add `include_default_values` parameter for `to_dict` [#12](https://github.com/danielgtaylor/python-betterproto/pull/12)
|
||||
- Fix class names being prepended with duplicates when using protocol buffers that are nested more than once [#21](https://github.com/danielgtaylor/python-betterproto/pull/21)
|
||||
- Add support for python 3.6 [#30](https://github.com/danielgtaylor/python-betterproto/pull/30)
|
||||
|
||||
## [1.2.2] - 2020-01-09
|
||||
|
||||
- Mention lack of Proto 2 support in README.
|
||||
- Fix serialization of constructor parameters [#10](https://github.com/danielgtaylor/python-betterproto/pull/10)
|
||||
- Fix `casing` parameter propagation [#7](https://github.com/danielgtaylor/python-betterproto/pull/7)
|
||||
|
||||
## [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.3...HEAD
|
||||
[1.2.3]: https://github.com/danielgtaylor/python-betterproto/compare/v1.2.2...v1.2.3
|
||||
[1.2.2]: https://github.com/danielgtaylor/python-betterproto/compare/v1.2.1...v1.2.2
|
||||
[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
|
||||
9
Pipfile
9
Pipfile
@@ -9,17 +9,24 @@ mypy = "*"
|
||||
isort = "*"
|
||||
pytest = "*"
|
||||
rope = "*"
|
||||
v = {editable = true,version = "*"}
|
||||
|
||||
[packages]
|
||||
protobuf = "*"
|
||||
jinja2 = "*"
|
||||
grpclib = "*"
|
||||
stringcase = "*"
|
||||
black = "*"
|
||||
backports-datetime-fromisoformat = "*"
|
||||
dataclasses = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
python_version = "3.6"
|
||||
|
||||
[scripts]
|
||||
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
|
||||
|
||||
360
Pipfile.lock
generated
360
Pipfile.lock
generated
@@ -1,360 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "28c38cd6c4eafb0b9ac9a64cf623145868fdee163111d3b941b34d23011db6ca"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.7"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"grpclib": {
|
||||
"hashes": [
|
||||
"sha256:d19e2ea87cb073e5b0825dfee15336fd2b1c09278d271816e04c90faddc107ea"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.3.0"
|
||||
},
|
||||
"h2": {
|
||||
"hashes": [
|
||||
"sha256:ac377fcf586314ef3177bfd90c12c7826ab0840edeb03f0f24f511858326049e",
|
||||
"sha256:b8a32bd282594424c0ac55845377eea13fa54fe4a8db012f3a198ed923dc3ab4"
|
||||
],
|
||||
"version": "==3.1.1"
|
||||
},
|
||||
"hpack": {
|
||||
"hashes": [
|
||||
"sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89",
|
||||
"sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"
|
||||
],
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"hyperframe": {
|
||||
"hashes": [
|
||||
"sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40",
|
||||
"sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f"
|
||||
],
|
||||
"version": "==5.2.0"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f",
|
||||
"sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.10.3"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
|
||||
],
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"multidict": {
|
||||
"hashes": [
|
||||
"sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f",
|
||||
"sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3",
|
||||
"sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef",
|
||||
"sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b",
|
||||
"sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73",
|
||||
"sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc",
|
||||
"sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3",
|
||||
"sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd",
|
||||
"sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351",
|
||||
"sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941",
|
||||
"sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d",
|
||||
"sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1",
|
||||
"sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b",
|
||||
"sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a",
|
||||
"sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3",
|
||||
"sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7",
|
||||
"sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0",
|
||||
"sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0",
|
||||
"sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014",
|
||||
"sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5",
|
||||
"sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036",
|
||||
"sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d",
|
||||
"sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a",
|
||||
"sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce",
|
||||
"sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1",
|
||||
"sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a",
|
||||
"sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9",
|
||||
"sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7",
|
||||
"sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"
|
||||
],
|
||||
"version": "==4.5.2"
|
||||
},
|
||||
"protobuf": {
|
||||
"hashes": [
|
||||
"sha256:125713564d8cfed7610e52444c9769b8dcb0b55e25cc7841f2290ee7bc86636f",
|
||||
"sha256:1accdb7a47e51503be64d9a57543964ba674edac103215576399d2d0e34eac77",
|
||||
"sha256:27003d12d4f68e3cbea9eb67427cab3bfddd47ff90670cb367fcd7a3a89b9657",
|
||||
"sha256:3264f3c431a631b0b31e9db2ae8c927b79fc1a7b1b06b31e8e5bcf2af91fe896",
|
||||
"sha256:3c5ab0f5c71ca5af27143e60613729e3488bb45f6d3f143dc918a20af8bab0bf",
|
||||
"sha256:45dcf8758873e3f69feab075e5f3177270739f146255225474ee0b90429adef6",
|
||||
"sha256:56a77d61a91186cc5676d8e11b36a5feb513873e4ae88d2ee5cf530d52bbcd3b",
|
||||
"sha256:5984e4947bbcef5bd849d6244aec507d31786f2dd3344139adc1489fb403b300",
|
||||
"sha256:6b0441da73796dd00821763bb4119674eaf252776beb50ae3883bed179a60b2a",
|
||||
"sha256:6f6677c5ade94d4fe75a912926d6796d5c71a2a90c2aeefe0d6f211d75c74789",
|
||||
"sha256:84a825a9418d7196e2acc48f8746cf1ee75877ed2f30433ab92a133f3eaf8fbe",
|
||||
"sha256:b842c34fe043ccf78b4a6cf1019d7b80113707d68c88842d061fa2b8fb6ddedc",
|
||||
"sha256:ca33d2f09dae149a1dcf942d2d825ebb06343b77b437198c9e2ef115cf5d5bc1",
|
||||
"sha256:db83b5c12c0cd30150bb568e6feb2435c49ce4e68fe2d7b903113f0e221e58fe",
|
||||
"sha256:f50f3b1c5c1c1334ca7ce9cad5992f098f460ffd6388a3cabad10b66c2006b09",
|
||||
"sha256:f99f127909731cafb841c52f9216e447d3e4afb99b17bebfad327a75aee206de"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.10.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
||||
],
|
||||
"version": "==1.12.0"
|
||||
},
|
||||
"stringcase": {
|
||||
"hashes": [
|
||||
"sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.2.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"atomicwrites": {
|
||||
"hashes": [
|
||||
"sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
|
||||
"sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
|
||||
],
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
||||
],
|
||||
"version": "==19.3.0"
|
||||
},
|
||||
"entrypoints": {
|
||||
"hashes": [
|
||||
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
|
||||
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
|
||||
],
|
||||
"version": "==0.3"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548",
|
||||
"sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.7.8"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26",
|
||||
"sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==0.23"
|
||||
},
|
||||
"isort": {
|
||||
"hashes": [
|
||||
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
|
||||
"sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.21"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832",
|
||||
"sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"
|
||||
],
|
||||
"version": "==7.2.0"
|
||||
},
|
||||
"mypy": {
|
||||
"hashes": [
|
||||
"sha256:1521c186a3d200c399bd5573c828ea2db1362af7209b2adb1bb8532cea2fb36f",
|
||||
"sha256:31a046ab040a84a0fc38bc93694876398e62bc9f35eca8ccbf6418b7297f4c00",
|
||||
"sha256:3b1a411909c84b2ae9b8283b58b48541654b918e8513c20a400bb946aa9111ae",
|
||||
"sha256:48c8bc99380575deb39f5d3400ebb6a8a1cb5cc669bbba4d3bb30f904e0a0e7d",
|
||||
"sha256:540c9caa57a22d0d5d3c69047cc9dd0094d49782603eb03069821b41f9e970e9",
|
||||
"sha256:672e418425d957e276c291930a3921b4a6413204f53fe7c37cad7bc57b9a3391",
|
||||
"sha256:6ed3b9b3fdc7193ea7aca6f3c20549b377a56f28769783a8f27191903a54170f",
|
||||
"sha256:9371290aa2cad5ad133e4cdc43892778efd13293406f7340b9ffe99d5ec7c1d9",
|
||||
"sha256:ace6ac1d0f87d4072f05b5468a084a45b4eda970e4d26704f201e06d47ab2990",
|
||||
"sha256:b428f883d2b3fe1d052c630642cc6afddd07d5cd7873da948644508be3b9d4a7",
|
||||
"sha256:d5bf0e6ec8ba346a2cf35cb55bf4adfddbc6b6576fcc9e10863daa523e418dbb",
|
||||
"sha256:d7574e283f83c08501607586b3167728c58e8442947e027d2d4c7dcd6d82f453",
|
||||
"sha256:dc889c84241a857c263a2b1cd1121507db7d5b5f5e87e77147097230f374d10b",
|
||||
"sha256:f4748697b349f373002656bf32fede706a0e713d67bfdcf04edf39b1f61d46eb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.740"
|
||||
},
|
||||
"mypy-extensions": {
|
||||
"hashes": [
|
||||
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
|
||||
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
|
||||
],
|
||||
"version": "==0.4.3"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47",
|
||||
"sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"
|
||||
],
|
||||
"version": "==19.2"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6",
|
||||
"sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"
|
||||
],
|
||||
"version": "==0.13.0"
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
|
||||
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
|
||||
],
|
||||
"version": "==1.8.0"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
|
||||
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
|
||||
],
|
||||
"version": "==2.5.0"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
|
||||
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
|
||||
],
|
||||
"version": "==2.1.1"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80",
|
||||
"sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"
|
||||
],
|
||||
"version": "==2.4.2"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:7e4800063ccfc306a53c461442526c5571e1462f61583506ce97e4da6a1d88c8",
|
||||
"sha256:ca563435f4941d0cb34767301c27bc65c510cb82e90b9ecf9cb52dc2c63caaa0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.2.1"
|
||||
},
|
||||
"rope": {
|
||||
"hashes": [
|
||||
"sha256:6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969",
|
||||
"sha256:c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf",
|
||||
"sha256:f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.14.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
||||
],
|
||||
"version": "==1.12.0"
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
"sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161",
|
||||
"sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e",
|
||||
"sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e",
|
||||
"sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0",
|
||||
"sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c",
|
||||
"sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47",
|
||||
"sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631",
|
||||
"sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4",
|
||||
"sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34",
|
||||
"sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b",
|
||||
"sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2",
|
||||
"sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e",
|
||||
"sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a",
|
||||
"sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233",
|
||||
"sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1",
|
||||
"sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36",
|
||||
"sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d",
|
||||
"sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a",
|
||||
"sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66",
|
||||
"sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"
|
||||
],
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95",
|
||||
"sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87",
|
||||
"sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed"
|
||||
],
|
||||
"version": "==3.7.4"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
|
||||
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
|
||||
],
|
||||
"version": "==0.1.7"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e",
|
||||
"sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"
|
||||
],
|
||||
"version": "==0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
12
README.md
12
README.md
@@ -2,11 +2,11 @@
|
||||
|
||||

|
||||
|
||||
This project aims to provide an improved experience when using Protobuf / gRPC in a modern Python environment by making use of modern language features and generating readable, understandable, idiomatic Python code. It will not support legacy features or environments. The following are supported:
|
||||
This project aims to provide an improved experience when using Protobuf / gRPC in a modern Python environment by making use of modern language features and generating readable, understandable, idiomatic Python code. It will not support legacy features or environments (e.g. Protobuf 2). The following are supported:
|
||||
|
||||
- Protobuf 3 & gRPC code generation
|
||||
- Both binary & JSON serialization is built-in
|
||||
- Python 3.7+ making use of:
|
||||
- Python 3.6+ making use of:
|
||||
- Enums
|
||||
- Dataclasses
|
||||
- `async`/`await`
|
||||
@@ -46,7 +46,7 @@ First, install the package. Note that the `[compiler]` feature flag tells it to
|
||||
|
||||
```sh
|
||||
# Install both the library and compiler
|
||||
$ pip install betterproto[compiler]
|
||||
$ pip install "betterproto[compiler]"
|
||||
|
||||
# Install just the library (to use the generated code output)
|
||||
$ pip install betterproto
|
||||
@@ -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)
|
||||
@@ -296,7 +296,7 @@ datetime.datetime(2019, 1, 1, 11, 59, 58, 800000, tzinfo=datetime.timezone.utc)
|
||||
|
||||
## Development
|
||||
|
||||
First, make sure you have Python 3.7+ and `pipenv` installed, along with the official [Protobuf Compiler](https://github.com/protocolbuffers/protobuf/releases) for your platform. Then:
|
||||
First, make sure you have Python 3.6+ and `pipenv` installed, along with the official [Protobuf Compiler](https://github.com/protocolbuffers/protobuf/releases) for your platform. Then:
|
||||
|
||||
```sh
|
||||
# Get set up with the virtual env & dependencies
|
||||
@@ -323,7 +323,7 @@ Here's how to run the tests.
|
||||
$ pipenv run generate
|
||||
|
||||
# Run the tests
|
||||
$ pipenv run tests
|
||||
$ pipenv run test
|
||||
```
|
||||
|
||||
### TODO
|
||||
|
||||
@@ -3,6 +3,7 @@ import enum
|
||||
import inspect
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
from abc import ABC
|
||||
from base64 import b64encode, b64decode
|
||||
from datetime import datetime, timedelta, timezone
|
||||
@@ -21,6 +22,7 @@ from typing import (
|
||||
TypeVar,
|
||||
Union,
|
||||
get_type_hints,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
import grpclib.client
|
||||
@@ -29,6 +31,16 @@ import stringcase
|
||||
|
||||
from .casing import safe_snake_case
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from grpclib._protocols import IProtoMessage
|
||||
|
||||
if not (sys.version_info.major == 3 and sys.version_info.minor >= 7):
|
||||
# Apply backport of datetime.fromisoformat from 3.7
|
||||
from backports.datetime_fromisoformat import MonkeyPatch
|
||||
|
||||
MonkeyPatch.patch_fromisoformat()
|
||||
|
||||
|
||||
# Proto 3 data types
|
||||
TYPE_ENUM = "enum"
|
||||
TYPE_BOOL = "bool"
|
||||
@@ -420,11 +432,15 @@ 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]
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
# Keep track of whether every field was default
|
||||
all_sentinel = True
|
||||
|
||||
# Set a default value for each field in the class after `__init__` has
|
||||
# already been run.
|
||||
group_map: Dict[str, dict] = {"fields": {}, "groups": {}}
|
||||
@@ -441,6 +457,7 @@ class Message(ABC):
|
||||
|
||||
if getattr(self, field.name) != PLACEHOLDER:
|
||||
# Skip anything not set to the sentinel value
|
||||
all_sentinel = False
|
||||
|
||||
if meta.group:
|
||||
# This was set, so make it the selected value of the one-of.
|
||||
@@ -451,7 +468,7 @@ class Message(ABC):
|
||||
setattr(self, field.name, self._get_field_default(field, meta))
|
||||
|
||||
# Now that all the defaults are set, reset it!
|
||||
self.__dict__["_serialized_on_wire"] = False
|
||||
self.__dict__["_serialized_on_wire"] = not all_sentinel
|
||||
self.__dict__["_unknown_fields"] = b""
|
||||
self.__dict__["_group_map"] = group_map
|
||||
|
||||
@@ -560,10 +577,10 @@ class Message(ABC):
|
||||
|
||||
value: Any = 0
|
||||
if hasattr(t, "__origin__"):
|
||||
if t.__origin__ == dict:
|
||||
if t.__origin__ in (dict, Dict):
|
||||
# This is some kind of map (dict in Python).
|
||||
value = {}
|
||||
elif t.__origin__ == list:
|
||||
elif t.__origin__ in (list, List):
|
||||
# This is some kind of list (repeated) field.
|
||||
value = []
|
||||
elif t.__origin__ == Union and t.__args__[1] == type(None):
|
||||
@@ -695,41 +712,50 @@ class Message(ABC):
|
||||
def FromString(cls: Type[T], data: bytes) -> T:
|
||||
return cls().parse(data)
|
||||
|
||||
def to_dict(self, casing: Casing = Casing.CAMEL) -> dict:
|
||||
def to_dict(
|
||||
self, casing: Casing = Casing.CAMEL, include_default_values: bool = False
|
||||
) -> dict:
|
||||
"""
|
||||
Returns a dict representation of this message instance which can be
|
||||
used to serialize to e.g. JSON. Defaults to camel casing for
|
||||
compatibility but can be set to other modes.
|
||||
|
||||
`include_default_values` can be set to `True` to include default
|
||||
values of fields. E.g. an `int32` type field with `0` value will
|
||||
not be in returned dict if `include_default_values` is set to
|
||||
`False`.
|
||||
"""
|
||||
output: Dict[str, Any] = {}
|
||||
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:
|
||||
if v != DATETIME_ZERO or include_default_values:
|
||||
output[cased_name] = _Timestamp.timestamp_to_json(v)
|
||||
elif isinstance(v, timedelta):
|
||||
if v != timedelta(0):
|
||||
if v != timedelta(0) or include_default_values:
|
||||
output[cased_name] = _Duration.delta_to_json(v)
|
||||
elif meta.wraps:
|
||||
if v is not None:
|
||||
if v is not None or include_default_values:
|
||||
output[cased_name] = v
|
||||
elif isinstance(v, list):
|
||||
# Convert each item.
|
||||
v = [i.to_dict() for i in v]
|
||||
output[cased_name] = v
|
||||
elif v._serialized_on_wire:
|
||||
output[cased_name] = v.to_dict()
|
||||
v = [i.to_dict(casing, include_default_values) for i in v]
|
||||
if v or include_default_values:
|
||||
output[cased_name] = v
|
||||
else:
|
||||
if v._serialized_on_wire or include_default_values:
|
||||
output[cased_name] = v.to_dict(casing, include_default_values)
|
||||
elif meta.proto_type == "map":
|
||||
for k in v:
|
||||
if hasattr(v[k], "to_dict"):
|
||||
v[k] = v[k].to_dict()
|
||||
v[k] = v[k].to_dict(casing, include_default_values)
|
||||
|
||||
if v:
|
||||
if v or include_default_values:
|
||||
output[cased_name] = v
|
||||
elif v != self._get_field_default(field, meta):
|
||||
elif v != self._get_field_default(field, meta) or include_default_values:
|
||||
if meta.proto_type in INT_64_TYPES:
|
||||
if isinstance(v, list):
|
||||
output[cased_name] = [str(n) for n in v]
|
||||
@@ -741,7 +767,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 +928,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 +1009,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 +1021,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:
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
@@ -121,19 +122,19 @@ def get_py_zero(type_num: int) -> str:
|
||||
|
||||
|
||||
def traverse(proto_file):
|
||||
def _traverse(path, items):
|
||||
def _traverse(path, items, prefix = ''):
|
||||
for i, item in enumerate(items):
|
||||
# Adjust the name since we flatten the heirarchy.
|
||||
item.name = next_prefix = prefix + item.name
|
||||
yield item, path + [i]
|
||||
|
||||
if isinstance(item, DescriptorProto):
|
||||
for enum in item.enum_type:
|
||||
enum.name = item.name + enum.name
|
||||
enum.name = next_prefix + enum.name
|
||||
yield enum, path + [i, 4]
|
||||
|
||||
if item.nested_type:
|
||||
for n, p in _traverse(path + [i, 3], item.nested_type):
|
||||
# Adjust the name since we flatten the heirarchy.
|
||||
n.name = item.name + n.name
|
||||
for n, p in _traverse(path + [i, 3], item.nested_type, next_prefix):
|
||||
yield n, p
|
||||
|
||||
return itertools.chain(
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
11
betterproto/tests/nestedtwice.json
Normal file
11
betterproto/tests/nestedtwice.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"root": {
|
||||
"name": "double-nested",
|
||||
"parent": {
|
||||
"child": [{"foo": "hello"}],
|
||||
"enumChild": ["A"],
|
||||
"rootParentChild": [{"a": "hello"}],
|
||||
"bar": true
|
||||
}
|
||||
}
|
||||
}
|
||||
26
betterproto/tests/nestedtwice.proto
Normal file
26
betterproto/tests/nestedtwice.proto
Normal file
@@ -0,0 +1,26 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message Test {
|
||||
message Root {
|
||||
message Parent {
|
||||
message RootParentChild {
|
||||
string a = 1;
|
||||
}
|
||||
enum EnumChild{
|
||||
A = 0;
|
||||
B = 1;
|
||||
}
|
||||
message Child {
|
||||
string foo = 1;
|
||||
}
|
||||
reserved 1;
|
||||
repeated Child child = 2;
|
||||
repeated EnumChild enumChild=3;
|
||||
repeated RootParentChild rootParentChild=4;
|
||||
bool bar = 5;
|
||||
}
|
||||
string name = 1;
|
||||
Parent parent = 2;
|
||||
}
|
||||
Root root = 1;
|
||||
}
|
||||
@@ -33,6 +33,21 @@ def test_has_field():
|
||||
assert betterproto.serialized_on_wire(foo.bar) == False
|
||||
|
||||
|
||||
def test_class_init():
|
||||
@dataclass
|
||||
class Bar(betterproto.Message):
|
||||
name: str = betterproto.string_field(1)
|
||||
|
||||
@dataclass
|
||||
class Foo(betterproto.Message):
|
||||
name: str = betterproto.string_field(1)
|
||||
child: Bar = betterproto.message_field(2)
|
||||
|
||||
foo = Foo(name="foo", child=Bar(name="bar"))
|
||||
|
||||
assert foo.to_dict() == {"name": "foo", "child": {"name": "bar"}}
|
||||
|
||||
|
||||
def test_enum_as_int_json():
|
||||
class TestEnum(betterproto.Enum):
|
||||
ZERO = 0
|
||||
@@ -162,3 +177,89 @@ def test_optional_flag():
|
||||
# Differentiate between not passed and the zero-value.
|
||||
assert Request().parse(b"").flag == None
|
||||
assert Request().parse(b"\n\x00").flag == False
|
||||
|
||||
|
||||
def test_to_dict_default_values():
|
||||
@dataclass
|
||||
class TestMessage(betterproto.Message):
|
||||
some_int: int = betterproto.int32_field(1)
|
||||
some_double: float = betterproto.double_field(2)
|
||||
some_str: str = betterproto.string_field(3)
|
||||
some_bool: bool = betterproto.bool_field(4)
|
||||
|
||||
# Empty dict
|
||||
test = TestMessage().from_dict({})
|
||||
|
||||
assert test.to_dict(include_default_values=True) == {
|
||||
"someInt": 0,
|
||||
"someDouble": 0.0,
|
||||
"someStr": "",
|
||||
"someBool": False,
|
||||
}
|
||||
|
||||
# All default values
|
||||
test = TestMessage().from_dict(
|
||||
{"someInt": 0, "someDouble": 0.0, "someStr": "", "someBool": False}
|
||||
)
|
||||
|
||||
assert test.to_dict(include_default_values=True) == {
|
||||
"someInt": 0,
|
||||
"someDouble": 0.0,
|
||||
"someStr": "",
|
||||
"someBool": False,
|
||||
}
|
||||
|
||||
# Some default and some other values
|
||||
@dataclass
|
||||
class TestMessage2(betterproto.Message):
|
||||
some_int: int = betterproto.int32_field(1)
|
||||
some_double: float = betterproto.double_field(2)
|
||||
some_str: str = betterproto.string_field(3)
|
||||
some_bool: bool = betterproto.bool_field(4)
|
||||
some_default_int: int = betterproto.int32_field(5)
|
||||
some_default_double: float = betterproto.double_field(6)
|
||||
some_default_str: str = betterproto.string_field(7)
|
||||
some_default_bool: bool = betterproto.bool_field(8)
|
||||
|
||||
test = TestMessage2().from_dict(
|
||||
{
|
||||
"someInt": 2,
|
||||
"someDouble": 1.2,
|
||||
"someStr": "hello",
|
||||
"someBool": True,
|
||||
"someDefaultInt": 0,
|
||||
"someDefaultDouble": 0.0,
|
||||
"someDefaultStr": "",
|
||||
"someDefaultBool": False,
|
||||
}
|
||||
)
|
||||
|
||||
assert test.to_dict(include_default_values=True) == {
|
||||
"someInt": 2,
|
||||
"someDouble": 1.2,
|
||||
"someStr": "hello",
|
||||
"someBool": True,
|
||||
"someDefaultInt": 0,
|
||||
"someDefaultDouble": 0.0,
|
||||
"someDefaultStr": "",
|
||||
"someDefaultBool": False,
|
||||
}
|
||||
|
||||
# Nested messages
|
||||
@dataclass
|
||||
class TestChildMessage(betterproto.Message):
|
||||
some_other_int: int = betterproto.int32_field(1)
|
||||
|
||||
@dataclass
|
||||
class TestParentMessage(betterproto.Message):
|
||||
some_int: int = betterproto.int32_field(1)
|
||||
some_double: float = betterproto.double_field(2)
|
||||
some_message: TestChildMessage = betterproto.message_field(3)
|
||||
|
||||
test = TestParentMessage().from_dict({"someInt": 0, "someDouble": 1.2,})
|
||||
|
||||
assert test.to_dict(include_default_values=True) == {
|
||||
"someInt": 0,
|
||||
"someDouble": 1.2,
|
||||
"someMessage": {"someOtherInt": 0},
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[tool.black]
|
||||
target-version = ['py37']
|
||||
target-version = ['py36']
|
||||
|
||||
[tool.isort]
|
||||
multi_line_output = 3
|
||||
|
||||
13
setup.py
13
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="betterproto",
|
||||
version="1.1.0",
|
||||
version="1.2.3",
|
||||
description="A better Protobuf / gRPC generator & library",
|
||||
long_description=open("README.md", "r").read(),
|
||||
long_description_content_type="text/markdown",
|
||||
@@ -17,8 +17,13 @@ setup(
|
||||
exclude=["tests", "*.tests", "*.tests.*", "output", "output.*"]
|
||||
),
|
||||
package_data={"betterproto": ["py.typed", "templates/template.py"]},
|
||||
python_requires=">=3.7",
|
||||
install_requires=["grpclib", "stringcase"],
|
||||
extras_require={"compiler": ["jinja2", "protobuf"]},
|
||||
python_requires=">=3.6",
|
||||
install_requires=[
|
||||
'dataclasses; python_version<"3.7"',
|
||||
'backports-datetime-fromisoformat; python_version<"3.7"',
|
||||
"grpclib",
|
||||
"stringcase",
|
||||
],
|
||||
extras_require={"compiler": ["black", "jinja2", "protobuf"]},
|
||||
zip_safe=False,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user