qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: John Snow <jsnow@redhat.com>
To: Markus Armbruster <armbru@redhat.com>
Cc: Cleber Rosa <crosa@redhat.com>,
	qemu-devel@nongnu.org, Eduardo Habkost <ehabkost@redhat.com>
Subject: Re: [PATCH v2 09/11] qapi/introspect.py: create a typed 'Annotated' data strutcure
Date: Mon, 7 Dec 2020 19:21:26 -0500	[thread overview]
Message-ID: <b27f7930-d86b-8357-84e4-7daef00023d7@redhat.com> (raw)
In-Reply-To: <87y2j1zk35.fsf@dusky.pond.sub.org>

On 11/16/20 5:12 AM, Markus Armbruster wrote:
> John Snow <jsnow@redhat.com> writes:
> 
>> This replaces _make_tree with Annotated(). By creating it as a generic
>> container, we can more accurately describe the exact nature of this
>> particular value. i.e., each Annotated object is actually an
>> Annotated<T>, describing its contained value.
>>
>> This adds stricter typing to Annotated nodes and extra annotated
>> information.
> 
> Inhowfar?
> 

The Generic[T] trick lets us express the type of the annotated node 
itself, which is more specific than Tuple[_something, ...etc...] and 
this type can be preserved when we peel the annotations off.

It's not super crucial, but like you say, the big benefit is the field 
names and strict types for the special-purpose structure.

>>               It also replaces a check of "isinstance tuple" with the
>> much more explicit "isinstance Annotated" which is guaranteed not to
>> break if a tuple is accidentally introduced into the type tree. (Perhaps
>> as a result of a bad conversion from a list.)
> 
> Sure this is worth writing home about?  Such accidents seem quite
> unlikely.
> 

We all have our phobias. I find "isinstance(x, 
extremely_common_stdlib_type)" to be extremely fragile and likely to 
frustrate.

Maybe what's unlikely is anyone editing this code ever again. You've 
mentioned wanting to look into changing how the schema information is 
stored in QEMU before, so a lot of this might not matter for too much 
longer, who knows.

> For me, the commit's benefit is making the structure of the annotated
> tree node more explicit (your first paragraph, I guess).  It's a bit of
> a pattern in developing Python code: we start with a Tuple because it's
> terse and easy, then things get more complex, terse becomes too terse,
> and we're replacing the Tuple with a class.
> 

Yep.

>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   scripts/qapi/introspect.py | 97 +++++++++++++++++++-------------------
>>   1 file changed, 48 insertions(+), 49 deletions(-)
>>
>> diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
>> index a0978cb3adb..a261e402d69 100644
>> --- a/scripts/qapi/introspect.py
>> +++ b/scripts/qapi/introspect.py
>> @@ -13,12 +13,13 @@
>>   from typing import (
>>       Any,
>>       Dict,
>> +    Generic,
>> +    Iterable,
>>       List,
>>       Optional,
>>       Sequence,
>> -    Tuple,
>> +    TypeVar,
>>       Union,
>> -    cast,
>>   )
>>   
>>   from .common import (
>> @@ -63,50 +64,48 @@
>>   _scalar = Union[str, bool, None]
>>   _nonscalar = Union[Dict[str, _stub], List[_stub]]
>>   _value = Union[_scalar, _nonscalar]
>> -TreeValue = Union[_value, 'Annotated']
>> +TreeValue = Union[_value, 'Annotated[_value]']
>>   
>>   # This is just an alias for an object in the structure described above:
>>   _DObject = Dict[str, object]
>>   
>> -# Represents the annotations themselves:
>> -Annotations = Dict[str, object]
>>   
>> -# Represents an annotated node (of some kind).
>> -Annotated = Tuple[_value, Annotations]
>> +_AnnoType = TypeVar('_AnnoType', bound=TreeValue)
>>   
>>   
>> -def _make_tree(obj: Union[_DObject, str], ifcond: List[str],
>> -               comment: Optional[str] = None) -> Annotated:
>> -    extra: Annotations = {
>> -        'if': ifcond,
>> -        'comment': comment,
>> -    }
>> -    return (obj, extra)
>> +class Annotated(Generic[_AnnoType]):
>> +    """
>> +    Annotated generally contains a SchemaInfo-like type (as a dict),
>> +    But it also used to wrap comments/ifconds around scalar leaf values,
>> +    for the benefit of features and enums.
>> +    """
>> +    # Remove after 3.7 adds @dataclass:
>> +    # pylint: disable=too-few-public-methods
>> +    def __init__(self, value: _AnnoType, ifcond: Iterable[str],
>> +                 comment: Optional[str] = None):
>> +        self.value = value
>> +        self.comment: Optional[str] = comment
>> +        self.ifcond: Sequence[str] = tuple(ifcond)
>>   
>>   
>> -def _tree_to_qlit(obj: TreeValue,
>> -                  level: int = 0,
>> +def _tree_to_qlit(obj: TreeValue, level: int = 0,
>>                     suppress_first_indent: bool = False) -> str:
>>   
>>       def indent(level: int) -> str:
>>           return level * 4 * ' '
>>   
>> -    if isinstance(obj, tuple):
>> -        ifobj, extra = obj
>> -        ifcond = cast(Optional[Sequence[str]], extra.get('if'))
>> -        comment = extra.get('comment')
>> -
>> +    if isinstance(obj, Annotated):
>>           msg = "Comments and Conditionals not implemented for dict values"
>> -        assert not (suppress_first_indent and (ifcond or comment)), msg
>> +        assert not (suppress_first_indent and (obj.comment or obj.ifcond)), msg
>>   
>>           ret = ''
>> -        if comment:
>> -            ret += indent(level) + '/* %s */\n' % comment
>> -        if ifcond:
>> -            ret += gen_if(ifcond)
>> -        ret += _tree_to_qlit(ifobj, level, suppress_first_indent)
>> -        if ifcond:
>> -            ret += '\n' + gen_endif(ifcond)
>> +        if obj.comment:
>> +            ret += indent(level) + '/* %s */\n' % obj.comment
>> +        if obj.ifcond:
>> +            ret += gen_if(obj.ifcond)
>> +        ret += _tree_to_qlit(obj.value, level, suppress_first_indent)
>> +        if obj.ifcond:
>> +            ret += '\n' + gen_endif(obj.ifcond)
>>           return ret
>>   
>>       ret = ''
>> @@ -153,7 +152,7 @@ def __init__(self, prefix: str, unmask: bool):
>>               ' * QAPI/QMP schema introspection', __doc__)
>>           self._unmask = unmask
>>           self._schema: Optional[QAPISchema] = None
>> -        self._trees: List[Annotated] = []
>> +        self._trees: List[Annotated[_DObject]] = []
>>           self._used_types: List[QAPISchemaType] = []
>>           self._name_map: Dict[str, str] = {}
>>           self._genc.add(mcgen('''
>> @@ -219,10 +218,9 @@ def _use_type(self, typ: QAPISchemaType) -> str:
>>           return self._name(typ.name)
>>   
>>       @classmethod
>> -    def _gen_features(cls,
>> -                      features: List[QAPISchemaFeature]
>> -                      ) -> List[Annotated]:
>> -        return [_make_tree(f.name, f.ifcond) for f in features]
>> +    def _gen_features(
>> +            cls, features: List[QAPISchemaFeature]) -> List[Annotated[str]]:
> 
> Indent this way from the start for lesser churn.
> 

OK

>> +        return [Annotated(f.name, f.ifcond) for f in features]
>>   
>>       def _gen_tree(self, name: str, mtype: str, obj: _DObject,
>>                     ifcond: List[str],
>> @@ -238,10 +236,10 @@ def _gen_tree(self, name: str, mtype: str, obj: _DObject,
>>           obj['meta-type'] = mtype
>>           if features:
>>               obj['features'] = self._gen_features(features)
>> -        self._trees.append(_make_tree(obj, ifcond, comment))
>> +        self._trees.append(Annotated(obj, ifcond, comment))
>>   
>>       def _gen_member(self,
>> -                    member: QAPISchemaObjectTypeMember) -> Annotated:
>> +                    member: QAPISchemaObjectTypeMember) -> Annotated[_DObject]:
> 
> Long line.  Ty hanging indent.
> 

OK. Admittedly, I hate hanging the return argument, I think it looks 
bad. Worst part of python types. :(

>>           obj: _DObject = {
>>               'name': member.name,
>>               'type': self._use_type(member.type)
>> @@ -250,19 +248,19 @@ def _gen_member(self,
>>               obj['default'] = None
>>           if member.features:
>>               obj['features'] = self._gen_features(member.features)
>> -        return _make_tree(obj, member.ifcond)
>> +        return Annotated(obj, member.ifcond)
>>   
>>       def _gen_variants(self, tag_name: str,
>>                         variants: List[QAPISchemaVariant]) -> _DObject:
>>           return {'tag': tag_name,
>>                   'variants': [self._gen_variant(v) for v in variants]}
>>   
>> -    def _gen_variant(self, variant: QAPISchemaVariant) -> Annotated:
>> +    def _gen_variant(self, variant: QAPISchemaVariant) -> Annotated[_DObject]:
>>           obj: _DObject = {
>>               'case': variant.name,
>>               'type': self._use_type(variant.type)
>>           }
>> -        return _make_tree(obj, variant.ifcond)
>> +        return Annotated(obj, variant.ifcond)
>>   
>>       def visit_builtin_type(self, name: str, info: Optional[QAPISourceInfo],
>>                              json_type: str) -> None:
>> @@ -272,10 +270,11 @@ def visit_enum_type(self, name: str, info: QAPISourceInfo,
>>                           ifcond: List[str], features: List[QAPISchemaFeature],
>>                           members: List[QAPISchemaEnumMember],
>>                           prefix: Optional[str]) -> None:
>> -        self._gen_tree(name, 'enum',
>> -                       {'values': [_make_tree(m.name, m.ifcond, None)
>> -                                   for m in members]},
>> -                       ifcond, features)
>> +        self._gen_tree(
>> +            name, 'enum',
>> +            {'values': [Annotated(m.name, m.ifcond) for m in members]},
>> +            ifcond, features
>> +        )
>>   
>>       def visit_array_type(self, name: str, info: Optional[QAPISourceInfo],
>>                            ifcond: List[str],
>> @@ -300,12 +299,12 @@ def visit_alternate_type(self, name: str, info: QAPISourceInfo,
>>                                ifcond: List[str],
>>                                features: List[QAPISchemaFeature],
>>                                variants: QAPISchemaVariants) -> None:
>> -        self._gen_tree(name, 'alternate',
>> -                       {'members': [
>> -                           _make_tree({'type': self._use_type(m.type)},
>> -                                      m.ifcond, None)
>> -                           for m in variants.variants]},
>> -                       ifcond, features)
>> +        self._gen_tree(
>> +            name, 'alternate',
>> +            {'members': [Annotated({'type': self._use_type(m.type)}, m.ifcond)
> 
> Long line.  Try breaking the line before m.ifcond, or before Annotated.
> 

OK.

>> +                         for m in variants.variants]},
>> +            ifcond, features
>> +        )
>>   
>>       def visit_command(self, name: str, info: QAPISourceInfo, ifcond: List[str],
>>                         features: List[QAPISchemaFeature],



  reply	other threads:[~2020-12-08  0:23 UTC|newest]

Thread overview: 48+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-10-26 19:42 [PATCH v2 00/11] qapi: static typing conversion, pt2 John Snow
2020-10-26 19:42 ` [PATCH v2 01/11] [DO-NOT-MERGE] docs: replace single backtick (`) with double-backtick (``) John Snow
2020-10-26 19:42 ` [PATCH v2 02/11] [DO-NOT-MERGE] docs/sphinx: change default role to "any" John Snow
2020-10-26 19:42 ` [PATCH v2 03/11] [DO-NOT-MERGE] docs: enable sphinx-autodoc for scripts/qapi John Snow
2020-10-26 19:42 ` [PATCH v2 04/11] qapi/introspect.py: add assertions and casts John Snow
2020-11-06 18:59   ` Cleber Rosa
2020-10-26 19:42 ` [PATCH v2 05/11] qapi/introspect.py: add preliminary type hint annotations John Snow
2020-11-07  2:12   ` Cleber Rosa
2020-12-07 21:29     ` John Snow
2020-11-13 16:48   ` Markus Armbruster
2020-12-07 23:48     ` John Snow
2020-12-16  7:51       ` Markus Armbruster
2020-12-16 17:49         ` Eduardo Habkost
2020-12-17  6:51           ` Markus Armbruster
2020-12-17  1:35         ` John Snow
2020-12-17  7:00           ` Markus Armbruster
2020-10-26 19:42 ` [PATCH v2 06/11] qapi/introspect.py: add _gen_features helper John Snow
2020-11-07  4:23   ` Cleber Rosa
2020-11-16  8:47   ` Markus Armbruster
2020-12-07 23:57     ` John Snow
2020-12-15 16:55       ` Markus Armbruster
2020-12-15 18:49         ` John Snow
2020-10-26 19:42 ` [PATCH v2 07/11] qapi/introspect.py: Unify return type of _make_tree() John Snow
2020-11-07  5:08   ` Cleber Rosa
2020-12-15  0:22     ` John Snow
2020-11-16  9:46   ` Markus Armbruster
2020-12-08  0:06     ` John Snow
2020-12-16  6:35       ` Markus Armbruster
2020-10-26 19:42 ` [PATCH v2 08/11] qapi/introspect.py: replace 'extra' dict with 'comment' argument John Snow
2020-11-07  5:10   ` Cleber Rosa
2020-11-16  9:55   ` Markus Armbruster
2020-12-08  0:12     ` John Snow
2020-10-26 19:42 ` [PATCH v2 09/11] qapi/introspect.py: create a typed 'Annotated' data strutcure John Snow
2020-11-07  5:45   ` Cleber Rosa
2020-11-16 10:12   ` Markus Armbruster
2020-12-08  0:21     ` John Snow [this message]
2020-12-16  7:08       ` Markus Armbruster
2020-12-17  1:30         ` John Snow
2020-10-26 19:42 ` [PATCH v2 10/11] qapi/introspect.py: improve readability of _tree_to_qlit John Snow
2020-11-07  5:54   ` Cleber Rosa
2020-11-16 10:17   ` Markus Armbruster
2020-12-15 15:25     ` John Snow
2020-10-26 19:42 ` [PATCH v2 11/11] qapi/introspect.py: Add docstring to _tree_to_qlit John Snow
2020-11-07  5:57   ` Cleber Rosa
2020-11-02 15:40 ` [PATCH v2 00/11] qapi: static typing conversion, pt2 John Snow
2020-11-04  9:51   ` Marc-André Lureau
2020-12-15 15:52     ` John Snow
2020-11-16 13:17 ` introspect.py output representation (was: [PATCH v2 00/11] qapi: static typing conversion, pt2) Markus Armbruster

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=b27f7930-d86b-8357-84e4-7daef00023d7@redhat.com \
    --to=jsnow@redhat.com \
    --cc=armbru@redhat.com \
    --cc=crosa@redhat.com \
    --cc=ehabkost@redhat.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 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).