Skip to content

Commit d3e4fbb

Browse files
authored
Add Documentation (#125)
Add sphinx docs with readthedocs integration. Docs can be built locally with `poe docs`.
1 parent 58556e0 commit d3e4fbb

14 files changed

+974
-99
lines changed
File renamed without changes.

.github/workflows/code-quality.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ on:
99
- '**'
1010

1111
jobs:
12-
black:
13-
name: Black
12+
check-formatting:
13+
name: Check code/doc formatting
1414
runs-on: ubuntu-latest
1515
steps:
1616
- uses: actions/checkout@v2
1717
- name: Run Black
1818
uses: lgeiger/black-action@master
1919
with:
2020
args: --check src/ tests/
21+
22+
- name: Install rST dependcies
23+
run: python -m pip install doc8
24+
- name: Lint documentation for errors
25+
run: python -m doc8 docs --max-line-length 88 --ignore-path-errors "docs/migrating.rst;D001"
26+
# it has a table which is longer than 88 characters long

.readthedocs.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
version: 2
2+
formats: []
3+
4+
build:
5+
image: latest
6+
7+
sphinx:
8+
configuration: docs/conf.py
9+
fail_on_warning: false
10+
11+
python:
12+
version: 3.7
13+
install:
14+
- method: pip
15+
path: .
16+
extra_requirements:
17+
- dev

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ datetime.datetime(2019, 1, 1, 11, 59, 58, 800000, tzinfo=datetime.timezone.utc)
326326
## Development
327327

328328
- _Join us on [Slack](https://join.slack.com/t/betterproto/shared_invite/zt-f0n0uolx-iN8gBNrkPxtKHTLpG3o1OQ)!_
329-
- _See how you can help → [Contributing](CONTRIBUTING.md)_
329+
- _See how you can help → [Contributing](.github/CONTRIBUTING.md)_
330330

331331
### Requirements
332332

docs/api.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
.. currentmodule:: betterproto
2+
3+
API reference
4+
=============
5+
6+
The following document outlines betterproto's api. **None** of these classes should be
7+
extended by the user manually.
8+
9+
10+
Message
11+
--------
12+
13+
.. autoclass:: betterproto.Message
14+
:members:
15+
:special-members: __bytes__
16+
17+
18+
.. autofunction:: betterproto.serialized_on_wire
19+
20+
.. autofunction:: betterproto.which_one_of
21+
22+
23+
Enumerations
24+
-------------
25+
26+
.. autoclass:: betterproto.Enum()
27+
:members:
28+
29+
30+
.. autoclass:: betterproto.Casing()
31+
:members:

docs/conf.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Configuration file for the Sphinx documentation builder.
2+
#
3+
# This file only contains a selection of the most common options. For a full
4+
# list see the documentation:
5+
# https://www.sphinx-doc.org/en/master/usage/configuration.html
6+
7+
# If extensions (or modules to document with autodoc) are in another directory,
8+
# add these directories to sys.path here. If the directory is relative to the
9+
# documentation root, use os.path.abspath to make it absolute, like shown here.
10+
11+
import pathlib
12+
13+
import toml
14+
15+
16+
# -- Project information -----------------------------------------------------
17+
18+
project = "betterproto"
19+
copyright = "2019 Daniel G. Taylor"
20+
author = "danielgtaylor"
21+
pyproject = toml.load(open(pathlib.Path(__file__).parent.parent / "pyproject.toml"))
22+
23+
24+
# The full version, including alpha/beta/rc tags.
25+
release = pyproject["tool"]["poetry"]["version"]
26+
27+
28+
# -- General configuration ---------------------------------------------------
29+
30+
# Add any Sphinx extension module names here, as strings. They can be
31+
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
32+
# ones.
33+
extensions = [
34+
"sphinx.ext.autodoc",
35+
"sphinx.ext.intersphinx",
36+
"sphinx.ext.napoleon",
37+
]
38+
39+
autodoc_member_order = "bysource"
40+
autodoc_typehints = "none"
41+
42+
extlinks = {
43+
"issue": ("https://github.com/danielgtaylor/python-betterproto/issues/%s", "GH-"),
44+
}
45+
46+
# Links used for cross-referencing stuff in other documentation
47+
intersphinx_mapping = {
48+
"py": ("https://docs.python.org/3", None),
49+
}
50+
51+
52+
# -- Options for HTML output -------------------------------------------------
53+
54+
# The name of the Pygments (syntax highlighting) style to use.
55+
pygments_style = "friendly"
56+
57+
# The theme to use for HTML and HTML Help pages. See the documentation for
58+
# a list of builtin themes.
59+
60+
html_theme = "sphinx_rtd_theme"

docs/index.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Welcome to betterproto's documentation!
2+
=======================================
3+
4+
betterproto is a protobuf compiler and interpreter. It improves the experience of using
5+
Protobuf and gRPC in Python, by generating readable, understandable, and idiomatic
6+
Python code, using modern language features.
7+
8+
9+
Features:
10+
~~~~~~~~~
11+
12+
- Generated messages are both binary & JSON serializable
13+
- Messages use relevant python types, e.g. ``Enum``, ``datetime`` and ``timedelta``
14+
objects
15+
- ``async``/``await`` support for gRPC Clients
16+
- Generates modern, readable, idiomatic python code
17+
18+
Contents:
19+
~~~~~~~~~
20+
21+
.. toctree::
22+
:maxdepth: 2
23+
24+
quick-start
25+
api
26+
migrating
27+
28+
29+
If you still can't find what you're looking for, try in one of the following pages:
30+
31+
* :ref:`genindex`
32+
* :ref:`modindex`
33+
* :ref:`search`

docs/migrating.rst

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
Migrating Guide
2+
===============
3+
4+
Google's protocolbuffers
5+
------------------------
6+
7+
betterproto has a mostly 1 to 1 drop in replacement for Google's protocolbuffers (after
8+
regenerating your protobufs of course) although there are some minor differences.
9+
10+
.. note::
11+
12+
betterproto implements the same basic methods including:
13+
14+
- :meth:`betterproto.Message.FromString`
15+
- :meth:`betterproto.Message.SerializeToString`
16+
17+
for compatibility purposes, however it is important to note that these are
18+
effectively aliases for :meth:`betterproto.Message.parse` and
19+
:meth:`betterproto.Message.__bytes__` respectively.
20+
21+
22+
Determining if a message was sent
23+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24+
25+
Sometimes it is useful to be able to determine whether a message has been sent on
26+
the wire. This is how the Google wrapper types work to let you know whether a value is
27+
unset (set as the default/zero value), or set as something else, for example.
28+
29+
Use ``betterproto.serialized_on_wire(message)`` to determine if it was sent. This is
30+
a little bit different from the official Google generated Python code, and it lives
31+
outside the generated ``Message`` class to prevent name clashes. Note that it only
32+
supports Proto 3 and thus can only be used to check if ``Message`` fields are set.
33+
You cannot check if a scalar was sent on the wire.
34+
35+
.. code-block:: python
36+
37+
# Old way (official Google Protobuf package)
38+
>>> mymessage.HasField('myfield')
39+
True
40+
41+
# New way (this project)
42+
>>> betterproto.serialized_on_wire(mymessage.myfield)
43+
True
44+
45+
46+
One-of Support
47+
~~~~~~~~~~~~~~
48+
49+
Protobuf supports grouping fields in a oneof clause. Only one of the fields in the group
50+
may be set at a given time. For example, given the proto:
51+
52+
.. code-block:: proto
53+
54+
syntax = "proto3";
55+
56+
message Test {
57+
oneof foo {
58+
bool on = 1;
59+
int32 count = 2;
60+
string name = 3;
61+
}
62+
}
63+
64+
You can use ``betterproto.which_one_of(message, group_name)`` to determine which of the
65+
fields was set. It returns a tuple of the field name and value, or a blank string and
66+
``None`` if unset. Again this is a little different than the official Google code
67+
generator:
68+
69+
.. code-block:: python
70+
71+
# Old way (official Google protobuf package)
72+
>>> message.WhichOneof("group")
73+
"foo"
74+
75+
# New way (this project)
76+
>>> betterproto.which_one_of(message, "group")
77+
("foo", "foo's value")
78+
79+
80+
Well-Known Google Types
81+
~~~~~~~~~~~~~~~~~~~~~~~
82+
83+
Google provides several well-known message types like a timestamp, duration, and several
84+
wrappers used to provide optional zero value support. Each of these has a special JSON
85+
representation and is handled a little differently from normal messages. The Python
86+
mapping for these is as follows:
87+
88+
+-------------------------------+-----------------------------------------------+--------------------------+
89+
| ``Google Message`` | ``Python Type`` | ``Default`` |
90+
+===============================+===============================================+==========================+
91+
| ``google.protobuf.duration`` | :class:`datetime.timedelta` | ``0`` |
92+
+-------------------------------+-----------------------------------------------+--------------------------+
93+
| ``google.protobuf.timestamp`` | ``Timezone-aware`` :class:`datetime.datetime` | ``1970-01-01T00:00:00Z`` |
94+
+-------------------------------+-----------------------------------------------+--------------------------+
95+
| ``google.protobuf.*Value`` | ``Optional[...]``/``None`` | ``None`` |
96+
+-------------------------------+-----------------------------------------------+--------------------------+
97+
| ``google.protobuf.*`` | ``betterproto.lib.google.protobuf.*`` | ``None`` |
98+
+-------------------------------+-----------------------------------------------+--------------------------+
99+
100+
101+
For the wrapper types, the Python type corresponds to the wrapped type, e.g.
102+
``google.protobuf.BoolValue`` becomes ``Optional[bool]`` while
103+
``google.protobuf.Int32Value`` becomes ``Optional[int]``. All of the optional values
104+
default to None, so don't forget to check for that possible state.
105+
106+
Given:
107+
108+
.. code-block:: proto
109+
110+
syntax = "proto3";
111+
112+
import "google/protobuf/duration.proto";
113+
import "google/protobuf/timestamp.proto";
114+
import "google/protobuf/wrappers.proto";
115+
116+
message Test {
117+
google.protobuf.BoolValue maybe = 1;
118+
google.protobuf.Timestamp ts = 2;
119+
google.protobuf.Duration duration = 3;
120+
}
121+
122+
You can use it as such:
123+
124+
.. code-block:: python
125+
126+
>>> t = Test().from_dict({"maybe": True, "ts": "2019-01-01T12:00:00Z", "duration": "1.200s"})
127+
>>> t
128+
Test(maybe=True, ts=datetime.datetime(2019, 1, 1, 12, 0, tzinfo=datetime.timezone.utc), duration=datetime.timedelta(seconds=1, microseconds=200000))
129+
130+
>>> t.ts - t.duration
131+
datetime.datetime(2019, 1, 1, 11, 59, 58, 800000, tzinfo=datetime.timezone.utc)
132+
133+
>>> t.ts.isoformat()
134+
'2019-01-01T12:00:00+00:00'
135+
136+
>>> t.maybe = None
137+
>>> t.to_dict()
138+
{'ts': '2019-01-01T12:00:00Z', 'duration': '1.200s'}
139+
140+
141+
[1.2.5] to [2.0.0b1]
142+
--------------------
143+
144+
Updated package structures
145+
~~~~~~~~~~~~~~~~~~~~~~~~~~
146+
147+
Generated code now strictly follows the *package structure* of the ``.proto`` files.
148+
Consequently ``.proto`` files without a package will be combined in a single
149+
``__init__.py`` file. To avoid overwriting existing ``__init__.py`` files, its best
150+
to compile into a dedicated subdirectory.
151+
152+
Upgrading:
153+
154+
- Remove your previously compiled ``.py`` files.
155+
- Create a new *empty* directory, e.g. ``generated`` or ``lib/generated/proto`` etc.
156+
- Regenerate your python files into this directory
157+
- Update import statements, e.g. ``import ExampleMessage from generated``

0 commit comments

Comments
 (0)