From: John Snow <jsnow@redhat.com>
To: Markus Armbruster <armbru@redhat.com>
Cc: "Peter Maydell" <peter.maydell@linaro.org>,
"Thomas Huth" <thuth@redhat.com>,
"Eduardo Habkost" <ehabkost@redhat.com>,
qemu-devel@nongnu.org, "Cleber Rosa" <crosa@redhat.com>,
"Alex Bennée" <alex.bennee@linaro.org>
Subject: Re: [PATCH v4 04/46] qapi: modify docstrings to be sphinx-compatible
Date: Thu, 1 Oct 2020 10:48:35 -0400 [thread overview]
Message-ID: <5f3e38fd-6d18-5d28-31cd-6c0faa8c675d@redhat.com> (raw)
In-Reply-To: <87blhm49bk.fsf@dusky.pond.sub.org>
On 10/1/20 4:52 AM, Markus Armbruster wrote:
> John Snow <jsnow@redhat.com> writes:
>
>> On 9/30/20 4:47 AM, Markus Armbruster wrote:
>>> John Snow <jsnow@redhat.com> writes:
>>>
>>>> I did not say "sphinx beautiful", just "sphinx compatible". They will
>>>> not throw errors when parsed and interpreted as ReST.
>>> "Bang on the keyboard until Sphinx doesn't throw errors anymore"
>>> might
>>> be good enough for a certain kind of mathematician, but a constructive
>>> solution needs a bit more direction. Is there a specification to
>>> follow? Other useful resources?
>>>
>>
>> I don't know if you are asking this question rhetorically, or in good faith.
>
> I ask to make sure I understand goals and limitations of your doc string
> work in this series.
>
> Also, even a passing to Sphinx becomes more useful when accompanied by a
> link to relevant documentation.
>
>> Let me preface this by saying: This series, and these 119 patches, are
>> not about finding a style guide for our docstring utilization or about
>> proposing one. It is also not about rigorously adding such
>> documentation or about finding ways to meaningfully publish it with
>> e.g. Sphinx, or the styling of such pages.
>>
>> Why bother to add docstrings at all, then? Because I needed them for
>> my own sake when learning this code and I felt it would be a waste to
>> just delete them, and I am of sound mind and able body and believe
>> that some documentation was better than none. They are useful even
>> just as plaintext.
>>
>> Having said that, let's explore the actual style I tend to use.
>>
>> I mentioned before in response to a review comment that there isn't
>> really any standard for docstrings. There are a few competing
>> "styles", but none of which are able to be programmatically checked
>> and validated.
>>
>> The primary guide for docstrings is PEP 257, of which I follow some
>> but not all of the recommendations.
>>
>> https://www.python.org/dev/peps/pep-0257/
>
> I find PEP 257 frustrating. It leaves me with more questions than
> answers.
>
Yeah, sorry. That's what we're dealing with. It's very open-ended.
>> In general,
>>
>> - Always use triple-double quotes (unenforced)
>> - Modules, classes, and functions should have docstrings (pylint)
>> - No empty lines before or after the docstring (unenforced)
>> - Multi-line docstrings should take the form (unenforced):
>>
>> """
>> single-line summary of function.
>>
>> Additional prose if needed describing the function.
>>
>> :param x: Input such that blah blah.
>> :raises y: When input ``x`` is unsuitable because blah blah.
>> :returns: A value that blah blah.
>
> This paragraph is already not PEP 257.
>
Right -- well, it isn't NOT PEP 257 either. It just suggests you have to
describe these features, but it doesn't say HOW.
>> """
>>
>> PEP257 suggests a form where the single-line summary appears on the
>> same line as the opening triple quotes. I don't like this, and prefer
>> symmetry. PEP257 *also* suggests that writing it my way is equivalent
>> to their way, because any docstring processor should trim the first
>> line. I take this as tacit admission that my way is acceptable and has
>> merit.
>
> I prefer the symmetric form myself.
>
>> What about the syntax or markup inside the docstring itself? there is
>> *absolutely no standard*, but Sphinx autodoc recognizes a few field
>> lists as significant in its parsing, so I prefer using them:
>
> Doc link?
>
Hard to search for in my opinion; you want to search for "sphinx python
domain", and then click on "field lists" on the sidebar.
https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#info-field-lists
It has a special understanding for:
param/parameter/arg/argument/key/keyword: I prefer "param" here.
Possibly key/keyword if we use a **kwargs form with a key that we
specially recognize, but I've not tested that yet. I know pycharm
understands "param" in a semantic way, and that's been good enough for me.
type: Defines the type of a parameter. In my opinion, do not use this.
Let native type hints do the lifting.
raises/raise/except/exception: I prefer "raises". "raises ErrorType
when...." is a good sentence.
var/ivar/cvar: Describes a variable, presumably in the body of the
function below. I've never used this, I always describe it in prose instead.
vartype: Defines a type for a variable; I would again defer to the
native type system instead now.
returns/return: I prefer "returns" for grammatical reasons again.
("Returns such-and-such when...")
rtype: again, type information. Don't use.
meta: For directives to sphinx, e.g. :meta private: or :meta public: to
toggle the visibility class from its default. I don't use this.
None of these are validated or checked in any meaningful way; you can
use arbitrarily field lists (and I do in a few places!) to define your
own terms and so on.
(I would like to improve autodoc in the future to validate your
docstrings such that you can enforce :param:, :raises: and :return: and
it uses the type hints and introspection information to raise an error
when you make an obvious mistake. I am not there yet, but I am using
Peter Maydell's work to help inform how I might write such an extension
to autodoc. This work is not critical, but it will likely occur
upstream, outside of the QEMU context because I believe this is a good
thing to do for the ecosystem in general, to allow autodoc to function
slightly more like e.g. Doxygen does.)
>> :param x: Denotes the parameter X. Do not use type information in the
>> string, we rely on mypy for that now.
>>
>> :raises y: explains a case in which an Exception type y may be raised
>> either directly by this code or anticipated to be allowed to be raised
>> by a helper call. (There's no standard here that I am aware of. I use
>> my judgment. Always document direct raise calls, but use your judgment
>> for sub-calls.)
>>
>> :returns: explains the semantics of the return value.
>>
>> That said, literally any sphinx/ReST markup is OK as long as it passes
>> make sphinxdocs. Some sphinx markup is prohibited, like adding new
>> full-throated sections. You can use arbitrary field lists, definition
>> lists, pre-formatted text, examples, code blocks, whatever.
>>
>> In general, you are not going to find the kind of semantic validation
>> you want to ensure that the parameter names are correct, or that you
>> spelled :param: right, or that you didn't miss a parameter or an
>> exception. None of that tooling exists for Python.
>>
>> Thus, it's all rather subjective. No right answers, no validation
>> tools. Just whatever seems reasonable to a human eye until such time
>> we actually decide to pursue publishing the API docs in the
>> development manual, if indeed we ever do so at all.
>>
>> That series sounds like a great opportunity to hash this all out. That
>> is when I would like to remove --missing-docstring, too. There will
>> absolutely be a "docstring series" in the future, but I am insisting
>> stubbornly it happen after strict typing.
>
> Okay. Nevertheless, I'd prefer a bit more information in the commit
> message. Here's my try:
>
> qapi: Modify docstrings to be sphinx-compatible
>
> I did not say "sphinx beautiful", just "sphinx compatible". They
> will not throw errors when parsed and interpreted as ReST. Finding
> a comprehensive style guide for our docstring utilization is left
> for another day.
>
> For now, use field lists recognized by Sphinx autodoc.
> FIXME link to their documentation
>
That I can do -- and I will double down on my IOU for a more formal
style guide: https://gitlab.com/jsnow/qemu/-/issues/7
I didn't bother writing it in any of the commits because I felt like
it'd get lost there and would be mostly useless; but a .rst doc inside
the package folder would be hard to miss.
I plan to check in something like ./python/README.rst or
./python/CODING_STYLE.rst to try and formalize a lot of what I am doing
here, where it's going to be harder to miss.
>>
>>>>
>>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>>> ---
>>>> scripts/qapi/gen.py | 6 ++++--
>>>> scripts/qapi/parser.py | 9 +++++----
>>>> 2 files changed, 9 insertions(+), 6 deletions(-)
>>>>
>>>> diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
>>>> index ca66c82b5b8..fc19b2aeb9b 100644
>>>> --- a/scripts/qapi/gen.py
>>>> +++ b/scripts/qapi/gen.py
>>>> @@ -154,9 +154,11 @@ def _bottom(self):
>>>> @contextmanager
>>>> def ifcontext(ifcond, *args):
>>>> - """A 'with' statement context manager to wrap with start_if()/end_if()
>>>> + """
>>>> + A 'with' statement context manager that wraps with `start_if` and `end_if`.
>>> Sadly, the fact that start_if() and end_if() are functions isn't
>>> immediately obvious anymore.
>>> I've seen :func:`start_if` elsewhere. Is this something we should
>>> or
>>> want to use?
>>>
>>
>> We *could*.
>>
>> `start_if` relies on the default role, which I have provisionally set
>> to "any" here, so this is shorthand for :any:`start_if`.
>>
>> The :any: role means: "cross-reference any type of thing." If there is
>> not exactly one thing that matches, it results in an error during the
>> documentation build.
>>
>> I like this, because it's nice short-hand syntax that I think
>> communicates effectively to the reader that this is a symbol of some
>> kind without needing a premium of ReST-ese.
>>
>> CONSTANTS are capitalized, Classes are title cased, and functions are
>> lower_case. `lower_case` references can be assumed to be functions,
>
> `lower_case` could also refer to an attribute, variable, or parameter.
>
Hm. Attribute yes, actually. variable and parameter no -- sphinx does
not presently provide syntax or roles for creating anchors to parameter
names or variables, so they are not able to be cross-referenced.
Attributes CAN be cross-referenced, but only when they are documented.
Another style guide thing:
#: x is a number that represents "The Answer". See `Douglas Adams`_.
self.x = 42
You can use the special comment form "#:" to add a one-line description
of an attribute that Sphinx will pick up. Sphinx skips these attributes
otherwise. If you consider them part of the interface of the module,
it's maybe a good idea to do this.
You can also use docstrings, but the ordering changes:
self.x = 42
"""x is a number that represents "The Answer". See `Douglas Adams`_.
I kind of like the #: form because it announces what follows, but I
admit it's a bit of special sphinx magic.
>> but I will admit that this is not enforced or necessarily true as we
>> add more cross reference types in the future.
>>
>> (I am trying to add QMP cross-reference syntax!)
>>
>> I still prefer `start_if` to :func:`start_if` simply because it's less
>> markup and is easier to read in plaintext contexts. You're right, it
>> doesn't look like a function anymore.
>
> Yes, :func:`start_if` is rather heavy. I asked because I wanted to
> understand what :func: buys us. Not meant as endorsement.
>
It specifically targets only cross-references of that exact type. In the
case that the :any: reference is ambiguous, :func: is the disambiguation.
> GDK-Doc seems smart enough to recognize start_if(). Sphinx isn't,
> because it's built around reST syntax. We put our money on the Sphinx
> horse, so...
>
>> I'm not sure if another annotations would work -- `start_if`() or
>> `start_if()`. Both seem kind of clunky to me, to be honest. Personal
>> feeling is "not really worth the hassle."
>
> You later reported the latter works.
>
> I prefer `start_if()` to `start_if`. Matter of taste.
>
Change made.
>>
>>>> - *args: any number of QAPIGenCCode
>>>> + :param ifcond: List of conditionals
>>>> + :param args: any number of `QAPIGenCCode`.
>>>> Example::
>>>> diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
>>>> index 9d1a3e2eea9..02983979965 100644
>>>> --- a/scripts/qapi/parser.py
>>>> +++ b/scripts/qapi/parser.py
>>>> @@ -381,10 +381,11 @@ def append(self, line):
>>>> The way that the line is dealt with depends on which
>>>> part of
>>>> the documentation we're parsing right now:
>>>> - * The body section: ._append_line is ._append_body_line
>>>> - * An argument section: ._append_line is ._append_args_line
>>>> - * A features section: ._append_line is ._append_features_line
>>>> - * An additional section: ._append_line is ._append_various_line
>>>> +
>>>> + * The body section: ._append_line is ._append_body_line
>>>> + * An argument section: ._append_line is ._append_args_line
>>>> + * A features section: ._append_line is ._append_features_line
>>>> + * An additional section: ._append_line is ._append_various_line
>>>> """
>>>> line = line[1:]
>>>> if not line:
>>> I understand why you insert a blank line (reST wants blank lines
>>> around
>>> lists), I don't understand why you indent. Can you explain?
>>
>> I was mistaken about it needing the indent!
>
> Easy enough to tidy up :)
>
Already done!
next prev parent reply other threads:[~2020-10-01 15:09 UTC|newest]
Thread overview: 69+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-09-30 4:31 [PATCH v4 00/46] qapi: static typing conversion, pt1 John Snow
2020-09-30 4:31 ` [PATCH v4 01/46] [DO-NOT-MERGE] docs: replace single backtick (`) with double-backtick (``) John Snow
2020-09-30 4:31 ` [PATCH v4 02/46] docs: repair broken references John Snow
2020-09-30 4:31 ` [PATCH v4 03/46] [DO-NOT-MERGE] docs/sphinx: change default role to "any" John Snow
2020-09-30 4:31 ` [PATCH v4 04/46] qapi: modify docstrings to be sphinx-compatible John Snow
2020-09-30 8:47 ` Markus Armbruster
2020-09-30 17:22 ` John Snow
2020-10-01 8:52 ` Markus Armbruster
2020-10-01 14:48 ` John Snow [this message]
2020-10-02 9:19 ` Markus Armbruster
2020-10-02 15:14 ` John Snow
2020-09-30 17:38 ` John Snow
2020-10-01 8:54 ` Markus Armbruster
2020-10-01 14:28 ` John Snow
2020-09-30 4:31 ` [PATCH v4 05/46] [DO-NOT-MERGE] docs: enable sphinx-autodoc for scripts/qapi John Snow
2020-09-30 4:31 ` [PATCH v4 06/46] qapi-gen: Separate arg-parsing from generation John Snow
2020-09-30 4:31 ` [PATCH v4 07/46] qapi: move generator entrypoint into module John Snow
2020-09-30 4:31 ` [PATCH v4 08/46] [DO-NOT-MERGE] docs: add scripts/qapi/main to python manual John Snow
2020-09-30 4:31 ` [PATCH v4 09/46] qapi: Prefer explicit relative imports John Snow
2020-09-30 4:31 ` [PATCH v4 10/46] qapi: Remove wildcard includes John Snow
2020-09-30 4:31 ` [PATCH v4 11/46] qapi: enforce import order/styling with isort John Snow
2020-09-30 4:31 ` [PATCH v4 12/46] qapi: delint using flake8 John Snow
2020-09-30 4:31 ` [PATCH v4 13/46] qapi: add pylintrc John Snow
2020-09-30 4:31 ` [PATCH v4 14/46] qapi/common.py: Remove python compatibility workaround John Snow
2020-09-30 4:31 ` [PATCH v4 15/46] qapi/common.py: Add indent manager John Snow
2020-09-30 4:31 ` [PATCH v4 16/46] qapi/common.py: delint with pylint John Snow
2020-09-30 4:31 ` [PATCH v4 17/46] qapi/common.py: Replace one-letter 'c' variable John Snow
2020-09-30 4:31 ` [PATCH v4 18/46] qapi/common.py: check with pylint John Snow
2020-09-30 4:31 ` [PATCH v4 19/46] qapi/common.py: add type hint annotations John Snow
2020-09-30 4:31 ` [PATCH v4 20/46] qapi/common.py: Convert comments into docstrings, and elaborate John Snow
2020-09-30 4:31 ` [PATCH v4 21/46] qapi/common.py: move build_params into gen.py John Snow
2020-09-30 4:31 ` [PATCH v4 22/46] qapi: establish mypy type-checking baseline John Snow
2020-09-30 4:31 ` [PATCH v4 23/46] qapi/events.py: add type hint annotations John Snow
2020-09-30 4:31 ` [PATCH v4 24/46] qapi/events.py: Move comments into docstrings John Snow
2020-09-30 4:31 ` [PATCH v4 25/46] qapi/commands.py: Don't re-bind to variable of different type John Snow
2020-09-30 4:31 ` [PATCH v4 26/46] qapi/commands.py: add type hint annotations John Snow
2020-09-30 4:31 ` [PATCH v4 27/46] qapi/commands.py: enable checking with mypy John Snow
2020-09-30 4:31 ` [PATCH v4 28/46] qapi/source.py: add type hint annotations John Snow
2020-09-30 4:31 ` [PATCH v4 29/46] qapi/source.py: delint with pylint John Snow
2020-09-30 4:31 ` [PATCH v4 30/46] qapi/gen.py: Fix edge-case of _is_user_module John Snow
2020-09-30 11:03 ` Eduardo Habkost
2020-09-30 4:31 ` [PATCH v4 31/46] qapi/gen.py: add type hint annotations John Snow
2020-09-30 4:31 ` [PATCH v4 32/46] qapi/gen.py: Enable checking with mypy John Snow
2020-09-30 4:31 ` [PATCH v4 33/46] qapi/gen.py: Remove unused parameter John Snow
2020-09-30 4:31 ` [PATCH v4 34/46] qapi/gen.py: update write() to be more idiomatic John Snow
2020-09-30 4:31 ` [PATCH v4 35/46] qapi/gen.py: delint with pylint John Snow
2020-09-30 4:31 ` [PATCH v4 36/46] qapi/introspect.py: assert obj is a dict when features are given John Snow
2020-09-30 11:04 ` Eduardo Habkost
2020-09-30 4:31 ` [PATCH v4 37/46] qapi/instrospect.py: add preliminary type hint annotations John Snow
2020-09-30 18:31 ` Eduardo Habkost
2020-09-30 4:31 ` [PATCH v4 38/46] qapi/introspect.py: add _gen_features helper John Snow
2020-09-30 17:21 ` Eduardo Habkost
2020-09-30 4:31 ` [PATCH v4 39/46] qapi/introspect.py: Unify return type of _make_tree() John Snow
2020-09-30 18:24 ` Eduardo Habkost
2020-09-30 18:32 ` John Snow
2020-09-30 18:57 ` Eduardo Habkost
2020-09-30 19:02 ` John Snow
2020-09-30 4:31 ` [PATCH v4 40/46] qapi/introspect.py: replace 'extra' dict with 'comment' argument John Snow
2020-09-30 18:24 ` Eduardo Habkost
2020-09-30 4:31 ` [PATCH v4 41/46] qapi/introspect.py: create a typed 'Node' data structure John Snow
2020-09-30 18:39 ` Eduardo Habkost
2020-09-30 18:58 ` John Snow
2020-09-30 19:52 ` Eduardo Habkost
2020-10-01 17:59 ` John Snow
2020-09-30 4:31 ` [PATCH v4 42/46] qapi/types.py: add type hint annotations John Snow
2020-09-30 4:31 ` [PATCH v4 43/46] qapi/types.py: remove one-letter variables John Snow
2020-09-30 4:31 ` [PATCH v4 44/46] qapi/visit.py: assert tag_member contains a QAPISchemaEnumType John Snow
2020-09-30 4:31 ` [PATCH v4 45/46] qapi/visit.py: remove unused parameters from gen_visit_object John Snow
2020-09-30 4:31 ` [PATCH v4 46/46] qapi/visit.py: add type hint annotations John Snow
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=5f3e38fd-6d18-5d28-31cd-6c0faa8c675d@redhat.com \
--to=jsnow@redhat.com \
--cc=alex.bennee@linaro.org \
--cc=armbru@redhat.com \
--cc=crosa@redhat.com \
--cc=ehabkost@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=qemu-devel@nongnu.org \
--cc=thuth@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).