Skip to content

Commit

Permalink
Merge pull request #24 from rbw0/branch0.3.2
Browse files Browse the repository at this point in the history
Adds limit support in get_all()
Enables easy access to last response (and request)
  • Loading branch information
rbw authored Apr 5, 2017
2 parents 7a69f38 + 580f1b6 commit c2f7e11
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 12 deletions.
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ Misc usage
# Delete
r.delete()
# Iterate over all records with state == 2 and print out number
for record in s.query(table='incident', query={'state': 2}).get_all():
# Iterate over the first 20 records with state 2 and print out number
for record in s.query(table='incident', query={'state': 2}).get_all(limit=20):
print(record['number'])
Expand Down
58 changes: 49 additions & 9 deletions pysnow.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import inspect

__author__ = "Robert Wikman <[email protected]>"
__version__ = "0.3.1"
__version__ = "0.3.2"


class UnexpectedResponse(Exception):
Expand Down Expand Up @@ -50,6 +50,10 @@ class NoResults(Exception):
pass


class NoRequestExecuted(Exception):
pass


class QueryTypeError(TypeError):
pass

Expand Down Expand Up @@ -327,30 +331,63 @@ def __init__(self, method, table, **kwargs):
self.default_payload = kwargs.pop('default_payload')
self.raise_on_empty = kwargs.pop('raise_on_empty')
self.session = kwargs.pop('session')
self.status_code = None
self._last_response = None

if method in ('GET', 'DELETE'):
self.query = kwargs.pop('query')

def _all_inner(self, fields):
@property
def last_response(self):
"""Return _last_response after making sure an inner `requests.request` has been performed
:raise:
:NoRequestExecuted: If no request has been executed
:return: last response
"""
if self._last_response is None:
raise NoRequestExecuted("%s hasn't been executed" % self)
return self._last_response

@last_response.setter
def last_response(self, response):
""" Sets last_response property
:param response: `requests.request` response
"""
self._last_response = response

@property
def status_code(self):
"""Return last_response.status_code after making sure an inner `requests.request` has been performed
:raise:
:NoRequestExecuted: If no request has been executed
:return: status_code of last_response
"""
if self.last_response is None:
raise NoRequestExecuted("%s hasn't been executed" % self)

return self.last_response.status_code

def _all_inner(self, fields, limit):
"""Yields all records for the query and follows links if present on the response after validating
:return: List of records with content
"""
response = self.session.get(self._get_url(self.table), params=self._get_formatted_query(fields))
response = self.session.get(self._get_url(self.table), params=self._get_formatted_query(fields, limit))
yield self._get_content(response)
while 'next' in response.links:
self.url_link = response.links['next']['url']
response = self.session.get(self.url_link)
yield self._get_content(response)

def get_all(self, fields=list()):
def get_all(self, fields=list(), limit=None):
"""Wrapper method that takes whatever was returned by the _all_inner() generators and chains it in one result
:param fields: List of fields to return in the result
:param limit: Limits the number of records returned
:return: Iterable chain object
"""
return itertools.chain.from_iterable(self._all_inner(fields))
return itertools.chain.from_iterable(self._all_inner(fields, limit))

def get_one(self, fields=list()):
"""Convenience function for queries returning only one result. Validates response before returning.
Expand All @@ -360,7 +397,7 @@ def get_one(self, fields=list()):
:MultipleResults: if more than one match is found
:return: Record content
"""
response = self.session.get(self._get_url(self.table), params=self._get_formatted_query(fields))
response = self.session.get(self._get_url(self.table), params=self._get_formatted_query(fields, limit=1))
content = self._get_content(response)
l = len(content)
if l > 1:
Expand Down Expand Up @@ -459,7 +496,7 @@ def _get_content(self, response):
:return: ServiceNow response content
"""
method = response.request.method
self.status_code = response.status_code
self.last_response = response

server_error = {
'summary': None,
Expand Down Expand Up @@ -549,7 +586,7 @@ def _get_attachment_url(self, sys_id=None):

return "%s/%s" % (url_str, "upload")

def _get_formatted_query(self, fields):
def _get_formatted_query(self, fields, limit):
"""
Converts the query to a ServiceNow-interpretable format
:return: ServiceNow query
Expand All @@ -572,6 +609,9 @@ def _get_formatted_query(self, fields):
result = {'sysparm_query': sysparm_query}
result.update(self.default_payload)

