Skip to content

Commit

Permalink
Merge pull request #20 from rbw0/branch0.3.1
Browse files Browse the repository at this point in the history
Branch0.3.1
  • Loading branch information
rbw authored Mar 22, 2017
2 parents 848363c + 471ea7a commit 7a69f38
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 52 deletions.
14 changes: 7 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Querying

| Although optional, queries is a simple and powerful way to specify what you're after.
\ Pysnow offers 3 ways to query the SN REST API.
- Using the `Query` builder (For complex queries)
- Using the `QueryBuilder` (For complex queries)
- Dict type queries (For simple queries, i.e. `equals`)
- SN Pass-through type queries (...)

Expand All @@ -68,12 +68,12 @@ Querying
end = dt.now() - td(days=20)
# Query incident records with number starting with 'INC0123', created between 1970-01-01 and 20 days back in time
q = pysnow.Query()\
.field('number').starts_with('INC0123')\
.AND()\
.field('sys_created_on').between(start, end)
qb = pysnow.QueryBuilder()\
.field('number').starts_with('INC0123')\
.AND()\
.field('sys_created_on').between(start, end)
r = s.query('incident', query=q)
r = s.query('incident', query=qb)
# Execute query and iterate over the results, returning only 'number', 'sys_created_on' and 'short_description'
for row in r.get_all(['number', 'sys_created_on', 'short_description']):
Expand All @@ -82,7 +82,7 @@ Querying



More in the `Query builder documentation <http://pysnow.readthedocs.io/en/latest/query.html>`_
More in the `QueryBuilder documentation <http://pysnow.readthedocs.io/en/latest/query.html>`_



Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ If there's a demand, delete_multiple() and update_multiple() will be implemented

Compatibility
-------------
pysnow is compatible with both Python 2 and 3. It's been tested in Python 2.7 and Python 3.4.
Python 2 and 3.

Quick links
-----------
Expand Down
6 changes: 3 additions & 3 deletions docs/query.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
``pysnow.Query`` --- Creates a new Query object
===================================================
``pysnow.QueryBuilder`` --- Creates a new QueryBuilder object
=============================================================

.. automodule:: pysnow
.. autoclass:: Query
.. autoclass:: QueryBuilder
:members:

35 changes: 25 additions & 10 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.0"
__version__ = "0.3.1"


class UnexpectedResponse(Exception):
Expand Down Expand Up @@ -70,24 +70,24 @@ class QueryMultipleExpressions(Exception):
pass


class Query(object):
class QueryBuilder(object):
def __init__(self):
"""The Query builder"""
"""Query builder - used for building complex queries"""
self._query = []
self.current_field = None
self.c_oper = None
self.l_oper = None

def AND(self):
"""AND operator for use between expressions"""
"""Operator for use between expressions"""
return self._add_logical_operator('^')

def OR(self):
"""OR operator for use between expressions"""
"""Operator for use between expressions"""
return self._add_logical_operator('^OR')

def NQ(self):
"""NQ operator for use between expressions"""
"""Operator for use between expressions"""
return self._add_logical_operator('^NQ')

def field(self, field):
Expand Down Expand Up @@ -129,11 +129,21 @@ def not_equals(self, value):

def greater_than(self, value):
"""Query records with the given field greater than the value specified"""
return self._add_condition('>', value, types=[int])
if hasattr(value, 'strftime'):
value = value.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(value, str):
raise QueryTypeError('Expected value of type `int` or instance of `datetime`, not %s' % type(value))

return self._add_condition('>', value, types=[int, str])

def less_than(self, value):
"""Query records with the given field less than the value specified"""
return self._add_condition('<', value, types=[int])
if hasattr(value, 'strftime'):
value = value.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(value, str):
raise QueryTypeError('Expected value of type `int` or instance of `datetime`, not %s' % type(value))

return self._add_condition('<', value, types=[int, str])

