From cd2a529740fa801ac712673eddd0de9898ccb6b2 Mon Sep 17 00:00:00 2001 From: Tyson Holub Date: Fri, 8 Mar 2019 17:09:06 -0500 Subject: [PATCH 1/6] Add API logging to client, add API method to client.Response --- LICENSE.txt | 2 +- USAGE.md | 23 ++++++++++++++++++++++- python_http_client/client.py | 33 +++++++++++++++++++++++++++++---- tests/test_unit.py | 7 ++++++- 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 149a03a..ea134f0 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2012 - 2018 SendGrid, Inc. +Copyright (c) 2012 - 2019 SendGrid, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/USAGE.md b/USAGE.md index 1c4c78b..6d18379 100644 --- a/USAGE.md +++ b/USAGE.md @@ -95,4 +95,25 @@ HTTP request to delete elements in a source. ```python response = client.api_keys._(api_keys_id).delete() # print(response) as shown above -``` \ No newline at end of file +``` + +## LOGGING +Logging namespace `python_http_client.client` is available on API Client. + +Example to stdout: + +``` +>>> import logging +>>> import sys +>>> logger = logging.getLogger('python_http_client.client') +>>> logger.setLevel(logging.DEBUG) +>>> handler = logging.StreamHandler(sys.stdout) +>>> handler.setLevel(logging.DEBUG) +>>> formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +>>> handler.setFormatter(formatter) +>>> logger.addHandler(handler) +>>> client.templates.get() +2019-03-08 17:21:25,329 - python_http_client.client - INFO - GET Request: https://api.sendgrid.com/v3/templates +2019-03-08 17:21:25,329 - python_http_client.client - INFO - HEADERS: {'Authorization': 'Bearer redacted_token', 'Accept': 'application/json', 'User-agent': 'sendgrid/5.6.0;python'} +2019-03-08 17:21:25,696 - python_http_client.client - INFO - GET Response: 200 {"templates":[]} +``` diff --git a/python_http_client/client.py b/python_http_client/client.py index fe7944b..178620d 100644 --- a/python_http_client/client.py +++ b/python_http_client/client.py @@ -1,5 +1,6 @@ """HTTP Client library""" import json +import logging from .exceptions import handle_error try: @@ -13,11 +14,13 @@ from urllib2 import HTTPError from urllib import urlencode +_logger = logging.getLogger(__name__) + class Response(object): """Holds the response from an API call.""" - def __init__(self, response): + def __init__(self, response, method): """ :param response: The return value from a open call on a urllib.build_opener() @@ -26,6 +29,14 @@ def __init__(self, response): self._status_code = response.getcode() self._body = response.read() self._headers = response.info() + self._method = method + + @property + def method(self): + """ + :return: string, method of API call + """ + return self._method @property def status_code(self): @@ -169,7 +180,8 @@ def _make_request(self, opener, request, timeout=None): """ timeout = timeout or self.timeout try: - return opener.open(request, timeout=timeout) + return (opener.open(request, timeout=timeout), + request.get_method()) except HTTPError as err: exc = handle_error(err) exc.__cause__ = None @@ -249,9 +261,22 @@ def http_request(*_, **kwargs): request.add_header('Content-Type', 'application/json') request.get_method = lambda: method timeout = kwargs.pop('timeout', None) - return Response( - self._make_request(opener, request, timeout=timeout) + _logger.info(u'{method} Request: {url}'.format( + method=request.get_method(), + url=request.get_full_url())) + if request.data: + _logger.info(u'PAYLOAD: {data}'.format( + data=request.data)) + _logger.info(u'HEADERS: {headers}'.format( + headers=request.headers)) + response = Response( + *self._make_request(opener, request, timeout=timeout) ) + _logger.info(u'{method} Response: {status} {body}'.format( + method=response.method, + status=response.status_code, + body=response.body)) + return response return http_request else: # Add a segment to the URL diff --git a/tests/test_unit.py b/tests/test_unit.py index 134dae1..a95f00f 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -58,7 +58,7 @@ def __init__(self, host, response_code, timeout=None): def _make_request(self, opener, request, timeout=None): if 200 <= self.response_code < 299: # if successful code - return MockResponse(self.response_code) + return MockResponse(self.response_code), request.get_method() else: raise handle_error(MockException(self.response_code)) @@ -151,24 +151,29 @@ def test__getattr__(self): mock_client._url_path + ['test'] r = mock_client.get() self.assertEqual(r.status_code, 200) + self.assertEqual(r.method, 'GET') # Test POST r = mock_client.put() self.assertEqual(r.status_code, 200) + self.assertEqual(r.method, 'PUT') # Test PATCH r = mock_client.patch() self.assertEqual(r.status_code, 200) + self.assertEqual(r.method, 'PATCH') # Test POST mock_client.response_code = 201 r = mock_client.post() self.assertEqual(r.status_code, 201) + self.assertEqual(r.method, 'POST') # Test DELETE mock_client.response_code = 204 r = mock_client.delete() self.assertEqual(r.status_code, 204) + self.assertEqual(r.method, 'DELETE') mock_client.response_code = 400 self.assertRaises(BadRequestsError, mock_client.get) From aa7aafe7196598a4749ee2f4e01ca5259cc95d26 Mon Sep 17 00:00:00 2001 From: Tyson Holub Date: Mon, 11 Mar 2019 16:23:32 -0400 Subject: [PATCH 2/6] remove unicode string prefix from API logging --- python_http_client/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python_http_client/client.py b/python_http_client/client.py index 178620d..248098e 100644 --- a/python_http_client/client.py +++ b/python_http_client/client.py @@ -261,18 +261,18 @@ def http_request(*_, **kwargs): request.add_header('Content-Type', 'application/json') request.get_method = lambda: method timeout = kwargs.pop('timeout', None) - _logger.info(u'{method} Request: {url}'.format( + _logger.info('{method} Request: {url}'.format( method=request.get_method(), url=request.get_full_url())) if request.data: - _logger.info(u'PAYLOAD: {data}'.format( + _logger.info('PAYLOAD: {data}'.format( data=request.data)) - _logger.info(u'HEADERS: {headers}'.format( + _logger.info('HEADERS: {headers}'.format( headers=request.headers)) response = Response( *self._make_request(opener, request, timeout=timeout) ) - _logger.info(u'{method} Response: {status} {body}'.format( + _logger.info('{method} Response: {status} {body}'.format( method=response.method, status=response.status_code, body=response.body)) From 98a80d7756c913cfdc1e4efcc42fa2b1dd9e9a3c Mon Sep 17 00:00:00 2001 From: Tyson Holub Date: Mon, 11 Mar 2019 17:37:52 -0400 Subject: [PATCH 3/6] log API Response on HTTPError --- python_http_client/client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python_http_client/client.py b/python_http_client/client.py index 248098e..03b1f36 100644 --- a/python_http_client/client.py +++ b/python_http_client/client.py @@ -185,6 +185,10 @@ def _make_request(self, opener, request, timeout=None): except HTTPError as err: exc = handle_error(err) exc.__cause__ = None + _logger.info('{method} Response: {status} {body}'.format( + method=request.get_method(), + status=exc.status_code, + body=exc.body)) raise exc def _(self, name): From cf861553d76782754dfae521f197d34c248ae4f5 Mon Sep 17 00:00:00 2001 From: Tyson Holub Date: Thu, 6 Aug 2020 14:04:52 -0400 Subject: [PATCH 4/6] changes per PR comments --- python_http_client/client.py | 32 +++++++++++--------------------- tests/test_unit.py | 7 +------ 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/python_http_client/client.py b/python_http_client/client.py index d232b5b..6b790a5 100644 --- a/python_http_client/client.py +++ b/python_http_client/client.py @@ -20,7 +20,7 @@ class Response(object): """Holds the response from an API call.""" - def __init__(self, response, method): + def __init__(self, response): """ :param response: The return value from a open call on a urllib.build_opener() @@ -29,14 +29,6 @@ def __init__(self, response, method): self._status_code = response.getcode() self._body = response.read() self._headers = response.info() - self._method = method - - @property - def method(self): - """ - :return: string, method of API call - """ - return self._method @property def status_code(self): @@ -181,15 +173,14 @@ def _make_request(self, opener, request, timeout=None): """ timeout = timeout or self.timeout try: - return (opener.open(request, timeout=timeout), - request.get_method()) + return opener.open(request, timeout=timeout) except HTTPError as err: - exc = handle_error(err) - exc.__cause__ = None - _logger.info('{method} Response: {status} {body}'.format( + _logger.debug('{method} Response: {status} {body}'.format( method=request.get_method(), status=exc.status_code, body=exc.body)) + exc = handle_error(err) + exc.__cause__ = None raise exc def _(self, name): @@ -272,20 +263,19 @@ def http_request( data=data, ) request.get_method = lambda: method - timeout = kwargs.pop('timeout', None) - _logger.info('{method} Request: {url}'.format( + _logger.debug('{method} Request: {url}'.format( method=request.get_method(), url=request.get_full_url())) if request.data: - _logger.info('PAYLOAD: {data}'.format( + _logger.debug('PAYLOAD: {data}'.format( data=request.data)) - _logger.info('HEADERS: {headers}'.format( + _logger.debug('HEADERS: {headers}'.format( headers=request.headers)) response = Response( - *self._make_request(opener, request, timeout=timeout) + self._make_request(opener, request, timeout=timeout) ) - _logger.info('{method} Response: {status} {body}'.format( - method=response.method, + _logger.debug('{method} Response: {status} {body}'.format( + method=method, status=response.status_code, body=response.body)) return response diff --git a/tests/test_unit.py b/tests/test_unit.py index f346848..8ef5116 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -59,7 +59,7 @@ def __init__(self, host, response_code, timeout=None): def _make_request(self, opener, request, timeout=None): if 200 <= self.response_code < 299: # if successful code - return MockResponse(self.response_code), request.get_method() + return MockResponse(self.response_code) else: raise handle_error(MockException(self.response_code)) @@ -165,29 +165,24 @@ def test__getattr__(self): mock_client._url_path += ['test'] r = mock_client.get() self.assertEqual(r.status_code, 200) - self.assertEqual(r.method, 'GET') # Test POST r = mock_client.put() self.assertEqual(r.status_code, 200) - self.assertEqual(r.method, 'PUT') # Test PATCH r = mock_client.patch() self.assertEqual(r.status_code, 200) - self.assertEqual(r.method, 'PATCH') # Test POST mock_client.response_code = 201 r = mock_client.post() self.assertEqual(r.status_code, 201) - self.assertEqual(r.method, 'POST') # Test DELETE mock_client.response_code = 204 r = mock_client.delete() self.assertEqual(r.status_code, 204) - self.assertEqual(r.method, 'DELETE') mock_client.response_code = 400 self.assertRaises(BadRequestsError, mock_client.get) From 7bfe98dc6c7ce77028c5b50f6167124ceccab939 Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Thu, 6 Aug 2020 14:10:15 -0500 Subject: [PATCH 5/6] Update USAGE.md --- USAGE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/USAGE.md b/USAGE.md index 6d18379..64031ba 100644 --- a/USAGE.md +++ b/USAGE.md @@ -113,7 +113,7 @@ Example to stdout: >>> handler.setFormatter(formatter) >>> logger.addHandler(handler) >>> client.templates.get() -2019-03-08 17:21:25,329 - python_http_client.client - INFO - GET Request: https://api.sendgrid.com/v3/templates -2019-03-08 17:21:25,329 - python_http_client.client - INFO - HEADERS: {'Authorization': 'Bearer redacted_token', 'Accept': 'application/json', 'User-agent': 'sendgrid/5.6.0;python'} -2019-03-08 17:21:25,696 - python_http_client.client - INFO - GET Response: 200 {"templates":[]} +2019-03-08 17:21:25,329 - python_http_client.client - DEBUG - GET Request: https://api.sendgrid.com/v3/templates +2019-03-08 17:21:25,329 - python_http_client.client - DEBUG - HEADERS: {'Authorization': 'Bearer redacted_token', 'Accept': 'application/json', 'User-agent': 'sendgrid/5.6.0;python'} +2019-03-08 17:21:25,696 - python_http_client.client - DEBUG - GET Response: 200 {"templates":[]} ``` From 14c67c3c35acf83812062a6af3129d480cd5b89a Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Thu, 6 Aug 2020 14:13:00 -0500 Subject: [PATCH 6/6] Update client.py --- python_http_client/client.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/python_http_client/client.py b/python_http_client/client.py index 6b790a5..d3b600a 100644 --- a/python_http_client/client.py +++ b/python_http_client/client.py @@ -263,22 +263,27 @@ def http_request( data=data, ) request.get_method = lambda: method + _logger.debug('{method} Request: {url}'.format( - method=request.get_method(), + method=method, url=request.get_full_url())) if request.data: _logger.debug('PAYLOAD: {data}'.format( data=request.data)) _logger.debug('HEADERS: {headers}'.format( headers=request.headers)) + response = Response( self._make_request(opener, request, timeout=timeout) ) + _logger.debug('{method} Response: {status} {body}'.format( method=method, status=response.status_code, body=response.body)) + return response + return http_request else: # Add a segment to the URL