Source code for bmtk.utils.brain_observatory.rma_template

import os
import warnings
import pandas as pd
import requests
from contextlib import closing
import urllib
import json
from pathlib import Path

from jinja2 import Template


[docs] class Api(object): # _log = logging.getLogger('allensdk.api.api') # _file_download_log = logging.getLogger('allensdk.api.api.retrieve_file_over_http') default_api_url = 'http://api.brain-map.org' download_url = 'http://download.alleninstitute.org' def __init__(self, api_base_url_string=None): if api_base_url_string is None: api_base_url_string = Api.default_api_url self.set_api_urls(api_base_url_string) self.default_working_directory = os.getcwd()
[docs] def set_api_urls(self, api_base_url_string): '''Set the internal RMA and well known file download endpoint urls based on a api server endpoint. Parameters ---------- api_base_url_string : string url of the api to point to ''' self.api_url = api_base_url_string # http://help.brain-map.org/display/api/Downloading+a+WellKnownFile self.well_known_file_endpoint = api_base_url_string + \ '/api/v2/well_known_file_download' # http://help.brain-map.org/display/api/Downloading+3-D+Expression+Grid+Data self.grid_data_endpoint = api_base_url_string + '/grid_data' # http://help.brain-map.org/display/api/Downloading+and+Displaying+SVG self.svg_endpoint = api_base_url_string + '/api/v2/svg' self.svg_download_endpoint = api_base_url_string + '/api/v2/svg_download' # http://help.brain-map.org/display/api/Downloading+an+Ontology%27s+Structure+Graph self.structure_graph_endpoint = api_base_url_string + \ '/api/v2/structure_graph_download' # http://help.brain-map.org/display/api/Searching+a+Specimen+or+Structure+Tree self.tree_search_endpoint = api_base_url_string + '/api/v2/tree_search' # http://help.brain-map.org/display/api/Searching+Annotated+SectionDataSets self.annotated_section_data_sets_endpoint = api_base_url_string + \ '/api/v2/annotated_section_data_sets' self.compound_annotated_section_data_sets_endpoint = api_base_url_string + \ '/api/v2/compound_annotated_section_data_sets' # http://help.brain-map.org/display/api/Image-to-Image+Synchronization#Image-to-ImageSynchronization-ImagetoImage self.image_to_atlas_endpoint = api_base_url_string + '/api/v2/image_to_atlas' self.image_to_image_endpoint = api_base_url_string + '/api/v2/image_to_image' self.image_to_image_2d_endpoint = api_base_url_string + '/api/v2/image_to_image_2d' self.reference_to_image_endpoint = api_base_url_string + '/api/v2/reference_to_image' self.image_to_reference_endpoint = api_base_url_string + '/api/v2/image_to_reference' self.structure_to_image_endpoint = api_base_url_string + '/api/v2/structure_to_image' # http://help.brain-map.org/display/mouseconnectivity/API self.section_image_download_endpoint = api_base_url_string + \ '/api/v2/section_image_download' self.atlas_image_download_endpoint = api_base_url_string + \ '/api/v2/atlas_image_download' self.projection_image_download_endpoint = api_base_url_string + \ '/api/v2/projection_image_download' self.image_download_endpoint = api_base_url_string + \ '/api/v2/image_download' self.informatics_archive_endpoint = Api.download_url + '/informatics-archive' self.rma_endpoint = api_base_url_string + '/api/v2/data'
[docs] def set_default_working_directory(self, working_directory): '''Set the working directory where files will be saved. Parameters ---------- working_directory : string the absolute path string of the working directory. ''' self.default_working_directory = working_directory
[docs] def read_data(self, parsed_json): '''Return the message data from the parsed query. Parameters ---------- parsed_json : dict A python structure corresponding to the JSON data returned from the API. Notes ----- See `API Response Formats - Response Envelope <http://help.brain-map.org/display/api/API+Response+Formats#APIResponseFormats-ResponseEnvelope>`_ for additional documentation. ''' return parsed_json['msg']
[docs] def json_msg_query(self, url, dataframe=False): ''' Common case where the url is fully constructed and the response data is stored in the 'msg' field. Parameters ---------- url : string Where to get the data in json form dataframe : boolean True converts to a pandas dataframe, False (default) doesn't Returns ------- dict or DataFrame returned data; type depends on dataframe option ''' data = self.do_query(lambda *a, **k: url, self.read_data) if dataframe is True: warnings.warn("dataframe argument is deprecated", DeprecationWarning) data = pd.DataFrame(data) return data
[docs] def do_query(self, url_builder_fn, json_traversal_fn, *args, **kwargs): '''Bundle an query url construction function with a corresponding response json traversal function. Parameters ---------- url_builder_fn : function A function that takes parameters and returns an rma url. json_traversal_fn : function A function that takes a json-parsed python data structure and returns data from it. post : boolean, optional kwarg True does an HTTP POST, False (default) does a GET args : arguments Arguments to be passed to the url builder function. kwargs : keyword arguments Keyword arguments to be passed to the rma builder function. Returns ------- any type The data extracted from the json response. Examples -------- `A simple Api subclass example <data_api_client.html#creating-new-api-query-classes>`_. ''' api_url = url_builder_fn(*args, **kwargs) post = kwargs.get('post', False) json_parsed_data = self.retrieve_parsed_json_over_http(api_url, post) return json_traversal_fn(json_parsed_data)
[docs] def do_rma_query(self, rma_builder_fn, json_traversal_fn, *args, **kwargs): '''Bundle an RMA query url construction function with a corresponding response json traversal function. ..note:: Deprecated in AllenSDK 0.9.2 `do_rma_query` will be removed in AllenSDK 1.0, it is replaced by `do_query` because the latter is more general. Parameters ---------- rma_builder_fn : function A function that takes parameters and returns an rma url. json_traversal_fn : function A function that takes a json-parsed python data structure and returns data from it. args : arguments Arguments to be passed to the rma builder function. kwargs : keyword arguments Keyword arguments to be passed to the rma builder function. Returns ------- any type The data extracted from the json response. Examples -------- `A simple Api subclass example <data_api_client.html#creating-new-api-query-classes>`_. ''' return self.do_query(rma_builder_fn, json_traversal_fn, *args, **kwargs)
[docs] def load_api_schema(self): '''Download the RMA schema from the current RMA endpoint Returns ------- dict the parsed json schema message Notes ----- This information and other `Allen Brain Atlas Data Portal Data Model <http://help.brain-map.org/display/api/Data+Model>`_ documentation is also available as a `Class Hierarchy <http://api.brain-map.org/class_hierarchy>`_ and `Class List <http://api.brain-map.org/class_hierarchy>`_. ''' schema_url = self.rma_endpoint + '/enumerate.json' json_parsed_schema_data = self.retrieve_parsed_json_over_http( schema_url) return json_parsed_schema_data
[docs] def construct_well_known_file_download_url(self, well_known_file_id): '''Join data api endpoint and id. Parameters ---------- well_known_file_id : integer or string representing an integer well known file id Returns ------- string the well-known-file download url for the current api api server See Also -------- retrieve_file_over_http: Can be used to retrieve the file from the url. ''' return self.well_known_file_endpoint + '/' + str(well_known_file_id)
[docs] def cleanup_truncated_file(self, file_path): '''Helper for removing files. Parameters ---------- file_path : string Absolute path including the file name to remove.''' try: os.remove(file_path) except OSError as e: warnings(f'{e}')
[docs] def retrieve_file_over_http(self, url, file_path, zipped=False): '''Get a file from the data api and save it. Parameters ---------- url : string Url[1]_ from which to get the file. file_path : string Absolute path including the file name to save. zipped : bool, optional If true, assume that the response is a zipped directory and attempt to extract contained files into the directory containing file_path. Default is False. See Also -------- construct_well_known_file_download_url: Can be used to construct the url. References ---------- .. [1] Allen Brain Atlas Data Portal: `Downloading a WellKnownFile <http://help.brain-map.org/display/api/Downloading+a+WellKnownFile>`_. ''' # self._file_download_log.info("Downloading URL: %s", url) try: if zipped: stream_zip_directory_over_http(url, os.path.dirname(file_path)) else: stream_file_over_http(url, file_path) except Exception as e: # self._file_download_log.error("Couldn't retrieve file %s from %s" % (file_path, url)) # self.cleanup_truncated_file(file_path) raise e
[docs] def retrieve_parsed_json_over_http(self, url, post=False): '''Get the document and put it in a Python data structure Parameters ---------- url : string Full API query url. post : boolean True does an HTTP POST, False (default) encodes the URL and does a GET Returns ------- dict Result document as parsed by the JSON library. ''' # self._log.info("Downloading URL: %s", url) if post is False: url = requests.utils.quote(url, ';/?:@&=+$,') response = urllib.request.urlopen(url) json_string = response.read().decode("utf-8") data = json.loads(json_string) else: data = json_utilities.read_url_post(url) return data
[docs] def retrieve_xml_over_http(self, url): '''Get the document and put it in a Python data structure Parameters ---------- url : string Full API query url. Returns ------- string Unparsed xml string. ''' self._log.info("Downloading URL: %s", url) response = requests.get(url) return response.content
[docs] def stream_zip_directory_over_http(url, directory, members=None, timeout=(9.05, 31.1)): ''' Supply an http get request and stream the response to a file. Parameters ---------- url : str Send the request to this url directory : str Extract the response to this directory members : list of str, optional Extract only these files timeout : float or tuple of float, optional Specify a timeout for the request. If a tuple, specify seperate connect and read timeouts. ''' buf = io.BytesIO() with closing( requests.get(url, stream=True, timeout=timeout) ) as request: stream.stream_response_to_file( request, buf ) zipper = zipfile.ZipFile(buf) zipper.extractall(path=directory, members=members) zipper.close()
[docs] def stream_file_over_http(url, file_path, timeout=(9.05, 31.1)): ''' Supply an http get request and stream the response to a file. Parameters ---------- url : str Send the request to this url file_path : str Stream the response to this path timeout : float or tuple of float, optional Specify a timeout for the request. If a tuple, specify seperate connect and read timeouts. ''' Path(file_path).parent.mkdir(parents=True, exist_ok=True) with closing(requests.get(url, stream=True, timeout=timeout)) as response: response.raise_for_status() with open(file_path, 'wb') as fil: for chunk in response.iter_content(chunk_size=8192): # Adjust chunk_size as needed if chunk: # Filter out keep-alive new chunks fil.write(chunk)
# stream.stream_response_to_file(response, path=fil)
[docs] class RmaApi(Api): ''' See: `RESTful Model Access (RMA) <http://help.brain-map.org/display/api/RESTful+Model+Access+%28RMA%29>`_ ''' MODEL = 'model::' PIPE = 'pipe::' SERVICE = 'service::' CRITERIA = 'rma::criteria' INCLUDE = 'rma::include' OPTIONS = 'rma::options' ORDER = 'order' NUM_ROWS = 'num_rows' ALL = 'all' START_ROW = 'start_row' COUNT = 'count' ONLY = 'only' EXCEPT = 'except' EXCPT = 'excpt' TABULAR = 'tabular' DEBUG = 'debug' PREVIEW = 'preview' TRUE = 'true' FALSE = 'false' IS = '$is' EQ = '$eq' def __init__(self, base_uri=None): super(RmaApi, self).__init__(base_uri)
[docs] def build_query_url(self, stage_clauses, fmt='json'): '''Combine one or more RMA query stages into a single RMA query. Parameters ---------- stage_clauses : list of strings subqueries fmt : string, optional json (default), xml, or csv Returns ------- string complete RMA url ''' if not type(stage_clauses) is list: stage_clauses = [stage_clauses] url = ''.join([ self.rma_endpoint, '/query.', fmt, '?q=', ','.join(stage_clauses)]) return url
[docs] def model_stage(self, model, **kwargs): '''Construct a model stage of an RMA query string. Parameters ---------- model : string The top level data type filters : dict key, value comparisons applied to the top-level model to narrow the results. criteria : string raw RMA criteria clause to choose what object are returned include : string raw RMA include clause to return associated objects only : list of strings, optional to be joined into an rma::options only filter to limit what data is returned except : list of strings, optional to be joined into an rma::options except filter to limit what data is returned tabular : list of string, optional return columns as a tabular data structure rather than a nested tree. count : boolean, optional False to skip the extra database count query. debug : string, optional 'true', 'false' or 'preview' num_rows : int or string, optional how many database rows are returned (may not correspond directly to JSON tree structure) start_row : int or string, optional which database row is start of returned data (may not correspond directly to JSON tree structure) Notes ----- See `RMA Path Syntax <http://help.brain-map.org/display/api/RMA+Path+Syntax#RMAPathSyntax-DoubleColonforAxis>`_ for a brief overview of the normalized RMA syntax. Normalized RMA syntax differs from the legacy syntax used in much of the RMA documentation. Using the &debug=true option with an RMA URL will include debugging information in the response, including the normalized query. ''' clauses = [RmaApi.MODEL + model] filters = kwargs.get('filters', None) if filters is not None: clauses.append(self.filters(filters)) criteria = kwargs.get('criteria', None) if criteria is not None: clauses.append(',') clauses.append(RmaApi.CRITERIA) clauses.append(',') clauses.extend(criteria) include = kwargs.get('include', None) if include is not None: clauses.append(',') clauses.append(RmaApi.INCLUDE) clauses.append(',') clauses.extend(include) options_clause = self.options_clause(**kwargs) if options_clause != '': clauses.append(',') clauses.append(options_clause) stage = ''.join(clauses) return stage
[docs] def pipe_stage(self, pipe_name, parameters): '''Connect model and service stages via their JSON responses. Notes ----- See: `Service Pipelines <http://help.brain-map.org/display/api/Service+Pipelines>`_ and `Connected Services and Pipes <http://help.brain-map.org/display/api/Connected+Services+and+Pipes>`_ ''' clauses = [RmaApi.PIPE + pipe_name] clauses.append(self.tuple_filters(parameters)) stage = ''.join(clauses) return stage
[docs] def service_stage(self, service_name, parameters=None): '''Construct an RMA query fragment to send a request to a connected service. Parameters ---------- service_name : string Name of a documented connected service. parameters : dict key-value pairs as in the online documentation. Notes ----- See: `Service Pipelines <http://help.brain-map.org/display/api/Service+Pipelines>`_ and `Connected Services and Pipes <http://help.brain-map.org/display/api/Connected+Services+and+Pipes>`_ ''' clauses = [RmaApi.SERVICE + service_name] if parameters is not None: clauses.append(self.tuple_filters(parameters)) stage = ''.join(clauses) return stage
[docs] def model_query(self, *args, **kwargs): '''Construct and execute a model stage of an RMA query string. Parameters ---------- model : string The top level data type filters : dict key, value comparisons applied to the top-level model to narrow the results. criteria : string raw RMA criteria clause to choose what object are returned include : string raw RMA include clause to return associated objects only : list of strings, optional to be joined into an rma::options only filter to limit what data is returned except : list of strings, optional to be joined into an rma::options except filter to limit what data is returned excpt : list of strings, optional synonym for except parameter to avoid a reserved word conflict. tabular : list of string, optional return columns as a tabular data structure rather than a nested tree. count : boolean, optional False to skip the extra database count query. debug : string, optional 'true', 'false' or 'preview' num_rows : int or string, optional how many database rows are returned (may not correspond directly to JSON tree structure) start_row : int or string, optional which database row is start of returned data (may not correspond directly to JSON tree structure) Notes ----- See `RMA Path Syntax <http://help.brain-map.org/display/api/RMA+Path+Syntax#RMAPathSyntax-DoubleColonforAxis>`_ for a brief overview of the normalized RMA syntax. Normalized RMA syntax differs from the legacy syntax used in much of the RMA documentation. Using the &debug=true option with an RMA URL will include debugging information in the response, including the normalized query. ''' return self.json_msg_query( self.build_query_url( self.model_stage(*args, **kwargs)))
[docs] def service_query(self, *args, **kwargs): '''Construct and Execute a single-stage RMA query to send a request to a connected service. Parameters ---------- service_name : string Name of a documented connected service. parameters : dict key-value pairs as in the online documentation. Notes ----- See: `Service Pipelines <http://help.brain-map.org/display/api/Service+Pipelines>`_ and `Connected Services and Pipes <http://help.brain-map.org/display/api/Connected+Services+and+Pipes>`_ ''' return self.json_msg_query( self.build_query_url( self.service_stage(*args, **kwargs)))
[docs] def options_clause(self, **kwargs): '''build rma:: options clause. Parameters ---------- only : list of strings, optional except : list of strings, optional tabular : list of string, optional count : boolean, optional debug : string, optional 'true', 'false' or 'preview' num_rows : int or string, optional start_row : int or string, optional ''' clause = '' options_params = [] only = kwargs.get(RmaApi.ONLY, None) if only is not None: options_params.append( self.only_except_tabular_clause(RmaApi.ONLY, only)) # handle alternate 'except' spelling to avoid reserved word conflict excpt = kwargs.get(RmaApi.EXCEPT, None) excpt2 = kwargs.get(RmaApi.EXCPT, None) if excpt is not None and excpt2 is not None: warnings.warn('excpt and except options should not be used together', Warning) elif excpt2 is not None: excpt = excpt2 if excpt is not None: options_params.append( self.only_except_tabular_clause(RmaApi.EXCEPT, excpt)) tabular = kwargs.get(RmaApi.TABULAR, None) if tabular is not None: options_params.append( self.only_except_tabular_clause(RmaApi.TABULAR, tabular)) num_rows = kwargs.get(RmaApi.NUM_ROWS, None) if num_rows is not None: if num_rows == RmaApi.ALL: options_params.append("[%s$eq'all']" % (RmaApi.NUM_ROWS)) else: options_params.append('[%s$eq%d]' % (RmaApi.NUM_ROWS, num_rows)) start_row = kwargs.get(RmaApi.START_ROW, None) if start_row is not None: options_params.append('[%s$eq%d]' % (RmaApi.START_ROW, start_row)) order = kwargs.get(RmaApi.ORDER, None) if order is not None: options_params.append(self.order_clause(order)) debug = kwargs.get(RmaApi.DEBUG, None) if debug is not None: options_params.append(self.debug_clause(debug)) cnt = kwargs.get(RmaApi.COUNT, None) if cnt is not None: if cnt is True or cnt == 'true': options_params.append('[%s$eq%s]' % (RmaApi.COUNT, RmaApi.TRUE)) elif cnt is False or cnt == 'false': options_params.append('[%s$eq%s]' % (RmaApi.COUNT, RmaApi.FALSE)) else: pass if len(options_params) > 0: clause = RmaApi.OPTIONS + ''.join(options_params) return clause
[docs] def only_except_tabular_clause(self, filter_type, attribute_list): '''Construct a clause to filter which attributes are returned for use in an rma::options clause. Parameters ---------- filter_type : string 'only', 'except', or 'tabular' attribute_list : list of strings for example ['acronym', 'products.name', 'structure.id'] Returns ------- clause : string The query clause for inclusion in an RMA query URL. Notes ----- The title of tabular columns can be set by adding '+as+<title>' to the attribute. The tabular filter type requests a response that is row-oriented rather than a nested structure. Because of this, the tabular option can mask the lazy query behavior of an rma::include clause. The tabular option does not mask the inner-join behavior of an rma::include clause. The tabular filter is required for .csv format RMA requests. ''' clause = '' if attribute_list is not None: clause = '[%s$eq%s]' % (filter_type, ','.join(attribute_list)) return clause
[docs] def order_clause(self, order_list=None): '''Construct a debug clause for use in an rma::options clause. Parameters ---------- order_list : list of strings for example ['acronym', 'products.name+asc', 'structure.id+desc'] Returns ------- clause : string The query clause for inclusion in an RMA query URL. Notes ----- Optionally adding '+asc' (default) or '+desc' after an attribute will change the sort order. ''' clause = '' if order_list is not None: clause = '[order$eq%s]' % (','.join(order_list)) return clause
[docs] def debug_clause(self, debug_value=None): '''Construct a debug clause for use in an rma::options clause. Parameters ---------- debug_value : string or boolean True, False, None (default) or 'preview' Returns ------- clause : string The query clause for inclusion in an RMA query URL. Notes ----- True will request debugging information in the response. False will request no debugging information. None will return an empty clause. 'preview' will request debugging information without the query being run. ''' clause = '' if debug_value is None: clause = '' if debug_value is True or debug_value == 'true': clause = '[debug$eqtrue]' elif debug_value is False or debug_value == 'false': clause = '[debug$eqfalse]' elif debug_value == 'preview': clause = "[debug$eq'preview']" return clause
# TODO: deprecate for something that can preserve order
[docs] def filters(self, filters): '''serialize RMA query filter clauses. Parameters ---------- filters : dict keys and values for narrowing a query. Returns ------- string filter clause for an RMA query string. ''' filters_builder = [] for (key, value) in filters.items(): filters_builder.append(self.filter(key, value)) return ''.join(filters_builder)
# TODO: this needs to be more rigorous.
[docs] def tuple_filters(self, filters): '''Construct an RMA filter clause. Notes ----- See `RMA Path Syntax - Square Brackets for Filters <http://help.brain-map.org/display/api/RMA+Path+Syntax#RMAPathSyntax-SquareBracketsforFilters>`_ for additional documentation. ''' filters_builder = [] for filt in sorted(filters): if filt[-1] is None: continue if len(filt) == 2: val = filt[1] if type(val) is list: val_array = [] for v in val: if type(v) is str: val_array.append(v) else: val_array.append(str(v)) val = ','.join(val_array) filters_builder.append("[%s$eq%s]" % (filt[0], val)) elif type(val) is int: filters_builder.append("[%s$eq%d]" % (filt[0], val)) elif type(val) is bool: if val: filters_builder.append("[%s$eqtrue]" % (filt[0])) else: filters_builder.append("[%s$eqfalse]" % (filt[0])) elif type(val) is str: filters_builder.append("[%s$eq%s]" % (filt[0], filt[1])) elif len(filt) == 3: filters_builder.append("[%s%s%s]" % (filt[0], filt[1], str(filt[2]))) return ''.join(filters_builder)
[docs] def quote_string(self, the_string): '''Wrap a clause in single quotes. Parameters ---------- the_string : string a clause to be included in an rma query that needs to be quoted Returns ------- string input wrapped in single quotes ''' return ''.join(["'", the_string, "'"])
[docs] def filter(self, key, value): '''serialize a single RMA query filter clause. Parameters ---------- key : string keys for narrowing a query. value : string value for narrowing a query. Returns ------- string a single filter clause for an RMA query string. ''' return "".join(['[', key, RmaApi.EQ, str(value), ']'])
[docs] def build_schema_query(self, clazz=None, fmt='json'): '''Build the URL that will fetch the data schema. Parameters ---------- clazz : string, optional Name of a specific class or None (default). fmt : string, optional json (default) or xml Returns ------- url : string The constructed URL Notes ----- If a class is specified, only the schema information for that class will be requested, otherwise the url requests the entire schema. ''' if clazz is not None: class_clause = '/' + clazz else: class_clause = '' url = ''.join([self.rma_endpoint, class_clause, '.', fmt]) return url
[docs] def get_schema(self, clazz=None): '''Retrieve schema information.''' schema_data = self.do_query(self.build_schema_query, self.read_data, clazz) return schema_data
[docs] class RmaTemplate(RmaApi): ''' See: `Atlas Drawings and Ontologies <http://help.brain-map.org/display/api/Atlas+Drawings+and+Ontologies>`_ ''' def __init__(self, base_uri=None, query_manifest=None): super(RmaTemplate, self).__init__(base_uri) self.templates = query_manifest
[docs] def to_filter_rhs(self, rhs): if type(rhs) == list: return ','.join(str(r) for r in rhs) return rhs
[docs] def template_query(self, template_name, entry_name, **kwargs): cb = self.templates[template_name] templates = [e for e in cb if e['name'] == entry_name] if len(templates) > 0: template = templates[0] else: raise Exception('Entry %s not found.' % (entry_name)) query_args = {'model': template['model']} if 'criteria' in template: criteria_template = Template(template['criteria']) if 'criteria_params' in template: criteria_params = {key: self.to_filter_rhs(kwargs.get(key)) for key in template['criteria_params'] if key in kwargs and kwargs.get(key) is not None} else: criteria_params = {} criteria_str = str(criteria_template.render(**criteria_params)) if criteria_str: query_args['criteria'] = criteria_str if 'include' in template: include_template = Template(template['include']) if 'include_params' in template: include_params = {key: self.to_filter_rhs(kwargs.get(key)) for key in template['include_params'] if key in kwargs and kwargs.get(key) is not None} else: include_params = {} include_str = str(include_template.render(**include_params)) if include_str: query_args['include'] = include_str if 'only' in kwargs: if kwargs.get('only') is not None: query_args['only'] = [self.quote_string( ','.join(kwargs.get('only')))] elif 'only' in template: query_args['only'] = [ self.quote_string(','.join(template['only']))] if 'except' in kwargs: if kwargs.get('except') is not None: query_args['except'] = [self.quote_string( ','.join(kwargs.get('except')))] elif 'except' in template: query_args['except'] = template['except'] if 'start_row' in kwargs: query_args['start_row'] = kwargs.get('start_row') elif 'start_row' in template: query_args['start_row'] = template['start_row'] if 'num_rows' in kwargs: query_args['num_rows'] = kwargs.get('num_rows') elif 'num_rows' in template: query_args['num_rows'] = template['num_rows'] if 'count' in kwargs: query_args['count'] = kwargs.get('count') elif 'count' in template: query_args['count'] = template['count'] if 'order' in kwargs: query_args['order'] = kwargs.get('order') elif 'order' in template: query_args['order'] = template['order'] query_args.update(kwargs) data = self.model_query(**query_args) return data