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

Add abbility to serialize and deserialize messages #84

Conversation

sebwoj
Copy link
Contributor

@sebwoj sebwoj commented Sep 25, 2018

Signed-off-by: Sebastian Wojciechowski s.wojciechowski89@gmail.com

@sebwoj sebwoj requested a review from a team as a code owner September 25, 2018 19:58
@sebwoj sebwoj force-pushed the Add_abbility_to_serialize_and_deserialize_messages branch from f511800 to c827d42 Compare September 25, 2018 20:31
@codecov-io
Copy link

codecov-io commented Sep 25, 2018

Codecov Report

Merging #84 into master will increase coverage by 0.18%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #84      +/-   ##
==========================================
+ Coverage   97.19%   97.38%   +0.18%     
==========================================
  Files          11       11              
  Lines         927      993      +66     
  Branches      127      129       +2     
==========================================
+ Hits          901      967      +66     
  Misses         17       17              
  Partials        9        9
Impacted Files Coverage Δ
fedora_messaging/message.py 100% <100%> (ø) ⬆️

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 337f04c...241304a. Read the comment docs.

Copy link
Member

@jeremycline jeremycline left a comment

Choose a reason for hiding this comment

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

Thanks for this. I left a lot of comments, but none of them are major.

str: Serialized messages.

Raises:
MsgDumpError: If at least one message is not instance of Message class or subclass, or
Copy link
Member

Choose a reason for hiding this comment

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

I think it'd be best to not have our own exception class for this. Since the messages can't pass validation if they're not valid JSON, the programmer has messed up here and shouldn't be handling this sort of exception. Best to let them just get a JSON encoding error directly.

serialized_messages.append(message_json)
except AttributeError:
_log.error("Improper object for messages serialization.")
raise MsgDumpError("Message have to be instance of Message class or subclass.")
Copy link
Member

Choose a reason for hiding this comment

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

Probably best to just raise a "TypeError" here


try:
return json.dumps(serialized_messages)
except Exception as e:
Copy link
Member

Choose a reason for hiding this comment

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

Since I recommend we don't raise a MsgDumpError, you can take this out of the try/except. Just as a note, though, it's best not to except Exception unless you really do mean to handle all exceptions (often done at the entry point to a program). Here we only would want to handle JSON-related errors since that makes it explicit what problems we're expecting to encounter here.

list: Deserialized message objects.

Raises:
MsgLoadError: Loading serialized from JSON is imposiible.
Copy link
Member

Choose a reason for hiding this comment

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

Same note here about just letting json raise its exception as above

serialized_messages = []
try:
for message in messages:
message_json = message.dump()
Copy link
Member

Choose a reason for hiding this comment

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

dump() returns a dictionary so this variable name is a bit confusing - a reader might think this is actually a JSON string.

