All of lore.kernel.org
 help / color / mirror / Atom feed
From: Cleber Rosa <crosa@redhat.com>
To: John Snow <jsnow@redhat.com>
Cc: Michael Roth <mdroth@linux.vnet.ibm.com>,
	Markus Armbruster <armbru@redhat.com>,
	Eduardo Habkost <ehabkost@redhat.com>,
	qemu-devel@nongnu.org
Subject: Re: [PATCH 12/16] qapi/expr.py: Add docstrings
Date: Thu, 24 Sep 2020 20:59:30 -0400	[thread overview]
Message-ID: <20200925005930.GE368253@localhost.localdomain> (raw)
In-Reply-To: <20200922211313.4082880-13-jsnow@redhat.com>

[-- Attachment #1: Type: text/plain, Size: 11152 bytes --]

On Tue, Sep 22, 2020 at 05:13:09PM -0400, John Snow wrote:
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>  scripts/qapi/expr.py | 157 ++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 155 insertions(+), 2 deletions(-)
> 
> diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
> index f244e9648c..4bba09f6e5 100644
> --- a/scripts/qapi/expr.py
> +++ b/scripts/qapi/expr.py
> @@ -1,7 +1,5 @@
>  # -*- coding: utf-8 -*-
>  #
> -# Check (context-free) QAPI schema expression structure
> -#
>  # Copyright IBM, Corp. 2011
>  # Copyright (c) 2013-2019 Red Hat Inc.
>  #
> @@ -14,6 +12,25 @@
>  # This work is licensed under the terms of the GNU GPL, version 2.
>  # See the COPYING file in the top-level directory.
>  
> +"""
> +Normalize and validate (context-free) QAPI schema expression structures.
> +
> +After QAPI expressions are parsed from disk, they are stored in
> +recursively nested Python data structures using Dict, List, str, bool,
> +and int. This module ensures that those nested structures have the
> +correct type(s) and key(s) where appropriate for the QAPI context-free
> +grammar.
> +
> +The QAPI schema expression language also allows for syntactic sugar;
> +this module also handles the normalization process of these nested
> +structures.
> +
> +See `check_exprs` for the main entry point.
> +
> +See `schema.QAPISchema` for processing into native Python data
> +structures and contextual semantic validation.
> +"""
> +
>  import re
>  from typing import (
>      Iterable,
> @@ -46,6 +63,7 @@
>  def check_name_is_str(name: object,
>                        info: QAPISourceInfo,
>                        source: str) -> None:
> +    """Ensures that `name` is a string. [Const]"""
>      if not isinstance(name, str):
>          raise QAPISemError(info, "%s requires a string name" % source)
>  
> @@ -56,6 +74,24 @@ def check_name_str(name: str,
>                     allow_optional: bool = False,
>                     enum_member: bool = False,
>                     permit_upper: bool = False) -> None:
> +    """
> +    Ensures a string is a legal name. [Const]
> +
> +    A name is legal in the default case when:
> +    - It matches ``(__[a-z0-9.-]+_)?[a-z][a-z0-9_-]*``
> +    - It does not start with ``q_`` or ``q-``
> +
> +    :param name:           Name to check.
> +    :param info:           QAPI source file information.
> +    :param source:         Human-readable str describing "what" this name is.
> +    :param allow_optional: Allow the very first character to be ``*``.
> +                           (Cannot be used with `enum_member`)
> +    :param enum_member:    Allow the very first character to be a digit.
> +                           (Cannot be used with `allow_optional`)
> +    :param permit_upper:   Allows upper-case characters wherever
> +                           lower-case characters are allowed.
> +    """
> +    assert not (allow_optional and enum_member)
>      membername = name
>  
>      if allow_optional and name.startswith('*'):
> @@ -76,6 +112,17 @@ def check_name_str(name: str,
>  
>  
>  def check_defn_name_str(name: str, info: QAPISourceInfo, meta: str) -> None:
> +    """
> +    Ensures a name is a legal definition name. [Const]
> +
> +    A legal definition name:
> +     - Adheres to the criteria in `check_name_str`, with uppercase permitted
> +     - Does not end with ``Kind`` or ``List``.
> +
> +    :param name: Name to check.
> +    :param info: QAPI source file information.
> +    :param meta: Type name of the QAPI expression.
> +    """
>      check_name_str(name, info, meta, permit_upper=True)
>      if name.endswith('Kind') or name.endswith('List'):
>          raise QAPISemError(
> @@ -87,6 +134,15 @@ def check_keys(value: _JSObject,
>                 source: str,
>                 required: List[str],
>                 optional: List[str]) -> None:
> +    """
> +    Ensures an object has a specific set of keys. [Const]
> +
> +    :param value:    The object to check.
> +    :param info:     QAPI source file information.
> +    :param source:   Human-readable str describing "what" this object is.
> +    :param required: Keys that *must* be present.
> +    :param optional: Keys that *may* be present.
> +    """
>  
>      def pprint(elems: Iterable[str]) -> str:
>          return ', '.join("'" + e + "'" for e in sorted(elems))
> @@ -109,6 +165,12 @@ def pprint(elems: Iterable[str]) -> str:
>  
>  
>  def check_flags(expr: Expression, info: QAPISourceInfo) -> None:
> +    """
> +    Ensures common fields in an Expression are correct. [Const]
> +
> +    :param expr: Expression to validate.
> +    :param info: QAPI source file information.
> +    """
>      for key in ['gen', 'success-response']:
>          if key in expr and expr[key] is not False:
>              raise QAPISemError(
> @@ -120,6 +182,18 @@ def check_flags(expr: Expression, info: QAPISourceInfo) -> None:
>  
>  
>  def check_if(expr: _JSObject, info: QAPISourceInfo, source: str) -> None:
> +    """
> +    Syntactically validate and normalize the ``if`` field of an object. [RW]
> +
> +    The ``if`` field may be either a `str` or a `List[str]`.
> +    A `str` element will be normalized to `List[str]`.
> +
> +    Sugared: `Union[str, List[str]]`
> +    Ifcond: `List[str]`
> +
> +    :param expr: A `dict`; the ``if`` field, if present, will be validated.
> +    :param info: QAPI source file information.
> +    """
>  
>      def check_if_str(ifcond: object) -> None:
>          if not isinstance(ifcond, str):
> @@ -148,6 +222,16 @@ def check_if_str(ifcond: object) -> None:
>  
>  
>  def normalize_members(members: object) -> None:
> +    """
> +    Normalize a "members" value. [RW]
> +
> +    If `members` is an object, for every value in that object, if that
> +    value is not itself already an object, normalize it to
> +    ``{'type': value}``.
> +
> +    Sugared: `Dict[str, Union[str, TypeRef]]`
> +    Members: `Dict[str, TypeRef]`
> +    """
>      if isinstance(members, dict):
>          for key, arg in members.items():
>              if isinstance(arg, dict):
> @@ -160,6 +244,18 @@ def check_type(value: Optional[object],
>                 source: str,
>                 allow_array: bool = False,
>                 allow_dict: Union[bool, str] = False) -> None:
> +    """
> +    Check the QAPI type of `value`. [RW]
> +
> +    Python types of `str` or `None` are always allowed.
> +
> +    :param value:       The value to check.
> +    :param info:        QAPI Source file information.
> +    :param source:      Human readable string describing "what" the value is.
> +    :param allow_array: Allow a `List[str]` of length 1,
> +                        which indicates an Array<T> type.
> +    :param allow_dict:  Allow a dict, treated as an anonymous type.
> +    """
>      if value is None:
>          return
>  
> @@ -205,6 +301,15 @@ def check_type(value: Optional[object],
>  
>  def check_features(features: Optional[object],
>                     info: QAPISourceInfo) -> None:
> +    """
> +    Syntactically validate and normalize the "features" field. [RW]
> +
> +    `features` may be a List of either `str` or `dict`.
> +    Any `str` element will be normalized to `{'name': element}`.
> +
> +    Sugared: `List[Union[str, Feature]]`
> +    Features: `List[Feature]`
> +    """
>      if features is None:
>          return
>      if not isinstance(features, list):
> @@ -222,6 +327,12 @@ def check_features(features: Optional[object],
>  
>  
>  def check_enum(expr: Expression, info: QAPISourceInfo) -> None:
> +    """
> +    Validate this `Expression` as an ``enum`` expression. [RW]
> +
> +    :param expr: `Expression` to validate.
> +    :param info: QAPI source file information.
> +    """
>      name = expr['enum']
>      members = expr['data']
>      prefix = expr.get('prefix')
> @@ -246,6 +357,12 @@ def check_enum(expr: Expression, info: QAPISourceInfo) -> None:
>  
>  
>  def check_struct(expr: Expression, info: QAPISourceInfo) -> None:
> +    """
> +    Validate this `Expression` as a ``struct`` expression. [RW]
> +
> +    :param expr: `Expression` to validate.
> +    :param info: QAPI source file information.
> +    """
>      name = cast(str, expr['struct'])  # Asserted in check_exprs
>      members = expr['data']
>  
> @@ -254,6 +371,12 @@ def check_struct(expr: Expression, info: QAPISourceInfo) -> None:
>  
>  
>  def check_union(expr: Expression, info: QAPISourceInfo) -> None:
> +    """
> +    Validate this `Expression` as a ``union`` expression. [RW]
> +
> +    :param expr: `Expression` to validate.
> +    :param info: QAPI source file information.
> +    """
>      name = cast(str, expr['union'])  # Asserted in check_exprs
>      base = expr.get('base')
>      discriminator = expr.get('discriminator')
> @@ -280,6 +403,12 @@ def check_union(expr: Expression, info: QAPISourceInfo) -> None:
>  
>  
>  def check_alternate(expr: Expression, info: QAPISourceInfo) -> None:
> +    """
> +    Validate this `Expression` as an ``alternate`` expression. [RW]
> +
> +    :param expr: Expression to validate.
> +    :param info: QAPI source file information.
> +    """
>      members = expr['data']
>  
>      if not members:
> @@ -297,6 +426,12 @@ def check_alternate(expr: Expression, info: QAPISourceInfo) -> None:
>  
>  
>  def check_command(expr: Expression, info: QAPISourceInfo) -> None:
> +    """
> +    Validate this `Expression` as a ``command`` expression. [RW]
> +
> +    :param expr: `Expression` to validate.
> +    :param info: QAPI source file information.
> +    """
>      args = expr.get('data')
>      rets = expr.get('returns')
>      boxed = expr.get('boxed', False)
> @@ -308,6 +443,16 @@ def check_command(expr: Expression, info: QAPISourceInfo) -> None:
>  
>  
>  def check_event(expr: Expression, info: QAPISourceInfo) -> None:
> +    """
> +    Normalize and syntactically validate the ``event`` expression. [RW]
> +
> +    Event:
> +        event:    `str`
> +        data:     `Optional[Dict[str, Member]]`
> +        boxed:    `Optional[bool]`
> +        if:       `Optional[Ifcond]`
> +        features: `Optional[Features]`
> +    """
>      args = expr.get('data')
>      boxed = expr.get('boxed', False)
>  
> @@ -317,6 +462,14 @@ def check_event(expr: Expression, info: QAPISourceInfo) -> None:
>  
>  
>  def check_exprs(exprs: List[_JSObject]) -> List[_JSObject]:
> +    """
> +    Validate and normalize a list of parsed QAPI schema expressions. [RW]
> +
> +    This function accepts a list of expressions + metadta as returned by
> +    the parser. It destructively normalizes the expressions in-place.
> +
> +    :param exprs: The list of expressions to normalize/validate.
> +    """

This is a huge improvement already, but maybe also take the
opportunity to add ":return:" too?  Anyway,

Reviewed-by: Cleber Rosa <crosa@redhat.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

  parent reply	other threads:[~2020-09-25  1:24 UTC|newest]

Thread overview: 70+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-09-22 21:12 [PATCH 00/16] qapi: static typing conversion, pt2 John Snow
2020-09-22 21:12 ` [PATCH 01/16] qapi/expr.py: Remove 'info' argument from nested check_if_str John Snow
2020-09-23 19:26   ` Eduardo Habkost
2020-09-24 23:06   ` Cleber Rosa
2020-09-22 21:12 ` [PATCH 02/16] qapi/expr.py: Check for dict instead of OrderedDict John Snow
2020-09-23 19:26   ` Eduardo Habkost
2020-09-24 23:07   ` Cleber Rosa
2020-09-22 21:13 ` [PATCH 03/16] qapi/expr.py: constrain incoming expression types John Snow
2020-09-23 19:42   ` Eduardo Habkost
2020-09-25  0:05     ` Cleber Rosa
2020-09-25  0:43       ` John Snow
2020-09-25  0:40     ` John Snow
2020-09-25  0:06   ` Cleber Rosa
2020-09-22 21:13 ` [PATCH 04/16] qapi/expr.py: Add assertion for union type 'check_dict' John Snow
2020-09-23 19:53   ` Eduardo Habkost
2020-09-25  0:47     ` John Snow
2020-09-25  1:08       ` Eduardo Habkost
2020-09-25  1:30         ` John Snow
2020-09-25  0:19   ` Cleber Rosa
2020-09-22 21:13 ` [PATCH 05/16] qapi/expr.py: move string check upwards in check_type John Snow
2020-09-23 19:56   ` Eduardo Habkost
2020-09-25  0:22   ` Cleber Rosa
2020-09-22 21:13 ` [PATCH 06/16] qapi/expr.py: Check type of 'data' member John Snow
2020-09-23 19:58   ` Eduardo Habkost
2020-09-25  0:31   ` Cleber Rosa
2020-09-25  0:50     ` John Snow
2020-09-25 16:48       ` Cleber Rosa
2020-09-22 21:13 ` [PATCH 07/16] qapi/expr.py: Add casts in a few select cases John Snow
2020-09-23 20:01   ` Eduardo Habkost
2020-09-25  0:36   ` Cleber Rosa
2020-09-22 21:13 ` [PATCH 08/16] qapi/expr.py: add type hint annotations John Snow
2020-09-23 20:06   ` Eduardo Habkost
2020-09-25  0:44   ` Cleber Rosa
2020-09-22 21:13 ` [PATCH 09/16] qapi/expr.py: rewrite check_if John Snow
2020-09-23 20:09   ` Eduardo Habkost
2020-09-25  0:50   ` Cleber Rosa
2020-09-22 21:13 ` [PATCH 10/16] qapi/expr.py: Remove single-letter variable John Snow
2020-09-23 20:11   ` Eduardo Habkost
2020-09-25  0:52   ` Cleber Rosa
2020-09-22 21:13 ` [PATCH 11/16] qapi/expr.py: enable pylint checks John Snow
2020-09-23 20:12   ` Eduardo Habkost
2020-09-25  0:53   ` Cleber Rosa
2020-09-22 21:13 ` [PATCH 12/16] qapi/expr.py: Add docstrings John Snow
2020-09-23 20:16   ` Eduardo Habkost
2020-09-25  0:52     ` John Snow
2020-09-25  0:59   ` Cleber Rosa [this message]
2020-09-25  1:10     ` John Snow
2020-09-22 21:13 ` [PATCH 13/16] qapi/expr.py: Modify check_keys to accept any Iterable John Snow
2020-09-23 20:17   ` Eduardo Habkost
2020-09-25  1:02   ` Cleber Rosa
2020-09-22 21:13 ` [PATCH 14/16] qapi/expr.py: Use tuples instead of lists for static data John Snow
2020-09-23 20:18   ` Eduardo Habkost
2020-09-25  1:03   ` Cleber Rosa
2020-09-25  1:12     ` John Snow
2020-09-22 21:13 ` [PATCH 15/16] qapi/expr.py: move related checks inside check_xxx functions John Snow
2020-09-23 20:25   ` Eduardo Habkost
2020-09-25  0:58     ` John Snow
2020-09-25  1:09   ` Cleber Rosa
2020-09-22 21:13 ` [PATCH 16/16] qapi/expr.py: Use an expression checker dispatch table John Snow
2020-09-23 20:36   ` Eduardo Habkost
2020-09-25  0:59     ` John Snow
2020-09-25  1:18   ` Cleber Rosa
2020-09-25  1:32     ` John Snow
2020-09-25  6:03       ` Helio Loureiro
2020-09-25 14:16         ` John Snow
2020-09-26 11:31           ` Helio Loureiro
2020-09-30  4:46             ` John Snow
2020-09-25 16:38       ` Cleber Rosa
2020-09-25 17:04         ` John Snow
2020-09-25 22:54 ` [PATCH 00/16] qapi: static typing conversion, pt2 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=20200925005930.GE368253@localhost.localdomain \
    --to=crosa@redhat.com \
    --cc=armbru@redhat.com \
    --cc=ehabkost@redhat.com \
    --cc=jsnow@redhat.com \
    --cc=mdroth@linux.vnet.ibm.com \
    --cc=qemu-devel@nongnu.org \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.