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

feat: add copy_from method for field assignment #215

Merged
merged 3 commits into from
Mar 16, 2021

Conversation

software-dov
Copy link
Contributor

Because of implementation detatils of the underlying protocol buffers
runtime, assigning to a proto-plus message field is achieved by
copying, not by updating references. This can lead to surprising and
unintuitive behavior for developers who are expecting python object
behavior, e.g. reference aliases.

This PR adds a 'Message.copy_from' method that is semantically
identical to regular assignment. This can be used at the discretion of
the developer to clarify expected behavior.

@google-cla google-cla bot added the cla: yes This human has signed the Contributor License Agreement. label Mar 15, 2021
@codecov
Copy link

codecov bot commented Mar 15, 2021

Codecov Report

Merging #215 (f44ca4e) into master (6a43682) will not change coverage.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff            @@
##            master      #215   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           20        20           
  Lines          862       895   +33     
  Branches       149       163   +14     
=========================================
+ Hits           862       895   +33     
Impacted Files Coverage Δ
proto/modules.py 100.00% <ø> (ø)
proto/enums.py 100.00% <100.00%> (ø)
proto/marshal/collections/repeated.py 100.00% <100.00%> (ø)
proto/marshal/marshal.py 100.00% <100.00%> (ø)
proto/message.py 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 521f33d...f44ca4e. Read the comment docs.

Because of implementation detatils of the underlying protocol buffers
runtime, assigning to a proto-plus message field is achieved by
copying, not by updating references. This can lead to surprising and
unintuitive behavior for developers who are expecting python object
behavior, e.g. reference aliases.

This PR adds a 'Message.copy_from' method that is semantically
identical to regular assignment. This can be used at the discretion of
the developer to clarify expected behavior.

:class:`proto.Message` defines a helper message, :meth:`~.Message.copy_from` to
help make the distinction clear when reading code.
The semantics of :meth:`~.Message.copy_from` are addentical to regular assignment.

Choose a reason for hiding this comment

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

addentical should be identical

And perhaps ... identical to the field assignment behavior described above. would help clarify, as "regular assignment" could imply the behavior most would expect outside the context of protobufs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doh! Good catch. Yes, the proposed wording is more clear.

proto/message.py Outdated
"""
if isinstance(other, type(self)):
# Just want the underlying proto.
other = Message.pb(other)

Choose a reason for hiding this comment

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

This is equivalent to doing other = other._pb right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it is equivalent. The code (and documentation) tries to use the accessor instead of touching the attribute directly.

Choose a reason for hiding this comment

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

👍🏻

proto/message.py Outdated
if isinstance(other, type(self)):
# Just want the underlying proto.
other = Message.pb(other)
elif isinstance(other, type(Message.pb(self))):

Choose a reason for hiding this comment

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

Why does it pass in this scenario?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because there's no transformation that needs to occur. It's just here to catch the input type and make sure that it doesn't generate the type mismatch exception below.

Choose a reason for hiding this comment

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

👍🏻

proto/message.py Outdated
elif isinstance(other, type(Message.pb(self))):
# Don't need to do anything.
pass
elif isinstance(other, collections.abc.Mapping):

Choose a reason for hiding this comment

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

Is this is in case of a repeated field?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's to catch the dictionary syntax:

Message.copy_from(message.submessage, {"name": "Lawrence", "armor_class": 15})

Choose a reason for hiding this comment

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

Oh good to know that's possible!


m = Mollusc()
s = Mollusc.Squid(mass_kg=20)
Mollusc.Squid.copy_from(m.squid, s)

Choose a reason for hiding this comment

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

The reason we can't have s.squid.copy_from is because of potential collisions with the proto definition, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct. The general problem is that we have to be very, very careful about adding fields and methods to the message class types because of potential name collisions with fields, and the general solution is to add the method (or field) to the metaclass. This means that method invocation becomes a little more indirect: we invoke it from the class and pass in the instance as the first parameter.

Choose a reason for hiding this comment

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

👍🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

= should have transitive assignment for nested fields, not copying behavior
3 participants