Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for recursive messages #130

Merged
merged 9 commits into from
Aug 30, 2020

Conversation

chris-chambers
Copy link
Contributor

@chris-chambers chris-chambers commented Jul 26, 2020

Changes message initialization (__post_init__) so that default values are no longer eagerly created to prevent infinite recursion when initializing recursive messages.

As a result, PLACEHOLDER will be present in the message for any uninitialized fields. So, an implementation of __get_attribute__ is added that checks for PLACEHOLDER and lazily creates and stores default field values.

And, because PLACEHOLDER values don't compare equal with zero values, a custom implementation of __eq__ is provided, and the code generation template is updated so that messages generate with @dataclass(eq=False, repr=False).

Fixes #13 and fixes #74.


This needs careful review! I'm not sure if this is the desired approach. It's just the least-friction way I could think of.

@chris-chambers
Copy link
Contributor Author

It looks like this may also fix #63. But, since I wasn't trying to do that, it should get some extra review.

if value is PLACEHOLDER:
continue
found = True
parts.extend([field_name, "=", repr(value), ","])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a space after this comma, to match PEP8

Copy link
Collaborator

@Gobot1234 Gobot1234 Jul 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to insert the part using an f-string and join them using ", " to avoid this.

On another note, since this is for constructing the class from the repr you should probably also check if the field is an Enum and then use Enum.__name__.value.name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow this:

On another note, since this is for constructing the class from the repr you should probably also check if the field is an Enum and then use Enum.name.value.name

repr isn't generally meant to be round-trippable. For example:

from dataclasses import dataclass
from enum import Enum

class Color(Enum):
    RED = 1
    BLUE = 2

@dataclass
class Foo(object):
    color: Color

repr(Foo(Color.RED)) # => "Foo(color=<Color.RED: 1>)"

This implementation of __repr__ gives a similar result to what the implementation provided by @dataclass gives.

Copy link
Collaborator

@Gobot1234 Gobot1234 Jul 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the python docs (https://docs.python.org/3/reference/datamodel.html#object.__repr__) where possible an object's __repr__ should allow for x = eval(repr(x)) where possible, with the current implementation it would not be possible to do this, so I was suggesting that Enum fields should be converted to there Enum class name and member name (the default for str(Enum)) to allow for this.

Copy link
Contributor Author

@chris-chambers chris-chambers Jul 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with your interpretation of the docs, but already the built in enum.Enum does not follow this convention. I'm just delegating to each field's existing __repr__ implementation, which is as much as we should do, in my opinion. I don't think Message's implementation of __repr__ should inspect its field's types and change its behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized that betterproto generates enums that inherit from betterproto.Enum, not enum.Enum. I apologize for my confusion.

Given that, it would be possible to implement betterproto.Enum.__repr__ and get the behavior you're talking about. And it seems like a perfectly good thing to do. It's independent of this PR, though, and would have the same benefit whether it lands before or after this, so I still wouldn't want to tie the two changes together.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also in favor of keeping general improvements out of scope of bugfix PR's, unless they are essential

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't suggesting altering the betterproto.Enum.__repr__ but altering Message.__repr__ to use enum.Enum.__str__ instead to follow the standard.

@chris-chambers
Copy link
Contributor Author

Sorry, while these PRs have been in review, I discovered that the encoding and decoding latency of betterproto is too high for my use-case, so I need to put my efforts into another solution.

Please, feel free to make whatever changes you wish, if you want to incorporate these changes. I enabled direct editing of the PR by contributors, so it should be easy to make changes. Or, if you prefer to close this and take another approach to solving this problem, that works for me.

@aleneum
Copy link

aleneum commented Aug 19, 2020

Hi @chris-chambers,

thank you for your effort nevertheless! I am in need of this feature. Is there something that could be done by me before this PR is ready to be merged (@Gobot1234)?

@Gobot1234
Copy link
Collaborator

It LGTM.

@Gobot1234
Copy link
Collaborator

I can also fix the conflicts at some point soon-ish

@chris-chambers
Copy link
Contributor Author

I've got time this afternoon, so I'll rebase and fix the merge conflict. So no need, @Gobot1234

@aleneum
Copy link

aleneum commented Aug 20, 2020

just fyi: I forked master, rebased and then merged the PR. There was one issue in the template where {% for message in description.messages %} wasnt correctly updated (to output_file) but this seems to be fixed already. Parsing a rather complex message with a redundant item composition no longer caused recursion errors for me.

Gobot1234 added a commit to Gobot1234/python-betterproto that referenced this pull request Aug 24, 2020
Gobot1234 added a commit to Gobot1234/python-betterproto that referenced this pull request Aug 24, 2020
Gobot1234 added a commit to Gobot1234/python-betterproto that referenced this pull request Aug 30, 2020
chris-chambers and others added 9 commits August 30, 2020 19:04
Changes message initialization (`__post_init__`) so that default values
are no longer eagerly created to prevent infinite recursion when
initializing recursive messages.

As a result, `PLACEHOLDER` will be present in the message for any
uninitialized fields.  So, an implementation of `__get_attribute__` is
added that checks for `PLACEHOLDER` and lazily creates and stores
default field values.

And, because `PLACEHOLDER` values don't compare equal with zero values,
a custom implementation of `__eq__` is provided, and the code generation
template is updated so that messages generate with `@dataclass(eq=False)`.
- Uses `is not` to compare types in `Message.__eq__`
- Adds a space after each comma in `Message.__repr__`
Co-authored-by: James <50501825+Gobot1234@users.noreply.github.com>
Co-authored-by: James <50501825+Gobot1234@users.noreply.github.com>
- Add more test cases using the standard test pattern
- Add sorted_field_names to message subclass metadata to support stable ordering
  of keys in repr.
- Tweak code in new message methods
@nat-n nat-n force-pushed the recursive-messages branch from 9e8fbbc to 2af6f63 Compare August 30, 2020 18:57
@nat-n
Copy link
Collaborator

nat-n commented Aug 30, 2020

Thanks to @chris-chambers et al. for the good work on this. Sorry for the slow turn around.

I pushed some final changes:

  • Rebase onto master
  • Add more test cases using the standard test pattern
  • Tweak some of the new code
  • Ensure repr keys are consistently ordered by field number

Ready to merge I'd say 👍

@nat-n nat-n merged commit 034e2e7 into danielgtaylor:master Aug 30, 2020
@abn abn mentioned this pull request Nov 24, 2020
cetanu pushed a commit that referenced this pull request Aug 1, 2022
* Implement Message.__bool__ for #130
* Add __bool__ to special members
* Tweak __bool__ docstring
* remove compiler: prefix

Co-authored-by: nat <n@natn.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants