Add Documentation (#125)
Add sphinx docs with readthedocs integration. Docs can be built locally with `poe docs`.
This commit is contained in:
parent
58556e0eb6
commit
d3e4fbb311
10
.github/workflows/code-quality.yml
vendored
10
.github/workflows/code-quality.yml
vendored
@ -9,8 +9,8 @@ on:
|
|||||||
- '**'
|
- '**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
black:
|
check-formatting:
|
||||||
name: Black
|
name: Check code/doc formatting
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@ -18,3 +18,9 @@ jobs:
|
|||||||
uses: lgeiger/black-action@master
|
uses: lgeiger/black-action@master
|
||||||
with:
|
with:
|
||||||
args: --check src/ tests/
|
args: --check src/ tests/
|
||||||
|
|
||||||
|
- name: Install rST dependcies
|
||||||
|
run: python -m pip install doc8
|
||||||
|
- name: Lint documentation for errors
|
||||||
|
run: python -m doc8 docs --max-line-length 88 --ignore-path-errors "docs/migrating.rst;D001"
|
||||||
|
# it has a table which is longer than 88 characters long
|
||||||
|
17
.readthedocs.yml
Normal file
17
.readthedocs.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
version: 2
|
||||||
|
formats: []
|
||||||
|
|
||||||
|
build:
|
||||||
|
image: latest
|
||||||
|
|
||||||
|
sphinx:
|
||||||
|
configuration: docs/conf.py
|
||||||
|
fail_on_warning: false
|
||||||
|
|
||||||
|
python:
|
||||||
|
version: 3.7
|
||||||
|
install:
|
||||||
|
- method: pip
|
||||||
|
path: .
|
||||||
|
extra_requirements:
|
||||||
|
- dev
|
@ -326,7 +326,7 @@ datetime.datetime(2019, 1, 1, 11, 59, 58, 800000, tzinfo=datetime.timezone.utc)
|
|||||||
## Development
|
## Development
|
||||||
|
|
||||||
- _Join us on [Slack](https://join.slack.com/t/betterproto/shared_invite/zt-f0n0uolx-iN8gBNrkPxtKHTLpG3o1OQ)!_
|
- _Join us on [Slack](https://join.slack.com/t/betterproto/shared_invite/zt-f0n0uolx-iN8gBNrkPxtKHTLpG3o1OQ)!_
|
||||||
- _See how you can help → [Contributing](CONTRIBUTING.md)_
|
- _See how you can help → [Contributing](.github/CONTRIBUTING.md)_
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
|
31
docs/api.rst
Normal file
31
docs/api.rst
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
.. currentmodule:: betterproto
|
||||||
|
|
||||||
|
API reference
|
||||||
|
=============
|
||||||
|
|
||||||
|
The following document outlines betterproto's api. **None** of these classes should be
|
||||||
|
extended by the user manually.
|
||||||
|
|
||||||
|
|
||||||
|
Message
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. autoclass:: betterproto.Message
|
||||||
|
:members:
|
||||||
|
:special-members: __bytes__
|
||||||
|
|
||||||
|
|
||||||
|
.. autofunction:: betterproto.serialized_on_wire
|
||||||
|
|
||||||
|
.. autofunction:: betterproto.which_one_of
|
||||||
|
|
||||||
|
|
||||||
|
Enumerations
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. autoclass:: betterproto.Enum()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
.. autoclass:: betterproto.Casing()
|
||||||
|
:members:
|
60
docs/conf.py
Normal file
60
docs/conf.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Configuration file for the Sphinx documentation builder.
|
||||||
|
#
|
||||||
|
# This file only contains a selection of the most common options. For a full
|
||||||
|
# list see the documentation:
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
import toml
|
||||||
|
|
||||||
|
|
||||||
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
|
project = "betterproto"
|
||||||
|
copyright = "2019 Daniel G. Taylor"
|
||||||
|
author = "danielgtaylor"
|
||||||
|
pyproject = toml.load(open(pathlib.Path(__file__).parent.parent / "pyproject.toml"))
|
||||||
|
|
||||||
|
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = pyproject["tool"]["poetry"]["version"]
|
||||||
|
|
||||||
|
|
||||||
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
|
extensions = [
|
||||||
|
"sphinx.ext.autodoc",
|
||||||
|
"sphinx.ext.intersphinx",
|
||||||
|
"sphinx.ext.napoleon",
|
||||||
|
]
|
||||||
|
|
||||||
|
autodoc_member_order = "bysource"
|
||||||
|
autodoc_typehints = "none"
|
||||||
|
|
||||||
|
extlinks = {
|
||||||
|
"issue": ("https://github.com/danielgtaylor/python-betterproto/issues/%s", "GH-"),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Links used for cross-referencing stuff in other documentation
|
||||||
|
intersphinx_mapping = {
|
||||||
|
"py": ("https://docs.python.org/3", None),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output -------------------------------------------------
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = "friendly"
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
|
||||||
|
html_theme = "sphinx_rtd_theme"
|
33
docs/index.rst
Normal file
33
docs/index.rst
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
Welcome to betterproto's documentation!
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
betterproto is a protobuf compiler and interpreter. It improves the experience of using
|
||||||
|
Protobuf and gRPC in Python, by generating readable, understandable, and idiomatic
|
||||||
|
Python code, using modern language features.
|
||||||
|
|
||||||
|
|
||||||
|
Features:
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
- Generated messages are both binary & JSON serializable
|
||||||
|
- Messages use relevant python types, e.g. ``Enum``, ``datetime`` and ``timedelta``
|
||||||
|
objects
|
||||||
|
- ``async``/``await`` support for gRPC Clients
|
||||||
|
- Generates modern, readable, idiomatic python code
|
||||||
|
|
||||||
|
Contents:
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
quick-start
|
||||||
|
api
|
||||||
|
migrating
|
||||||
|
|
||||||
|
|
||||||
|
If you still can't find what you're looking for, try in one of the following pages:
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
157
docs/migrating.rst
Normal file
157
docs/migrating.rst
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
Migrating Guide
|
||||||
|
===============
|
||||||
|
|
||||||
|
Google's protocolbuffers
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
betterproto has a mostly 1 to 1 drop in replacement for Google's protocolbuffers (after
|
||||||
|
regenerating your protobufs of course) although there are some minor differences.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
betterproto implements the same basic methods including:
|
||||||
|
|
||||||
|
- :meth:`betterproto.Message.FromString`
|
||||||
|
- :meth:`betterproto.Message.SerializeToString`
|
||||||
|
|
||||||
|
for compatibility purposes, however it is important to note that these are
|
||||||
|
effectively aliases for :meth:`betterproto.Message.parse` and
|
||||||
|
:meth:`betterproto.Message.__bytes__` respectively.
|
||||||
|
|
||||||
|
|
||||||
|
Determining if a message was sent
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Sometimes it is useful to be able to determine whether a message has been sent on
|
||||||
|
the wire. This is how the Google wrapper types work to let you know whether a value is
|
||||||
|
unset (set as the default/zero value), or set as something else, for example.
|
||||||
|
|
||||||
|
Use ``betterproto.serialized_on_wire(message)`` to determine if it was sent. This is
|
||||||
|
a little bit different from the official Google generated Python code, and it lives
|
||||||
|
outside the generated ``Message`` class to prevent name clashes. Note that it only
|
||||||
|
supports Proto 3 and thus can only be used to check if ``Message`` fields are set.
|
||||||
|
You cannot check if a scalar was sent on the wire.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Old way (official Google Protobuf package)
|
||||||
|
>>> mymessage.HasField('myfield')
|
||||||
|
True
|
||||||
|
|
||||||
|
# New way (this project)
|
||||||
|
>>> betterproto.serialized_on_wire(mymessage.myfield)
|
||||||
|
True
|
||||||
|
|
||||||
|
|
||||||
|
One-of Support
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Protobuf supports grouping fields in a oneof clause. Only one of the fields in the group
|
||||||
|
may be set at a given time. For example, given the proto:
|
||||||
|
|
||||||
|
.. code-block:: proto
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
message Test {
|
||||||
|
oneof foo {
|
||||||
|
bool on = 1;
|
||||||
|
int32 count = 2;
|
||||||
|
string name = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
You can use ``betterproto.which_one_of(message, group_name)`` to determine which of the
|
||||||
|
fields was set. It returns a tuple of the field name and value, or a blank string and
|
||||||
|
``None`` if unset. Again this is a little different than the official Google code
|
||||||
|
generator:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Old way (official Google protobuf package)
|
||||||
|
>>> message.WhichOneof("group")
|
||||||
|
"foo"
|
||||||
|
|
||||||
|
# New way (this project)
|
||||||
|
>>> betterproto.which_one_of(message, "group")
|
||||||
|
("foo", "foo's value")
|
||||||
|
|
||||||
|
|
||||||
|
Well-Known Google Types
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Google provides several well-known message types like a timestamp, duration, and several
|
||||||
|
wrappers used to provide optional zero value support. Each of these has a special JSON
|
||||||
|
representation and is handled a little differently from normal messages. The Python
|
||||||
|
mapping for these is as follows:
|
||||||
|
|
||||||
|
+-------------------------------+-----------------------------------------------+--------------------------+
|
||||||
|
| ``Google Message`` | ``Python Type`` | ``Default`` |
|
||||||
|
+===============================+===============================================+==========================+
|
||||||
|
| ``google.protobuf.duration`` | :class:`datetime.timedelta` | ``0`` |
|
||||||
|
+-------------------------------+-----------------------------------------------+--------------------------+
|
||||||
|
| ``google.protobuf.timestamp`` | ``Timezone-aware`` :class:`datetime.datetime` | ``1970-01-01T00:00:00Z`` |
|
||||||
|
+-------------------------------+-----------------------------------------------+--------------------------+
|
||||||
|
| ``google.protobuf.*Value`` | ``Optional[...]``/``None`` | ``None`` |
|
||||||
|
+-------------------------------+-----------------------------------------------+--------------------------+
|
||||||
|
| ``google.protobuf.*`` | ``betterproto.lib.google.protobuf.*`` | ``None`` |
|
||||||
|
+-------------------------------+-----------------------------------------------+--------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
For the wrapper types, the Python type corresponds to the wrapped type, e.g.
|
||||||
|
``google.protobuf.BoolValue`` becomes ``Optional[bool]`` while
|
||||||
|
``google.protobuf.Int32Value`` becomes ``Optional[int]``. All of the optional values
|
||||||
|
default to None, so don't forget to check for that possible state.
|
||||||
|
|
||||||
|
Given:
|
||||||
|
|
||||||
|
.. code-block:: proto
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "google/protobuf/wrappers.proto";
|
||||||
|
|
||||||
|
message Test {
|
||||||
|
google.protobuf.BoolValue maybe = 1;
|
||||||
|
google.protobuf.Timestamp ts = 2;
|
||||||
|
google.protobuf.Duration duration = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
You can use it as such:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
>>> t = Test().from_dict({"maybe": True, "ts": "2019-01-01T12:00:00Z", "duration": "1.200s"})
|
||||||
|
>>> t
|
||||||
|
Test(maybe=True, ts=datetime.datetime(2019, 1, 1, 12, 0, tzinfo=datetime.timezone.utc), duration=datetime.timedelta(seconds=1, microseconds=200000))
|
||||||
|
|
||||||
|
>>> t.ts - t.duration
|
||||||
|
datetime.datetime(2019, 1, 1, 11, 59, 58, 800000, tzinfo=datetime.timezone.utc)
|
||||||
|
|
||||||
|
>>> t.ts.isoformat()
|
||||||
|
'2019-01-01T12:00:00+00:00'
|
||||||
|
|
||||||
|
>>> t.maybe = None
|
||||||
|
>>> t.to_dict()
|
||||||
|
{'ts': '2019-01-01T12:00:00Z', 'duration': '1.200s'}
|
||||||
|
|
||||||
|
|
||||||
|
[1.2.5] to [2.0.0b1]
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Updated package structures
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Generated code now strictly follows the *package structure* of the ``.proto`` files.
|
||||||
|
Consequently ``.proto`` files without a package will be combined in a single
|
||||||
|
``__init__.py`` file. To avoid overwriting existing ``__init__.py`` files, its best
|
||||||
|
to compile into a dedicated subdirectory.
|
||||||
|
|
||||||
|
Upgrading:
|
||||||
|
|
||||||
|
- Remove your previously compiled ``.py`` files.
|
||||||
|
- Create a new *empty* directory, e.g. ``generated`` or ``lib/generated/proto`` etc.
|
||||||
|
- Regenerate your python files into this directory
|
||||||
|
- Update import statements, e.g. ``import ExampleMessage from generated``
|
192
docs/quick-start.rst
Normal file
192
docs/quick-start.rst
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
Getting Started
|
||||||
|
===============
|
||||||
|
|
||||||
|
Installation
|
||||||
|
++++++++++++
|
||||||
|
|
||||||
|
Installation from PyPI is as simple as running:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
python3 -m pip install -U betterproto
|
||||||
|
|
||||||
|
If you are using Windows, then the following should be used instead:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
py -3 -m pip install -U betterproto
|
||||||
|
|
||||||
|
To include the protoc plugin, install betterproto[compiler] instead of betterproto,
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
python3 -m pip install -U "betterproto[compiler]"
|
||||||
|
|
||||||
|
Compiling proto files
|
||||||
|
+++++++++++++++++++++
|
||||||
|
|
||||||
|
|
||||||
|
Given you installed the compiler and have a proto file, e.g ``example.proto``:
|
||||||
|
|
||||||
|
.. code-block:: proto
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package hello;
|
||||||
|
|
||||||
|
// Greeting represents a message you can tell a user.
|
||||||
|
message Greeting {
|
||||||
|
string message = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
To compile the proto you would run the following:
|
||||||
|
|
||||||
|
You can run the following to invoke protoc directly:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
mkdir hello
|
||||||
|
protoc -I . --python_betterproto_out=lib example.proto
|
||||||
|
|
||||||
|
or run the following to invoke protoc via grpcio-tools:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
pip install grpcio-tools
|
||||||
|
python -m grpc_tools.protoc -I . --python_betterproto_out=lib example.proto
|
||||||
|
|
||||||
|
|
||||||
|
This will generate ``lib/__init__.py`` which looks like:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||||
|
# sources: example.proto
|
||||||
|
# plugin: python-betterproto
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import betterproto
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Greeting(betterproto.Message):
|
||||||
|
"""Greeting represents a message you can tell a user."""
|
||||||
|
|
||||||
|
message: str = betterproto.string_field(1)
|
||||||
|
|
||||||
|
|
||||||
|
Then to use it:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
>>> from lib import Greeting
|
||||||
|
|
||||||
|
>>> test = Greeting()
|
||||||
|
>>> test
|
||||||
|
Greeting(message='')
|
||||||
|
|
||||||
|
>>> test.message = "Hey!"
|
||||||
|
>>> test
|
||||||
|
Greeting(message="Hey!")
|
||||||
|
|
||||||
|
>>> bytes(test)
|
||||||
|
b'\n\x04Hey!'
|
||||||
|
>>> Greeting().parse(serialized)
|
||||||
|
Greeting(message="Hey!")
|
||||||
|
|
||||||
|
|
||||||
|
Async gRPC Support
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
The generated code includes `grpclib <https://grpclib.readthedocs.io/en/latest>`_ based
|
||||||
|
stub (client) classes for rpc services declared in the input proto files.
|
||||||
|
It is enabled by default.
|
||||||
|
|
||||||
|
|
||||||
|
Given a service definition similar to the one below:
|
||||||
|
|
||||||
|
.. code-block:: proto
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package echo;
|
||||||
|
|
||||||
|
message EchoRequest {
|
||||||
|
string value = 1;
|
||||||
|
// Number of extra times to echo
|
||||||
|
uint32 extra_times = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message EchoResponse {
|
||||||
|
repeated string values = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message EchoStreamResponse {
|
||||||
|
string value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service Echo {
|
||||||
|
rpc Echo(EchoRequest) returns (EchoResponse);
|
||||||
|
rpc EchoStream(EchoRequest) returns (stream EchoStreamResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
The generated client can be used like so:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from grpclib.client import Channel
|
||||||
|
import echo
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
channel = Channel(host="127.0.0.1", port=50051)
|
||||||
|
service = echo.EchoStub(channel)
|
||||||
|
response = await service.echo(value="hello", extra_times=1)
|
||||||
|
print(response)
|
||||||
|
|
||||||
|
async for response in service.echo_stream(value="hello", extra_times=1):
|
||||||
|
print(response)
|
||||||
|
|
||||||
|
# don't forget to close the channel when you're done!
|
||||||
|
channel.close()
|
||||||
|
|
||||||
|
asyncio.run(main()) # python 3.7 only
|
||||||
|
|
||||||
|
# outputs
|
||||||
|
EchoResponse(values=['hello', 'hello'])
|
||||||
|
EchoStreamResponse(value='hello')
|
||||||
|
EchoStreamResponse(value='hello')
|
||||||
|
|
||||||
|
|
||||||
|
JSON
|
||||||
|
++++
|
||||||
|
Message objects include :meth:`betterproto.Message.to_json` and
|
||||||
|
:meth:`betterproto.Message.from_json` methods for JSON (de)serialisation, and
|
||||||
|
:meth:`betterproto.Message.to_dict`, :meth:`betterproto.Message.from_dict` for
|
||||||
|
converting back and forth from JSON serializable dicts.
|
||||||
|
|
||||||
|
For compatibility the default is to convert field names to
|
||||||
|
:attr:`betterproto.Casing.CAMEL`. You can control this behavior by passing a
|
||||||
|
different casing value, e.g:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MyMessage(betterproto.Message):
|
||||||
|
a_long_field_name: str = betterproto.string_field(1)
|
||||||
|
|
||||||
|
|
||||||
|
>>> test = MyMessage(a_long_field_name="Hello World!")
|
||||||
|
>>> test.to_dict(betterproto.Casing.SNAKE)
|
||||||
|
{"a_long_field_name": "Hello World!"}
|
||||||
|
>>> test.to_dict(betterproto.Casing.CAMEL)
|
||||||
|
{"aLongFieldName": "Hello World!"}
|
||||||
|
|
||||||
|
>>> test.to_json(indent=2)
|
||||||
|
'{\n "aLongFieldName": "Hello World!"\n}'
|
||||||
|
|
||||||
|
>>> test.from_dict({"aLongFieldName": "Goodbye World!"})
|
||||||
|
>>> test.a_long_field_name
|
||||||
|
"Goodbye World!"
|
@ -1,16 +0,0 @@
|
|||||||
# Upgrade Guide
|
|
||||||
|
|
||||||
## [1.2.5] to [2.0.0b1]
|
|
||||||
|
|
||||||
### Updated package structures
|
|
||||||
|
|
||||||
Generated code now strictly follows the *package structure* of the `.proto` files.
|
|
||||||
Consequently `.proto` files without a package will be combined in a single `__init__.py` file.
|
|
||||||
To avoid overwriting existing `__init__.py` files, its best to compile into a dedicated subdirectory.
|
|
||||||
|
|
||||||
Upgrading:
|
|
||||||
|
|
||||||
- Remove your previously compiled `.py` files.
|
|
||||||
- Create a new *empty* directory, e.g. `generated` or `lib/generated/proto` etcetera.
|
|
||||||
- Regenerate your python files into this directory
|
|
||||||
- Update import statements, e.g. `import ExampleMessage from generated`
|
|
308
poetry.lock
generated
308
poetry.lock
generated
@ -1,3 +1,11 @@
|
|||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "A configurable sidebar-enabled Sphinx theme"
|
||||||
|
name = "alabaster"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.7.12"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||||
@ -43,6 +51,17 @@ docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
|
|||||||
tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
|
tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
|
||||||
tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
|
tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Internationalization utilities"
|
||||||
|
name = "babel"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "2.8.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pytz = ">=2015.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Backport of Python 3.7's datetime.fromisoformat"
|
description = "Backport of Python 3.7's datetime.fromisoformat"
|
||||||
@ -148,7 +167,7 @@ description = "Code coverage measurement for Python"
|
|||||||
name = "coverage"
|
name = "coverage"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||||
version = "5.2.1"
|
version = "5.3"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
toml = ["toml"]
|
toml = ["toml"]
|
||||||
@ -182,6 +201,14 @@ optional = false
|
|||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Docutils -- Python Documentation Utilities"
|
||||||
|
name = "docutils"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
version = "0.16"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "dev"
|
||||||
description = "A platform independent file lock."
|
description = "A platform independent file lock."
|
||||||
@ -276,6 +303,14 @@ optional = false
|
|||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
version = "2.10"
|
version = "2.10"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Getting image size from png/jpeg/jpeg2000/gif file"
|
||||||
|
name = "imagesize"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "1.2.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "dev"
|
||||||
description = "Read metadata from Python packages"
|
description = "Read metadata from Python packages"
|
||||||
@ -389,7 +424,7 @@ description = "Bring colors to your terminal."
|
|||||||
name = "pastel"
|
name = "pastel"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
@ -453,7 +488,7 @@ description = "Pygments is a syntax highlighting package written in Python."
|
|||||||
name = "pygments"
|
name = "pygments"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.5"
|
||||||
version = "2.7.0"
|
version = "2.7.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "dev"
|
||||||
@ -532,6 +567,14 @@ pytest = ">=5.0"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["pre-commit", "tox", "pytest-asyncio"]
|
dev = ["pre-commit", "tox", "pytest-asyncio"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "World timezone definitions, modern and historical"
|
||||||
|
name = "pytz"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "2020.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Alternative regular expression module, to replace re."
|
description = "Alternative regular expression module, to replace re."
|
||||||
@ -566,6 +609,131 @@ optional = false
|
|||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms."
|
||||||
|
name = "snowballstemmer"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "2.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Python documentation generator"
|
||||||
|
name = "sphinx"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "3.1.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Jinja2 = ">=2.3"
|
||||||
|
Pygments = ">=2.0"
|
||||||
|
alabaster = ">=0.7,<0.8"
|
||||||
|
babel = ">=1.3"
|
||||||
|
colorama = ">=0.3.5"
|
||||||
|
docutils = ">=0.12"
|
||||||
|
imagesize = "*"
|
||||||
|
packaging = "*"
|
||||||
|
requests = ">=2.5.0"
|
||||||
|
setuptools = "*"
|
||||||
|
snowballstemmer = ">=1.1"
|
||||||
|
sphinxcontrib-applehelp = "*"
|
||||||
|
sphinxcontrib-devhelp = "*"
|
||||||
|
sphinxcontrib-htmlhelp = "*"
|
||||||
|
sphinxcontrib-jsmath = "*"
|
||||||
|
sphinxcontrib-qthelp = "*"
|
||||||
|
sphinxcontrib-serializinghtml = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["sphinxcontrib-websupport"]
|
||||||
|
lint = ["flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.780)", "docutils-stubs"]
|
||||||
|
test = ["pytest", "pytest-cov", "html5lib", "typed-ast", "cython"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Read the Docs theme for Sphinx"
|
||||||
|
name = "sphinx-rtd-theme"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.5.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
sphinx = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
|
||||||
|
name = "sphinxcontrib-applehelp"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "1.0.2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||||
|
test = ["pytest"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
|
||||||
|
name = "sphinxcontrib-devhelp"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "1.0.2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||||
|
test = ["pytest"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
|
||||||
|
name = "sphinxcontrib-htmlhelp"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "1.0.3"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||||
|
test = ["pytest", "html5lib"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "A sphinx extension which renders display math in HTML via JavaScript"
|
||||||
|
name = "sphinxcontrib-jsmath"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "1.0.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["pytest", "flake8", "mypy"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
|
||||||
|
name = "sphinxcontrib-qthelp"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "1.0.3"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||||
|
test = ["pytest"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
|
||||||
|
name = "sphinxcontrib-serializinghtml"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "1.1.4"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||||
|
test = ["pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||||
@ -680,11 +848,15 @@ testing = ["jaraco.itertools", "func-timeout"]
|
|||||||
compiler = ["black", "jinja2", "protobuf"]
|
compiler = ["black", "jinja2", "protobuf"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
content-hash = "2a56c1e83222f20c06385fd175a00e158419e11780c14ca5153b23e1dfa3d651"
|
content-hash = "aa1cdf753b393b5b61a5794989b6883709d82dd13e924c1b1d92d93c21dd256c"
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
python-versions = "^3.6"
|
python-versions = "^3.6"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
|
alabaster = [
|
||||||
|
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
|
||||||
|
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
|
||||||
|
]
|
||||||
appdirs = [
|
appdirs = [
|
||||||
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
|
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
|
||||||
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
|
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
|
||||||
@ -700,6 +872,10 @@ attrs = [
|
|||||||
{file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"},
|
{file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"},
|
||||||
{file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"},
|
{file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"},
|
||||||
]
|
]
|
||||||
|
babel = [
|
||||||
|
{file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"},
|
||||||
|
{file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"},
|
||||||
|
]
|
||||||
backports-datetime-fromisoformat = [
|
backports-datetime-fromisoformat = [
|
||||||
{file = "backports-datetime-fromisoformat-1.0.0.tar.gz", hash = "sha256:9577a2a9486cd7383a5f58b23bb8e81cf0821dbbc0eb7c87d3fa198c1df40f5c"},
|
{file = "backports-datetime-fromisoformat-1.0.0.tar.gz", hash = "sha256:9577a2a9486cd7383a5f58b23bb8e81cf0821dbbc0eb7c87d3fa198c1df40f5c"},
|
||||||
]
|
]
|
||||||
@ -733,40 +909,40 @@ colorama = [
|
|||||||
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
|
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
|
||||||
]
|
]
|
||||||
coverage = [
|
coverage = [
|
||||||
{file = "coverage-5.2.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4"},
|
{file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"},
|
||||||
{file = "coverage-5.2.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01"},
|
{file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"},
|
||||||
{file = "coverage-5.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8"},
|
{file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"},
|
||||||
{file = "coverage-5.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59"},
|
{file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"},
|
||||||
{file = "coverage-5.2.1-cp27-cp27m-win32.whl", hash = "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3"},
|
{file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"},
|
||||||
{file = "coverage-5.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f"},
|
{file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"},
|
||||||
{file = "coverage-5.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd"},
|
{file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"},
|
||||||
{file = "coverage-5.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651"},
|
{file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"},
|
||||||
{file = "coverage-5.2.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b"},
|
{file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"},
|
||||||
{file = "coverage-5.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d"},
|
{file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"},
|
||||||
{file = "coverage-5.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3"},
|
{file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"},
|
||||||
{file = "coverage-5.2.1-cp35-cp35m-win32.whl", hash = "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"},
|
{file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"},
|
||||||
{file = "coverage-5.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962"},
|
{file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"},
|
||||||
{file = "coverage-5.2.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082"},
|
{file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"},
|
||||||
{file = "coverage-5.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716"},
|
{file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"},
|
||||||
{file = "coverage-5.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb"},
|
{file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"},
|
||||||
{file = "coverage-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d"},
|
{file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"},
|
||||||
{file = "coverage-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546"},
|
{file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"},
|
||||||
{file = "coverage-5.2.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811"},
|
{file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"},
|
||||||
{file = "coverage-5.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258"},
|
{file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"},
|
||||||
{file = "coverage-5.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034"},
|
{file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"},
|
||||||
{file = "coverage-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46"},
|
{file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"},
|
||||||
{file = "coverage-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8"},
|
{file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"},
|
||||||
{file = "coverage-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0"},
|
{file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"},
|
||||||
{file = "coverage-5.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd"},
|
{file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"},
|
||||||
{file = "coverage-5.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b"},
|
{file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"},
|
||||||
{file = "coverage-5.2.1-cp38-cp38-win32.whl", hash = "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd"},
|
{file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"},
|
||||||
{file = "coverage-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d"},
|
{file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"},
|
||||||
{file = "coverage-5.2.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3"},
|
{file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"},
|
||||||
{file = "coverage-5.2.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4"},
|
{file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"},
|
||||||
{file = "coverage-5.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4"},
|
{file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"},
|
||||||
{file = "coverage-5.2.1-cp39-cp39-win32.whl", hash = "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89"},
|
{file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"},
|
||||||
{file = "coverage-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b"},
|
{file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"},
|
||||||
{file = "coverage-5.2.1.tar.gz", hash = "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b"},
|
{file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"},
|
||||||
]
|
]
|
||||||
curtsies = [
|
curtsies = [
|
||||||
{file = "curtsies-0.3.4-py2.py3-none-any.whl", hash = "sha256:068db8e5d8a2f23b765d648a66dfa9445cf2412177126ae946a7357ade992640"},
|
{file = "curtsies-0.3.4-py2.py3-none-any.whl", hash = "sha256:068db8e5d8a2f23b765d648a66dfa9445cf2412177126ae946a7357ade992640"},
|
||||||
@ -780,6 +956,10 @@ distlib = [
|
|||||||
{file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
|
{file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
|
||||||
{file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"},
|
{file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"},
|
||||||
]
|
]
|
||||||
|
docutils = [
|
||||||
|
{file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"},
|
||||||
|
{file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"},
|
||||||
|
]
|
||||||
filelock = [
|
filelock = [
|
||||||
{file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
|
{file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
|
||||||
{file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
|
{file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
|
||||||
@ -904,6 +1084,10 @@ idna = [
|
|||||||
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
||||||
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
|
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
|
||||||
]
|
]
|
||||||
|
imagesize = [
|
||||||
|
{file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
|
||||||
|
{file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
|
||||||
|
]
|
||||||
importlib-metadata = [
|
importlib-metadata = [
|
||||||
{file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
|
{file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
|
||||||
{file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
|
{file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
|
||||||
@ -999,8 +1183,8 @@ packaging = [
|
|||||||
{file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
|
{file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
|
||||||
]
|
]
|
||||||
pastel = [
|
pastel = [
|
||||||
{file = "pastel-0.2.0-py2.py3-none-any.whl", hash = "sha256:18b559dc3ad4ba9b8bd5baebe6503f25f36d21460f021cf27a8d889cb5d17840"},
|
{file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"},
|
||||||
{file = "pastel-0.2.0.tar.gz", hash = "sha256:46155fc523bdd4efcd450bbcb3f2b94a6e3b25edc0eb493e081104ad09e1ca36"},
|
{file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"},
|
||||||
]
|
]
|
||||||
pathspec = [
|
pathspec = [
|
||||||
{file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
|
{file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
|
||||||
@ -1039,8 +1223,8 @@ py = [
|
|||||||
{file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
|
{file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
|
||||||
]
|
]
|
||||||
pygments = [
|
pygments = [
|
||||||
{file = "Pygments-2.7.0-py3-none-any.whl", hash = "sha256:2df50d16b45b977217e02cba6c8422aaddb859f3d0570a88e09b00eafae89c6e"},
|
{file = "Pygments-2.7.1-py3-none-any.whl", hash = "sha256:307543fe65c0947b126e83dd5a61bd8acbd84abec11f43caebaf5534cbc17998"},
|
||||||
{file = "Pygments-2.7.0.tar.gz", hash = "sha256:2594e8fdb06fef91552f86f4fd3a244d148ab24b66042036e64f29a291515048"},
|
{file = "Pygments-2.7.1.tar.gz", hash = "sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7"},
|
||||||
]
|
]
|
||||||
pyparsing = [
|
pyparsing = [
|
||||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||||
@ -1061,6 +1245,10 @@ pytest-mock = [
|
|||||||
{file = "pytest-mock-3.3.1.tar.gz", hash = "sha256:a4d6d37329e4a893e77d9ffa89e838dd2b45d5dc099984cf03c703ac8411bb82"},
|
{file = "pytest-mock-3.3.1.tar.gz", hash = "sha256:a4d6d37329e4a893e77d9ffa89e838dd2b45d5dc099984cf03c703ac8411bb82"},
|
||||||
{file = "pytest_mock-3.3.1-py3-none-any.whl", hash = "sha256:024e405ad382646318c4281948aadf6fe1135632bea9cc67366ea0c4098ef5f2"},
|
{file = "pytest_mock-3.3.1-py3-none-any.whl", hash = "sha256:024e405ad382646318c4281948aadf6fe1135632bea9cc67366ea0c4098ef5f2"},
|
||||||
]
|
]
|
||||||
|
pytz = [
|
||||||
|
{file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"},
|
||||||
|
{file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"},
|
||||||
|
]
|
||||||
regex = [
|
regex = [
|
||||||
{file = "regex-2020.7.14-cp27-cp27m-win32.whl", hash = "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"},
|
{file = "regex-2020.7.14-cp27-cp27m-win32.whl", hash = "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"},
|
||||||
{file = "regex-2020.7.14-cp27-cp27m-win_amd64.whl", hash = "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644"},
|
{file = "regex-2020.7.14-cp27-cp27m-win_amd64.whl", hash = "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644"},
|
||||||
@ -1092,6 +1280,42 @@ six = [
|
|||||||
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
|
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
|
||||||
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
|
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
|
||||||
]
|
]
|
||||||
|
snowballstemmer = [
|
||||||
|
{file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"},
|
||||||
|
{file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"},
|
||||||
|
]
|
||||||
|
sphinx = [
|
||||||
|
{file = "Sphinx-3.1.2-py3-none-any.whl", hash = "sha256:97dbf2e31fc5684bb805104b8ad34434ed70e6c588f6896991b2fdfd2bef8c00"},
|
||||||
|
{file = "Sphinx-3.1.2.tar.gz", hash = "sha256:b9daeb9b39aa1ffefc2809b43604109825300300b987a24f45976c001ba1a8fd"},
|
||||||
|
]
|
||||||
|
sphinx-rtd-theme = [
|
||||||
|
{file = "sphinx_rtd_theme-0.5.0-py2.py3-none-any.whl", hash = "sha256:373413d0f82425aaa28fb288009bf0d0964711d347763af2f1b65cafcb028c82"},
|
||||||
|
{file = "sphinx_rtd_theme-0.5.0.tar.gz", hash = "sha256:22c795ba2832a169ca301cd0a083f7a434e09c538c70beb42782c073651b707d"},
|
||||||
|
]
|
||||||
|
sphinxcontrib-applehelp = [
|
||||||
|
{file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},
|
||||||
|
{file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"},
|
||||||
|
]
|
||||||
|
sphinxcontrib-devhelp = [
|
||||||
|
{file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"},
|
||||||
|
{file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"},
|
||||||
|
]
|
||||||
|
sphinxcontrib-htmlhelp = [
|
||||||
|
{file = "sphinxcontrib-htmlhelp-1.0.3.tar.gz", hash = "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"},
|
||||||
|
{file = "sphinxcontrib_htmlhelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f"},
|
||||||
|
]
|
||||||
|
sphinxcontrib-jsmath = [
|
||||||
|
{file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
|
||||||
|
{file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
|
||||||
|
]
|
||||||
|
sphinxcontrib-qthelp = [
|
||||||
|
{file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"},
|
||||||
|
{file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"},
|
||||||
|
]
|
||||||
|
sphinxcontrib-serializinghtml = [
|
||||||
|
{file = "sphinxcontrib-serializinghtml-1.1.4.tar.gz", hash = "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc"},
|
||||||
|
{file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"},
|
||||||
|
]
|
||||||
toml = [
|
toml = [
|
||||||
{file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
|
{file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
|
||||||
{file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
|
{file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
|
||||||
|
@ -33,6 +33,8 @@ pytest-asyncio = "^0.12.0"
|
|||||||
pytest-cov = "^2.9.0"
|
pytest-cov = "^2.9.0"
|
||||||
pytest-mock = "^3.1.1"
|
pytest-mock = "^3.1.1"
|
||||||
tox = "^3.15.1"
|
tox = "^3.15.1"
|
||||||
|
sphinx = "3.1.2"
|
||||||
|
sphinx-rtd-theme = "0.5.0"
|
||||||
asv = "^0.4.2"
|
asv = "^0.4.2"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
@ -48,6 +50,7 @@ test = { cmd = "pytest --cov src", help = "Run tests" }
|
|||||||
types = { cmd = "mypy src --ignore-missing-imports", help = "Check types with mypy" }
|
types = { cmd = "mypy src --ignore-missing-imports", help = "Check types with mypy" }
|
||||||
format = { cmd = "black . --exclude tests/output_", help = "Apply black formatting to source code" }
|
format = { cmd = "black . --exclude tests/output_", help = "Apply black formatting to source code" }
|
||||||
clean = { cmd = "rm -rf .coverage .mypy_cache .pytest_cache dist betterproto.egg-info **/__pycache__ tests/output_*", help = "Clean out generated files from the workspace" }
|
clean = { cmd = "rm -rf .coverage .mypy_cache .pytest_cache dist betterproto.egg-info **/__pycache__ tests/output_*", help = "Clean out generated files from the workspace" }
|
||||||
|
docs = { cmd = "sphinx-build docs docs/build", help = "Build the sphinx docs"}
|
||||||
bench = { shell = "asv run master^! && asv run HEAD^! && asv compare master HEAD", help = "Benchmark current commit vs. master branch"}
|
bench = { shell = "asv run master^! && asv run HEAD^! && asv compare master HEAD", help = "Benchmark current commit vs. master branch"}
|
||||||
|
|
||||||
# CI tasks
|
# CI tasks
|
||||||
|
@ -120,8 +120,8 @@ DATETIME_ZERO = datetime_default_gen()
|
|||||||
class Casing(enum.Enum):
|
class Casing(enum.Enum):
|
||||||
"""Casing constants for serialization."""
|
"""Casing constants for serialization."""
|
||||||
|
|
||||||
CAMEL = camel_case
|
CAMEL = camel_case #: A camelCase sterilization function.
|
||||||
SNAKE = snake_case
|
SNAKE = snake_case #: A snake_case sterilization function.
|
||||||
|
|
||||||
|
|
||||||
PLACEHOLDER: Any = object()
|
PLACEHOLDER: Any = object()
|
||||||
@ -249,11 +249,26 @@ def map_field(
|
|||||||
|
|
||||||
|
|
||||||
class Enum(enum.IntEnum):
|
class Enum(enum.IntEnum):
|
||||||
"""Protocol buffers enumeration base class. Acts like `enum.IntEnum`."""
|
"""
|
||||||
|
The base class for protobuf enumerations, all generated enumerations will inherit
|
||||||
|
from this. Bases :class:`enum.IntEnum`.
|
||||||
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_string(cls, name: str) -> int:
|
def from_string(cls, name: str) -> "Enum":
|
||||||
"""Return the value which corresponds to the string name."""
|
"""
|
||||||
|
Return the value which corresponds to the string name.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
name: :class:`str`
|
||||||
|
The name of the enum member to get
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
:exc:`ValueError`
|
||||||
|
The member was not found in the Enum.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return cls._member_map_[name]
|
return cls._member_map_[name]
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
@ -497,9 +512,15 @@ class ProtoClassMetadata:
|
|||||||
|
|
||||||
class Message(ABC):
|
class Message(ABC):
|
||||||
"""
|
"""
|
||||||
A protobuf message base class. Generated code will inherit from this and
|
The base class for protobuf messages, all generated messages will inherit from
|
||||||
register the message fields which get used by the serializers and parsers
|
this. This class registers the message fields which are used by the serializers and
|
||||||
to go between Python, binary and JSON protobuf message representations.
|
parsers to go between the Python, binary and JSON representations of the message.
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: bytes(x)
|
||||||
|
|
||||||
|
Calls :meth:`__bytes__`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_serialized_on_wire: bool
|
_serialized_on_wire: bool
|
||||||
@ -605,7 +626,7 @@ class Message(ABC):
|
|||||||
|
|
||||||
def __bytes__(self) -> bytes:
|
def __bytes__(self) -> bytes:
|
||||||
"""
|
"""
|
||||||
Get the binary encoded Protobuf representation of this instance.
|
Get the binary encoded Protobuf representation of this message instance.
|
||||||
"""
|
"""
|
||||||
output = bytearray()
|
output = bytearray()
|
||||||
for field_name, meta in self._betterproto.meta_by_field_name.items():
|
for field_name, meta in self._betterproto.meta_by_field_name.items():
|
||||||
@ -684,7 +705,20 @@ class Message(ABC):
|
|||||||
return bytes(output)
|
return bytes(output)
|
||||||
|
|
||||||
# For compatibility with other libraries
|
# For compatibility with other libraries
|
||||||
SerializeToString = __bytes__
|
def SerializeToString(self: T) -> bytes:
|
||||||
|
"""
|
||||||
|
Get the binary encoded Protobuf representation of this message instance.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This is a method for compatibility with other libraries,
|
||||||
|
you should really use ``bytes(x)``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`bytes`
|
||||||
|
The binary encoded Protobuf representation of this message instance
|
||||||
|
"""
|
||||||
|
return bytes(self)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _type_hint(cls, field_name: str) -> Type:
|
def _type_hint(cls, field_name: str) -> Type:
|
||||||
@ -788,6 +822,16 @@ class Message(ABC):
|
|||||||
"""
|
"""
|
||||||
Parse the binary encoded Protobuf into this message instance. This
|
Parse the binary encoded Protobuf into this message instance. This
|
||||||
returns the instance itself and is therefore assignable and chainable.
|
returns the instance itself and is therefore assignable and chainable.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
data: :class:`bytes`
|
||||||
|
The data to parse the protobuf from.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`Message`
|
||||||
|
The initialized message.
|
||||||
"""
|
"""
|
||||||
# Got some data over the wire
|
# Got some data over the wire
|
||||||
self._serialized_on_wire = True
|
self._serialized_on_wire = True
|
||||||
@ -838,20 +882,47 @@ class Message(ABC):
|
|||||||
# For compatibility with other libraries.
|
# For compatibility with other libraries.
|
||||||
@classmethod
|
@classmethod
|
||||||
def FromString(cls: Type[T], data: bytes) -> T:
|
def FromString(cls: Type[T], data: bytes) -> T:
|
||||||
|
"""
|
||||||
|
Parse the binary encoded Protobuf into this message instance. This
|
||||||
|
returns the instance itself and is therefore assignable and chainable.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This is a method for compatibility with other libraries,
|
||||||
|
you should really use :meth:`parse`.
|
||||||
|
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
data: :class:`bytes`
|
||||||
|
The data to parse the protobuf from.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`Message`
|
||||||
|
The initialized message.
|
||||||
|
"""
|
||||||
return cls().parse(data)
|
return cls().parse(data)
|
||||||
|
|
||||||
def to_dict(
|
def to_dict(
|
||||||
self, casing: Casing = Casing.CAMEL, include_default_values: bool = False
|
self, casing: Casing = Casing.CAMEL, include_default_values: bool = False
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Returns a dict representation of this message instance which can be
|
Returns a JSON serializable dict representation of this object.
|
||||||
used to serialize to e.g. JSON. Defaults to camel casing for
|
|
||||||
compatibility but can be set to other modes.
|
|
||||||
|
|
||||||
`include_default_values` can be set to `True` to include default
|
Parameters
|
||||||
values of fields. E.g. an `int32` type field with `0` value will
|
-----------
|
||||||
not be in returned dict if `include_default_values` is set to
|
casing: :class:`Casing`
|
||||||
`False`.
|
The casing to use for key values. Default is :attr:`Casing.CAMEL` for
|
||||||
|
compatibility purposes.
|
||||||
|
include_default_values: :class:`bool`
|
||||||
|
If ``True`` will include the default values of fields. Default is ``False``.
|
||||||
|
E.g. an ``int32`` field will be included with a value of ``0`` if this is
|
||||||
|
set to ``True``, otherwise this would be ignored.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Dict[:class:`str`, Any]
|
||||||
|
The JSON serializable dict representation of this object.
|
||||||
"""
|
"""
|
||||||
output: Dict[str, Any] = {}
|
output: Dict[str, Any] = {}
|
||||||
field_types = self._type_hints()
|
field_types = self._type_hints()
|
||||||
@ -938,10 +1009,20 @@ class Message(ABC):
|
|||||||
output[cased_name] = value
|
output[cased_name] = value
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def from_dict(self: T, value: dict) -> T:
|
def from_dict(self: T, value: Dict[str, Any]) -> T:
|
||||||
"""
|
"""
|
||||||
Parse the key/value pairs in `value` into this message instance. This
|
Parse the key/value pairs into the current message instance. This returns the
|
||||||
returns the instance itself and is therefore assignable and chainable.
|
instance itself and is therefore assignable and chainable.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
value: Dict[:class:`str`, Any]
|
||||||
|
The dictionary to parse from.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`Message`
|
||||||
|
The initialized message.
|
||||||
"""
|
"""
|
||||||
self._serialized_on_wire = True
|
self._serialized_on_wire = True
|
||||||
for key in value:
|
for key in value:
|
||||||
@ -998,28 +1079,70 @@ class Message(ABC):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def to_json(self, indent: Union[None, int, str] = None) -> str:
|
def to_json(self, indent: Union[None, int, str] = None) -> str:
|
||||||
"""Returns the encoded JSON representation of this message instance."""
|
"""A helper function to parse the message instance into its JSON
|
||||||
|
representation.
|
||||||
|
|
||||||
|
This is equivalent to::
|
||||||
|
|
||||||
|
json.dumps(message.to_dict(), indent=indent)
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
indent: Optional[Union[:class:`int`, :class:`str`]]
|
||||||
|
The indent to pass to :func:`json.dumps`.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`str`
|
||||||
|
The JSON representation of the message.
|
||||||
|
"""
|
||||||
return json.dumps(self.to_dict(), indent=indent)
|
return json.dumps(self.to_dict(), indent=indent)
|
||||||
|
|
||||||
def from_json(self: T, value: Union[str, bytes]) -> T:
|
def from_json(self: T, value: Union[str, bytes]) -> T:
|
||||||
"""
|
"""A helper function to return the message instance from its JSON
|
||||||
Parse the key/value pairs in `value` into this message instance. This
|
representation. This returns the instance itself and is therefore assignable
|
||||||
returns the instance itself and is therefore assignable and chainable.
|
and chainable.
|
||||||
|
|
||||||
|
This is equivalent to::
|
||||||
|
|
||||||
|
return message.from_dict(json.loads(value))
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
value: Union[:class:`str`, :class:`bytes`]
|
||||||
|
The value to pass to :func:`json.loads`.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`Message`
|
||||||
|
The initialized message.
|
||||||
"""
|
"""
|
||||||
return self.from_dict(json.loads(value))
|
return self.from_dict(json.loads(value))
|
||||||
|
|
||||||
|
|
||||||
def serialized_on_wire(message: Message) -> bool:
|
def serialized_on_wire(message: Message) -> bool:
|
||||||
"""
|
"""
|
||||||
True if this message was or should be serialized on the wire. This can
|
If this message was or should be serialized on the wire. This can be used to detect
|
||||||
be used to detect presence (e.g. optional wrapper message) and is used
|
presence (e.g. optional wrapper message) and is used internally during
|
||||||
internally during parsing/serialization.
|
parsing/serialization.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`bool`
|
||||||
|
Whether this message was or should be serialized on the wire.
|
||||||
"""
|
"""
|
||||||
return message._serialized_on_wire
|
return message._serialized_on_wire
|
||||||
|
|
||||||
|
|
||||||
def which_one_of(message: Message, group_name: str) -> Tuple[str, Any]:
|
def which_one_of(message: Message, group_name: str) -> Tuple[str, Any]:
|
||||||
"""Return the name and value of a message's one-of field group."""
|
"""
|
||||||
|
Return the name and value of a message's one-of field group.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Tuple[:class:`str`, Any]
|
||||||
|
The field name and the value for that field.
|
||||||
|
"""
|
||||||
field_name = message._group_current.get(group_name)
|
field_name = message._group_current.get(group_name)
|
||||||
if not field_name:
|
if not field_name:
|
||||||
return ("", None)
|
return ("", None)
|
||||||
|
@ -21,14 +21,24 @@ def safe_snake_case(value: str) -> str:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def snake_case(value: str, strict: bool = True):
|
def snake_case(value: str, strict: bool = True) -> str:
|
||||||
"""
|
"""
|
||||||
Join words with an underscore into lowercase and remove symbols.
|
Join words with an underscore into lowercase and remove symbols.
|
||||||
@param value: value to convert
|
|
||||||
@param strict: force single underscores
|
Parameters
|
||||||
|
-----------
|
||||||
|
value: :class:`str`
|
||||||
|
The value to convert.
|
||||||
|
strict: :class:`bool`
|
||||||
|
Whether or not to force single underscores.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`str`
|
||||||
|
The value in snake_case.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def substitute_word(symbols, word, is_start):
|
def substitute_word(symbols: str, word: str, is_start: bool) -> str:
|
||||||
if not word:
|
if not word:
|
||||||
return ""
|
return ""
|
||||||
if strict:
|
if strict:
|
||||||
@ -52,11 +62,21 @@ def snake_case(value: str, strict: bool = True):
|
|||||||
return snake
|
return snake
|
||||||
|
|
||||||
|
|
||||||
def pascal_case(value: str, strict: bool = True):
|
def pascal_case(value: str, strict: bool = True) -> str:
|
||||||
"""
|
"""
|
||||||
Capitalize each word and remove symbols.
|
Capitalize each word and remove symbols.
|
||||||
@param value: value to convert
|
|
||||||
@param strict: output only alphanumeric characters
|
Parameters
|
||||||
|
-----------
|
||||||
|
value: :class:`str`
|
||||||
|
The value to convert.
|
||||||
|
strict: :class:`bool`
|
||||||
|
Whether or not to output only alphanumeric characters.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`str`
|
||||||
|
The value in PascalCase.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def substitute_word(symbols, word):
|
def substitute_word(symbols, word):
|
||||||
@ -77,14 +97,39 @@ def pascal_case(value: str, strict: bool = True):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def camel_case(value: str, strict: bool = True):
|
def camel_case(value: str, strict: bool = True) -> str:
|
||||||
"""
|
"""
|
||||||
Capitalize all words except first and remove symbols.
|
Capitalize all words except first and remove symbols.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
value: :class:`str`
|
||||||
|
The value to convert.
|
||||||
|
strict: :class:`bool`
|
||||||
|
Whether or not to output only alphanumeric characters.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`str`
|
||||||
|
The value in camelCase.
|
||||||
"""
|
"""
|
||||||
return lowercase_first(pascal_case(value, strict=strict))
|
return lowercase_first(pascal_case(value, strict=strict))
|
||||||
|
|
||||||
|
|
||||||
def lowercase_first(value: str):
|
def lowercase_first(value: str) -> str:
|
||||||
|
"""
|
||||||
|
Lower cases the first character of the value.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
value: :class:`str`
|
||||||
|
The value to lower case.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`str`
|
||||||
|
The lower cased string.
|
||||||
|
"""
|
||||||
return value[0:1].lower() + value[1:]
|
return value[0:1].lower() + value[1:]
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user