Merge branch 'master' into michael-sayapin/master
This commit is contained in:
		
							
								
								
									
										66
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										66
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,63 +10,65 @@ jobs: | ||||
|     name: Consult black on python formatting | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/checkout@v1 | ||||
|       - uses: actions/setup-python@v1 | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-python@v2 | ||||
|         with: | ||||
|           python-version: 3.7 | ||||
|       - uses: dschep/install-pipenv-action@v1 | ||||
|       - uses: Gr1N/setup-poetry@v2 | ||||
|       - uses: actions/cache@v2 | ||||
|         with: | ||||
|           path: ~/.cache/pypoetry/virtualenvs | ||||
|           key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} | ||||
|           restore-keys: | | ||||
|             ${{ runner.os }}-poetry- | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           pipenv install --dev --python ${pythonLocation}/python | ||||
|         run: poetry install | ||||
|       - name: Run black | ||||
|         run: | | ||||
|           pipenv run black . --check --diff --exclude tests/output_ | ||||
|         run: make check-style | ||||
|  | ||||
|   run-tests: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     name: Run tests with tox | ||||
|  | ||||
|     strategy: | ||||
|       matrix: | ||||
|         python-version: [ '3.6', '3.7' ] | ||||
|  | ||||
|     name: Python ${{ matrix.python-version }} test | ||||
|         python-version: [ '3.6', '3.7', '3.8'] | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/checkout@v1 | ||||
|       - uses: actions/setup-python@v1 | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-python@v2 | ||||
|         with: | ||||
|           python-version: ${{ matrix.python-version }} | ||||
|       - uses: dschep/install-pipenv-action@v1 | ||||
|       - uses: Gr1N/setup-poetry@v2 | ||||
|       - uses: actions/cache@v2 | ||||
|         with: | ||||
|           path: ~/.cache/pypoetry/virtualenvs | ||||
|           key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} | ||||
|           restore-keys: | | ||||
|             ${{ runner.os }}-poetry- | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           sudo apt install protobuf-compiler libprotobuf-dev | ||||
|           pipenv install --dev --python ${pythonLocation}/python | ||||
|           poetry install | ||||
|       - name: Run tests | ||||
|         run: | | ||||
|           cp .env.default .env | ||||
|           pipenv run pip install -e . | ||||
|           pipenv run generate | ||||
|           pipenv run test | ||||
|           make generate | ||||
|           make test | ||||
|  | ||||
|   build-release: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/checkout@v1 | ||||
|       - uses: actions/setup-python@v1 | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-python@v2 | ||||
|         with: | ||||
|           python-version: 3.7 | ||||
|       - uses: dschep/install-pipenv-action@v1 | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           sudo apt install protobuf-compiler libprotobuf-dev | ||||
|           pipenv install --dev --python ${pythonLocation}/python | ||||
|       - uses: Gr1N/setup-poetry@v2 | ||||
|       - name: Build package | ||||
|         run: poetry build | ||||
|       - name: Publish package to PyPI | ||||
|         if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') | ||||
|         run: pipenv run python setup.py sdist | ||||
|       - name: Publish package | ||||
|         if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') | ||||
|         uses: pypa/gh-action-pypi-publish@v1.0.0a0 | ||||
|         with: | ||||
|           user: __token__ | ||||
|           password: ${{ secrets.pypi }} | ||||
|         run: poetry publish -n | ||||
|         env: | ||||
|           POETRY_PYPI_TOKEN_PYPI: ${{ secrets.pypi }} | ||||
|   | ||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,5 @@ | ||||
| .coverage | ||||
| .DS_Store | ||||
| .env | ||||
| .vscode/settings.json | ||||
| .mypy_cache | ||||
| @@ -10,3 +12,5 @@ dist | ||||
| **/*.egg-info | ||||
| output | ||||
| .idea | ||||
| .DS_Store | ||||
| .tox | ||||
|   | ||||
							
								
								
									
										42
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| .PHONY: help setup generate test types format clean plugin full-test check-style | ||||
|  | ||||
| help:               ## - Show this help. | ||||
| 	@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' | ||||
|  | ||||
| # Dev workflow tasks | ||||
|  | ||||
| generate:           ## - Generate test cases (do this once before running test) | ||||
| 	poetry run ./betterproto/tests/generate.py | ||||
|  | ||||
| test:               ## - Run tests | ||||
| 	poetry run pytest --cov betterproto | ||||
|  | ||||
| types:              ## - Check types with mypy | ||||
| 	poetry run mypy betterproto --ignore-missing-imports | ||||
|  | ||||
| format:             ## - Apply black formatting to source code | ||||
| 	poetry run black . --exclude tests/output_ | ||||
|  | ||||
| clean:              ## - Clean out generated files from the workspace | ||||
| 	rm -rf .coverage \ | ||||
| 	       .mypy_cache \ | ||||
| 	       .pytest_cache \ | ||||
| 	       dist \ | ||||
| 	       **/__pycache__ \ | ||||
| 	       betterproto/tests/output_* | ||||
|  | ||||
| # Manual testing | ||||
|  | ||||
| # By default write plugin output to a directory called output | ||||
| o=output | ||||
| plugin:             ## - Execute the protoc plugin, with output write to `output` or the value passed to `-o` | ||||
| 	mkdir -p $(o) | ||||
| 	protoc --plugin=protoc-gen-custom=betterproto/plugin.py $(i) --custom_out=$(o) | ||||
|  | ||||
| # CI tasks | ||||
|  | ||||
| full-test: generate ## - Run full testing sequence with multiple pythons | ||||
| 	poetry run tox | ||||
|  | ||||
| check-style:        ## - Check if code style is correct | ||||
| 	poetry run black . --check --diff --exclude tests/output_ | ||||
							
								
								
									
										32
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								Pipfile
									
									
									
									
									
								
							| @@ -1,32 +0,0 @@ | ||||
