From c819580ce872f251e8ec23deee95d9fb15ca19c9 Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 6 Sep 2013 10:51:13 +0200 Subject: [PATCH] Get status of Github API (#188) --- github/MainClass.py | 44 +++++++++++++ github/Requester.py | 24 ++++--- github/Status.py | 56 ++++++++++++++++ github/StatusMessage.py | 66 +++++++++++++++++++ github/tests/AllTests.py | 1 + .../ReplayData/Status.testGetLastMessage.txt | 11 ++++ .../ReplayData/Status.testGetMessages.txt | 11 ++++ .../tests/ReplayData/Status.testGetStatus.txt | 11 ++++ github/tests/Status.py | 43 ++++++++++++ 9 files changed, 257 insertions(+), 10 deletions(-) create mode 100644 github/Status.py create mode 100644 github/StatusMessage.py create mode 100644 github/tests/ReplayData/Status.testGetLastMessage.txt create mode 100644 github/tests/ReplayData/Status.testGetMessages.txt create mode 100644 github/tests/ReplayData/Status.testGetStatus.txt create mode 100644 github/tests/Status.py diff --git a/github/MainClass.py b/github/MainClass.py index 2b1e298d..a4af1a61 100644 --- a/github/MainClass.py +++ b/github/MainClass.py @@ -40,6 +40,8 @@ import github.GithubObject import HookDescription import GitignoreTemplate import Notification +import Status +import StatusMessage DEFAULT_BASE_URL = "https://api.github.com" @@ -369,3 +371,45 @@ class Github(object): :return: the unpickled object """ return self.create_from_raw_data(*pickle.load(f)) + + def get_api_status(self): + """ + This doesn't work with a Github Enterprise installation, because it always targets https://status.github.com. + + :calls: `GET /api/status.json `_ + :rtype: :class:`github.Status.Status` + """ + headers, attributes = self.__requester.requestJsonAndCheck( + "GET", + "/api/status.json", + cnx="status" + ) + return Status.Status(self.__requester, headers, attributes, completed=True) + + def get_last_api_status_message(self): + """ + This doesn't work with a Github Enterprise installation, because it always targets https://status.github.com. + + :calls: `GET /api/last-message.json `_ + :rtype: :class:`github.StatusMessage.StatusMessage` + """ + headers, attributes = self.__requester.requestJsonAndCheck( + "GET", + "/api/last-message.json", + cnx="status" + ) + return StatusMessage.StatusMessage(self.__requester, headers, attributes, completed=True) + + def get_api_status_messages(self): + """ + This doesn't work with a Github Enterprise installation, because it always targets https://status.github.com. + + :calls: `GET /api/messages.json `_ + :rtype: list of :class:`github.StatusMessage.StatusMessage` + """ + headers, data = self.__requester.requestJsonAndCheck( + "GET", + "/api/messages.json", + cnx="status" + ) + return [StatusMessage.StatusMessage(self.__requester, headers, attributes, completed=True) for attributes in data] diff --git a/github/Requester.py b/github/Requester.py index 670d66b6..836b776e 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -164,8 +164,8 @@ class Requester: 'See http://developer.github.com/v3/#user-agent-required' self.__userAgent = user_agent - def requestJsonAndCheck(self, verb, url, parameters=None, headers=None, input=None): - return self.__check(*self.requestJson(verb, url, parameters, headers, input)) + def requestJsonAndCheck(self, verb, url, parameters=None, headers=None, input=None, cnx=None): + return self.__check(*self.requestJson(verb, url, parameters, headers, input, cnx)) def requestMultipartAndCheck(self, verb, url, parameters=None, headers=None, input=None): return self.__check(*self.requestMultipart(verb, url, parameters, headers, input)) @@ -200,11 +200,11 @@ class Requester: except ValueError, e: return {'data': data} - def requestJson(self, verb, url, parameters=None, headers=None, input=None): + def requestJson(self, verb, url, parameters=None, headers=None, input=None, cnx=None): def encode(input): return "application/json", json.dumps(input) - return self.__requestEncode(verb, url, parameters, headers, input, encode) + return self.__requestEncode(cnx, verb, url, parameters, headers, input, encode) def requestMultipart(self, verb, url, parameters=None, headers=None, input=None): def encode(input): @@ -220,9 +220,9 @@ class Requester: encoded_input += "--" + boundary + "--" + eol return "multipart/form-data; boundary=" + boundary, encoded_input - return self.__requestEncode(verb, url, parameters, headers, input, encode) + return self.__requestEncode(None, verb, url, parameters, headers, input, encode) - def __requestEncode(self, verb, url, parameters, requestHeaders, input, encode): + def __requestEncode(self, cnx, verb, url, parameters, requestHeaders, input, encode): assert verb in ["HEAD", "GET", "POST", "PATCH", "PUT", "DELETE"] if parameters is None: parameters = dict() @@ -241,7 +241,7 @@ class Requester: self.NEW_DEBUG_FRAME(requestHeaders) - status, responseHeaders, output = self.__requestRaw(verb, url, requestHeaders, encoded_input) + status, responseHeaders, output = self.__requestRaw(cnx, verb, url, requestHeaders, encoded_input) if "x-ratelimit-remaining" in responseHeaders and "x-ratelimit-limit" in responseHeaders: self.rate_limiting = (int(responseHeaders["x-ratelimit-remaining"]), int(responseHeaders["x-ratelimit-limit"])) @@ -255,8 +255,12 @@ class Requester: return status, responseHeaders, output - def __requestRaw(self, verb, url, requestHeaders, input): - cnx = self.__createConnection() + def __requestRaw(self, cnx, verb, url, requestHeaders, input): + if cnx is None: + cnx = self.__createConnection() + else: + assert cnx == "status" + cnx = self.__httpsConnectionClass("status.github.com", 443) cnx.request( verb, url, @@ -310,7 +314,7 @@ class Requester: kwds["strict"] = True # Useless in Python3, would generate a deprecation warning if atLeastPython26: # pragma no branch (Branch useful only with Python 2.5) kwds["timeout"] = self.__timeout # Did not exist before Python2.6 - return self.__connectionClass(host=self.__hostname, port=self.__port, **kwds) + return self.__connectionClass(self.__hostname, self.__port, **kwds) def __log(self, verb, url, requestHeaders, input, status, responseHeaders, output): logger = logging.getLogger(__name__) diff --git a/github/Status.py b/github/Status.py new file mode 100644 index 00000000..6c117241 --- /dev/null +++ b/github/Status.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +############################ Copyrights and license ############################ +# # +# Copyright 2013 Vincent Jacques # +# # +# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +import github.GithubObject + + +class Status(github.GithubObject.NonCompletableGithubObject): + """ + This class represents status as defined in https://status.github.com/api + """ + + @property + def status(self): + """ + :type: string + """ + return self._NoneIfNotSet(self._status) + + @property + def last_updated(self): + """ + :type: datetime.datetime + """ + return self._NoneIfNotSet(self._last_updated) + + def _initAttributes(self): + self._status = github.GithubObject.NotSet + self._last_updated = github.GithubObject.NotSet + + def _useAttributes(self, attributes): + if "status" in attributes: # pragma no branch + assert attributes["status"] is None or isinstance(attributes["status"], (str, unicode)), attributes["status"] + self._status = attributes["status"] + if "last_updated" in attributes: # pragma no branch + assert attributes["last_updated"] is None or isinstance(attributes["last_updated"], (str, unicode)), attributes["last_updated"] + self._last_updated = self._parseDatetime(attributes["last_updated"]) diff --git a/github/StatusMessage.py b/github/StatusMessage.py new file mode 100644 index 00000000..266e1b05 --- /dev/null +++ b/github/StatusMessage.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +############################ Copyrights and license ############################ +# # +# Copyright 2013 Vincent Jacques # +# # +# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +import github.GithubObject + + +class StatusMessage(github.GithubObject.NonCompletableGithubObject): + """ + This class represents status messages as defined in https://status.github.com/api + """ + + @property + def body(self): + """ + :type: string + """ + return self._NoneIfNotSet(self._body) + + @property + def status(self): + """ + :type: string + """ + return self._NoneIfNotSet(self._status) + + @property + def created_on(self): + """ + :type: datetime.datetime + """ + return self._NoneIfNotSet(self._created_on) + + def _initAttributes(self): + self._status = github.GithubObject.NotSet + self._created_on = github.GithubObject.NotSet + + def _useAttributes(self, attributes): + if "body" in attributes: # pragma no branch + assert attributes["body"] is None or isinstance(attributes["body"], (str, unicode)), attributes["body"] + self._body = attributes["body"] + if "status" in attributes: # pragma no branch + assert attributes["status"] is None or isinstance(attributes["status"], (str, unicode)), attributes["status"] + self._status = attributes["status"] + if "created_on" in attributes: # pragma no branch + assert attributes["created_on"] is None or isinstance(attributes["created_on"], (str, unicode)), attributes["created_on"] + self._created_on = self._parseDatetime(attributes["created_on"]) diff --git a/github/tests/AllTests.py b/github/tests/AllTests.py index 5c2ce6da..4ddefece 100644 --- a/github/tests/AllTests.py +++ b/github/tests/AllTests.py @@ -56,6 +56,7 @@ from PullRequestFile import * from RateLimiting import * from Repository import * from RepositoryKey import * +from Status import * from Tag import * from Team import * from UserKey import * diff --git a/github/tests/ReplayData/Status.testGetLastMessage.txt b/github/tests/ReplayData/Status.testGetLastMessage.txt new file mode 100644 index 00000000..3d4b27e8 --- /dev/null +++ b/github/tests/ReplayData/Status.testGetLastMessage.txt @@ -0,0 +1,11 @@ +https +GET +status.github.com +443 +/api/last-message.json +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('content-length', '93'), ('vary', 'Accept-Encoding'), ('server', 'GitHub.com'), ('connection', 'keep-alive'), ('date', 'Fri, 06 Sep 2013 08:34:01 GMT'), ('content-type', 'application/json;charset=utf-8')] +{"status":"good","body":"Everything operating normally.","created_on":"2013-09-01T15:41:46Z"} + diff --git a/github/tests/ReplayData/Status.testGetMessages.txt b/github/tests/ReplayData/Status.testGetMessages.txt new file mode 100644 index 00000000..6c79412a --- /dev/null +++ b/github/tests/ReplayData/Status.testGetMessages.txt @@ -0,0 +1,11 @@ +https +GET +status.github.com +443 +/api/messages.json +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('content-length', '1492'), ('vary', 'Accept-Encoding'), ('server', 'GitHub.com'), ('connection', 'keep-alive'), ('date', 'Fri, 06 Sep 2013 08:41:31 GMT'), ('content-type', 'application/json;charset=utf-8')] +[{"status":"good","body":"Everything operating normally.","created_on":"2013-09-01T15:41:46Z"},{"status":"minor","body":"GitHub Pages are currently unavailable. We're investigating the problem.","created_on":"2013-09-01T15:26:59Z"},{"status":"good","body":"Everything operating normally.","created_on":"2013-09-01T15:17:24Z"},{"status":"minor","body":"We are investigating an increased rate of errors on GitHub.com","created_on":"2013-09-01T15:14:24Z"},{"status":"good","body":"Everything operating normally.","created_on":"2013-09-01T06:52:46Z"},{"status":"minor","body":"Some GitHub pages are again unavailable. We are continuing to investigate.","created_on":"2013-09-01T06:50:31Z"},{"status":"good","body":"Everything operating normally.","created_on":"2013-09-01T06:47:25Z"},{"status":"minor","body":"Some GitHub pages are temporarily unavailable.","created_on":"2013-09-01T06:43:03Z"},{"status":"good","body":"We're back up, now featuring a massively upgraded DB cluster with SSDs and 10Gbps networking! Thanks for your patience.","created_on":"2013-08-31T12:13:04Z"},{"status":"major","body":"We're beginning our scheduled maintenance now, and expect to be back up in 20 minutes. https://github.com/blog/1603-site-maintenance-august-31st-2013","created_on":"2013-08-31T12:00:13Z"},{"status":"good","body":"Everything operating normally.","created_on":"2013-08-31T11:45:50Z"},{"status":"minor","body":"We are investigating issues with GitHub Pages","created_on":"2013-08-31T11:43:39Z"}] + diff --git a/github/tests/ReplayData/Status.testGetStatus.txt b/github/tests/ReplayData/Status.testGetStatus.txt new file mode 100644 index 00000000..e01aec02 --- /dev/null +++ b/github/tests/ReplayData/Status.testGetStatus.txt @@ -0,0 +1,11 @@ +https +GET +status.github.com +443 +/api/status.json +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('content-length', '55'), ('vary', 'Accept-Encoding'), ('server', 'GitHub.com'), ('connection', 'keep-alive'), ('date', 'Fri, 06 Sep 2013 08:29:36 GMT'), ('content-type', 'application/json;charset=utf-8')] +{"status":"good","last_updated":"2013-09-06T08:29:27Z"} + diff --git a/github/tests/Status.py b/github/tests/Status.py new file mode 100644 index 00000000..0bb3c935 --- /dev/null +++ b/github/tests/Status.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +############################ Copyrights and license ############################ +# # +# Copyright 2013 Vincent Jacques # +# # +# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +################################################################################ + +import Framework + +import github +import datetime + + +class Status(Framework.TestCase): + def testGetStatus(self): + status = self.g.get_api_status() + self.assertEqual(status.status, "good") + self.assertEqual(status.last_updated, datetime.datetime(2013, 9, 6, 8, 29, 27)) + + def testGetLastMessage(self): + message = self.g.get_last_api_status_message() + self.assertEqual(message.status, "good") + self.assertEqual(message.body, "Everything operating normally.") + self.assertEqual(message.created_on, datetime.datetime(2013, 9, 1, 15, 41, 46)) + + def testGetMessages(self): + self.assertListKeyEqual(self.g.get_api_status_messages(), lambda m: m.status, ["good", "minor", "good", "minor", "good", "minor", "good", "minor", "good", "major", "good", "minor"])