def between(self, start, end):
"""Query records in a start and end range
Expand Down Expand Up @@ -224,6 +234,11 @@ def __str__(self):
return str().join(self._query)


# For backwards compatibility
class Query(QueryBuilder):
pass


class Client(object):
def __init__(self, instance, user, password, raise_on_empty=True, default_payload=None):
"""Sets configuration and creates a session object used in `Request` later on
Expand Down Expand Up @@ -540,7 +555,7 @@ def _get_formatted_query(self, fields):
:return: ServiceNow query
"""

if isinstance(self.query, Query):
if isinstance(self.query, QueryBuilder):
sysparm_query = str(self.query)
elif isinstance(self.query, dict): # Dict-type query
try:
Expand All @@ -552,7 +567,7 @@ def _get_formatted_query(self, fields):
elif isinstance(self.query, str): # String-type query
sysparm_query = self.query
else:
raise InvalidUsage("You must pass a query using either a dictionary or string (for advanced queries)")
raise InvalidUsage("Query must be instance of %s, %s or %s" % (QueryBuilder, str, dict))

result = {'sysparm_query': sysparm_query}
result.update(self.default_payload)
Expand Down
15 changes: 15 additions & 0 deletions tests/test_incident.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ def test_connection(self):
self.assertEqual(self.client.raise_on_empty, self.mock_connection['raise_on_empty'])
self.assertEqual(self.client.default_payload, {})

@httpretty.activate
def test_invalid_query_type(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=1).get_one()
self.assertFalse('Query of type int should fail')
except pysnow.InvalidUsage:
pass

@httpretty.activate
def test_get_incident_by_dict_query(self):
"""
Expand Down
66 changes: 35 additions & 31 deletions tests/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,192 +7,196 @@
class TestIncident(unittest.TestCase):
def test_query_no_field(self):
try:
q = str(pysnow.Query().equals('test'))
q = str(pysnow.QueryBuilder().equals('test'))
self.assertFalse(q)
except pysnow.QueryMissingField:
pass

def test_query_no_expression(self):
try:
q = str(pysnow.Query().field('test'))
q = str(pysnow.QueryBuilder().field('test'))
self.assertFalse(q)
except pysnow.QueryEmpty:
pass

def test_query_no_query(self):
try:
q = str(pysnow.Query())
q = str(pysnow.QueryBuilder())
self.assertFalse(q)
except pysnow.QueryEmpty:
pass

def test_query_multiple_expressions(self):
try:
q = str(pysnow.Query().field('test').equals('test').between(1, 2))
q = str(pysnow.QueryBuilder().field('test').equals('test').between(1, 2))
self.assertFalse(q)
except pysnow.QueryMultipleExpressions:
pass

def test_query_unfinished_logical(self):
try:
q = str(pysnow.Query().field('test').equals('test').AND())
q = str(pysnow.QueryBuilder().field('test').equals('test').AND())
self.assertFalse(q)
except pysnow.QueryMissingField:
pass

def test_query_logical_and(self):
# Make sure AND() operator between expressions works
q = pysnow.Query().field('test').equals('test').AND().field('test2').equals('test')
q = pysnow.QueryBuilder().field('test').equals('test').AND().field('test2').equals('test')

self.assertEquals(str(q), 'test=test^test2=test')

def test_query_logical_or(self):
# Make sure OR() operator between expressions works
q = pysnow.Query().field('test').equals('test').OR().field('test2').equals('test')
q = pysnow.QueryBuilder().field('test').equals('test').OR().field('test2').equals('test')

self.assertEquals(str(q), 'test=test^ORtest2=test')

def test_query_logical_nq(self):
# Make sure NQ() operator between expressions works
q = pysnow.Query().field('test').equals('test').NQ().field('test2').equals('test')
q = pysnow.QueryBuilder().field('test').equals('test').NQ().field('test2').equals('test')

self.assertEquals(str(q), 'test=test^NQtest2=test')

def test_query_cond_between(self):
# Make sure between with str arguments fails
try:
q = pysnow.Query().field('test').between('test', 'test')
q = pysnow.QueryBuilder().field('test').between('test', 'test')
self.assertFalse(q)
except pysnow.QueryTypeError:
pass

# Make sure between with int arguments works
q1 = str(pysnow.Query().field('test').between(1, 2))
q1 = str(pysnow.QueryBuilder().field('test').between(1, 2))
self.assertEquals(str(q1), 'testBETWEEN1@2')

start = dt(1970, 1, 1)
end = dt(1970, 1, 2)

# Make sure between with dates works
q2 = str(pysnow.Query().field('test').between(start, end))
q2 = str(pysnow.QueryBuilder().field('test').between(start, end))
self.assertEquals(str(q2), 'testBETWEENjavascript:gs.dateGenerate("1970-01-01 00:00:00")'
'@javascript:gs.dateGenerate("1970-01-02 00:00:00")')

def test_query_cond_starts_with(self):
# Make sure type checking works
try:
q = pysnow.Query().field('test').starts_with(1)
q = pysnow.QueryBuilder().field('test').starts_with(1)
self.assertFalse(q)
except pysnow.QueryTypeError:
pass