| [[source]] | ||||
| name = "pypi" | ||||
| url = "https://pypi.org/simple" | ||||
| verify_ssl = true | ||||
|  | ||||
| [dev-packages] | ||||
| flake8 = "*" | ||||
| mypy = "*" | ||||
| isort = "*" | ||||
| pytest = "*" | ||||
| pytest-asyncio = "*" | ||||
| rope = "*" | ||||
|  | ||||
| [packages] | ||||
| protobuf = "*" | ||||
| jinja2 = "*" | ||||
| grpclib = "*" | ||||
| stringcase = "*" | ||||
| black = "*" | ||||
| backports-datetime-fromisoformat = "*" | ||||
| dataclasses = "*" | ||||
|  | ||||
| [requires] | ||||
| 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 | ||||
							
								
								
									
										444
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										444
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,444 +0,0 @@ | ||||
| { | ||||
|     "_meta": { | ||||
|         "hash": { | ||||
|             "sha256": "44ae793965dc2b6ec17f0435a388846248b8a703cf857470b66af84227535950" | ||||
|         }, | ||||
|         "pipfile-spec": 6, | ||||
|         "requires": { | ||||
|             "python_version": "3.6" | ||||
|         }, | ||||
|         "sources": [ | ||||
|             { | ||||
|                 "name": "pypi", | ||||
|                 "url": "https://pypi.org/simple", | ||||
|                 "verify_ssl": true | ||||
|             } | ||||
|         ] | ||||
|     }, | ||||
|     "default": { | ||||
|         "appdirs": { | ||||
|             "hashes": [ | ||||
|                 "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", | ||||
|                 "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" | ||||
|             ], | ||||
|             "version": "==1.4.4" | ||||
|         }, | ||||
|         "attrs": { | ||||
|             "hashes": [ | ||||
|                 "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", | ||||
|                 "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" | ||||
|             ], | ||||
|             "version": "==19.3.0" | ||||
|         }, | ||||
|         "backports-datetime-fromisoformat": { | ||||
|             "hashes": [ | ||||
|                 "sha256:9577a2a9486cd7383a5f58b23bb8e81cf0821dbbc0eb7c87d3fa198c1df40f5c" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==1.0.0" | ||||
|         }, | ||||
|         "black": { | ||||
|             "hashes": [ | ||||
|                 "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", | ||||
|                 "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==19.10b0" | ||||
|         }, | ||||
|         "click": { | ||||
|             "hashes": [ | ||||
|                 "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", | ||||
|                 "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" | ||||
|             ], | ||||
|             "version": "==7.1.2" | ||||
|         }, | ||||
|         "dataclasses": { | ||||
|             "hashes": [ | ||||
|                 "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f", | ||||
|                 "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.6" | ||||
|         }, | ||||
|         "grpclib": { | ||||
|             "hashes": [ | ||||
|                 "sha256:b27d56c987b89023d5640fe9668943e49b46703fc85d8182a58c9f3b19120cdc" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.3.2rc1" | ||||
|         }, | ||||
|         "h2": { | ||||
|             "hashes": [ | ||||
|                 "sha256:61e0f6601fa709f35cdb730863b4e5ec7ad449792add80d1410d4174ed139af5", | ||||
|                 "sha256:875f41ebd6f2c44781259005b157faed1a5031df3ae5aa7bcb4628a6c0782f14" | ||||
|             ], | ||||
|             "version": "==3.2.0" | ||||
|         }, | ||||
|         "hpack": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89", | ||||
|                 "sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2" | ||||
|             ], | ||||
|             "version": "==3.0.0" | ||||
|         }, | ||||
|         "hyperframe": { | ||||
|             "hashes": [ | ||||
|                 "sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40", | ||||
|                 "sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f" | ||||
|             ], | ||||
|             "version": "==5.2.0" | ||||
|         }, | ||||
|         "jinja2": { | ||||
|             "hashes": [ | ||||
|                 "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484", | ||||
|                 "sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==3.0.0a1" | ||||
|         }, | ||||
|         "markupsafe": { | ||||
|             "hashes": [ | ||||
|                 "sha256:06358015a4dee8ee23ae426bf885616ab3963622defd829eb45b44e3dee3515f", | ||||
|                 "sha256:0b0c4fc852c5f02c6277ef3b33d23fcbe89b1b227460423e3335374da046b6db", | ||||
|                 "sha256:267677fc42afed5094fc5ea1c4236bbe4b6a00fe4b08e93451e65ae9048139c7", | ||||
|                 "sha256:303cb70893e2c345588fb5d5b86e0ca369f9bb56942f03064c5e3e75fa7a238a", | ||||
|                 "sha256:3c9b624a0d9ed5a5093ac4edc4e823e6b125441e60ef35d36e6f4a6fdacd5054", | ||||
|                 "sha256:42033e14cae1f6c86fc0c3e90d04d08ce73ac8e46ba420a0d22d545c2abd4977", | ||||
|                 "sha256:4e4a99b6af7bdc0856b50020c095848ec050356a001e1f751510aef6ab14d0e0", | ||||
|                 "sha256:4eb07faad54bb07427d848f31030a65a49ebb0cec0b30674f91cf1ddd456bfe4", | ||||
|                 "sha256:63a7161cd8c2bc563feeda45df62f42c860dd0675e2b8da2667f25bb3c95eaba", | ||||
|                 "sha256:68e0fd039b68d2945b4beb947d4023ca7f8e95b708031c345762efba214ea761", | ||||
|                 "sha256:8092a63397025c2f655acd42784b2a1528339b90b987beb9253f22e8cdbb36c3", | ||||
|                 "sha256:841218860683c0f2223e24756843d84cc49cccdae6765e04962607754a52d3e0", | ||||
|                 "sha256:94076b2314bd2f6cfae508ad65b4d493e3a58a50112b7a2cbb6287bdbc404ae8", | ||||
|                 "sha256:9d22aff1c5322e402adfb3ce40839a5056c353e711c033798cf4f02eb9f5124d", | ||||
|                 "sha256:b0e4584f62b3e5f5c1a7bcefd2b52f236505e6ef032cc508caa4f4c8dc8d3af1", | ||||
|                 "sha256:b1163ffc1384d242964426a8164da12dbcdbc0de18ea36e2c34b898ed38c3b45", | ||||
|                 "sha256:beac28ed60c8e838301226a7a85841d0af2068eba2dcb1a58c2d32d6c05e440e", | ||||
|                 "sha256:c29f096ce79c03054a1101d6e5fe6bf04b0bb489165d5e0e9653fb4fe8048ee1", | ||||
|                 "sha256:c58779966d53e5f14ba393d64e2402a7926601d1ac8adeb4e83893def79d0428", | ||||
|                 "sha256:cfe14b37908eaf7d5506302987228bff69e1b8e7071ccd4e70fd0283b1b47f0b", | ||||
|                 "sha256:e834249c45aa9837d0753351cdca61a4b8b383cc9ad0ff2325c97ff7b69e72a6", | ||||
|                 "sha256:eed1b234c4499811ee85bcefa22ef5e466e75d132502226ed29740d593316c1f" | ||||
|             ], | ||||
|             "version": "==2.0.0a1" | ||||
|         }, | ||||
|         "multidict": { | ||||
|             "hashes": [ | ||||
|                 "sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a", | ||||
|                 "sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000", | ||||
|                 "sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2", | ||||
|                 "sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507", | ||||
|                 "sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5", | ||||
|                 "sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7", | ||||
|                 "sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d", | ||||
|                 "sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463", | ||||
|                 "sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19", | ||||
|                 "sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3", | ||||
|                 "sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b", | ||||
|                 "sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c", | ||||
|                 "sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87", | ||||
|                 "sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7", | ||||
|                 "sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430", | ||||
|                 "sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255", | ||||
|                 "sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d" | ||||
|             ], | ||||
|             "version": "==4.7.6" | ||||
|         }, | ||||
|         "pathspec": { | ||||
|             "hashes": [ | ||||
|                 "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0", | ||||
|                 "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061" | ||||
|             ], | ||||
|             "version": "==0.8.0" | ||||
|         }, | ||||
|         "protobuf": { | ||||
|             "hashes": [ | ||||
|                 "sha256:04d0b2bd99050d09393875a5a25fd12337b17f3ac2e29c0c1b8e65b277cbfe72", | ||||
|                 "sha256:05288e44638e91498f13127a3699a6528dec6f9d3084d60959d721bfb9ea5b98", | ||||
|                 "sha256:175d85370947f89e33b3da93f4ccdda3f326bebe3e599df5915ceb7f804cd9df", | ||||
|                 "sha256:440a8c77531b3652f24999b249256ed01fd44c498ab0973843066681bd276685", | ||||
|                 "sha256:49fb6fab19cd3f30fa0e976eeedcbf2558e9061e5fa65b4fe51ded1f4002e04d", | ||||
|                 "sha256:4c7cae1f56056a4a2a2e3b00b26ab8550eae738bd9548f4ea0c2fcb88ed76ae5", | ||||
|                 "sha256:519abfacbb421c3591d26e8daf7a4957763428db7267f7207e3693e29f6978db", | ||||
|                 "sha256:60f32af25620abc4d7928d8197f9f25d49d558c5959aa1e08c686f974ac0b71a", | ||||
|                 "sha256:613ac49f6db266fba243daf60fb32af107cfe3678e5c003bb40a381b6786389d", | ||||
|                 "sha256:954bb14816edd24e746ba1a6b2d48c43576393bbde2fb8e1e3bd6d4504c7feac", | ||||
|                 "sha256:9b1462c033a2cee7f4e8eb396905c69de2c532c3b835ff8f71f8e5fb77c38023", | ||||
|                 "sha256:c0767f4d93ce4288475afe0571663c78870924f1f8881efd5406c10f070c75e4", | ||||
|                 "sha256:c45f5980ce32879391144b5766120fd7b8803129f127ce36bd060dd38824801f", | ||||
|                 "sha256:eeb7502f59e889a88bcb59f299493e215d1864f3d75335ea04a413004eb4fe24", | ||||
|                 "sha256:fdb1742f883ee4662e39fcc5916f2725fec36a5191a52123fec60f8c53b70495", | ||||
|                 "sha256:fe554066c4962c2db0a1d4752655223eb948d2bfa0fb1c4a7f2c00ec07324f1c" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==3.12.1" | ||||
|         }, | ||||
|         "regex": { | ||||
|             "hashes": [ | ||||
|                 "sha256:1386e75c9d1574f6aa2e4eb5355374c8e55f9aac97e224a8a5a6abded0f9c927", | ||||
|                 "sha256:27ff7325b297fb6e5ebb70d10437592433601c423f5acf86e5bc1ee2919b9561", | ||||
|                 "sha256:329ba35d711e3428db6b45a53b1b13a0a8ba07cbbcf10bbed291a7da45f106c3", | ||||
|                 "sha256:3a9394197664e35566242686d84dfd264c07b20f93514e2e09d3c2b3ffdf78fe", | ||||
|                 "sha256:51f17abbe973c7673a61863516bdc9c0ef467407a940f39501e786a07406699c", | ||||
|                 "sha256:579ea215c81d18da550b62ff97ee187b99f1b135fd894a13451e00986a080cad", | ||||
|                 "sha256:70c14743320a68c5dac7fc5a0f685be63bc2024b062fe2aaccc4acc3d01b14a1", | ||||
|                 "sha256:7e61be8a2900897803c293247ef87366d5df86bf701083b6c43119c7c6c99108", | ||||
|                 "sha256:8044d1c085d49673aadb3d7dc20ef5cb5b030c7a4fa253a593dda2eab3059929", | ||||
|                 "sha256:89d76ce33d3266173f5be80bd4efcbd5196cafc34100fdab814f9b228dee0fa4", | ||||
|                 "sha256:99568f00f7bf820c620f01721485cad230f3fb28f57d8fbf4a7967ec2e446994", | ||||
|                 "sha256:a7c37f048ec3920783abab99f8f4036561a174f1314302ccfa4e9ad31cb00eb4", | ||||
|                 "sha256:c2062c7d470751b648f1cacc3f54460aebfc261285f14bc6da49c6943bd48bdd", | ||||
|                 "sha256:c9bce6e006fbe771a02bda468ec40ffccbf954803b470a0345ad39c603402577", | ||||
|                 "sha256:ce367d21f33e23a84fb83a641b3834dd7dd8e9318ad8ff677fbfae5915a239f7", | ||||
|                 "sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5", | ||||
|                 "sha256:ce5cc53aa9fbbf6712e92c7cf268274eaff30f6bd12a0754e8133d85a8fb0f5f", | ||||
|                 "sha256:d466967ac8e45244b9dfe302bbe5e3337f8dc4dec8d7d10f5e950d83b140d33a", | ||||
|                 "sha256:d881c2e657c51d89f02ae4c21d9adbef76b8325fe4d5cf0e9ad62f850f3a98fd", | ||||
|                 "sha256:e565569fc28e3ba3e475ec344d87ed3cd8ba2d575335359749298a0899fe122e", | ||||
|                 "sha256:ea55b80eb0d1c3f1d8d784264a6764f931e172480a2f1868f2536444c5f01e01" | ||||
|             ], | ||||
|             "version": "==2020.5.14" | ||||
|         }, | ||||
|         "six": { | ||||
|             "hashes": [ | ||||
|                 "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", | ||||
|                 "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" | ||||
|             ], | ||||
|             "version": "==1.15.0" | ||||
|         }, | ||||
|         "stringcase": { | ||||
|             "hashes": [ | ||||
|                 "sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==1.2.0" | ||||
|         }, | ||||
|         "toml": { | ||||
|             "hashes": [ | ||||
|                 "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", | ||||
|                 "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" | ||||
|             ], | ||||
|             "version": "==0.10.1" | ||||
|         }, | ||||
|         "typed-ast": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", | ||||
|                 "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", | ||||
|                 "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", | ||||
|                 "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", | ||||
|                 "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", | ||||
|                 "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", | ||||
|                 "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", | ||||
|                 "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", | ||||
|                 "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", | ||||
|                 "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", | ||||
|                 "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", | ||||
|                 "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", | ||||
|                 "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", | ||||
|                 "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", | ||||
|                 "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", | ||||
|                 "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", | ||||
|                 "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", | ||||
|                 "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", | ||||
|                 "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", | ||||
|                 "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", | ||||
|                 "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" | ||||
|             ], | ||||
|             "version": "==1.4.1" | ||||
|         } | ||||
|     }, | ||||
|     "develop": { | ||||
|         "attrs": { | ||||
|             "hashes": [ | ||||
|                 "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", | ||||
|                 "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" | ||||
|             ], | ||||
|             "version": "==19.3.0" | ||||
|         }, | ||||
|         "flake8": { | ||||
|             "hashes": [ | ||||
|                 "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634", | ||||
|                 "sha256:ccaa799ef9893cebe69fdfefed76865aeaefbb94cb8545617b2298786a4de9a5" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==3.8.2" | ||||
|         }, | ||||
|         "importlib-metadata": { | ||||
|             "hashes": [ | ||||
|                 "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", | ||||
|                 "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" | ||||
|             ], | ||||
|             "markers": "python_version < '3.8'", | ||||
|             "version": "==1.6.0" | ||||
|         }, | ||||
|         "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:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be", | ||||
|                 "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982" | ||||
|             ], | ||||
|             "version": "==8.3.0" | ||||
|         }, | ||||
|         "mypy": { | ||||
|             "hashes": [ | ||||
|                 "sha256:15b948e1302682e3682f11f50208b726a246ab4e6c1b39f9264a8796bb416aa2", | ||||
|                 "sha256:219a3116ecd015f8dca7b5d2c366c973509dfb9a8fc97ef044a36e3da66144a1", | ||||
|                 "sha256:3b1fc683fb204c6b4403a1ef23f0b1fac8e4477091585e0c8c54cbdf7d7bb164", | ||||
|                 "sha256:3beff56b453b6ef94ecb2996bea101a08f1f8a9771d3cbf4988a61e4d9973761", | ||||
|                 "sha256:7687f6455ec3ed7649d1ae574136835a4272b65b3ddcf01ab8704ac65616c5ce", | ||||
|                 "sha256:7ec45a70d40ede1ec7ad7f95b3c94c9cf4c186a32f6bacb1795b60abd2f9ef27", | ||||
|                 "sha256:86c857510a9b7c3104cf4cde1568f4921762c8f9842e987bc03ed4f160925754", | ||||
|                 "sha256:8a627507ef9b307b46a1fea9513d5c98680ba09591253082b4c48697ba05a4ae", | ||||
|                 "sha256:8dfb69fbf9f3aeed18afffb15e319ca7f8da9642336348ddd6cab2713ddcf8f9", | ||||
|                 "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600", | ||||
|                 "sha256:a8ffcd53cb5dfc131850851cc09f1c44689c2812d0beb954d8138d4f5fc17f65", | ||||
|                 "sha256:b90928f2d9eb2f33162405f32dde9f6dcead63a0971ca8a1b50eb4ca3e35ceb8", | ||||
|                 "sha256:c56ffe22faa2e51054c5f7a3bc70a370939c2ed4de308c690e7949230c995913", | ||||
|                 "sha256:f91c7ae919bbc3f96cd5e5b2e786b2b108343d1d7972ea130f7de27fdd547cf3" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.770" | ||||
|         }, | ||||
|         "mypy-extensions": { | ||||
|             "hashes": [ | ||||
|                 "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", | ||||
|                 "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" | ||||
|             ], | ||||
|             "version": "==0.4.3" | ||||
|         }, | ||||
|         "packaging": { | ||||
|             "hashes": [ | ||||
|                 "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", | ||||
|                 "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" | ||||
|             ], | ||||
|             "version": "==20.4" | ||||
|         }, | ||||
|         "pluggy": { | ||||
|             "hashes": [ | ||||
|                 "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", | ||||
|                 "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" | ||||
|             ], | ||||
|             "version": "==0.13.1" | ||||
|         }, | ||||
|         "py": { | ||||
|             "hashes": [ | ||||
|                 "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", | ||||
|                 "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" | ||||
|             ], | ||||
|             "version": "==1.8.1" | ||||
|         }, | ||||
|         "pycodestyle": { | ||||
|             "hashes": [ | ||||
|                 "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", | ||||
|                 "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" | ||||
|             ], | ||||
|             "version": "==2.6.0" | ||||
|         }, | ||||
|         "pyflakes": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", | ||||
|                 "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" | ||||
|             ], | ||||
|             "version": "==2.2.0" | ||||
|         }, | ||||
|         "pyparsing": { | ||||
|             "hashes": [ | ||||
|                 "sha256:67199f0c41a9c702154efb0e7a8cc08accf830eb003b4d9fa42c4059002e2492", | ||||
|                 "sha256:700d17888d441604b0bd51535908dcb297561b040819cccde647a92439db5a2a" | ||||
|             ], | ||||
|             "version": "==3.0.0a1" | ||||
|         }, | ||||
|         "pytest": { | ||||
|             "hashes": [ | ||||
|                 "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3", | ||||
|                 "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==5.4.2" | ||||
|         }, | ||||
|         "pytest-asyncio": { | ||||
|             "hashes": [ | ||||
|                 "sha256:475bd2f3dc0bc11d2463656b3cbaafdbec5a47b47508ea0b329ee693040eebd2" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.12.0" | ||||
|         }, | ||||
|         "rope": { | ||||
|             "hashes": [ | ||||
|                 "sha256:658ad6705f43dcf3d6df379da9486529cf30e02d9ea14c5682aa80eb33b649e1" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.17.0" | ||||
|         }, | ||||
|         "six": { | ||||
|             "hashes": [ | ||||
|                 "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", | ||||
|                 "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" | ||||
|             ], | ||||
|             "version": "==1.15.0" | ||||
|         }, | ||||
|         "typed-ast": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", | ||||
|                 "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", | ||||
|                 "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", | ||||
|                 "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", | ||||
|                 "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", | ||||
|                 "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", | ||||
|                 "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", | ||||
|                 "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", | ||||
|                 "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", | ||||
|                 "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", | ||||
|                 "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", | ||||
|                 "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", | ||||
|                 "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", | ||||
|                 "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", | ||||
|                 "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", | ||||
|                 "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", | ||||
|                 "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", | ||||
|                 "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", | ||||
|                 "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", | ||||
|                 "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", | ||||
|                 "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" | ||||
|             ], | ||||
|             "version": "==1.4.1" | ||||
|         }, | ||||
|         "typing-extensions": { | ||||
|             "hashes": [ | ||||
|                 "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5", | ||||
|                 "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae", | ||||
|                 "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392" | ||||
|             ], | ||||
|             "version": "==3.7.4.2" | ||||
|         }, | ||||
|         "wcwidth": { | ||||
|             "hashes": [ | ||||
|                 "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1", | ||||
|                 "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1" | ||||
|             ], | ||||
|             "version": "==0.1.9" | ||||
|         }, | ||||
|         "zipp": { | ||||
|             "hashes": [ | ||||
|                 "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", | ||||
|                 "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" | ||||
|             ], | ||||
|             "version": "==3.1.0" | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										34
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								README.md
									
									
									
									
									
								
							| @@ -46,10 +46,10 @@ 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 | ||||
| pip install betterproto | ||||
| ``` | ||||
|  | ||||
| Now, given you installed the compiler and have a proto file, e.g `example.proto`: | ||||
| @@ -68,7 +68,7 @@ message Greeting { | ||||
| You can run the following: | ||||
|  | ||||
| ```sh | ||||
| $ protoc -I . --python_betterproto_out=. example.proto | ||||
| protoc -I . --python_betterproto_out=. example.proto | ||||
| ``` | ||||
|  | ||||
| This will generate `hello.py` which looks like: | ||||
| @@ -299,25 +299,26 @@ datetime.datetime(2019, 1, 1, 11, 59, 58, 800000, tzinfo=datetime.timezone.utc) | ||||
|  | ||||
| Join us on [Slack](https://join.slack.com/t/betterproto/shared_invite/zt-f0n0uolx-iN8gBNrkPxtKHTLpG3o1OQ)! | ||||
|  | ||||
| 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: | ||||
| First, make sure you have Python 3.6+ and `poetry` 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 | ||||
| $ pipenv install --dev | ||||
| poetry install | ||||
|  | ||||
| # Link the local package | ||||
| $ pipenv shell | ||||
| $ pip install -e . | ||||
| # Activate the poetry environment | ||||
| poetry shell | ||||
| ``` | ||||
|  | ||||
| To benefit from the collection of standard development tasks ensure you have make installed and run `make help` to see available tasks. | ||||
|  | ||||
| ### Code style | ||||
|  | ||||
| This project enforces [black](https://github.com/psf/black) python code formatting. | ||||
|  | ||||
| Before commiting changes run: | ||||
|  | ||||
| ```bash | ||||
| pipenv run black . | ||||
| ```sh | ||||
| make format | ||||
| ``` | ||||
|  | ||||
| To avoid merge conflicts later, non-black formatted python code will fail in CI. | ||||
| @@ -350,11 +351,16 @@ Custom tests are found in `tests/test_*.py` and are run with pytest. | ||||
| Here's how to run the tests. | ||||
|  | ||||
| ```sh | ||||
| # Generate assets from sample .proto files | ||||
| $ pipenv run generate | ||||
| # Generate assets from sample .proto files required by the tests | ||||
| make generate | ||||
| # Run the tests | ||||
| make test | ||||
| ``` | ||||
|  | ||||
| # Run all tests | ||||
| $ pipenv run test | ||||
| To run tests as they are run in CI (with tox) run: | ||||
|  | ||||
| ```sh | ||||
| make full-test | ||||
| ``` | ||||
|  | ||||
| ### (Re)compiling Google Well-known Types | ||||
|   | ||||
| @@ -5,8 +5,9 @@ import json | ||||
| import struct | ||||
| import sys | ||||
| from abc import ABC | ||||
| from base64 import b64encode, b64decode | ||||
| from base64 import b64decode, b64encode | ||||
| from datetime import datetime, timedelta, timezone | ||||
| import stringcase | ||||
| from typing import ( | ||||
|     Any, | ||||
|     AsyncGenerator, | ||||
| @@ -14,28 +15,20 @@ from typing import ( | ||||
|     Collection, | ||||
|     Dict, | ||||
|     Generator, | ||||
|     Iterator, | ||||
|     List, | ||||
|     Mapping, | ||||
|     Optional, | ||||
|     Set, | ||||
|     SupportsBytes, | ||||
|     Tuple, | ||||
|     Type, | ||||
|     TypeVar, | ||||
|     Union, | ||||
|     get_type_hints, | ||||
|     TYPE_CHECKING, | ||||
| ) | ||||
|  | ||||
|  | ||||
| import grpclib.const | ||||
| import stringcase | ||||
|  | ||||
| from ._types import ST, T | ||||
| from .casing import safe_snake_case | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from grpclib._protocols import IProtoMessage | ||||
|     from grpclib.client import Channel | ||||
|     from grpclib.metadata import Deadline | ||||
| from .grpc.grpclib_client import ServiceStub | ||||
|  | ||||
| if not (sys.version_info.major == 3 and sys.version_info.minor >= 7): | ||||
|     # Apply backport of datetime.fromisoformat from 3.7 | ||||
| @@ -429,10 +422,6 @@ def parse_fields(value: bytes) -> Generator[ParsedField, None, None]: | ||||
|         ) | ||||
|  | ||||
|  | ||||
| # Bound type variable to allow methods to return `self` of subclasses | ||||
| T = TypeVar("T", bound="Message") | ||||
|  | ||||
|  | ||||
| class ProtoClassMetadata: | ||||
|     oneof_group_by_field: Dict[str, str] | ||||
|     oneof_field_by_group: Dict[str, Set[dataclasses.Field]] | ||||
| @@ -451,7 +440,7 @@ class ProtoClassMetadata: | ||||
|  | ||||
|     def __init__(self, cls: Type["Message"]): | ||||
|         by_field = {} | ||||
|         by_group = {} | ||||
|         by_group: Dict[str, Set] = {} | ||||
|         by_field_name = {} | ||||
|         by_field_number = {} | ||||
|  | ||||
| @@ -604,7 +593,7 @@ class Message(ABC): | ||||
|             serialize_empty = False | ||||
|             if isinstance(value, Message) and value._serialized_on_wire: | ||||
|                 # Empty messages can still be sent on the wire if they were | ||||
|                 # set (or received empty). | ||||
|                 # set (or recieved empty). | ||||
|                 serialize_empty = True | ||||
|  | ||||
|             if value == self._get_field_default(field_name) and not ( | ||||
| @@ -791,7 +780,7 @@ class Message(ABC): | ||||
|  | ||||
|     def to_dict( | ||||
|         self, casing: Casing = Casing.CAMEL, include_default_values: bool = False | ||||
|     ) -> dict: | ||||
|     ) -> Dict[str, Any]: | ||||
|         """ | ||||
|         Returns a dict representation of this message instance which can be | ||||
|         used to serialize to e.g. JSON. Defaults to camel casing for | ||||
| @@ -1024,83 +1013,3 @@ def _get_wrapper(proto_type: str) -> Type: | ||||
|         TYPE_STRING: StringValue, | ||||
|         TYPE_BYTES: BytesValue, | ||||
|     }[proto_type] | ||||
|  | ||||
|  | ||||
| _Value = Union[str, bytes] | ||||
| _MetadataLike = Union[Mapping[str, _Value], Collection[Tuple[str, _Value]]] | ||||
|  | ||||
|  | ||||
| class ServiceStub(ABC): | ||||
|     """ | ||||
|     Base class for async gRPC service stubs. | ||||
|     """ | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         channel: "Channel", | ||||
|         *, | ||||
|         timeout: Optional[float] = None, | ||||
|         deadline: Optional["Deadline"] = None, | ||||
|         metadata: Optional[_MetadataLike] = None, | ||||
|     ) -> None: | ||||
|         self.channel = channel | ||||
|         self.timeout = timeout | ||||
|         self.deadline = deadline | ||||
|         self.metadata = metadata | ||||
|  | ||||
|     def __resolve_request_kwargs( | ||||
|         self, | ||||
|         timeout: Optional[float], | ||||
|         deadline: Optional["Deadline"], | ||||
|         metadata: Optional[_MetadataLike], | ||||
|     ): | ||||
|         return { | ||||
|             "timeout": self.timeout if timeout is None else timeout, | ||||
|             "deadline": self.deadline if deadline is None else deadline, | ||||
|             "metadata": self.metadata if metadata is None else metadata, | ||||
|         } | ||||
|  | ||||
|     async def _unary_unary( | ||||
|         self, | ||||
|         route: str, | ||||
|         request: "IProtoMessage", | ||||
|         response_type: Type[T], | ||||
|         *, | ||||
|         timeout: Optional[float] = None, | ||||
|         deadline: Optional["Deadline"] = None, | ||||
|         metadata: Optional[_MetadataLike] = None, | ||||
|     ) -> T: | ||||
|         """Make a unary request and return the response.""" | ||||
|         async with self.channel.request( | ||||
|             route, | ||||
|             grpclib.const.Cardinality.UNARY_UNARY, | ||||
|             type(request), | ||||
|             response_type, | ||||
|             **self.__resolve_request_kwargs(timeout, deadline, metadata), | ||||
|         ) as stream: | ||||
|             await stream.send_message(request, end=True) | ||||
|             response = await stream.recv_message() | ||||
|             assert response is not None | ||||
|             return response | ||||
|  | ||||
|     async def _unary_stream( | ||||
|         self, | ||||
|         route: str, | ||||
|         request: "IProtoMessage", | ||||
|         response_type: Type[T], | ||||
|         *, | ||||
|         timeout: Optional[float] = None, | ||||
|         deadline: Optional["Deadline"] = None, | ||||
|         metadata: Optional[_MetadataLike] = None, | ||||
|     ) -> AsyncGenerator[T, None]: | ||||
|         """Make a unary request and return the stream response iterator.""" | ||||
|         async with self.channel.request( | ||||
|             route, | ||||
|             grpclib.const.Cardinality.UNARY_STREAM, | ||||
|             type(request), | ||||
|             response_type, | ||||
|             **self.__resolve_request_kwargs(timeout, deadline, metadata), | ||||
|         ) as stream: | ||||
|             await stream.send_message(request, end=True) | ||||
|             async for message in stream: | ||||
|                 yield message | ||||
|   | ||||
							
								
								
									
										9
									
								
								betterproto/_types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								betterproto/_types.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| from typing import TYPE_CHECKING, TypeVar | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from . import Message | ||||
|     from grpclib._protocols import IProtoMessage | ||||
|  | ||||
| # Bound type variable to allow methods to return `self` of subclasses | ||||
| T = TypeVar("T", bound="Message") | ||||
| ST = TypeVar("ST", bound="IProtoMessage") | ||||
							
								
								
									
										0
									
								
								betterproto/grpc/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								betterproto/grpc/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										170
									
								
								betterproto/grpc/grpclib_client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								betterproto/grpc/grpclib_client.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| from abc import ABC | ||||
| import asyncio | ||||
| import grpclib.const | ||||
| from typing import ( | ||||
|     Any, | ||||
|     AsyncIterable, | ||||
|     AsyncIterator, | ||||
|     Collection, | ||||
|     Iterable, | ||||
|     Mapping, | ||||
|     Optional, | ||||
|     Tuple, | ||||
|     TYPE_CHECKING, | ||||
|     Type, | ||||
|     Union, | ||||
| ) | ||||
| from .._types import ST, T | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from grpclib._protocols import IProtoMessage | ||||
|     from grpclib.client import Channel, Stream | ||||
|     from grpclib.metadata import Deadline | ||||
|  | ||||
|  | ||||
| _Value = Union[str, bytes] | ||||
| _MetadataLike = Union[Mapping[str, _Value], Collection[Tuple[str, _Value]]] | ||||
| _MessageSource = Union[Iterable["IProtoMessage"], AsyncIterable["IProtoMessage"]] | ||||
|  | ||||
|  | ||||
| class ServiceStub(ABC): | ||||
|     """ | ||||
|     Base class for async gRPC clients. | ||||
|     """ | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         channel: "Channel", | ||||
|         *, | ||||
|         timeout: Optional[float] = None, | ||||
|         deadline: Optional["Deadline"] = None, | ||||
|         metadata: Optional[_MetadataLike] = None, | ||||
|     ) -> None: | ||||
|         self.channel = channel | ||||
|         self.timeout = timeout | ||||
|         self.deadline = deadline | ||||
|         self.metadata = metadata | ||||
|  | ||||
|     def __resolve_request_kwargs( | ||||
|         self, | ||||
|         timeout: Optional[float], | ||||
|         deadline: Optional["Deadline"], | ||||
|         metadata: Optional[_MetadataLike], | ||||
|     ): | ||||
|         return { | ||||
|             "timeout": self.timeout if timeout is None else timeout, | ||||
|             "deadline": self.deadline if deadline is None else deadline, | ||||
|             "metadata": self.metadata if metadata is None else metadata, | ||||
|         } | ||||
|  | ||||
|     async def _unary_unary( | ||||
|         self, | ||||
|         route: str, | ||||
|         request: "IProtoMessage", | ||||
|         response_type: Type[T], | ||||
|         *, | ||||
|         timeout: Optional[float] = None, | ||||
|         deadline: Optional["Deadline"] = None, | ||||
|         metadata: Optional[_MetadataLike] = None, | ||||
|     ) -> T: | ||||
|         """Make a unary request and return the response.""" | ||||
|         async with self.channel.request( | ||||
|             route, | ||||
|             grpclib.const.Cardinality.UNARY_UNARY, | ||||
|             type(request), | ||||
|             response_type, | ||||
|             **self.__resolve_request_kwargs(timeout, deadline, metadata), | ||||
|         ) as stream: | ||||
|             await stream.send_message(request, end=True) | ||||
|             response = await stream.recv_message() | ||||
|             assert response is not None | ||||
|             return response | ||||
|  | ||||
|     async def _unary_stream( | ||||
|         self, | ||||
|         route: str, | ||||
|         request: "IProtoMessage", | ||||
|         response_type: Type[T], | ||||
|         *, | ||||
|         timeout: Optional[float] = None, | ||||
|         deadline: Optional["Deadline"] = None, | ||||
|         metadata: Optional[_MetadataLike] = None, | ||||
|     ) -> AsyncIterator[T]: | ||||
|         """Make a unary request and return the stream response iterator.""" | ||||
|         async with self.channel.request( | ||||
|             route, | ||||
|             grpclib.const.Cardinality.UNARY_STREAM, | ||||
|             type(request), | ||||
|             response_type, | ||||
|             **self.__resolve_request_kwargs(timeout, deadline, metadata), | ||||
|         ) as stream: | ||||
|             await stream.send_message(request, end=True) | ||||
|             async for message in stream: | ||||
|                 yield message | ||||
|  | ||||
|     async def _stream_unary( | ||||
|         self, | ||||
|         route: str, | ||||
|         request_iterator: _MessageSource, | ||||
|         request_type: Type[ST], | ||||
|         response_type: Type[T], | ||||
|         *, | ||||
|         timeout: Optional[float] = None, | ||||
|         deadline: Optional["Deadline"] = None, | ||||
|         metadata: Optional[_MetadataLike] = None, | ||||
|     ) -> T: | ||||
|         """Make a stream request and return the response.""" | ||||
|         async with self.channel.request( | ||||
|             route, | ||||
|             grpclib.const.Cardinality.STREAM_UNARY, | ||||
|             request_type, | ||||
|             response_type, | ||||
|             **self.__resolve_request_kwargs(timeout, deadline, metadata), | ||||
|         ) as stream: | ||||
|             await self._send_messages(stream, request_iterator) | ||||
|             response = await stream.recv_message() | ||||
|             assert response is not None | ||||
|             return response | ||||
|  | ||||
|     async def _stream_stream( | ||||
|         self, | ||||
|         route: str, | ||||
|         request_iterator: _MessageSource, | ||||
|         request_type: Type[ST], | ||||
|         response_type: Type[T], | ||||
|         *, | ||||
|         timeout: Optional[float] = None, | ||||
|         deadline: Optional["Deadline"] = None, | ||||
|         metadata: Optional[_MetadataLike] = None, | ||||
|     ) -> AsyncIterator[T]: | ||||
|         """ | ||||
|         Make a stream request and return an AsyncIterator to iterate over response | ||||
|         messages. | ||||
|         """ | ||||
|         async with self.channel.request( | ||||
|             route, | ||||
|             grpclib.const.Cardinality.STREAM_STREAM, | ||||
|             request_type, | ||||
|             response_type, | ||||
|             **self.__resolve_request_kwargs(timeout, deadline, metadata), | ||||
|         ) as stream: | ||||
|             await stream.send_request() | ||||
|             sending_task = asyncio.ensure_future( | ||||
|                 self._send_messages(stream, request_iterator) | ||||
|             ) | ||||
|             try: | ||||
|                 async for response in stream: | ||||
|                     yield response | ||||
|             except: | ||||
|                 sending_task.cancel() | ||||
|                 raise | ||||
|  | ||||
|     @staticmethod | ||||
|     async def _send_messages(stream, messages: _MessageSource): | ||||
|         if isinstance(messages, AsyncIterable): | ||||
|             async for message in messages: | ||||
|                 await stream.send_message(message) | ||||
|         else: | ||||
|             for message in messages: | ||||
|                 await stream.send_message(message) | ||||
|         await stream.end() | ||||
							
								
								
									
										0
									
								
								betterproto/grpc/util/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								betterproto/grpc/util/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										198
									
								
								betterproto/grpc/util/async_channel.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								betterproto/grpc/util/async_channel.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,198 @@ | ||||
| import asyncio | ||||
| from typing import ( | ||||
|     AsyncIterable, | ||||
|     AsyncIterator, | ||||
|     Iterable, | ||||
|     Optional, | ||||
|     TypeVar, | ||||
|     Union, | ||||
| ) | ||||
|  | ||||
| T = TypeVar("T") | ||||
|  | ||||
|  | ||||
| class ChannelClosed(Exception): | ||||
|     """ | ||||
|     An exception raised on an attempt to send through a closed channel | ||||
|     """ | ||||
|  | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class ChannelDone(Exception): | ||||
|     """ | ||||
|     An exception raised on an attempt to send recieve from a channel that is both closed | ||||
|     and empty. | ||||
|     """ | ||||
|  | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class AsyncChannel(AsyncIterable[T]): | ||||
|     """ | ||||
|     A buffered async channel for sending items between coroutines with FIFO ordering. | ||||
|  | ||||
|     This makes decoupled bidirection steaming gRPC requests easy if used like: | ||||
|  | ||||
|     .. code-block:: python | ||||
|         client = GeneratedStub(grpclib_chan) | ||||
|         request_chan = await AsyncChannel() | ||||
|         # We can start be sending all the requests we already have | ||||
|         await request_chan.send_from([ReqestObject(...), ReqestObject(...)]) | ||||
|         async for response in client.rpc_call(request_chan): | ||||
|             # The response iterator will remain active until the connection is closed | ||||
|             ... | ||||
|             # More items can be sent at any time | ||||
|             await request_chan.send(ReqestObject(...)) | ||||
|             ... | ||||
|             # The channel must be closed to complete the gRPC connection | ||||
|             request_chan.close() | ||||
|  | ||||
|     Items can be sent through the channel by either: | ||||
|     - providing an iterable to the send_from method | ||||
|     - passing them to the send method one at a time | ||||
|  | ||||
|     Items can be recieved from the channel by either: | ||||
|     - iterating over the channel with a for loop to get all items | ||||
|     - calling the recieve method to get one item at a time | ||||
|  | ||||
|     If the channel is empty then recievers will wait until either an item appears or the | ||||
|     channel is closed. | ||||
|  | ||||
|     Once the channel is closed then subsequent attempt to send through the channel will | ||||
|     fail with a ChannelClosed exception. | ||||
|  | ||||
|     When th channel is closed and empty then it is done, and further attempts to recieve | ||||
|     from it will fail with a ChannelDone exception | ||||
|  | ||||
|     If multiple coroutines recieve from the channel concurrently, each item sent will be | ||||
|     recieved by only one of the recievers. | ||||
|  | ||||
|     :param source: | ||||
|         An optional iterable will items that should be sent through the channel | ||||
|         immediately. | ||||
|     :param buffer_limit: | ||||
|         Limit the number of items that can be buffered in the channel, A value less than | ||||
|         1 implies no limit. If the channel is full then attempts to send more items will | ||||
|         result in the sender waiting until an item is recieved from the channel. | ||||
|     :param close: | ||||
|         If set to True then the channel will automatically close after exhausting source | ||||
|         or immediately if no source is provided. | ||||
|     """ | ||||
|  | ||||
|     def __init__( | ||||
|         self, *, buffer_limit: int = 0, close: bool = False, | ||||
|     ): | ||||
|         self._queue: asyncio.Queue[Union[T, object]] = asyncio.Queue(buffer_limit) | ||||
|         self._closed = False | ||||
|         self._waiting_recievers: int = 0 | ||||
|         # Track whether flush has been invoked so it can only happen once | ||||
|         self._flushed = False | ||||
|  | ||||
|     def __aiter__(self) -> AsyncIterator[T]: | ||||
|         return self | ||||
|  | ||||
|     async def __anext__(self) -> T: | ||||
|         if self.done(): | ||||
|             raise StopAsyncIteration | ||||
|         self._waiting_recievers += 1 | ||||
|         try: | ||||
|             result = await self._queue.get() | ||||
|             if result is self.__flush: | ||||
|                 raise StopAsyncIteration | ||||
|             return result | ||||
|         finally: | ||||
|             self._waiting_recievers -= 1 | ||||
|             self._queue.task_done() | ||||
|  | ||||
|     def closed(self) -> bool: | ||||
|         """ | ||||
|         Returns True if this channel is closed and no-longer accepting new items | ||||
|         """ | ||||
|         return self._closed | ||||
|  | ||||
|     def done(self) -> bool: | ||||
|         """ | ||||
|         Check if this channel is done. | ||||
|  | ||||
|         :return: True if this channel is closed and and has been drained of items in | ||||
|         which case any further attempts to recieve an item from this channel will raise | ||||
|         a ChannelDone exception. | ||||
|         """ | ||||
|         # After close the channel is not yet done until there is at least one waiting | ||||
|         # reciever per enqueued item. | ||||
|         return self._closed and self._queue.qsize() <= self._waiting_recievers | ||||
|  | ||||
|     async def send_from( | ||||
|         self, source: Union[Iterable[T], AsyncIterable[T]], close: bool = False | ||||
|     ) -> "AsyncChannel[T]": | ||||
|         """ | ||||
|         Iterates the given [Async]Iterable and sends all the resulting items. | ||||
|         If close is set to True then subsequent send calls will be rejected with a | ||||
|         ChannelClosed exception. | ||||
|         :param source: an iterable of items to send | ||||
|         :param close: | ||||
|             if True then the channel will be closed after the source has been exhausted | ||||
|  | ||||
|         """ | ||||
|         if self._closed: | ||||
|             raise ChannelClosed("Cannot send through a closed channel") | ||||
|         if isinstance(source, AsyncIterable): | ||||
|             async for item in source: | ||||
|                 await self._queue.put(item) | ||||
|         else: | ||||
|             for item in source: | ||||
|                 await self._queue.put(item) | ||||
|         if close: | ||||
|             # Complete the closing process | ||||
|             self.close() | ||||
|         return self | ||||
|  | ||||
|     async def send(self, item: T) -> "AsyncChannel[T]": | ||||
|         """ | ||||
|         Send a single item over this channel. | ||||
|         :param item: The item to send | ||||
|         """ | ||||
|         if self._closed: | ||||
|             raise ChannelClosed("Cannot send through a closed channel") | ||||
|         await self._queue.put(item) | ||||
|         return self | ||||
|  | ||||
|     async def recieve(self) -> Optional[T]: | ||||
|         """ | ||||
|         Returns the next item from this channel when it becomes available, | ||||
|         or None if the channel is closed before another item is sent. | ||||
|         :return: An item from the channel | ||||
|         """ | ||||
|         if self.done(): | ||||
|             raise ChannelDone("Cannot recieve from a closed channel") | ||||
|         self._waiting_recievers += 1 | ||||
|         try: | ||||
|             result = await self._queue.get() | ||||
|             if result is self.__flush: | ||||
|                 return None | ||||
|             return result | ||||
|         finally: | ||||
|             self._waiting_recievers -= 1 | ||||
|             self._queue.task_done() | ||||
|  | ||||
|     def close(self): | ||||
|         """ | ||||
|         Close this channel to new items | ||||
|         """ | ||||
|         self._closed = True | ||||
|         asyncio.ensure_future(self._flush_queue()) | ||||
|  | ||||
|     async def _flush_queue(self): | ||||
|         """ | ||||
|         To be called after the channel is closed. Pushes a number of self.__flush | ||||
|         objects to the queue to ensure no waiting consumers get deadlocked. | ||||
|         """ | ||||
|         if not self._flushed: | ||||
|             self._flushed = True | ||||
|             deadlocked_recievers = max(0, self._waiting_recievers - self._queue.qsize()) | ||||
|             for _ in range(deadlocked_recievers): | ||||
|                 await self._queue.put(self.__flush) | ||||
|  | ||||
|     # A special signal object for flushing the queue when the channel is closed | ||||
|     __flush = object() | ||||
| @@ -6,10 +6,10 @@ import re | ||||
| import stringcase | ||||
| import sys | ||||
| import textwrap | ||||
| from typing import List | ||||
| from typing import List, Union | ||||
| import betterproto | ||||
| from betterproto.casing import safe_snake_case | ||||
| from betterproto.compile.importing import get_ref_type | ||||
| import betterproto | ||||
|  | ||||
| try: | ||||
|     # betterproto[compiler] specific dependencies | ||||
| @@ -41,9 +41,9 @@ def py_type( | ||||
|     message: DescriptorProto, | ||||
|     descriptor: FieldDescriptorProto, | ||||
| ) -> str: | ||||
|     if descriptor.type in [1, 2, 6, 7, 15, 16]: | ||||
|     if descriptor.type in [1, 2]: | ||||
|         return "float" | ||||
|     elif descriptor.type in [3, 4, 5, 13, 17, 18]: | ||||
|     elif descriptor.type in [3, 4, 5, 6, 7, 13, 15, 16, 17, 18]: | ||||
|         return "int" | ||||
|     elif descriptor.type == 8: | ||||
|         return "bool" | ||||
| @@ -58,8 +58,8 @@ def py_type( | ||||
|         raise NotImplementedError(f"Unknown type {descriptor.type}") | ||||
|  | ||||
|  | ||||
| def get_py_zero(type_num: int) -> str: | ||||
|     zero = 0 | ||||
| def get_py_zero(type_num: int) -> Union[str, float]: | ||||
|     zero: Union[str, float] = 0 | ||||
|     if type_num in []: | ||||
|         zero = 0.0 | ||||
|     elif type_num == 8: | ||||
| @@ -311,9 +311,6 @@ def generate_code(request, response): | ||||
|                 } | ||||
|  | ||||
|                 for j, method in enumerate(service.method): | ||||
|                     if method.client_streaming: | ||||
|                         raise NotImplementedError("Client streaming not yet supported") | ||||
|  | ||||
|                     input_message = None | ||||
|                     input_type = get_ref_type( | ||||
|                         package, output["imports"], method.input_type | ||||
| @@ -347,8 +344,12 @@ def generate_code(request, response): | ||||
|                         } | ||||
|                     ) | ||||
|  | ||||
|                     if method.client_streaming: | ||||
|                         output["typing_imports"].add("AsyncIterable") | ||||
|                         output["typing_imports"].add("Iterable") | ||||
|                         output["typing_imports"].add("Union") | ||||
|                     if method.server_streaming: | ||||
|                         output["typing_imports"].add("AsyncGenerator") | ||||
|                         output["typing_imports"].add("AsyncIterator") | ||||
|  | ||||
|                 output["services"].append(data) | ||||
|  | ||||
|   | ||||
| @@ -63,11 +63,28 @@ class {{ service.py_name }}Stub(betterproto.ServiceStub): | ||||
|  | ||||
|     {% endif %} | ||||
|     {% for method in service.methods %} | ||||
|     async def {{ method.py_name }}(self{% if method.input_message and method.input_message.properties %}, *, {% for field in method.input_message.properties %}{{ field.py_name }}: {% if field.zero == "None" and not field.type.startswith("Optional[") %}Optional[{{ field.type }}]{% else %}{{ field.type }}{% endif %} = {{ field.zero }}{% if not loop.last %}, {% endif %}{% endfor %}{% endif %}) -> {% if method.server_streaming %}AsyncGenerator[{{ method.output }}, None]{% else %}{{ method.output }}{% endif %}: | ||||
|     async def {{ method.py_name }}(self | ||||
|         {%- if not method.client_streaming -%} | ||||
|             {%- if method.input_message and method.input_message.properties -%}, *, | ||||
|                 {%- for field in method.input_message.properties -%} | ||||
|                     {{ field.py_name }}: {% if field.zero == "None" and not field.type.startswith("Optional[") -%} | ||||
|                                             Optional[{{ field.type }}] | ||||
|                                          {%- else -%} | ||||
|                                             {{ field.type }} | ||||
|                                          {%- endif -%} = {{ field.zero }} | ||||
|                     {%- if not loop.last %}, {% endif -%} | ||||
|                 {%- endfor -%} | ||||
|             {%- endif -%} | ||||
|         {%- else -%} | ||||
|             {# Client streaming: need a request iterator instead #} | ||||
|             , request_iterator: Union[AsyncIterable["{{ method.input }}"], Iterable["{{ method.input }}"]] | ||||
|         {%- endif -%} | ||||
|             ) -> {% if method.server_streaming %}AsyncIterator[{{ method.output }}]{% else %}{{ method.output }}{% endif %}: | ||||
|         {% if method.comment %} | ||||
| {{ method.comment }} | ||||
|  | ||||
|         {% endif %} | ||||
|         {% if not method.client_streaming %} | ||||
|         request = {{ method.input }}() | ||||
|         {% for field in method.input_message.properties %} | ||||
|             {% if field.field_type == 'message' %} | ||||
| @@ -77,20 +94,41 @@ class {{ service.py_name }}Stub(betterproto.ServiceStub): | ||||
|         request.{{ field.py_name }} = {{ field.py_name }} | ||||
|             {% endif %} | ||||
|         {% endfor %} | ||||
|         {% endif %} | ||||
|  | ||||
|         {% if method.server_streaming %} | ||||
|             {% if method.client_streaming %} | ||||
|         async for response in self._stream_stream( | ||||
|             "{{ method.route }}", | ||||
|             request_iterator, | ||||
|             {{ method.input }}, | ||||
|             {{ method.output }}, | ||||
|         ): | ||||
|             yield response | ||||
|             {% else %}{# i.e. not client streaming #} | ||||
|         async for response in self._unary_stream( | ||||
|             "{{ method.route }}", | ||||
|             request, | ||||
|             {{ method.output }}, | ||||
|         ): | ||||
|             yield response | ||||
|         {% else %} | ||||
|  | ||||
|             {% endif %}{# if client streaming #} | ||||
|         {% else %}{# i.e. not server streaming #} | ||||
|             {% if method.client_streaming %} | ||||
|         return await self._stream_unary( | ||||
|             "{{ method.route }}", | ||||
|             request_iterator, | ||||
|             {{ method.input }}, | ||||
|             {{ method.output }} | ||||
|         ) | ||||
|             {% else %}{# i.e. not client streaming #} | ||||
|         return await self._unary_unary( | ||||
|             "{{ method.route }}", | ||||
|             request, | ||||
|             {{ method.output }}, | ||||
|             {{ method.output }} | ||||
|         ) | ||||
|             {% endif %}{# client streaming #} | ||||
|         {% endif %} | ||||
|  | ||||
|     {% endfor %} | ||||
|   | ||||
							
								
								
									
										114
									
								
								betterproto/tests/generate.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										114
									
								
								betterproto/tests/generate.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -1,6 +1,7 @@ | ||||
| #!/usr/bin/env python | ||||
| import glob | ||||
| import asyncio | ||||
| import os | ||||
| from pathlib import Path | ||||
| import shutil | ||||
| import subprocess | ||||
| import sys | ||||
| @@ -20,58 +21,63 @@ from betterproto.tests.util import ( | ||||
| os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python" | ||||
|  | ||||
|  | ||||
| def clear_directory(path: str): | ||||
|     for file_or_directory in glob.glob(os.path.join(path, "*")): | ||||
|         if os.path.isdir(file_or_directory): | ||||
| def clear_directory(dir_path: Path): | ||||
|     for file_or_directory in dir_path.glob("*"): | ||||
|         if file_or_directory.is_dir(): | ||||
|             shutil.rmtree(file_or_directory) | ||||
|         else: | ||||
|             os.remove(file_or_directory) | ||||
|             file_or_directory.unlink() | ||||
|  | ||||
|  | ||||
| def generate(whitelist: Set[str]): | ||||
|     path_whitelist = {os.path.realpath(e) for e in whitelist if os.path.exists(e)} | ||||
|     name_whitelist = {e for e in whitelist if not os.path.exists(e)} | ||||
| async def generate(whitelist: Set[str], verbose: bool): | ||||
|     test_case_names = set(get_directories(inputs_path)) - {"__pycache__"} | ||||
|  | ||||
|     test_case_names = set(get_directories(inputs_path)) | ||||
|  | ||||
|     failed_test_cases = [] | ||||
|     path_whitelist = set() | ||||
|     name_whitelist = set() | ||||
|     for item in whitelist: | ||||
|         if item in test_case_names: | ||||
|             name_whitelist.add(item) | ||||
|             continue | ||||
|         path_whitelist.add(item) | ||||
|  | ||||
|     generation_tasks = [] | ||||
|     for test_case_name in sorted(test_case_names): | ||||
|         test_case_input_path = os.path.realpath( | ||||
|             os.path.join(inputs_path, test_case_name) | ||||
|         ) | ||||
|  | ||||
|         test_case_input_path = inputs_path.joinpath(test_case_name).resolve() | ||||
|         if ( | ||||
|             whitelist | ||||
|             and test_case_input_path not in path_whitelist | ||||
|             and str(test_case_input_path) not in path_whitelist | ||||
|             and test_case_name not in name_whitelist | ||||
|         ): | ||||
|             continue | ||||
|         generation_tasks.append( | ||||
|             generate_test_case_output(test_case_input_path, test_case_name, verbose) | ||||
|         ) | ||||
|  | ||||
|         print(f"Generating output for {test_case_name}") | ||||
|         try: | ||||
|             generate_test_case_output(test_case_name, test_case_input_path) | ||||
|         except subprocess.CalledProcessError as e: | ||||
|     failed_test_cases = [] | ||||
|     # Wait for all subprocs and match any failures to names to report | ||||
|     for test_case_name, result in zip( | ||||
|         sorted(test_case_names), await asyncio.gather(*generation_tasks) | ||||
|     ): | ||||
|         if result != 0: | ||||
|             failed_test_cases.append(test_case_name) | ||||
|  | ||||
|     if failed_test_cases: | ||||
|         sys.stderr.write("\nFailed to generate the following test cases:\n") | ||||
|         sys.stderr.write( | ||||
|             "\n\033[31;1;4mFailed to generate the following test cases:\033[0m\n" | ||||
|         ) | ||||
|         for failed_test_case in failed_test_cases: | ||||
|             sys.stderr.write(f"- {failed_test_case}\n") | ||||
|  | ||||
|  | ||||
| def generate_test_case_output(test_case_name, test_case_input_path=None): | ||||
|     if not test_case_input_path: | ||||
|         test_case_input_path = os.path.realpath( | ||||
|             os.path.join(inputs_path, test_case_name) | ||||
|         ) | ||||
| async def generate_test_case_output( | ||||
|     test_case_input_path: Path, test_case_name: str, verbose: bool | ||||
| ) -> int: | ||||
|     """ | ||||
|     Returns the max of the subprocess return values | ||||
|     """ | ||||
|  | ||||
|     test_case_output_path_reference = os.path.join( | ||||
|         output_path_reference, test_case_name | ||||
|     ) | ||||
|     test_case_output_path_betterproto = os.path.join( | ||||
|         output_path_betterproto, test_case_name | ||||
|     ) | ||||
|     test_case_output_path_reference = output_path_reference.joinpath(test_case_name) | ||||
|     test_case_output_path_betterproto = output_path_betterproto.joinpath(test_case_name) | ||||
|  | ||||
|     os.makedirs(test_case_output_path_reference, exist_ok=True) | ||||
|     os.makedirs(test_case_output_path_betterproto, exist_ok=True) | ||||
| @@ -79,14 +85,36 @@ def generate_test_case_output(test_case_name, test_case_input_path=None): | ||||
|     clear_directory(test_case_output_path_reference) | ||||
|     clear_directory(test_case_output_path_betterproto) | ||||
|  | ||||
|     protoc_reference(test_case_input_path, test_case_output_path_reference) | ||||
|     protoc_plugin(test_case_input_path, test_case_output_path_betterproto) | ||||
|     ( | ||||
|         (ref_out, ref_err, ref_code), | ||||
|         (plg_out, plg_err, plg_code), | ||||
|     ) = await asyncio.gather( | ||||
|         protoc_reference(test_case_input_path, test_case_output_path_reference), | ||||
|         protoc_plugin(test_case_input_path, test_case_output_path_betterproto), | ||||
|     ) | ||||
|  | ||||
|     message = f"Generated output for {test_case_name!r}" | ||||
|     if verbose: | ||||
|         print(f"\033[31;1;4m{message}\033[0m") | ||||
|         if ref_out: | ||||
|             sys.stdout.buffer.write(ref_out) | ||||
|         if ref_err: | ||||
|             sys.stderr.buffer.write(ref_err) | ||||
|         if plg_out: | ||||
|             sys.stdout.buffer.write(plg_out) | ||||
|         if plg_err: | ||||
|             sys.stderr.buffer.write(plg_err) | ||||
|         sys.stdout.buffer.flush() | ||||
|         sys.stderr.buffer.flush() | ||||
|     else: | ||||
|         print(message) | ||||
|  | ||||
|     return max(ref_code, plg_code) | ||||
|  | ||||
|  | ||||
| HELP = "\n".join( | ||||
|     [ | ||||
|         "Usage: python generate.py", | ||||
|         "       python generate.py [DIRECTORIES or NAMES]", | ||||
|     ( | ||||
|         "Usage: python generate.py [-h] [-v] [DIRECTORIES or NAMES]", | ||||
|         "Generate python classes for standard tests.", | ||||
|         "", | ||||
|         "DIRECTORIES    One or more relative or absolute directories of test-cases to generate classes for.", | ||||
| @@ -94,7 +122,7 @@ HELP = "\n".join( | ||||
|         "", | ||||
|         "NAMES          One or more test-case names to generate classes for.", | ||||
|         "               python generate.py bool double enums", | ||||
|     ] | ||||
|     ) | ||||
| ) | ||||
|  | ||||
|  | ||||
| @@ -102,9 +130,13 @@ def main(): | ||||
|     if set(sys.argv).intersection({"-h", "--help"}): | ||||
|         print(HELP) | ||||
|         return | ||||
|     whitelist = set(sys.argv[1:]) | ||||
|  | ||||
|     generate(whitelist) | ||||
|     if sys.argv[1:2] == ["-v"]: | ||||
|         verbose = True | ||||
|         whitelist = set(sys.argv[2:]) | ||||
|     else: | ||||
|         verbose = False | ||||
|         whitelist = set(sys.argv[1:]) | ||||
|     asyncio.get_event_loop().run_until_complete(generate(whitelist, verbose)) | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|   | ||||
							
								
								
									
										0
									
								
								betterproto/tests/grpc/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								betterproto/tests/grpc/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										154
									
								
								betterproto/tests/grpc/test_grpclib_client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								betterproto/tests/grpc/test_grpclib_client.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | ||||
| import asyncio | ||||
| from betterproto.tests.output_betterproto.service.service import ( | ||||
|     DoThingResponse, | ||||
|     DoThingRequest, | ||||
|     GetThingRequest, | ||||
|     GetThingResponse, | ||||
|     TestStub as ThingServiceClient, | ||||
| ) | ||||
| import grpclib | ||||
| from grpclib.testing import ChannelFor | ||||
| import pytest | ||||
| from betterproto.grpc.util.async_channel import AsyncChannel | ||||
| from .thing_service import ThingService | ||||
|  | ||||
|  | ||||
| async def _test_client(client, name="clean room", **kwargs): | ||||
|     response = await client.do_thing(name=name) | ||||
|     assert response.names == [name] | ||||
|  | ||||
|  | ||||
| def _assert_request_meta_recieved(deadline, metadata): | ||||
|     def server_side_test(stream): | ||||
|         assert stream.deadline._timestamp == pytest.approx( | ||||
|             deadline._timestamp, 1 | ||||
|         ), "The provided deadline should be recieved serverside" | ||||
|         assert ( | ||||
|             stream.metadata["authorization"] == metadata["authorization"] | ||||
|         ), "The provided authorization metadata should be recieved serverside" | ||||
|  | ||||
|     return server_side_test | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_simple_service_call(): | ||||
|     async with ChannelFor([ThingService()]) as channel: | ||||
|         await _test_client(ThingServiceClient(channel)) | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_service_call_with_upfront_request_params(): | ||||
|     # Setting deadline | ||||
|     deadline = grpclib.metadata.Deadline.from_timeout(22) | ||||
|     metadata = {"authorization": "12345"} | ||||
|     async with ChannelFor( | ||||
|         [ThingService(test_hook=_assert_request_meta_recieved(deadline, metadata),)] | ||||
|     ) as channel: | ||||
|         await _test_client( | ||||
|             ThingServiceClient(channel, deadline=deadline, metadata=metadata) | ||||
|         ) | ||||
|  | ||||
|     # Setting timeout | ||||
|     timeout = 99 | ||||
|     deadline = grpclib.metadata.Deadline.from_timeout(timeout) | ||||
|     metadata = {"authorization": "12345"} | ||||
|     async with ChannelFor( | ||||
|         [ThingService(test_hook=_assert_request_meta_recieved(deadline, metadata),)] | ||||
|     ) as channel: | ||||
|         await _test_client( | ||||
|             ThingServiceClient(channel, timeout=timeout, metadata=metadata) | ||||
|         ) | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_service_call_lower_level_with_overrides(): | ||||
|     THING_TO_DO = "get milk" | ||||
|  | ||||
|     # Setting deadline | ||||
|     deadline = grpclib.metadata.Deadline.from_timeout(22) | ||||
|     metadata = {"authorization": "12345"} | ||||
|     kwarg_deadline = grpclib.metadata.Deadline.from_timeout(28) | ||||
|     kwarg_metadata = {"authorization": "12345"} | ||||
|     async with ChannelFor( | ||||
|         [ThingService(test_hook=_assert_request_meta_recieved(deadline, metadata),)] | ||||
|     ) as channel: | ||||
|         client = ThingServiceClient(channel, deadline=deadline, metadata=metadata) | ||||
|         response = await client._unary_unary( | ||||
|             "/service.Test/DoThing", | ||||
|             DoThingRequest(THING_TO_DO), | ||||
|             DoThingResponse, | ||||
|             deadline=kwarg_deadline, | ||||
|             metadata=kwarg_metadata, | ||||
|         ) | ||||
|         assert response.names == [THING_TO_DO] | ||||
|  | ||||
|     # Setting timeout | ||||
|     timeout = 99 | ||||
|     deadline = grpclib.metadata.Deadline.from_timeout(timeout) | ||||
|     metadata = {"authorization": "12345"} | ||||
|     kwarg_timeout = 9000 | ||||
|     kwarg_deadline = grpclib.metadata.Deadline.from_timeout(kwarg_timeout) | ||||
|     kwarg_metadata = {"authorization": "09876"} | ||||
|     async with ChannelFor( | ||||
|         [ | ||||
|             ThingService( | ||||
|                 test_hook=_assert_request_meta_recieved(kwarg_deadline, kwarg_metadata), | ||||
|             ) | ||||
|         ] | ||||
|     ) as channel: | ||||
|         client = ThingServiceClient(channel, deadline=deadline, metadata=metadata) | ||||
|         response = await client._unary_unary( | ||||
|             "/service.Test/DoThing", | ||||
|             DoThingRequest(THING_TO_DO), | ||||
|             DoThingResponse, | ||||
|             timeout=kwarg_timeout, | ||||
|             metadata=kwarg_metadata, | ||||
|         ) | ||||
|         assert response.names == [THING_TO_DO] | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_async_gen_for_unary_stream_request(): | ||||
|     thing_name = "my milkshakes" | ||||
|  | ||||
|     async with ChannelFor([ThingService()]) as channel: | ||||
|         client = ThingServiceClient(channel) | ||||
|         expected_versions = [5, 4, 3, 2, 1] | ||||
|         async for response in client.get_thing_versions(name=thing_name): | ||||
|             assert response.name == thing_name | ||||
|             assert response.version == expected_versions.pop() | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_async_gen_for_stream_stream_request(): | ||||
|     some_things = ["cake", "cricket", "coral reef"] | ||||
|     more_things = ["ball", "that", "56kmodem", "liberal humanism", "cheesesticks"] | ||||
|     expected_things = (*some_things, *more_things) | ||||
|  | ||||
|     async with ChannelFor([ThingService()]) as channel: | ||||
|         client = ThingServiceClient(channel) | ||||
|         # Use an AsyncChannel to decouple sending and recieving, it'll send some_things | ||||
|         # immediately and we'll use it to send more_things later, after recieving some | ||||
|         # results | ||||
|         request_chan = AsyncChannel() | ||||
|         send_initial_requests = asyncio.ensure_future( | ||||
|             request_chan.send_from(GetThingRequest(name) for name in some_things) | ||||
|         ) | ||||
|         response_index = 0 | ||||
|         async for response in client.get_different_things(request_chan): | ||||
|             assert response.name == expected_things[response_index] | ||||
|             assert response.version == response_index + 1 | ||||
|             response_index += 1 | ||||
|             if more_things: | ||||
|                 # Send some more requests as we recieve reponses to be sure coordination of | ||||
|                 # send/recieve events doesn't matter | ||||
|                 await request_chan.send(GetThingRequest(more_things.pop(0))) | ||||
|             elif not send_initial_requests.done(): | ||||
|                 # Make sure the sending task it completed | ||||
|                 await send_initial_requests | ||||
|             else: | ||||
|                 # No more things to send make sure channel is closed | ||||
|                 request_chan.close() | ||||
|         assert response_index == len( | ||||
|             expected_things | ||||
|         ), "Didn't recieve all exptected responses" | ||||
							
								
								
									
										100
									
								
								betterproto/tests/grpc/test_stream_stream.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								betterproto/tests/grpc/test_stream_stream.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| import asyncio | ||||
| import betterproto | ||||
| from betterproto.grpc.util.async_channel import AsyncChannel | ||||
| from dataclasses import dataclass | ||||
| import pytest | ||||
| from typing import AsyncIterator | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| class Message(betterproto.Message): | ||||
|     body: str = betterproto.string_field(1) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def expected_responses(): | ||||
|     return [Message("Hello world 1"), Message("Hello world 2"), Message("Done")] | ||||
|  | ||||
|  | ||||
| class ClientStub: | ||||
|     async def connect(self, requests: AsyncIterator): | ||||
|         await asyncio.sleep(0.1) | ||||
|         async for request in requests: | ||||
|             await asyncio.sleep(0.1) | ||||
|             yield request | ||||
|         await asyncio.sleep(0.1) | ||||
|         yield Message("Done") | ||||
|  | ||||
|  | ||||
| async def to_list(generator: AsyncIterator): | ||||
|     result = [] | ||||
|     async for value in generator: | ||||
|         result.append(value) | ||||
|     return result | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def client(): | ||||
|     # channel = Channel(host='127.0.0.1', port=50051) | ||||
|     # return ClientStub(channel) | ||||
|     return ClientStub() | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_send_from_before_connect_and_close_automatically( | ||||
|     client, expected_responses | ||||
| ): | ||||
|     requests = AsyncChannel() | ||||
|     await requests.send_from( | ||||
|         [Message(body="Hello world 1"), Message(body="Hello world 2")], close=True | ||||
|     ) | ||||
|     responses = client.connect(requests) | ||||
|  | ||||
|     assert await to_list(responses) == expected_responses | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_send_from_after_connect_and_close_automatically( | ||||
|     client, expected_responses | ||||
| ): | ||||
|     requests = AsyncChannel() | ||||
|     responses = client.connect(requests) | ||||
|     await requests.send_from( | ||||
|         [Message(body="Hello world 1"), Message(body="Hello world 2")], close=True | ||||
|     ) | ||||
|  | ||||
|     assert await to_list(responses) == expected_responses | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_send_from_close_manually_immediately(client, expected_responses): | ||||
|     requests = AsyncChannel() | ||||
|     responses = client.connect(requests) | ||||
|     await requests.send_from( | ||||
|         [Message(body="Hello world 1"), Message(body="Hello world 2")], close=False | ||||
|     ) | ||||
|     requests.close() | ||||
|  | ||||
|     assert await to_list(responses) == expected_responses | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_send_individually_and_close_before_connect(client, expected_responses): | ||||
|     requests = AsyncChannel() | ||||
|     await requests.send(Message(body="Hello world 1")) | ||||
|     await requests.send(Message(body="Hello world 2")) | ||||
|     requests.close() | ||||
|     responses = client.connect(requests) | ||||
|  | ||||
|     assert await to_list(responses) == expected_responses | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_send_individually_and_close_after_connect(client, expected_responses): | ||||
|     requests = AsyncChannel() | ||||
|     await requests.send(Message(body="Hello world 1")) | ||||
|     await requests.send(Message(body="Hello world 2")) | ||||
|     responses = client.connect(requests) | ||||
|     requests.close() | ||||
|  | ||||
|     assert await to_list(responses) == expected_responses | ||||
							
								
								
									
										83
									
								
								betterproto/tests/grpc/thing_service.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								betterproto/tests/grpc/thing_service.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| from betterproto.tests.output_betterproto.service.service import ( | ||||
|     DoThingResponse, | ||||
|     DoThingRequest, | ||||
|     GetThingRequest, | ||||
|     GetThingResponse, | ||||
|     TestStub as ThingServiceClient, | ||||
| ) | ||||
| import grpclib | ||||
| from typing import Any, Dict | ||||
|  | ||||
|  | ||||
| class ThingService: | ||||
|     def __init__(self, test_hook=None): | ||||
|         # This lets us pass assertions to the servicer ;) | ||||
|         self.test_hook = test_hook | ||||
|  | ||||
|     async def do_thing( | ||||
|         self, stream: "grpclib.server.Stream[DoThingRequest, DoThingResponse]" | ||||
|     ): | ||||
|         request = await stream.recv_message() | ||||
|         if self.test_hook is not None: | ||||
|             self.test_hook(stream) | ||||
|         await stream.send_message(DoThingResponse([request.name])) | ||||
|  | ||||
|     async def do_many_things( | ||||
|         self, stream: "grpclib.server.Stream[DoThingRequest, DoThingResponse]" | ||||
|     ): | ||||
|         thing_names = [request.name for request in stream] | ||||
|         if self.test_hook is not None: | ||||
|             self.test_hook(stream) | ||||
|         await stream.send_message(DoThingResponse(thing_names)) | ||||
|  | ||||
|     async def get_thing_versions( | ||||
|         self, stream: "grpclib.server.Stream[GetThingRequest, GetThingResponse]" | ||||
|     ): | ||||
|         request = await stream.recv_message() | ||||
|         if self.test_hook is not None: | ||||
|             self.test_hook(stream) | ||||
|         for version_num in range(1, 6): | ||||
|             await stream.send_message( | ||||
|                 GetThingResponse(name=request.name, version=version_num) | ||||
|             ) | ||||
|  | ||||
|     async def get_different_things( | ||||
|         self, stream: "grpclib.server.Stream[GetThingRequest, GetThingResponse]" | ||||
|     ): | ||||
|         if self.test_hook is not None: | ||||
|             self.test_hook(stream) | ||||
|         #  Respond to each input item immediately | ||||
|         response_num = 0 | ||||
|         async for request in stream: | ||||
|             response_num += 1 | ||||
|             await stream.send_message( | ||||
|                 GetThingResponse(name=request.name, version=response_num) | ||||
|             ) | ||||
|  | ||||
|     def __mapping__(self) -> Dict[str, "grpclib.const.Handler"]: | ||||
|         return { | ||||
|             "/service.Test/DoThing": grpclib.const.Handler( | ||||
|                 self.do_thing, | ||||
|                 grpclib.const.Cardinality.UNARY_UNARY, | ||||
|                 DoThingRequest, | ||||
|                 DoThingResponse, | ||||
|             ), | ||||
|             "/service.Test/DoManyThings": grpclib.const.Handler( | ||||
|                 self.do_many_things, | ||||
|                 grpclib.const.Cardinality.STREAM_UNARY, | ||||
|                 DoThingRequest, | ||||
|                 DoThingResponse, | ||||
|             ), | ||||
|             "/service.Test/GetThingVersions": grpclib.const.Handler( | ||||
|                 self.get_thing_versions, | ||||
|                 grpclib.const.Cardinality.UNARY_STREAM, | ||||
|                 GetThingRequest, | ||||
|                 GetThingResponse, | ||||
|             ), | ||||
|             "/service.Test/GetDifferentThings": grpclib.const.Handler( | ||||
|                 self.get_different_things, | ||||
|                 grpclib.const.Cardinality.STREAM_STREAM, | ||||
|                 GetThingRequest, | ||||
|                 GetThingResponse, | ||||
|             ), | ||||
|         } | ||||
							
								
								
									
										6
									
								
								betterproto/tests/inputs/fixed/fixed.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								betterproto/tests/inputs/fixed/fixed.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| { | ||||
|   "foo": 4294967295, | ||||
|   "bar": -2147483648, | ||||
|   "baz": "18446744073709551615", | ||||
|   "qux": "-9223372036854775808" | ||||
| } | ||||
							
								
								
									
										8
									
								
								betterproto/tests/inputs/fixed/fixed.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								betterproto/tests/inputs/fixed/fixed.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| syntax = "proto3"; | ||||
|  | ||||
| message Test { | ||||
|   fixed32 foo = 1; | ||||
|   sfixed32 bar = 2; | ||||
|   fixed64 baz = 3; | ||||
|   sfixed64 qux = 4; | ||||
| } | ||||
| @@ -23,7 +23,7 @@ test_cases = [ | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| @pytest.mark.parametrize(["service_method", "wrapper_class", "value"], test_cases) | ||||
| async def test_channel_receives_wrapped_type( | ||||
| async def test_channel_recieves_wrapped_type( | ||||
|     service_method: Callable[[TestStub], Any], wrapper_class: Callable, value | ||||
| ): | ||||
|     wrapped_value = wrapper_class() | ||||
|   | ||||
| @@ -3,13 +3,25 @@ syntax = "proto3"; | ||||
| package service; | ||||
|  | ||||
| message DoThingRequest { | ||||
|   int32 iterations = 1; | ||||
|   string name = 1; | ||||
| } | ||||
|  | ||||
| message DoThingResponse { | ||||
|   int32 successfulIterations = 1; | ||||
|   repeated string names = 1; | ||||
| } | ||||
|  | ||||
| message GetThingRequest { | ||||
|   string name = 1; | ||||
| } | ||||
|  | ||||
| message GetThingResponse { | ||||
|   string name = 1; | ||||
|   int32 version = 2; | ||||
| } | ||||
|  | ||||
| service Test { | ||||
|   rpc DoThing (DoThingRequest) returns (DoThingResponse); | ||||
|   rpc DoManyThings (stream DoThingRequest) returns (DoThingResponse); | ||||
|   rpc GetThingVersions (GetThingRequest) returns (stream GetThingResponse); | ||||
|   rpc GetDifferentThings (stream GetThingRequest) returns (stream GetThingResponse); | ||||
| } | ||||
|   | ||||
| @@ -1,132 +0,0 @@ | ||||
| import betterproto | ||||
| import grpclib | ||||
| from grpclib.testing import ChannelFor | ||||
| import pytest | ||||
| from typing import Dict | ||||
|  | ||||
| from betterproto.tests.output_betterproto.service.service import ( | ||||
|     DoThingResponse, | ||||
|     DoThingRequest, | ||||
|     TestStub as ExampleServiceStub, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class ExampleService: | ||||
|     def __init__(self, test_hook=None): | ||||
|         # This lets us pass assertions to the servicer ;) | ||||
|         self.test_hook = test_hook | ||||
|  | ||||
|     async def DoThing( | ||||
|         self, stream: "grpclib.server.Stream[DoThingRequest, DoThingResponse]" | ||||
|     ): | ||||
|         request = await stream.recv_message() | ||||
|         print("self.test_hook", self.test_hook) | ||||
|         if self.test_hook is not None: | ||||
|             self.test_hook(stream) | ||||
|         for iteration in range(request.iterations): | ||||
|             pass | ||||
|         await stream.send_message(DoThingResponse(request.iterations)) | ||||
|  | ||||
|     def __mapping__(self) -> Dict[str, grpclib.const.Handler]: | ||||
|         return { | ||||
|             "/service.Test/DoThing": grpclib.const.Handler( | ||||
|                 self.DoThing, | ||||
|                 grpclib.const.Cardinality.UNARY_UNARY, | ||||
|                 DoThingRequest, | ||||
|                 DoThingResponse, | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|  | ||||
| async def _test_stub(stub, iterations=42, **kwargs): | ||||
|     response = await stub.do_thing(iterations=iterations) | ||||
|     assert response.successful_iterations == iterations | ||||
|  | ||||
|  | ||||
| def _get_server_side_test(deadline, metadata): | ||||
|     def server_side_test(stream): | ||||
|         assert stream.deadline._timestamp == pytest.approx( | ||||
|             deadline._timestamp, 1 | ||||
|         ), "The provided deadline should be recieved serverside" | ||||
|         assert ( | ||||
|             stream.metadata["authorization"] == metadata["authorization"] | ||||
|         ), "The provided authorization metadata should be recieved serverside" | ||||
|  | ||||
|     return server_side_test | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_simple_service_call(): | ||||
|     async with ChannelFor([ExampleService()]) as channel: | ||||
|         await _test_stub(ExampleServiceStub(channel)) | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_service_call_with_upfront_request_params(): | ||||
|     # Setting deadline | ||||
|     deadline = grpclib.metadata.Deadline.from_timeout(22) | ||||
|     metadata = {"authorization": "12345"} | ||||
|     async with ChannelFor( | ||||
|         [ExampleService(test_hook=_get_server_side_test(deadline, metadata))] | ||||
|     ) as channel: | ||||
|         await _test_stub( | ||||
|             ExampleServiceStub(channel, deadline=deadline, metadata=metadata) | ||||
|         ) | ||||
|  | ||||
|     # Setting timeout | ||||
|     timeout = 99 | ||||
|     deadline = grpclib.metadata.Deadline.from_timeout(timeout) | ||||
|     metadata = {"authorization": "12345"} | ||||
|     async with ChannelFor( | ||||
|         [ExampleService(test_hook=_get_server_side_test(deadline, metadata))] | ||||
|     ) as channel: | ||||
|         await _test_stub( | ||||
|             ExampleServiceStub(channel, timeout=timeout, metadata=metadata) | ||||
|         ) | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_service_call_lower_level_with_overrides(): | ||||
|     ITERATIONS = 99 | ||||
|  | ||||
|     # Setting deadline | ||||
|     deadline = grpclib.metadata.Deadline.from_timeout(22) | ||||
|     metadata = {"authorization": "12345"} | ||||
|     kwarg_deadline = grpclib.metadata.Deadline.from_timeout(28) | ||||
|     kwarg_metadata = {"authorization": "12345"} | ||||
|     async with ChannelFor( | ||||
|         [ExampleService(test_hook=_get_server_side_test(deadline, metadata))] | ||||
|     ) as channel: | ||||
|         stub = ExampleServiceStub(channel, deadline=deadline, metadata=metadata) | ||||
|         response = await stub._unary_unary( | ||||
|             "/service.Test/DoThing", | ||||
|             DoThingRequest(ITERATIONS), | ||||
|             DoThingResponse, | ||||
|             deadline=kwarg_deadline, | ||||
|             metadata=kwarg_metadata, | ||||
|         ) | ||||
|         assert response.successful_iterations == ITERATIONS | ||||
|  | ||||
|     # Setting timeout | ||||
|     timeout = 99 | ||||
|     deadline = grpclib.metadata.Deadline.from_timeout(timeout) | ||||
|     metadata = {"authorization": "12345"} | ||||
|     kwarg_timeout = 9000 | ||||
|     kwarg_deadline = grpclib.metadata.Deadline.from_timeout(kwarg_timeout) | ||||
|     kwarg_metadata = {"authorization": "09876"} | ||||
|     async with ChannelFor( | ||||
|         [ | ||||
|             ExampleService( | ||||
|                 test_hook=_get_server_side_test(kwarg_deadline, kwarg_metadata) | ||||
|             ) | ||||
|         ] | ||||
|     ) as channel: | ||||
|         stub = ExampleServiceStub(channel, deadline=deadline, metadata=metadata) | ||||
|         response = await stub._unary_unary( | ||||
|             "/service.Test/DoThing", | ||||
|             DoThingRequest(ITERATIONS), | ||||
|             DoThingResponse, | ||||
|             timeout=kwarg_timeout, | ||||
|             metadata=kwarg_metadata, | ||||
|         ) | ||||
|         assert response.successful_iterations == ITERATIONS | ||||
| @@ -23,9 +23,9 @@ from google.protobuf.json_format import Parse | ||||
|  | ||||
| class TestCases: | ||||
|     def __init__(self, path, services: Set[str], xfail: Set[str]): | ||||
|         _all = set(get_directories(path)) | ||||
|         _all = set(get_directories(path)) - {"__pycache__"} | ||||
|         _services = services | ||||
|         _messages = _all - services | ||||
|         _messages = (_all - services) - {"__pycache__"} | ||||
|         _messages_with_json = { | ||||
|             test for test in _messages if get_test_case_json_data(test) | ||||
|         } | ||||
|   | ||||
| @@ -1,23 +1,24 @@ | ||||
| import asyncio | ||||
| import os | ||||
| import subprocess | ||||
| from typing import Generator | ||||
| from pathlib import Path | ||||
| from typing import Generator, IO, Optional | ||||
|  | ||||
| os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python" | ||||
|  | ||||
| root_path = os.path.dirname(os.path.realpath(__file__)) | ||||
| inputs_path = os.path.join(root_path, "inputs") | ||||
| output_path_reference = os.path.join(root_path, "output_reference") | ||||
| output_path_betterproto = os.path.join(root_path, "output_betterproto") | ||||
| root_path = Path(__file__).resolve().parent | ||||
| inputs_path = root_path.joinpath("inputs") | ||||
| output_path_reference = root_path.joinpath("output_reference") | ||||
| output_path_betterproto = root_path.joinpath("output_betterproto") | ||||
|  | ||||
| if os.name == "nt": | ||||
|     plugin_path = os.path.join(root_path, "..", "plugin.bat") | ||||
|     plugin_path = root_path.joinpath("..", "plugin.bat").resolve() | ||||
| else: | ||||
|     plugin_path = os.path.join(root_path, "..", "plugin.py") | ||||
|     plugin_path = root_path.joinpath("..", "plugin.py").resolve() | ||||
|  | ||||
|  | ||||
| def get_files(path, end: str) -> Generator[str, None, None]: | ||||
| def get_files(path, suffix: str) -> Generator[str, None, None]: | ||||
|     for r, dirs, files in os.walk(path): | ||||
|         for filename in [f for f in files if f.endswith(end)]: | ||||
|         for filename in [f for f in files if f.endswith(suffix)]: | ||||
|             yield os.path.join(r, filename) | ||||
|  | ||||
|  | ||||
| @@ -27,36 +28,30 @@ def get_directories(path): | ||||
|             yield directory | ||||
|  | ||||
|  | ||||
| def relative(file: str, path: str): | ||||
|     return os.path.join(os.path.dirname(file), path) | ||||
|  | ||||
|  | ||||
| def read_relative(file: str, path: str): | ||||
|     with open(relative(file, path)) as fh: | ||||
|         return fh.read() | ||||
|  | ||||
|  | ||||
| def protoc_plugin(path: str, output_dir: str) -> subprocess.CompletedProcess: | ||||
|     return subprocess.run( | ||||
| async def protoc_plugin(path: str, output_dir: str): | ||||
|     proc = await asyncio.create_subprocess_shell( | ||||
|         f"protoc --plugin=protoc-gen-custom={plugin_path} --custom_out={output_dir} --proto_path={path} {path}/*.proto", | ||||
|         shell=True, | ||||
|         check=True, | ||||
|         stdout=asyncio.subprocess.PIPE, | ||||
|         stderr=asyncio.subprocess.PIPE, | ||||
|     ) | ||||
|     return (*(await proc.communicate()), proc.returncode) | ||||
|  | ||||
|  | ||||
| def protoc_reference(path: str, output_dir: str): | ||||
|     subprocess.run( | ||||
| async def protoc_reference(path: str, output_dir: str): | ||||
|     proc = await asyncio.create_subprocess_shell( | ||||
|         f"protoc --python_out={output_dir} --proto_path={path} {path}/*.proto", | ||||
|         shell=True, | ||||
|         stdout=asyncio.subprocess.PIPE, | ||||
|         stderr=asyncio.subprocess.PIPE, | ||||
|     ) | ||||
|     return (*(await proc.communicate()), proc.returncode) | ||||
|  | ||||
|  | ||||
| def get_test_case_json_data(test_case_name, json_file_name=None): | ||||
| def get_test_case_json_data(test_case_name: str, json_file_name: Optional[str] = None): | ||||
|     test_data_file_name = json_file_name if json_file_name else f"{test_case_name}.json" | ||||
|     test_data_file_path = os.path.join(inputs_path, test_case_name, test_data_file_name) | ||||
|     test_data_file_path = inputs_path.joinpath(test_case_name, test_data_file_name) | ||||
|  | ||||
|     if not os.path.exists(test_data_file_path): | ||||
|     if not test_data_file_path.exists(): | ||||
|         return None | ||||
|  | ||||
|     with open(test_data_file_path) as fh: | ||||
|     with test_data_file_path.open("r") as fh: | ||||
|         return fh.read() | ||||
|   | ||||
							
								
								
									
										977
									
								
								poetry.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										977
									
								
								poetry.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,977 @@ | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." | ||||
| name = "appdirs" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "1.4.4" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Atomic file writes." | ||||
| marker = "sys_platform == \"win32\"" | ||||
| name = "atomicwrites" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
| version = "1.4.0" | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "Classes Without Boilerplate" | ||||
| name = "attrs" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
| version = "19.3.0" | ||||
|  | ||||
| [package.extras] | ||||
| azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] | ||||
| dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] | ||||
| docs = ["sphinx", "zope.interface"] | ||||
| tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "Backport of Python 3.7's datetime.fromisoformat" | ||||
| marker = "python_version < \"3.7\"" | ||||
| name = "backports-datetime-fromisoformat" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "1.0.0" | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "The uncompromising code formatter." | ||||
| name = "black" | ||||
| optional = false | ||||
| python-versions = ">=3.6" | ||||
| version = "19.10b0" | ||||
|  | ||||
| [package.dependencies] | ||||
| appdirs = "*" | ||||
| attrs = ">=18.1.0" | ||||
| click = ">=6.5" | ||||
| pathspec = ">=0.6,<1" | ||||
| regex = "*" | ||||
| toml = ">=0.9.4" | ||||
| typed-ast = ">=1.4.0" | ||||
|  | ||||
| [package.extras] | ||||
| d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "A thin, practical wrapper around terminal coloring, styling, and positioning" | ||||
| name = "blessings" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
| version = "1.7" | ||||
|  | ||||
| [package.dependencies] | ||||
| six = "*" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Fancy Interface to the Python Interpreter" | ||||
| name = "bpython" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "0.19" | ||||
|  | ||||
| [package.dependencies] | ||||
| curtsies = ">=0.1.18" | ||||
| greenlet = "*" | ||||
| pygments = "*" | ||||
| requests = "*" | ||||
| six = ">=1.5" | ||||
|  | ||||
| [package.extras] | ||||
| jedi = ["jedi"] | ||||
| urwid = ["urwid"] | ||||
| watch = ["watchdog"] | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Python package for providing Mozilla's CA Bundle." | ||||
| name = "certifi" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "2020.6.20" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Universal encoding detector for Python 2 and 3" | ||||
| name = "chardet" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "3.0.4" | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "Composable command line interface toolkit" | ||||
| name = "click" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" | ||||
| version = "7.1.2" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Cross-platform colored terminal text." | ||||
| marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" | ||||
| name = "colorama" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" | ||||
| version = "0.4.3" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Code coverage measurement for Python" | ||||
| name = "coverage" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" | ||||
| version = "5.1" | ||||
|  | ||||
| [package.extras] | ||||
| toml = ["toml"] | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Curses-like terminal wrapper, with colored strings!" | ||||
| name = "curtsies" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "0.3.1" | ||||
|  | ||||
| [package.dependencies] | ||||
| blessings = ">=1.5" | ||||
| wcwidth = ">=0.1.4" | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "A backport of the dataclasses module for Python 3.6" | ||||
| marker = "python_version >= \"3.6\" and python_version < \"3.7\" or python_version < \"3.7\"" | ||||
| name = "dataclasses" | ||||
| optional = false | ||||
| python-versions = ">=3.6, <3.7" | ||||
| version = "0.7" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Distribution utilities" | ||||
| name = "distlib" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "0.3.0" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "A platform independent file lock." | ||||
| name = "filelock" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "3.0.12" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Lightweight in-process concurrent programming" | ||||
| name = "greenlet" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "0.4.16" | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "Pure-Python gRPC implementation for asyncio" | ||||
| name = "grpclib" | ||||
| optional = false | ||||
| python-versions = ">=3.6" | ||||
| version = "0.3.2" | ||||
|  | ||||
| [package.dependencies] | ||||
| h2 = "*" | ||||
| multidict = "*" | ||||
|  | ||||
| [package.dependencies.dataclasses] | ||||
| python = "<3.7" | ||||
| version = "*" | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "HTTP/2 State-Machine based protocol implementation" | ||||
| name = "h2" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "3.2.0" | ||||
|  | ||||
| [package.dependencies] | ||||
| hpack = ">=3.0,<4" | ||||
| hyperframe = ">=5.2.0,<6" | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "Pure-Python HPACK header compression" | ||||
| name = "hpack" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "3.0.0" | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "HTTP/2 framing layer for Python" | ||||
| name = "hyperframe" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "5.2.0" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Internationalized Domain Names in Applications (IDNA)" | ||||
| name = "idna" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
| version = "2.9" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Read metadata from Python packages" | ||||
| marker = "python_version < \"3.8\"" | ||||
| name = "importlib-metadata" | ||||
| optional = false | ||||
| python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" | ||||
| version = "1.6.1" | ||||
|  | ||||
| [package.dependencies] | ||||
| zipp = ">=0.5" | ||||
|  | ||||
| [package.extras] | ||||
| docs = ["sphinx", "rst.linker"] | ||||
| testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Read resources from Python packages" | ||||
| marker = "python_version < \"3.7\"" | ||||
| name = "importlib-resources" | ||||
| optional = false | ||||
| python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" | ||||
| version = "2.0.1" | ||||
|  | ||||
| [package.dependencies] | ||||
| [package.dependencies.importlib-metadata] | ||||
| python = "<3.8" | ||||
| version = "*" | ||||
|  | ||||
| [package.dependencies.zipp] | ||||
| python = "<3.8" | ||||
| version = ">=0.4" | ||||
|  | ||||
| [package.extras] | ||||
| docs = ["sphinx", "rst.linker", "jaraco.packaging"] | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "A very fast and expressive template engine." | ||||
| name = "jinja2" | ||||
| optional = true | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" | ||||
| version = "2.11.2" | ||||
|  | ||||
| [package.dependencies] | ||||
| MarkupSafe = ">=0.23" | ||||
|  | ||||
| [package.extras] | ||||
| i18n = ["Babel (>=0.8)"] | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "Safely add untrusted strings to HTML/XML markup." | ||||
| name = "markupsafe" | ||||
| optional = true | ||||
| python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" | ||||
| version = "1.1.1" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "More routines for operating on iterables, beyond itertools" | ||||
| name = "more-itertools" | ||||
| optional = false | ||||
| python-versions = ">=3.5" | ||||
| version = "8.4.0" | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "multidict implementation" | ||||
| name = "multidict" | ||||
| optional = false | ||||
| python-versions = ">=3.5" | ||||
| version = "4.7.6" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Optional static typing for Python" | ||||
| name = "mypy" | ||||
| optional = false | ||||
| python-versions = ">=3.5" | ||||
| version = "0.770" | ||||
|  | ||||
| [package.dependencies] | ||||
| mypy-extensions = ">=0.4.3,<0.5.0" | ||||
| typed-ast = ">=1.4.0,<1.5.0" | ||||
| typing-extensions = ">=3.7.4" | ||||
|  | ||||
| [package.extras] | ||||
| dmypy = ["psutil (>=4.0)"] | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Experimental type system extensions for programs checked with the mypy typechecker." | ||||
| name = "mypy-extensions" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "0.4.3" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Core utilities for Python packages" | ||||
| name = "packaging" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
| version = "20.4" | ||||
|  | ||||
| [package.dependencies] | ||||
| pyparsing = ">=2.0.2" | ||||
| six = "*" | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "Utility library for gitignore style pattern matching of file paths." | ||||
| name = "pathspec" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" | ||||
| version = "0.8.0" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "plugin and hook calling mechanisms for python" | ||||
| name = "pluggy" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
| version = "0.13.1" | ||||
|  | ||||
| [package.dependencies] | ||||
| [package.dependencies.importlib-metadata] | ||||
| python = "<3.8" | ||||
| version = ">=0.12" | ||||
|  | ||||
| [package.extras] | ||||
| dev = ["pre-commit", "tox"] | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "Protocol Buffers" | ||||
| name = "protobuf" | ||||
| optional = true | ||||
| python-versions = "*" | ||||
| version = "3.12.2" | ||||
|  | ||||
| [package.dependencies] | ||||
| setuptools = "*" | ||||
| six = ">=1.9" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "library with cross-python path, ini-parsing, io, code, log facilities" | ||||
| name = "py" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
| version = "1.8.2" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Pygments is a syntax highlighting package written in Python." | ||||
| name = "pygments" | ||||
| optional = false | ||||
| python-versions = ">=3.5" | ||||
| version = "2.6.1" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Python parsing module" | ||||
| name = "pyparsing" | ||||
| optional = false | ||||
| python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" | ||||
| version = "2.4.7" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "pytest: simple powerful testing with Python" | ||||
| name = "pytest" | ||||
| optional = false | ||||
| python-versions = ">=3.5" | ||||
| version = "5.4.3" | ||||
|  | ||||
| [package.dependencies] | ||||
| atomicwrites = ">=1.0" | ||||
| attrs = ">=17.4.0" | ||||
| colorama = "*" | ||||
| more-itertools = ">=4.0.0" | ||||
| packaging = "*" | ||||
| pluggy = ">=0.12,<1.0" | ||||
| py = ">=1.5.0" | ||||
| wcwidth = "*" | ||||
|  | ||||
| [package.dependencies.importlib-metadata] | ||||
| python = "<3.8" | ||||
| version = ">=0.12" | ||||
|  | ||||
| [package.extras] | ||||
| checkqa-mypy = ["mypy (v0.761)"] | ||||
| testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Pytest support for asyncio." | ||||
| name = "pytest-asyncio" | ||||
| optional = false | ||||
| python-versions = ">= 3.5" | ||||
| version = "0.12.0" | ||||
|  | ||||
| [package.dependencies] | ||||
| pytest = ">=5.4.0" | ||||
|  | ||||
| [package.extras] | ||||
| testing = ["async_generator (>=1.3)", "coverage", "hypothesis (>=5.7.1)"] | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Pytest plugin for measuring coverage." | ||||
| name = "pytest-cov" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" | ||||
| version = "2.10.0" | ||||
|  | ||||
| [package.dependencies] | ||||
| coverage = ">=4.4" | ||||
| pytest = ">=4.6" | ||||
|  | ||||
| [package.extras] | ||||
| testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "Alternative regular expression module, to replace re." | ||||
| name = "regex" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "2020.6.8" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Python HTTP for Humans." | ||||
| name = "requests" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" | ||||
| version = "2.24.0" | ||||
|  | ||||
| [package.dependencies] | ||||
| certifi = ">=2017.4.17" | ||||
| chardet = ">=3.0.2,<4" | ||||
| idna = ">=2.5,<3" | ||||
| urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" | ||||
|  | ||||
| [package.extras] | ||||
| security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] | ||||
| socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "Python 2 and 3 compatibility utilities" | ||||
| name = "six" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" | ||||
| version = "1.15.0" | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "String case converter." | ||||
| name = "stringcase" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "1.2.0" | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "Python Library for Tom's Obvious, Minimal Language" | ||||
| name = "toml" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "0.10.1" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "tox is a generic virtualenv management and test command line tool" | ||||
| name = "tox" | ||||
| optional = false | ||||
| python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" | ||||
| version = "3.15.2" | ||||
|  | ||||
| [package.dependencies] | ||||
| colorama = ">=0.4.1" | ||||
| filelock = ">=3.0.0" | ||||
| packaging = ">=14" | ||||
| pluggy = ">=0.12.0" | ||||
| py = ">=1.4.17" | ||||
| six = ">=1.14.0" | ||||
| toml = ">=0.9.4" | ||||
| virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" | ||||
|  | ||||
| [package.dependencies.importlib-metadata] | ||||
| python = "<3.8" | ||||
| version = ">=0.12,<2" | ||||
|  | ||||
| [package.extras] | ||||
| docs = ["sphinx (>=2.0.0)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] | ||||
| testing = ["freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-xdist (>=1.22.2)", "pytest-randomly (>=1.0.0)", "flaky (>=3.4.0)", "psutil (>=5.6.1)"] | ||||
|  | ||||
| [[package]] | ||||
| category = "main" | ||||
| description = "a fork of Python 2 and 3 ast modules with type comment support" | ||||
| name = "typed-ast" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "1.4.1" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Backported and Experimental Type Hints for Python 3.5+" | ||||
| name = "typing-extensions" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "3.7.4.2" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "HTTP library with thread-safe connection pooling, file post, and more." | ||||
| name = "urllib3" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" | ||||
| version = "1.25.9" | ||||
|  | ||||
| [package.extras] | ||||
| brotli = ["brotlipy (>=0.6.0)"] | ||||
| secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] | ||||
| socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Virtual Python Environment builder" | ||||
| name = "virtualenv" | ||||
| optional = false | ||||
| python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" | ||||
| version = "20.0.23" | ||||
|  | ||||
| [package.dependencies] | ||||
| appdirs = ">=1.4.3,<2" | ||||
| distlib = ">=0.3.0,<1" | ||||
| filelock = ">=3.0.0,<4" | ||||
| six = ">=1.9.0,<2" | ||||
|  | ||||
| [package.dependencies.importlib-metadata] | ||||
| python = "<3.8" | ||||
| version = ">=0.12,<2" | ||||
|  | ||||
| [package.dependencies.importlib-resources] | ||||
| python = "<3.7" | ||||
| version = ">=1.0" | ||||
|  | ||||
| [package.extras] | ||||
| docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"] | ||||
| testing = ["pytest (>=4)", "coverage (>=5)", "coverage-enable-subprocess (>=1)", "pytest-xdist (>=1.31.0)", "pytest-mock (>=2)", "pytest-env (>=0.6.2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "flaky (>=3)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Measures the displayed width of unicode strings in a terminal" | ||||
| name = "wcwidth" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| version = "0.2.4" | ||||
|  | ||||
| [[package]] | ||||
| category = "dev" | ||||
| description = "Backport of pathlib-compatible object wrapper for zip files" | ||||
| marker = "python_version < \"3.8\"" | ||||
| name = "zipp" | ||||
| optional = false | ||||
| python-versions = ">=3.6" | ||||
| version = "3.1.0" | ||||
|  | ||||
| [package.extras] | ||||
| docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] | ||||
| testing = ["jaraco.itertools", "func-timeout"] | ||||
|  | ||||
| [extras] | ||||
| compiler = ["black", "jinja2", "protobuf"] | ||||
|  | ||||
| [metadata] | ||||
| content-hash = "ecafcaed2d4a25c2829e6dc3ef3c56cd72a8bc28c25c7aeae3484c978c816722" | ||||
| python-versions = "^3.6" | ||||
|  | ||||
| [metadata.files] | ||||
| appdirs = [ | ||||
|     {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, | ||||
|     {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, | ||||
| ] | ||||
| atomicwrites = [ | ||||
|     {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, | ||||
|     {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, | ||||
| ] | ||||
| attrs = [ | ||||
|     {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, | ||||
|     {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, | ||||
| ] | ||||
| backports-datetime-fromisoformat = [ | ||||
|     {file = "backports-datetime-fromisoformat-1.0.0.tar.gz", hash = "sha256:9577a2a9486cd7383a5f58b23bb8e81cf0821dbbc0eb7c87d3fa198c1df40f5c"}, | ||||
| ] | ||||
| black = [ | ||||
|     {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, | ||||
|     {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, | ||||
| ] | ||||
| blessings = [ | ||||
|     {file = "blessings-1.7-py2-none-any.whl", hash = "sha256:caad5211e7ba5afe04367cdd4cfc68fa886e2e08f6f35e76b7387d2109ccea6e"}, | ||||
|     {file = "blessings-1.7-py3-none-any.whl", hash = "sha256:b1fdd7e7a675295630f9ae71527a8ebc10bfefa236b3d6aa4932ee4462c17ba3"}, | ||||
|     {file = "blessings-1.7.tar.gz", hash = "sha256:98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d"}, | ||||
| ] | ||||
| bpython = [ | ||||
|     {file = "bpython-0.19-py2.py3-none-any.whl", hash = "sha256:95d95783bfadfa0a25300a648de5aba4423b0ee76b034022a81dde2b5e853c00"}, | ||||
|     {file = "bpython-0.19.tar.gz", hash = "sha256:476ce09a896c4d34bf5e56aca64650c56fdcfce45781a20dc1521221df8cc49c"}, | ||||
| ] | ||||
| certifi = [ | ||||
|     {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, | ||||
|     {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, | ||||
| ] | ||||
| chardet = [ | ||||
|     {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, | ||||
|     {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, | ||||
| ] | ||||
| click = [ | ||||
|     {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, | ||||
|     {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, | ||||
| ] | ||||
| colorama = [ | ||||
|     {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, | ||||
|     {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, | ||||
| ] | ||||
| coverage = [ | ||||
|     {file = "coverage-5.1-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65"}, | ||||
|     {file = "coverage-5.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2"}, | ||||
|     {file = "coverage-5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04"}, | ||||
|     {file = "coverage-5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6"}, | ||||
|     {file = "coverage-5.1-cp27-cp27m-win32.whl", hash = "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796"}, | ||||
|     {file = "coverage-5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730"}, | ||||
|     {file = "coverage-5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0"}, | ||||
|     {file = "coverage-5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a"}, | ||||
|     {file = "coverage-5.1-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf"}, | ||||
|     {file = "coverage-5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9"}, | ||||
|     {file = "coverage-5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768"}, | ||||
|     {file = "coverage-5.1-cp35-cp35m-win32.whl", hash = "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2"}, | ||||
|     {file = "coverage-5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7"}, | ||||
|     {file = "coverage-5.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0"}, | ||||
|     {file = "coverage-5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019"}, | ||||
|     {file = "coverage-5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c"}, | ||||
|     {file = "coverage-5.1-cp36-cp36m-win32.whl", hash = "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1"}, | ||||
|     {file = "coverage-5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7"}, | ||||
|     {file = "coverage-5.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355"}, | ||||
|     {file = "coverage-5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489"}, | ||||
|     {file = "coverage-5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd"}, | ||||
|     {file = "coverage-5.1-cp37-cp37m-win32.whl", hash = "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e"}, | ||||
|     {file = "coverage-5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a"}, | ||||
|     {file = "coverage-5.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55"}, | ||||
|     {file = "coverage-5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c"}, | ||||
|     {file = "coverage-5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef"}, | ||||
|     {file = "coverage-5.1-cp38-cp38-win32.whl", hash = "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24"}, | ||||
|     {file = "coverage-5.1-cp38-cp38-win_amd64.whl", hash = "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0"}, | ||||
|     {file = "coverage-5.1-cp39-cp39-win32.whl", hash = "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4"}, | ||||
|     {file = "coverage-5.1-cp39-cp39-win_amd64.whl", hash = "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e"}, | ||||
|     {file = "coverage-5.1.tar.gz", hash = "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"}, | ||||
| ] | ||||
| curtsies = [ | ||||
|     {file = "curtsies-0.3.1-py2.py3-none-any.whl", hash = "sha256:9169d734323a1356e7563b1ca0bff3c5358c1b1dcce52506a9d4d8ab8a8f5604"}, | ||||
|     {file = "curtsies-0.3.1.tar.gz", hash = "sha256:b2c913a8113c4382e1a221679f2338139b112839deb16c00ee873e57a4b33bd4"}, | ||||
| ] | ||||
| dataclasses = [ | ||||
|     {file = "dataclasses-0.7-py3-none-any.whl", hash = "sha256:3459118f7ede7c8bea0fe795bff7c6c2ce287d01dd226202f7c9ebc0610a7836"}, | ||||
|     {file = "dataclasses-0.7.tar.gz", hash = "sha256:494a6dcae3b8bcf80848eea2ef64c0cc5cd307ffc263e17cdf42f3e5420808e6"}, | ||||
| ] | ||||
| distlib = [ | ||||
|     {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, | ||||
| ] | ||||
| filelock = [ | ||||
|     {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, | ||||
|     {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, | ||||
| ] | ||||
| greenlet = [ | ||||
|     {file = "greenlet-0.4.16-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:80cb0380838bf4e48da6adedb0c7cd060c187bb4a75f67a5aa9ec33689b84872"}, | ||||
|     {file = "greenlet-0.4.16-cp27-cp27m-win32.whl", hash = "sha256:df7de669cbf21de4b04a3ffc9920bc8426cab4c61365fa84d79bf97401a8bef7"}, | ||||
|     {file = "greenlet-0.4.16-cp27-cp27m-win_amd64.whl", hash = "sha256:1429dc183b36ec972055e13250d96e174491559433eb3061691b446899b87384"}, | ||||
|     {file = "greenlet-0.4.16-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:5ea034d040e6ab1d2ae04ab05a3f37dbd719c4dee3804b13903d4cc794b1336e"}, | ||||
|     {file = "greenlet-0.4.16-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c196a5394c56352e21cb7224739c6dd0075b69dd56f758505951d1d8d68cf8a9"}, | ||||
|     {file = "greenlet-0.4.16-cp35-cp35m-win32.whl", hash = "sha256:1000038ba0ea9032948e2156a9c15f5686f36945e8f9906e6b8db49f358e7b52"}, | ||||
|     {file = "greenlet-0.4.16-cp35-cp35m-win_amd64.whl", hash = "sha256:1b805231bfb7b2900a16638c3c8b45c694334c811f84463e52451e00c9412691"}, | ||||
|     {file = "greenlet-0.4.16-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e5db19d4a7d41bbeb3dd89b49fc1bc7e6e515b51bbf32589c618655a0ebe0bf0"}, | ||||
|     {file = "greenlet-0.4.16-cp36-cp36m-win32.whl", hash = "sha256:eac2a3f659d5f41d6bbfb6a97733bc7800ea5e906dc873732e00cebb98cec9e4"}, | ||||
|     {file = "greenlet-0.4.16-cp36-cp36m-win_amd64.whl", hash = "sha256:7eed31f4efc8356e200568ba05ad645525f1fbd8674f1e5be61a493e715e3873"}, | ||||
|     {file = "greenlet-0.4.16-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:682328aa576ec393c1872615bcb877cf32d800d4a2f150e1a5dc7e56644010b1"}, | ||||
|     {file = "greenlet-0.4.16-cp37-cp37m-win32.whl", hash = "sha256:3a35e33902b2e6079949feed7a2dafa5ac6f019da97bd255842bb22de3c11bf5"}, | ||||
|     {file = "greenlet-0.4.16-cp37-cp37m-win_amd64.whl", hash = "sha256:b0b2a984bbfc543d144d88caad6cc7ff4a71be77102014bd617bd88cfb038727"}, | ||||
|     {file = "greenlet-0.4.16-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:d83c1d38658b0f81c282b41238092ed89d8f93c6e342224ab73fb39e16848721"}, | ||||
|     {file = "greenlet-0.4.16-cp38-cp38-win32.whl", hash = "sha256:e695ac8c3efe124d998230b219eb51afb6ef10524a50b3c45109c4b77a8a3a92"}, | ||||
|     {file = "greenlet-0.4.16-cp38-cp38-win_amd64.whl", hash = "sha256:133ba06bad4e5f2f8bf6a0ac434e0fd686df749a86b3478903b92ec3a9c0c90b"}, | ||||
|     {file = "greenlet-0.4.16.tar.gz", hash = "sha256:6e06eac722676797e8fce4adb8ad3dc57a1bb3adfb0dd3fdf8306c055a38456c"}, | ||||
| ] | ||||
| grpclib = [ | ||||
|     {file = "grpclib-0.3.2.tar.gz", hash = "sha256:d1e76c56f5b9cf268942b93d1ef2046e3983c35ed3e6b592f02f1d9f0db36a81"}, | ||||
| ] | ||||
| h2 = [ | ||||
|     {file = "h2-3.2.0-py2.py3-none-any.whl", hash = "sha256:61e0f6601fa709f35cdb730863b4e5ec7ad449792add80d1410d4174ed139af5"}, | ||||
|     {file = "h2-3.2.0.tar.gz", hash = "sha256:875f41ebd6f2c44781259005b157faed1a5031df3ae5aa7bcb4628a6c0782f14"}, | ||||
| ] | ||||
| hpack = [ | ||||
|     {file = "hpack-3.0.0-py2.py3-none-any.whl", hash = "sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89"}, | ||||
|     {file = "hpack-3.0.0.tar.gz", hash = "sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"}, | ||||
| ] | ||||
| hyperframe = [ | ||||
|     {file = "hyperframe-5.2.0-py2.py3-none-any.whl", hash = "sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40"}, | ||||
|     {file = "hyperframe-5.2.0.tar.gz", hash = "sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f"}, | ||||
| ] | ||||
| idna = [ | ||||
|     {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, | ||||
|     {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, | ||||
| ] | ||||
| importlib-metadata = [ | ||||
|     {file = "importlib_metadata-1.6.1-py2.py3-none-any.whl", hash = "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958"}, | ||||
|     {file = "importlib_metadata-1.6.1.tar.gz", hash = "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545"}, | ||||
| ] | ||||
| importlib-resources = [ | ||||
|     {file = "importlib_resources-2.0.1-py2.py3-none-any.whl", hash = "sha256:83985739b3a6679702f9ab33f0ad016ad564664d0568a31ac14d7c64789453e6"}, | ||||
|     {file = "importlib_resources-2.0.1.tar.gz", hash = "sha256:f5edfcece1cc9435d0979c19e08739521f4cf1aa1adaf6e571f732df6f568962"}, | ||||
| ] | ||||
| jinja2 = [ | ||||
|     {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, | ||||
|     {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, | ||||
| ] | ||||
| markupsafe = [ | ||||
|     {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, | ||||
|     {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, | ||||
|     {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, | ||||
| ] | ||||
| more-itertools = [ | ||||
|     {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"}, | ||||
|     {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"}, | ||||
| ] | ||||
| multidict = [ | ||||
|     {file = "multidict-4.7.6-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000"}, | ||||
|     {file = "multidict-4.7.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a"}, | ||||
|     {file = "multidict-4.7.6-cp35-cp35m-win32.whl", hash = "sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5"}, | ||||
|     {file = "multidict-4.7.6-cp35-cp35m-win_amd64.whl", hash = "sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3"}, | ||||
|     {file = "multidict-4.7.6-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87"}, | ||||
|     {file = "multidict-4.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2"}, | ||||
|     {file = "multidict-4.7.6-cp36-cp36m-win32.whl", hash = "sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7"}, | ||||
|     {file = "multidict-4.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463"}, | ||||
|     {file = "multidict-4.7.6-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d"}, | ||||
|     {file = "multidict-4.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255"}, | ||||
|     {file = "multidict-4.7.6-cp37-cp37m-win32.whl", hash = "sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507"}, | ||||
|     {file = "multidict-4.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c"}, | ||||
|     {file = "multidict-4.7.6-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b"}, | ||||
|     {file = "multidict-4.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7"}, | ||||
|     {file = "multidict-4.7.6-cp38-cp38-win32.whl", hash = "sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d"}, | ||||
|     {file = "multidict-4.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19"}, | ||||
|     {file = "multidict-4.7.6.tar.gz", hash = "sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430"}, | ||||
| ] | ||||
| mypy = [ | ||||
|     {file = "mypy-0.770-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600"}, | ||||
|     {file = "mypy-0.770-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:86c857510a9b7c3104cf4cde1568f4921762c8f9842e987bc03ed4f160925754"}, | ||||
|     {file = "mypy-0.770-cp35-cp35m-win_amd64.whl", hash = "sha256:a8ffcd53cb5dfc131850851cc09f1c44689c2812d0beb954d8138d4f5fc17f65"}, | ||||
|     {file = "mypy-0.770-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:7687f6455ec3ed7649d1ae574136835a4272b65b3ddcf01ab8704ac65616c5ce"}, | ||||
|     {file = "mypy-0.770-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3beff56b453b6ef94ecb2996bea101a08f1f8a9771d3cbf4988a61e4d9973761"}, | ||||
|     {file = "mypy-0.770-cp36-cp36m-win_amd64.whl", hash = "sha256:15b948e1302682e3682f11f50208b726a246ab4e6c1b39f9264a8796bb416aa2"}, | ||||
|     {file = "mypy-0.770-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:b90928f2d9eb2f33162405f32dde9f6dcead63a0971ca8a1b50eb4ca3e35ceb8"}, | ||||
|     {file = "mypy-0.770-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c56ffe22faa2e51054c5f7a3bc70a370939c2ed4de308c690e7949230c995913"}, | ||||
|     {file = "mypy-0.770-cp37-cp37m-win_amd64.whl", hash = "sha256:8dfb69fbf9f3aeed18afffb15e319ca7f8da9642336348ddd6cab2713ddcf8f9"}, | ||||
|     {file = "mypy-0.770-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:219a3116ecd015f8dca7b5d2c366c973509dfb9a8fc97ef044a36e3da66144a1"}, | ||||
|     {file = "mypy-0.770-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7ec45a70d40ede1ec7ad7f95b3c94c9cf4c186a32f6bacb1795b60abd2f9ef27"}, | ||||
|     {file = "mypy-0.770-cp38-cp38-win_amd64.whl", hash = "sha256:f91c7ae919bbc3f96cd5e5b2e786b2b108343d1d7972ea130f7de27fdd547cf3"}, | ||||
|     {file = "mypy-0.770-py3-none-any.whl", hash = "sha256:3b1fc683fb204c6b4403a1ef23f0b1fac8e4477091585e0c8c54cbdf7d7bb164"}, | ||||
|     {file = "mypy-0.770.tar.gz", hash = "sha256:8a627507ef9b307b46a1fea9513d5c98680ba09591253082b4c48697ba05a4ae"}, | ||||
| ] | ||||
| mypy-extensions = [ | ||||
|     {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, | ||||
|     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, | ||||
| ] | ||||
| packaging = [ | ||||
|     {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, | ||||
|     {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, | ||||
| ] | ||||
| pathspec = [ | ||||
|     {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, | ||||
|     {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, | ||||
| ] | ||||
| pluggy = [ | ||||
|     {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, | ||||
|     {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, | ||||
| ] | ||||
| protobuf = [ | ||||
|     {file = "protobuf-3.12.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e1464a4a2cf12f58f662c8e6421772c07947266293fb701cb39cd9c1e183f63c"}, | ||||
|     {file = "protobuf-3.12.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:6f349adabf1c004aba53f7b4633459f8ca8a09654bf7e69b509c95a454755776"}, | ||||
|     {file = "protobuf-3.12.2-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:be04fe14ceed7f8641e30f36077c1a654ff6f17d0c7a5283b699d057d150d82a"}, | ||||
|     {file = "protobuf-3.12.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f4b73736108a416c76c17a8a09bc73af3d91edaa26c682aaa460ef91a47168d3"}, | ||||
|     {file = "protobuf-3.12.2-cp35-cp35m-win32.whl", hash = "sha256:5524c7020eb1fb7319472cb75c4c3206ef18b34d6034d2ee420a60f99cddeb07"}, | ||||
|     {file = "protobuf-3.12.2-cp35-cp35m-win_amd64.whl", hash = "sha256:bff02030bab8b969f4de597543e55bd05e968567acb25c0a87495a31eb09e925"}, | ||||
|     {file = "protobuf-3.12.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c9ca9f76805e5a637605f171f6c4772fc4a81eced4e2f708f79c75166a2c99ea"}, | ||||
|     {file = "protobuf-3.12.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:304e08440c4a41a0f3592d2a38934aad6919d692bb0edfb355548786728f9a5e"}, | ||||
|     {file = "protobuf-3.12.2-cp36-cp36m-win32.whl", hash = "sha256:b5a114ea9b7fc90c2cc4867a866512672a47f66b154c6d7ee7e48ddb68b68122"}, | ||||
|     {file = "protobuf-3.12.2-cp36-cp36m-win_amd64.whl", hash = "sha256:85b94d2653b0fdf6d879e39d51018bf5ccd86c81c04e18a98e9888694b98226f"}, | ||||
|     {file = "protobuf-3.12.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7ab28a8f1f043c58d157bceb64f80e4d2f7f1b934bc7ff5e7f7a55a337ea8b0"}, | ||||
|     {file = "protobuf-3.12.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eafe9fa19fcefef424ee089fb01ac7177ff3691af7cc2ae8791ae523eb6ca907"}, | ||||
|     {file = "protobuf-3.12.2-cp37-cp37m-win32.whl", hash = "sha256:612bc97e42b22af10ba25e4140963fbaa4c5181487d163f4eb55b0b15b3dfcd2"}, | ||||
|     {file = "protobuf-3.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:e72736dd822748b0721f41f9aaaf6a5b6d5cfc78f6c8690263aef8bba4457f0e"}, | ||||
|     {file = "protobuf-3.12.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:87535dc2d2ef007b9d44e309d2b8ea27a03d2fa09556a72364d706fcb7090828"}, | ||||
|     {file = "protobuf-3.12.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:50b5fee674878b14baea73b4568dc478c46a31dd50157a5b5d2f71138243b1a9"}, | ||||
| ] | ||||
| py = [ | ||||
|     {file = "py-1.8.2-py2.py3-none-any.whl", hash = "sha256:a673fa23d7000440cc885c17dbd34fafcb7d7a6e230b29f6766400de36a33c44"}, | ||||
|     {file = "py-1.8.2.tar.gz", hash = "sha256:f3b3a4c36512a4c4f024041ab51866f11761cc169670204b235f6b20523d4e6b"}, | ||||
| ] | ||||
| pygments = [ | ||||
|     {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, | ||||
|     {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, | ||||
| ] | ||||
| pyparsing = [ | ||||
|     {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, | ||||
|     {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, | ||||
| ] | ||||
| pytest = [ | ||||
|     {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, | ||||
|     {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, | ||||
| ] | ||||
| pytest-asyncio = [ | ||||
|     {file = "pytest-asyncio-0.12.0.tar.gz", hash = "sha256:475bd2f3dc0bc11d2463656b3cbaafdbec5a47b47508ea0b329ee693040eebd2"}, | ||||
| ] | ||||
| pytest-cov = [ | ||||
|     {file = "pytest-cov-2.10.0.tar.gz", hash = "sha256:1a629dc9f48e53512fcbfda6b07de490c374b0c83c55ff7a1720b3fccff0ac87"}, | ||||
|     {file = "pytest_cov-2.10.0-py2.py3-none-any.whl", hash = "sha256:6e6d18092dce6fad667cd7020deed816f858ad3b49d5b5e2b1cc1c97a4dba65c"}, | ||||
| ] | ||||
| regex = [ | ||||
|     {file = "regex-2020.6.8-cp27-cp27m-win32.whl", hash = "sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c"}, | ||||
|     {file = "regex-2020.6.8-cp27-cp27m-win_amd64.whl", hash = "sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938"}, | ||||
|     {file = "regex-2020.6.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:92d8a043a4241a710c1cf7593f5577fbb832cf6c3a00ff3fc1ff2052aff5dd89"}, | ||||
|     {file = "regex-2020.6.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bae83f2a56ab30d5353b47f9b2a33e4aac4de9401fb582b55c42b132a8ac3868"}, | ||||
|     {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:b2ba0f78b3ef375114856cbdaa30559914d081c416b431f2437f83ce4f8b7f2f"}, | ||||
|     {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:95fa7726d073c87141f7bbfb04c284901f8328e2d430eeb71b8ffdd5742a5ded"}, | ||||
|     {file = "regex-2020.6.8-cp36-cp36m-win32.whl", hash = "sha256:e3cdc9423808f7e1bb9c2e0bdb1c9dc37b0607b30d646ff6faf0d4e41ee8fee3"}, | ||||
|     {file = "regex-2020.6.8-cp36-cp36m-win_amd64.whl", hash = "sha256:c78e66a922de1c95a208e4ec02e2e5cf0bb83a36ceececc10a72841e53fbf2bd"}, | ||||
|     {file = "regex-2020.6.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:08997a37b221a3e27d68ffb601e45abfb0093d39ee770e4257bd2f5115e8cb0a"}, | ||||
|     {file = "regex-2020.6.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2f6f211633ee8d3f7706953e9d3edc7ce63a1d6aad0be5dcee1ece127eea13ae"}, | ||||
|     {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:55b4c25cbb3b29f8d5e63aeed27b49fa0f8476b0d4e1b3171d85db891938cc3a"}, | ||||
|     {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:89cda1a5d3e33ec9e231ece7307afc101b5217523d55ef4dc7fb2abd6de71ba3"}, | ||||
|     {file = "regex-2020.6.8-cp37-cp37m-win32.whl", hash = "sha256:690f858d9a94d903cf5cada62ce069b5d93b313d7d05456dbcd99420856562d9"}, | ||||
|     {file = "regex-2020.6.8-cp37-cp37m-win_amd64.whl", hash = "sha256:1700419d8a18c26ff396b3b06ace315b5f2a6e780dad387e4c48717a12a22c29"}, | ||||
|     {file = "regex-2020.6.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:654cb773b2792e50151f0e22be0f2b6e1c3a04c5328ff1d9d59c0398d37ef610"}, | ||||
|     {file = "regex-2020.6.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:52e1b4bef02f4040b2fd547357a170fc1146e60ab310cdbdd098db86e929b387"}, | ||||
|     {file = "regex-2020.6.8-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:cf59bbf282b627130f5ba68b7fa3abdb96372b24b66bdf72a4920e8153fc7910"}, | ||||
|     {file = "regex-2020.6.8-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5aaa5928b039ae440d775acea11d01e42ff26e1561c0ffcd3d805750973c6baf"}, | ||||
|     {file = "regex-2020.6.8-cp38-cp38-win32.whl", hash = "sha256:97712e0d0af05febd8ab63d2ef0ab2d0cd9deddf4476f7aa153f76feef4b2754"}, | ||||
|     {file = "regex-2020.6.8-cp38-cp38-win_amd64.whl", hash = "sha256:6ad8663c17db4c5ef438141f99e291c4d4edfeaacc0ce28b5bba2b0bf273d9b5"}, | ||||
|     {file = "regex-2020.6.8.tar.gz", hash = "sha256:e9b64e609d37438f7d6e68c2546d2cb8062f3adb27e6336bc129b51be20773ac"}, | ||||
| ] | ||||
| requests = [ | ||||
|     {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, | ||||
|     {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, | ||||
| ] | ||||
| six = [ | ||||
|     {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, | ||||
|     {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, | ||||
| ] | ||||
| stringcase = [ | ||||
|     {file = "stringcase-1.2.0.tar.gz", hash = "sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008"}, | ||||
| ] | ||||
| toml = [ | ||||
|     {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, | ||||
|     {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, | ||||
| ] | ||||
| tox = [ | ||||
|     {file = "tox-3.15.2-py2.py3-none-any.whl", hash = "sha256:50a188b8e17580c1fb931f494a754e6507d4185f54fb18aca5ba3e12d2ffd55e"}, | ||||
|     {file = "tox-3.15.2.tar.gz", hash = "sha256:c696d36cd7c6a28ada2da780400e44851b20ee19ef08cfe73344a1dcebbbe9f3"}, | ||||
| ] | ||||
| typed-ast = [ | ||||
|     {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, | ||||
|     {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, | ||||
|     {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, | ||||
|     {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, | ||||
|     {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, | ||||
|     {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, | ||||
|     {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, | ||||
|     {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, | ||||
|     {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, | ||||
|     {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, | ||||
|     {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, | ||||
|     {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, | ||||
|     {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, | ||||
|     {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, | ||||
|     {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, | ||||
|     {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, | ||||
|     {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, | ||||
|     {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, | ||||
|     {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, | ||||
|     {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, | ||||
|     {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, | ||||
| ] | ||||
| typing-extensions = [ | ||||
|     {file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"}, | ||||
|     {file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"}, | ||||
|     {file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"}, | ||||
| ] | ||||
| urllib3 = [ | ||||
|     {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, | ||||
|     {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, | ||||
| ] | ||||
| virtualenv = [ | ||||
|     {file = "virtualenv-20.0.23-py2.py3-none-any.whl", hash = "sha256:ccfb8e1e05a1174f7bd4c163700277ba730496094fe1a58bea9d4ac140a207c8"}, | ||||
|     {file = "virtualenv-20.0.23.tar.gz", hash = "sha256:5102fbf1ec57e80671ef40ed98a84e980a71194cedf30c87c2b25c3a9e0b0107"}, | ||||
| ] | ||||
| wcwidth = [ | ||||
|     {file = "wcwidth-0.2.4-py2.py3-none-any.whl", hash = "sha256:79375666b9954d4a1a10739315816324c3e73110af9d0e102d906fdb0aec009f"}, | ||||
|     {file = "wcwidth-0.2.4.tar.gz", hash = "sha256:8c6b5b6ee1360b842645f336d9e5d68c55817c26d3050f46b235ef2bc650e48f"}, | ||||
| ] | ||||
| zipp = [ | ||||
|     {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, | ||||
|     {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, | ||||
| ] | ||||
| @@ -1,9 +1,61 @@ | ||||
| [tool.poetry] | ||||
| name = "betterproto" | ||||
| version = "1.2.5" | ||||
| description = "A better Protobuf / gRPC generator & library" | ||||
| authors = ["Daniel G. Taylor <danielgtaylor@gmail.com>"] | ||||
| readme = "README.md" | ||||
| repository = "https://github.com/danielgtaylor/python-betterproto" | ||||
| keywords = ["protobuf", "gRPC"] | ||||
| license = "MIT" | ||||
|  | ||||
| exclude = ["betterproto/tests"] | ||||
|  | ||||
| [tool.poetry.dependencies] | ||||
| python = "^3.6" | ||||
| backports-datetime-fromisoformat = { version = "^1.0.0", python = "<3.7" } | ||||
| black = { version = "^19.10b0", optional = true } | ||||
| dataclasses = { version = "^0.7", python = ">=3.6, <3.7" } | ||||
| grpclib = "^0.3.1" | ||||
| jinja2 = { version = "^2.11.2", optional = true } | ||||
| protobuf = { version = "^3.12.2", optional = true } | ||||
| stringcase = "^1.2.0" | ||||
|  | ||||
| [tool.poetry.dev-dependencies] | ||||
| black = "^19.10b0" | ||||
| bpython = "^0.19" | ||||
| jinja2 = "^2.11.2" | ||||
| mypy = "^0.770" | ||||
| protobuf = "^3.12.2" | ||||
| pytest = "^5.4.2" | ||||
| pytest-asyncio = "^0.12.0" | ||||
| pytest-cov = "^2.9.0" | ||||
| tox = "^3.15.1" | ||||
|  | ||||
| [tool.poetry.scripts] | ||||
| protoc-gen-python_betterproto = "betterproto.plugin:main" | ||||
|  | ||||
| [tool.poetry.extras] | ||||
| compiler = ["black", "jinja2", "protobuf"] | ||||
|  | ||||
| [tool.black] | ||||
| target-version = ['py36'] | ||||
|  | ||||
| [tool.isort] | ||||
| multi_line_output = 3 | ||||
| include_trailing_comma = true | ||||
| force_grid_wrap = 0 | ||||
| use_parentheses = true | ||||
| line_length = 88 | ||||
| [tool.coverage.run] | ||||
| omit = ["betterproto/tests/*"] | ||||
|  | ||||
| [tool.tox] | ||||
| legacy_tox_ini = """ | ||||
| [tox] | ||||
| isolated_build = true | ||||
| envlist = py36, py37, py38 | ||||
|  | ||||
| [testenv] | ||||
| whitelist_externals = poetry | ||||
| commands = | ||||
|     poetry install -v --extras compiler | ||||
|     poetry run pytest --cov betterproto | ||||
| """ | ||||
|  | ||||
| [build-system] | ||||
| requires = ["poetry>=0.12"] | ||||
| build-backend = "poetry.masonry.api" | ||||
|   | ||||
							
								
								
									
										29
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								setup.py
									
									
									
									
									
								
							| @@ -1,29 +0,0 @@ | ||||
| from setuptools import setup, find_packages | ||||
|  | ||||
| setup( | ||||
|     name="betterproto", | ||||
|     version="1.2.5", | ||||
|     description="A better Protobuf / gRPC generator & library", | ||||
|     long_description=open("README.md", "r", encoding="utf-8").read(), | ||||
|     long_description_content_type="text/markdown", | ||||
|     url="http://github.com/danielgtaylor/python-betterproto", | ||||
|     author="Daniel G. Taylor", | ||||
|     author_email="danielgtaylor@gmail.com", | ||||
|     license="MIT", | ||||
|     entry_points={ | ||||
|         "console_scripts": ["protoc-gen-python_betterproto=betterproto.plugin:main"] | ||||
|     }, | ||||
|     packages=find_packages( | ||||
|         exclude=["tests", "*.tests", "*.tests.*", "output", "output.*"] | ||||
|     ), | ||||
|     package_data={"betterproto": ["py.typed", "templates/template.py.j2"]}, | ||||
|     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