Skip to content

Commit

Permalink
Merge pull request #88 from rbw0/branch-0.6.9
Browse files Browse the repository at this point in the history
Branch 0.6.9
  • Loading branch information
rbw authored Mar 11, 2018
2 parents 01c8736 + 1b64c05 commit a467b4e
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 15 deletions.
11 changes: 6 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ Documentation

The `documentation <http://pysnow.readthedocs.org/>`_ is divided into four sections:

- `General <http://pysnow.readthedocs.io/en/latest/#general>`_ (install, compatibility etc)
- `Library API <http://pysnow.readthedocs.io/en/latest/#api>`_
- `Usage <http://pysnow.readthedocs.io/en/latest/#usage>`_
- `Full examples <http://pysnow.readthedocs.io/en/latest/#examples>`_
- `General <http://pysnow.readthedocs.io/en/latest/#general>`_ (install, compatibility etc)
- `Library API <http://pysnow.readthedocs.io/en/latest/#api>`_
- `Usage <http://pysnow.readthedocs.io/en/latest/#usage>`_
- `Full examples <http://pysnow.readthedocs.io/en/latest/#examples>`_

Other projects using pysnow
---------------------------
Expand All @@ -58,4 +58,5 @@ lingfish, jcpunk, AMMullan, amontalban, ryancurrah, jdugan1024, punkrokk

JetBrains
---------
Thank you Jetbrains (www.jetbrains.com) for supporting with IDE licenses!
Thank you `Jetbrains <http://www.jetbrains.com>`_ for creating pycharm and providing me with free licenses

32 changes: 32 additions & 0 deletions docs/api/exceptions.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Exceptions
==========

.. automodule:: pysnow.exceptions

Generic Exceptions
------------------
.. autoclass:: InvalidUsage

Response Exceptions
-------------------
.. autoclass:: ResponseError
.. autoclass:: MissingResult
.. autoclass:: UnexpectedResponseFormat
.. autoclass:: ReportUnavailable
.. autoclass:: NoResults
.. autoclass:: MultipleResults

OAuthClient Exceptions
----------------------
.. autoclass:: MissingToken
.. autoclass:: TokenCreateError

QueryBuilder Exceptions
-----------------------
.. autoclass:: QueryTypeError
.. autoclass:: QueryMissingField
.. autoclass:: QueryEmpty
.. autoclass:: QueryExpressionError
.. autoclass:: QueryMultipleExpressions


36 changes: 36 additions & 0 deletions docs/full_examples/retry.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Session with auto-retry
=======================

You might run into issues if you're creating too many requests against the ServiceNow API.
Fortunately, the `requests` library enables users to create their own transport adapter with a retry mechanism from the `urllib3` library.

You can read more about transport adapters and the retry mechanism here:
- http://docs.python-requests.org/en/master/user/advanced/#transport-adapters
- https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html#module-urllib3.util.retry


This example shows how to automatically retry on an error for about 2 seconds and then fall back to the default error handling.

.. code-block:: python
import requests
import pysnow
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
s = requests.Session()
s.auth = requests.auth.HTTPBasicAuth('<username>', '<password>')
# set auto retry for about 2 seconds on some common errors
adapter = HTTPAdapter(
max_retries=Retry(
total=3,
backoff_factor=0.3,
status_forcelist=(401, 408, 429, 431, 500, 502, 503, 504, 511)
)
)
s.mount('https://', adapter)
sn = pysnow.Client(instance='<instance>', session=s)
34 changes: 34 additions & 0 deletions docs/full_examples/threads.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Using threaded queries
======================

This is an example of multiple threads doing simple fetches.


.. note::
This example uses `concurrent.futures` and expects you to be familiar with :meth:`pysnow.Resource.get`.


.. code-block:: python
import concurrent.futures
import pysnow
def just_print(client, query):
# Run the query
response = client.get(query=query)
# Iterate over the result and print out `sys_id` and `state` of the matching records.
for record in response.all():
print(record['sys_id'], record['state'])
# Create client object
c = pysnow.Client(instance='myinstance', user='myusername', password='mypassword')
# list of simple items to query
queries = ({'api': '/table/incident', 'q': {'state': 1}}, {'api': '/table/incident', 'q': {'state': 3}})
# build taskqueue
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as taskpool:
for query in queries:
connection = c.resource(api_path=query['api'])
taskpool.submit(just_print, connection, query['q'])
3 changes: 3 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Library written in Python that makes interacting with the ServiceNow REST API mu
api/resource
api/params_builder
api/response
api/exceptions

.. _usage:

Expand All @@ -50,3 +51,5 @@ Library written in Python that makes interacting with the ServiceNow REST API mu
full_examples/oauth_client
full_examples/query_builder
full_examples/attachments
full_examples/threads
full_examples/retry
3 changes: 2 additions & 1 deletion pysnow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
from .oauth_client import OAuthClient
from .query_builder import QueryBuilder
from .resource import Resource
from .params_builder import ParamsBuilder

__author__ = "Robert Wikman <[email protected]>"
__version__ = "0.6.8"
__version__ = "0.6.9"

# Set default logging handler to avoid "No handler found" warnings.
import logging
Expand Down
3 changes: 1 addition & 2 deletions pysnow/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
import warnings

import requests
from requests.auth import HTTPBasicAuth

import pysnow

from requests.auth import HTTPBasicAuth
from .legacy_request import LegacyRequest
from .exceptions import InvalidUsage
from .resource import Resource
Expand Down
2 changes: 1 addition & 1 deletion pysnow/legacy_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ def _get_formatted_query(self, fields, limit, order_by, offset):
sysparm_query = str(self.query)
elif isinstance(self.query, dict): # Dict-type query
sysparm_query = '^'.join(['%s=%s' % (k, v) for k, v in six.iteritems(self.query)])
elif isinstance(self.query, str): # String-type query
elif isinstance(self.query, six.string_types): # String-type query
sysparm_query = self.query
else:
raise InvalidUsage("Query must be instance of %s, %s or %s" % (QueryBuilder, str, dict))
Expand Down
2 changes: 1 addition & 1 deletion pysnow/params_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def stringify_query(query):
elif isinstance(query, dict):
# Dict-type query
return '^'.join(['%s=%s' % (k, v) for k, v in six.iteritems(query)])
elif isinstance(query, str):
elif isinstance(query, six.string_types):
# Regular string-type query
return query
else:
Expand Down
9 changes: 5 additions & 4 deletions pysnow/query_builder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-

import inspect
import six

from .exceptions import (QueryEmpty,
QueryExpressionError,
Expand Down Expand Up @@ -85,7 +86,7 @@ def equals(self, data):
- QueryTypeError: if `data` is of an unexpected type
"""

if isinstance(data, str):
if isinstance(data, six.string_types):
return self._add_condition('=', data, types=[int, str])
elif isinstance(data, list):
return self._add_condition('IN', ",".join(map(str, data)), types=[str])
Expand All @@ -100,7 +101,7 @@ def not_equals(self, data):
- QueryTypeError: if `data` is of an unexpected type
"""

if isinstance(data, str):
if isinstance(data, six.string_types):
return self._add_condition('!=', data, types=[int, str])
elif isinstance(data, list):
return self._add_condition('NOT IN', ",".join(data), types=[str])
Expand All @@ -117,7 +118,7 @@ def greater_than(self, greater_than):

if hasattr(greater_than, 'strftime'):
greater_than = greater_than.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(greater_than, str):
elif isinstance(greater_than, six.string_types):
raise QueryTypeError('Expected value of type `int` or instance of `datetime`, not %s' % type(greater_than))

return self._add_condition('>', greater_than, types=[int, str])
Expand All @@ -132,7 +133,7 @@ def less_than(self, less_than):

if hasattr(less_than, 'strftime'):
less_than = less_than.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(less_than, str):
elif isinstance(less_than, six.string_types):
raise QueryTypeError('Expected value of type `int` or instance of `datetime`, not %s' % type(less_than))

return self._add_condition('<', less_than, types=[int, str])
Expand Down
3 changes: 3 additions & 0 deletions pysnow/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ def _request(self):

return SnowRequest(url_builder=self._url_builder, parameters=parameters, parent=self, **self.kwargs)

def get_record_link(self, sys_id):
return "%s/%s" % (self._url_builder.full_path, sys_id)

def get(self, query, limit=None, offset=None, fields=list()):
"""Queries the API resource
Expand Down
3 changes: 2 additions & 1 deletion pysnow/url_builder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-

import re
import six
from .exceptions import InvalidUsage


Expand All @@ -27,7 +28,7 @@ def validate_path(path):
:InvalidUsage: If validation fails.
"""

if not isinstance(path, str) or not re.match('^/(?:[._a-zA-Z0-9-]/?)+[^/]$', path):
if not isinstance(path, six.string_types) or not re.match('^/(?:[._a-zA-Z0-9-]/?)+[^/]$', path):
raise InvalidUsage(
"Path validation failed - Expected: '/<component>[/component], got: %s" % path
)
Expand Down
9 changes: 9 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ def test_valid_resource_paths(self):
self.assertEquals(r._api_path, api_path)
self.assertEquals(r._base_path, base_path)

def test_valid_resource_paths_unicode(self):
""":meth:`resource` should return a :class:`pysnow.Response` object with paths set to the expected value"""
api_path = u'/api/path'
base_path = u'/base/path'
c = Client(user="foo", password="foo", instance="instance")
r = c.resource(api_path=api_path, base_path=base_path)
self.assertEquals(r._api_path, api_path)
self.assertEquals(r._base_path, base_path)

def test_client_with_session(self):
"""Should be able to create a client given a requests session object."""
session = requests.Session()
Expand Down

0 comments on commit a467b4e

Please sign in to comment.