# Make sure a valid operation works
q = pysnow.Query().field('test').starts_with('val')
q = pysnow.QueryBuilder().field('test').starts_with('val')

self.assertEquals(str(q), 'testSTARTSWITHval')

def test_query_cond_ends_with(self):
# Make sure type checking works
try:
q = pysnow.Query().field('test').ends_with(1)
q = pysnow.QueryBuilder().field('test').ends_with(1)
self.assertFalse(q)
except pysnow.QueryTypeError:
pass

# Make sure a valid operation works
q = pysnow.Query().field('test').ends_with('val')
q = pysnow.QueryBuilder().field('test').ends_with('val')

self.assertEquals(str(q), 'testENDSWITHval')

def test_query_cond_contains(self):
# Make sure type checking works
try:
q = pysnow.Query().field('test').contains(1)
q = pysnow.QueryBuilder().field('test').contains(1)
self.assertFalse(q)
except pysnow.QueryTypeError:
pass

# Make sure a valid operation works
q = pysnow.Query().field('test').contains('val')
q = pysnow.QueryBuilder().field('test').contains('val')

self.assertEquals(str(q), 'testLIKEval')

def test_query_cond_not_contains(self):
# Make sure type checking works
try:
q = pysnow.Query().field('test').not_contains(1)
q = pysnow.QueryBuilder().field('test').not_contains(1)
self.assertFalse(q)
except pysnow.QueryTypeError:
pass

# Make sure a valid operation works
q = pysnow.Query().field('test').not_contains('val')
q = pysnow.QueryBuilder().field('test').not_contains('val')

self.assertEquals(str(q), 'testNOTLIKEval')

def test_query_cond_is_empty(self):
# Make sure a valid operation works
q = pysnow.Query().field('test').is_empty()
q = pysnow.QueryBuilder().field('test').is_empty()

self.assertEquals(str(q), 'testISEMPTY')

def test_query_cond_equals(self):
# Make sure type checking works
try:
q = pysnow.Query().field('test').equals(dt)
q = pysnow.QueryBuilder().field('test').equals(dt)
self.assertFalse(q)
except pysnow.QueryTypeError:
pass

# Make sure a valid operation works
q = pysnow.Query().field('test').equals('test')
q = pysnow.QueryBuilder().field('test').equals('test')

self.assertEquals(str(q), 'test=test')

def test_query_cond_not_equals(self):
# Make sure type checking works
try:
q = pysnow.Query().field('test').not_equals(dt)
q = pysnow.QueryBuilder().field('test').not_equals(dt)
self.assertFalse(q)
except pysnow.QueryTypeError:
pass

# Make sure a valid operation works
q = pysnow.Query().field('test').not_equals('test')
q = pysnow.QueryBuilder().field('test').not_equals('test')

self.assertEquals(str(q), 'test!=test')

def test_query_cond_greater_than(self):
# Make sure type checking works
try:
q = pysnow.Query().field('test').greater_than('a')
q = pysnow.QueryBuilder().field('test').greater_than('a')
self.assertFalse(q)
except pysnow.QueryTypeError:
pass

# Make sure a valid operation works
q = pysnow.Query().field('test').greater_than(1)

q = pysnow.QueryBuilder().field('test').greater_than(1)
self.assertEquals(str(q), 'test>1')

q = pysnow.QueryBuilder().field('test').greater_than(dt(2016, 2, 1))
self.assertEquals(str(q), 'test>2016-02-01 00:00:00')

def test_query_cond_less_than(self):
# Make sure type checking works
try:
q = pysnow.Query().field('test').less_than('a')
q = pysnow.QueryBuilder().field('test').less_than('a')
self.assertFalse(q)
except pysnow.QueryTypeError:
pass

# Make sure a valid operation works
q = pysnow.Query().field('test').less_than(1)

q = pysnow.QueryBuilder().field('test').less_than(1)
self.assertEquals(str(q), 'test<1')

q = pysnow.QueryBuilder().field('test').less_than(dt(2016, 2, 1))
self.assertEquals(str(q), 'test<2016-02-01 00:00:00')

def test_complex_query(self):
start = dt(2016, 2, 1)
end = dt(2016, 2, 10)

q = pysnow.Query()\
q = pysnow.QueryBuilder()\
.field('f1').equals('val1')\
.AND()\
.field('f2').between(start, end)\
Expand Down

0 comments on commit 7a69f38

Please sign in to comment.