if limit:
result.update({'sysparm_limit': limit, 'sysparm_suppress_pagination_header': True})

if len(fields) > 0:
if isinstance(fields, list):
result.update({'sysparm_fields': ",".join(fields)})
Expand Down
64 changes: 63 additions & 1 deletion tests/test_incident.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def setUp(self):
password=self.mock_connection['pass'],
raise_on_empty=self.mock_connection['raise_on_empty'])

# Use `nosetests -l debug` to enable this logger
logging.basicConfig(level=logging.DEBUG)
self.log = logging.getLogger('debug')

Expand Down Expand Up @@ -76,6 +77,39 @@ def test_invalid_query_type(self):
except pysnow.InvalidUsage:
pass

@httpretty.activate
def test_last_response_not_executed(self):
json_body = json.dumps({'result': [{'number': self.mock_incident['number']}]})
httpretty.register_uri(httpretty.GET,
"https://%s/%s" % (self.mock_connection['fqdn'], self.mock_incident['path']),
body=json_body,
status=200,
content_type="application/json")

try:
self.client.query(table='incident', query={}).last_response
self.assertFalse('Getting last_response should fail when no `Request` has been executed')
except pysnow.NoRequestExecuted:
pass

@httpretty.activate
def test_last_response(self):
json_body = json.dumps({'result': [{'number': self.mock_incident['number']}]})
httpretty.register_uri(httpretty.GET,
"https://%s/%s" % (self.mock_connection['fqdn'], self.mock_incident['path']),
body=json_body,
status=200,
content_type="application/json")

r = self.client.query(table='incident', query={})
r.get_one()

# Make sure we get the expected status code back
self.assertEquals(r.status_code, 200)

# Make sure last_response is not None
self.assertNotEquals(r.last_response, None)

@httpretty.activate
def test_get_incident_by_dict_query(self):
"""
Expand All @@ -93,6 +127,32 @@ def test_get_incident_by_dict_query(self):
# Make sure we got an incident back with the expected number
self.assertEquals(r.get_one()['number'], self.mock_incident['number'])

@httpretty.activate
def test_get_limited_result(self):
"""
Make sure fetching by dict type query works
"""
json_body = json.dumps({'result': [{'number': self.mock_incident['number']}]})
httpretty.register_uri(httpretty.GET,
"https://%s/%s" % (self.mock_connection['fqdn'], self.mock_incident['path']),
body=json_body,
status=200,
content_type="application/json")

r = self.client.query(table='incident', query={})

# Trigger a request by fetching next element from the generator
next(r.get_all(limit=2))

# Get last request QS
qs = httpretty.last_request().querystring

# Make sure sysparm_limit equals limit
self.assertEqual(int(qs['sysparm_limit'][0]), 2)

# Make sure sysparm_suppress_pagination_header is True
self.assertTrue(qs['sysparm_suppress_pagination_header'])

@httpretty.activate
def test_get_incident_by_string_query(self):
"""
Expand Down Expand Up @@ -220,8 +280,10 @@ def test_get_incident_no_results(self):
client.raise_on_empty = False
r = client.query(table='incident', query={'number': self.mock_incident['number']})

res = r.get_one()

# Quietly continue if `raise_on_empty` if False
self.assertEquals(r.get_one(), {})
self.assertEquals(res, {})
self.assertEquals(r.status_code, 404)

@httpretty.activate
Expand Down

0 comments on commit c2f7e11

Please sign in to comment.