"""
try:
deserialized_messages = [
message_str for message_str in json.loads(serialized_messages)
Copy link
Member

Choose a reason for hiding this comment

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

Since we dumped an array, shouldn't this just be message_dicts = json.loads(serialized_messages)?

try:
headers = msg_json["headers"]
except KeyError:
_log.error("Message saved without headers.")
Copy link
Member

Choose a reason for hiding this comment

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

I'm inclined to say this should just be an exception - whatever got handed in is not in the format we expect.

try:
MessageClass = get_class(headers["fedora_messaging_schema"])
except KeyError:
_log.error("Message (headers=%r) saved without a schema header.", headers)
Copy link
Member

Choose a reason for hiding this comment

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

Again, just let this be an exception. Whatever we got aren't properly serialized messages.

Returns:
dict: A dictionary of messge attributes.
"""
return {
Copy link
Member

Choose a reason for hiding this comment

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

It'd probably be nice to save the message's id as well, so test writers who want to use this functionality can rely on stable message ids.

@sebwoj sebwoj force-pushed the Add_abbility_to_serialize_and_deserialize_messages branch from c827d42 to b711de7 Compare September 27, 2018 21:00
@sebwoj
Copy link
Contributor Author

sebwoj commented Sep 27, 2018

PR is updated.
Do you think that exceptions generated by json.dumps(ValueError, TypeError and OverflowError) in
f-m.message.dumps should be mentioned in method docstring?

@sebwoj sebwoj force-pushed the Add_abbility_to_serialize_and_deserialize_messages branch from b711de7 to ca2fa95 Compare September 27, 2018 21:10
Copy link
Member

@jeremycline jeremycline left a comment

Choose a reason for hiding this comment

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

Sorry about leaving this so long, I've been neglecting my code review emails!

There are a few more cases where I don't think we should handle errors and set defaults on loading, but otherwise I think this looks solid. Can you write unit tests for the new functions, as well?

Thanks!


try:
body = message_dict["body"]
except KeyError:
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should default this to something, just let the KeyError propagate.

try:
id = message_dict["id"]
except KeyError:
_log.error("Message saved without id.")
Copy link
Member

Choose a reason for hiding this comment

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

Also here, messages should always have an id so this should just let the KeyError get raised.

queue = message_dict["queue"]
except KeyError:
_log.error("Message saved without queue.")
queue = {}
Copy link
Member

Choose a reason for hiding this comment

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

This should probably default to None

try:
topic = message_dict["topic"]
except KeyError:
_log.error("Message saved without body.")
Copy link
Member

Choose a reason for hiding this comment

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

This should just let the KeyError get raised

try:
severity = headers["fedora_messaging_severity"]
except KeyError:
_log.error("Message saved without a severity. Defaulting to INFO.")
Copy link
Member

Choose a reason for hiding this comment

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

I think we should let the KeyError raise here as well. The reason the other code defaults it is because right now all the messages are being sent in fedmsg without it, so we fix things up for that.

raise ValidationError(e)

message.queue = queue
if id:
Copy link
Member

Choose a reason for hiding this comment

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

If we assume id will always be set, you can drop this if

Signed-off-by: Sebastian Wojciechowski <s.wojciechowski89@gmail.com>
Signed-off-by: Sebastian Wojciechowski <s.wojciechowski89@gmail.com>
@sebwoj sebwoj force-pushed the Add_abbility_to_serialize_and_deserialize_messages branch from ca2fa95 to 17b16c4 Compare October 11, 2018 15:20
@sebwoj
Copy link
Contributor Author

sebwoj commented Oct 11, 2018

@jeremycline Do you know how to fix "MessageLoadsTests.test_validation_failure"?
I was struggling with this, but i cant find out what is wrong.

Don't rely on the equality check implementation of ValidationError

Signed-off-by: Jeremy Cline <jcline@redhat.com>
@jeremycline
Copy link
Member

@sebwoj, hey, my guess is the equality method on the exceptions isn't implemented. I don't recall what Python does off the top of my head, but doing Exception() == Exception() results in False so it could just be checking to see if they are the same instance of the object. I added a commit to just check the message. It looks like there are a couple other test failures, though.

Signed-off-by: Sebastian Wojciechowski <s.wojciechowski89@gmail.com>
Signed-off-by: Sebastian Wojciechowski <s.wojciechowski89@gmail.com>
Copy link
Member

@jeremycline jeremycline left a comment

Choose a reason for hiding this comment

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

There's a minor spelling mistake and I'm curious about your thoughts on the interface, but this looks really good.

Dump message attributes.

Returns:
dict: A dictionary of messge attributes.
Copy link
Member

Choose a reason for hiding this comment

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

"messge" -> "message"

@@ -510,3 +618,18 @@ def flatpaks(self):
list(str): A list of affected flatpaks names.
"""
return []

def dump(self):
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if this is going to be an API most people want to use, or if they should only use the "dumps" and "loads" interfaces outside the class. Maybe we should mark this as private (call it _dump) initially so we don't pollute the namespace with stuff most people won't want to use. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As far as I know(c++ exp) private methods are not available outside the class(except friends). So, if we are using "Message.dump" in "dumps" function, it means that we have to allow access from outside. I don't have a big experience in python and I know that underscore(even dunder) can't prevent clients from access private methods, so I'm not sure about final solution. I would like to let you decide in this topic.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, yes, Python has no way to force clients to not touch internals of objects, it's more like guidelines. I think we should make it private (to begin with, anyway) since I can't think of a reason people should be using it.

Signed-off-by: Sebastian Wojciechowski <s.wojciechowski89@gmail.com>
Copy link
Member

@jeremycline jeremycline left a comment

Choose a reason for hiding this comment

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

✨ 🍰 ✨

@mergify mergify bot merged commit 3a1f528 into fedora-infra:master Oct 18, 2018
@sebwoj sebwoj deleted the Add_abbility_to_serialize_and_deserialize_messages branch October 23, 2018 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants