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

Refactor the webservice code #753

Merged
merged 14 commits into from Jul 10, 2017
Merged

Refactor the webservice code #753

merged 14 commits into from Jul 10, 2017

Conversation

samj1912
Copy link
Collaborator

No description provided.

@samj1912 samj1912 requested review from zas, mineo and phw June 24, 2017 10:29
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@metabrainz metabrainz deleted a comment Jun 24, 2017
@samj1912 samj1912 force-pushed the wsref branch 3 times, most recently from 1bf7eb3 to 1641d8c Compare June 24, 2017 11:53
@Sophist-UK
Copy link
Contributor

@samj1912 Are you planning a parallel PR to change any plugins which use webservices?

@samj1912
Copy link
Collaborator Author

@Sophist-UK Yeah, I will be porting the plugins completely to be Picard 2.0 compatible.

@@ -150,6 +79,8 @@ def __init__(self, parent=None):
}
self._init_queues()
self._init_timers()
self.format_parser = None
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 the default parser should be a function that raises an exception (because if it's called, you've passed parse_format=True to some function on an object that's not configured for that).

(Nitpicking:) I also find the name format in this context not good, it actually refers to the either the content type or (in the case of the parse_format argument) the body of the response. Maybe just parser and parse_response or parse_response_body are better?



class XmlWebService(QtCore.QObject):
class WebService(QtCore.QObject):
Copy link
Member

Choose a reason for hiding this comment

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

Is it a good idea to provide multiple WebService implementations? Each one would have its own set of queues, which I do find a bit weird...

queryargs=queryargs)


class MBAPIHelper():
Copy link
Member

Choose a reason for hiding this comment

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

Why not make this one and the acoustid one simply inherit from APIHelper?

@samj1912
Copy link
Collaborator Author

samj1912 commented Jul 9, 2017

@mineo changes done

response_parser = self.response_parser(parse_response_format)
try:
document = response_parser(reply)
except Exception as e:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should have a specific exception class here (ExceptionResponseParserUnknown?)

if name in cls.PARSERS:
return cls.PARSERS[name]['parser']
else:
log.error('Parser of type %s not found', name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Raise ExceptionResponseParserUnknown here

from picard import (PICARD_APP_NAME,
PICARD_ORG_NAME,
PICARD_VERSION_STR,
config,
log)
from picard.const import (ACOUSTID_KEY,
ACOUSTID_HOST,
from picard.const import (ACOUSTID_HOST,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Those are now only used for request delays (REQUEST_DELAY dict below), i think it can be moved to API helpers as well. Not sure exactly how though ;)

CAA_HOST,
CAA_PORT)

from picard.webservice import PICARD_VERSION_STR, CLIENT_STRING, REQUEST_DELAY
Copy link
Collaborator

Choose a reason for hiding this comment

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

PICARD_VERSION_STR should be imported from picard

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

@zas zas merged commit 333b249 into metabrainz:master Jul 10, 2017
@@ -60,79 +54,16 @@
PICARD_APP_NAME,
PICARD_VERSION_STR)))

DEFAULT_RESPONSE_PARSER = "xml"
Copy link
Member

Choose a reason for hiding this comment

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

I have to admit that the name "Parser" always suggests to me that the thing in question itself is able to parse something.

response_parser = self.response_parser(parse_response_format)
try:
document = response_parser(reply)
except UnknownResponseParserError 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.

Isn't it the call to self.response_parser above that would raise this exception?


class UnknownResponseParserError(Exception):
Copy link
Member

Choose a reason for hiding this comment

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

It would make sense to have this exception carry at least information about which parser is unknown.

if xml:
request.setRawHeader(b"Accept", b"application/xml")
if parse_response_format:
request.setRawHeader(b"Accept", self.response_header(parse_response_format).encode('utf-8'))
Copy link
Member

Choose a reason for hiding this comment

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

For both the Accept and Content-Type headers it's more common to not refer to a format, but MIME or media types (see https://tools.ietf.org/html/rfc7231#section-3.1.1).


@classmethod
def add_parser(cls, name, header, parser):
cls.PARSERS[name] = {'header': header, 'parser': parser}
Copy link
Member

Choose a reason for hiding this comment

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

For other cases where there's only a small set of dict keys (some in script.py and searchdialog.py) we're using collections.namedtuple.

cls.PARSERS[name] = {'header': header, 'parser': parser}

@classmethod
def response_header(cls, name):
Copy link
Member

Choose a reason for hiding this comment

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

See above somewhere, this returns the mime type, not a complete header.

host, port = ACOUSTID_HOST, ACOUSTID_PORT
body = self._encode_acoustid_args(args, format='json')
return self.post(host, port, '/v2/submit', body, handler, priority=True, important=False, mblogin=False)
log.error('Parser of type %s not found', name)
Copy link
Member

Choose a reason for hiding this comment

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

This will implicitly return None if the parser is not found (unlike response_parser, which raises an exception). Since this method is called earlier during the lifetime of an HTTP request, wouldn't it make sense to signal that error here already with something more than a log message?

'<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">%s</metadata>' % data)


class APIHelper(object):
Copy link
Member

Choose a reason for hiding this comment

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

Deriving from object is not necessary in Python 3 anymore (but I fail to find the documentation about it everytime I look for it)

import unittest
from picard import config
from picard.webservice import WebService
from mock import patch, MagicMock
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 a bit surprised the test passes on travis, the mock module included with Python 3 is only available as unittest.mock?

mock_add_task.assert_called()
self.assertIn(host, mock_add_task.call_args[0])
self.assertIn(port, mock_add_task.call_args[0])
self.assertIn("'GET'", repr(mock_add_task.call_args))
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 100% sure there's a better way to test this than looking at the repr of call_args :-) self.assertIn("GET", mock_add_task.call_args[0][0].args) will do it, for example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants