def _get_formatted_query(self, fields, limit, order_by, offset): """ Converts the query to a ServiceNow-interpretable format :return: ServiceNow query """ if not isinstance(order_by, list): raise InvalidUsage("Argument order_by must be of type list()") if not isinstance(fields, list): raise InvalidUsage("Argument fields must be of type list()") if isinstance(self.query, query.QueryBuilder): sysparm_query = str(self.query) elif isinstance(self.query, dict): # Dict-type query try: items = self.query.iteritems() # Python 2 except AttributeError: items = self.query.items() # Python 3 sysparm_query = '^'.join( ['%s=%s' % (field, value) for field, value in items]) elif isinstance(self.query, str): # String-type query sysparm_query = self.query else: raise InvalidUsage("Query must be instance of %s, %s or %s" % (query.QueryBuilder, str, dict)) for field in order_by: if field[0] == '-': sysparm_query += "^ORDERBYDESC%s" % field[1:] else: sysparm_query += "^ORDERBY%s" % field params = {'sysparm_query': sysparm_query} params.update(self.request_params) if limit is not None: params.update({ 'sysparm_limit': limit, 'sysparm_suppress_pagination_header': True }) if offset is not None: params.update({'sysparm_offset': offset}) if len(fields) > 0: params.update({'sysparm_fields': ",".join(fields)}) return params
def attach(self, file): """Attaches the queried record with `file` and returns the response after validating the response :param file: File to attach to the record :raise: :NoResults: if query returned no results :NotImplementedError: if query returned more than one result (currently not supported) :return: The attachment record metadata """ try: result = self.get_one() if 'sys_id' not in result: raise NoResults() except MultipleResults: raise NotImplementedError( 'Attaching a file to multiple records is not supported') except NoResults: raise NoResults( 'Attempted to attach file to a non-existing record') if not os.path.isfile(file): raise InvalidUsage( "Attachment '%s' must be an existing regular file" % file) response = self.session.post( self._get_attachment_url('upload'), data={ 'table_name': self.table, 'table_sys_id': result['sys_id'], 'file_name': ntpath.basename(file) }, files={'file': open(file, 'rb')}, headers={'content-type': None} # Temporarily override header ) return self._get_content(response)
def update(self, payload): """Updates the queried record with `payload` and returns the updated record after validating the response :param payload: Payload to update the record with :raise: :NoResults: if query returned no results :NotImplementedError: if query returned more than one result (currently not supported) :return: The updated record """ try: result = self.get_one() if 'sys_id' not in result: raise NoResults() except MultipleResults: raise NotImplementedError( "Update of multiple records is not supported") except NoResults as e: e.args = ('Cannot update a non-existing record', ) raise if not isinstance(payload, dict): raise InvalidUsage("Update payload must be of type dict") response = self.session.put( self._get_table_url(sys_id=result['sys_id']), data=json.dumps(payload)) return self._get_content(response)
def __init__(self, instance=None, host=None, user=None, password=None, raise_on_empty=True, request_params=None, use_ssl=True, session=None): """Creates a client ready to handle requests :param instance: instance name, used to construct host :param host: host can be passed as an alternative to instance :param user: username :param password: password :param raise_on_empty: whether or not to raise an exception on 404 (no matching records) :param request_params: request params to send with requests :param use_ssl: Enable or disable SSL :param session: a requests session object """ if (host and instance) is not None: raise InvalidUsage( "Instance and host are mutually exclusive, you cannot use both." ) if type(use_ssl) is not bool: raise InvalidUsage("Argument use_ssl must be of type bool") if type(raise_on_empty) is not bool: raise InvalidUsage("Argument raise_on_empty must be of type bool") if not (host or instance): raise InvalidUsage("You must supply an instance name or a host") if not (user and password) and not session: raise InvalidUsage( "You must provide either username and password or a session object" ) elif (user and session) is not None: raise InvalidUsage( "Provide either username and password or a session, not both.") if request_params is not None: if isinstance(request_params, dict): self.request_params = request_params else: raise InvalidUsage("Request params must be of type dict") else: self.request_params = {} self.instance = instance self.host = host self._user = user self._password = password self.raise_on_empty = raise_on_empty self.use_ssl = use_ssl self.base_url = self._get_base_url() self.session = self._get_session(session)
def set_token(self, token): """Validates token and creates a pysnow compatible session :param token: dict containing the information required to create an OAuth2Session """ expected_keys = set(("token_type", "refresh_token", "access_token", "scope", "expires_in", "expires_at")) if not expected_keys <= set(token): raise InvalidUsage("Token should contain a dictionary obtained from generate_token()") self.token = token self.session = self._get_oauth_session()
def __init__(self, instance, user=None, password=None, raise_on_empty=True, default_payload=None, session=None): """Sets configuration and creates a session object used in `Request` later on You must either provide a username and password or a requests session. If you provide a requests session it must handle the authentication. For example, providing a session can be used to do OAuth authentication. :param instance: instance name, used to resolve FQDN in `Request` :param user: username :param password: password :param raise_on_empty: whether or not to raise an exception on 404 (no matching records) :param default_payload: default payload to send with all requests, set i.e. 'sysparm_limit' here :param session: a requests session object """ if ((not (user and password)) and not session) or ((user or password) and session): raise InvalidUsage( "You must either provide username and password or a session") # Connection properties self.instance = instance self._user = user self._password = password self.raise_on_empty = raise_on_empty self.default_payload = default_payload or dict() # Sets default payload for all requests, i.e. sysparm_limit, sysparm_offset etc if not isinstance(self.default_payload, dict): raise InvalidUsage("Payload must be of type dict") # Create new session object self.session = self._get_session(session)
def _get_formatted_query(self, fields, limit=None): """ Converts the query to a ServiceNow-interpretable format :return: ServiceNow query """ if isinstance(self.query, query.QueryBuilder): sysparm_query = str(self.query) elif isinstance(self.query, dict): # Dict-type query try: items = self.query.iteritems() # Python 2 except AttributeError: items = self.query.items() # Python 3 sysparm_query = '^'.join( ['%s=%s' % (field, value) for field, value in items]) elif isinstance(self.query, str): # String-type query sysparm_query = self.query else: raise InvalidUsage("Query must be instance of %s, %s or %s" % (query.QueryBuilder, str, dict)) result = {'sysparm_query': sysparm_query} result.update(self.default_payload) if limit is not None: result.update({ 'sysparm_limit': limit, 'sysparm_suppress_pagination_header': True }) if len(fields) > 0: if isinstance(fields, list): result.update({'sysparm_fields': ",".join(fields)}) else: raise InvalidUsage("You must pass the fields as a list") return result
def clone(self, reset_fields=list()): """Clones the queried record :param reset_fields: Fields to reset :raise: :NoResults: if query returned no results :NotImplementedError: if query returned more than one result (currently not supported) :UnexpectedResponse: informs the user about what likely went wrong :return: The cloned record """ if not isinstance(reset_fields, list): raise InvalidUsage("reset_fields must be a list() of fields") try: response = self.get_one() if 'sys_id' not in response: raise NoResults() except MultipleResults: raise NotImplementedError( 'Cloning multiple records is not supported') except NoResults as e: e.args = ('Cannot clone a non-existing record', ) raise payload = {} # Iterate over fields in the result for field in response: # Ignore fields in reset_fields if field in reset_fields: continue item = response[field] # Check if the item is of type dict and has a sys_id ref (value) if isinstance(item, dict) and 'value' in item: payload[field] = item['value'] else: payload[field] = item try: return self.insert(payload) except UnexpectedResponse as e: if e.status_code == 403: # User likely attempted to clone a record without resetting a unique field e.args = ( 'Unable to create clone. Make sure unique fields has been reset.', ) raise
def upload(self, sys_id, file_path, name=None, multipart=False): """Attaches a new file to the provided record :param sys_id: the sys_id of the record to attach the file to :param file_path: local absolute path of the file to upload :param name: custom name for the uploaded file (instead of basename) :param multipart: whether or not to use multipart :return: the inserted record """ if not isinstance(multipart, bool): raise InvalidUsage('Multipart must be of type bool') resource = self.resource if name is None: name = os.path.basename(file_path) resource.parameters.add_custom({ 'table_name': self.table_name, 'table_sys_id': sys_id, 'file_name': name }) data = open(file_path, 'rb').read() headers = {} if multipart: headers["Content-Type"] = "multipart/form-data" path_append = '/upload' else: headers["Content-Type"] = magic.from_file( file_path, mime=True) if HAS_MAGIC else "text/plain" path_append = '/file' return resource.request(method='POST', data=data, headers=headers, path_append=path_append)
def __init__(self, client_id=None, client_secret=None, token_updater=None, *args, **kwargs): if not (client_secret and client_id): raise InvalidUsage('You must supply a client_id and client_secret') if token_updater is None: warnings.warn("No token_updater was supplied to OauthClient, you won't be notified of refreshes") if kwargs.get('session') or kwargs.get('user'): warnings.warn('pysnow.OAuthClient manages sessions internally, ' 'provided user / password credentials or sessions will be ignored.') # Forcibly set session, user and password kwargs['session'] = OAuth2Session(client=LegacyApplicationClient(client_id=client_id)) kwargs['user'] = None kwargs['password'] = None super(OAuthClient, self).__init__(*args, **kwargs) self.token_updater = token_updater self.client_id = client_id self.client_secret = client_secret self.token_url = "%s/oauth_token.do" % self._get_base_url()
def __init__(self, instance=None, host=None, user=None, password=None, raise_on_empty=True, default_payload=None, request_params=None, use_ssl=True, session=None): """Sets configuration and creates a session object used in `Request` later on You must either provide a username and password or a requests session. If you provide a requests session it must handle the authentication. For example, providing a session can be used to do OAuth authentication. :param instance: instance name, used to construct host :param host: host can be passed as an alternative to instance :param user: username :param password: password :param raise_on_empty: whether or not to raise an exception on 404 (no matching records) :param default_payload: deprecated, use request_params :param request_params: request params to send with requests :param use_ssl: Enable or disable SSL :param session: a requests session object """ # We allow either host or instance, not both if (host and instance) is not None: raise InvalidUsage("Got both 'instance' and 'host'. Panic.") if ((not (user and password)) and not session) or ((user or password) and session): raise InvalidUsage( "You must either provide username and password or a session") # Check if host or instance was passed if instance: self.host = "%s.service-now.com" % instance elif host: self.host = host else: raise InvalidUsage( "You must pass 'instance' or 'host' to create a client") # Check if SSL was requested if use_ssl is True: self.base_url = "https://%s" % self.host elif use_ssl is False: self.base_url = "http://%s" % self.host else: raise InvalidUsage("use_ssl: boolean value expected") # Connection properties self.instance = instance self._user = user self._password = password self.raise_on_empty = raise_on_empty self.request_params = self.default_payload = {} # default_payload is deprecated, let user know if default_payload is not None: warnings.warn( "default_payload is deprecated, please use request_params instead", DeprecationWarning) self.request_params = self.default_payload = default_payload if request_params is not None: self.request_params = request_params # Sets request parameters for requests if not isinstance(self.request_params, dict): raise InvalidUsage("Payload must be of type dict") # Create new session object self.session = self._get_session(session)