123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- # pylint: disable=C,R,W
- """Defines the templating context for SQL Lab"""
- from datetime import datetime, timedelta
- import inspect
- import json
- import random
- import time
- import uuid
- from dateutil.relativedelta import relativedelta
- from flask import g, request
- from jinja2.sandbox import SandboxedEnvironment
- from superset import app
- config = app.config
- BASE_CONTEXT = {
- 'datetime': datetime,
- 'random': random,
- 'relativedelta': relativedelta,
- 'time': time,
- 'timedelta': timedelta,
- 'uuid': uuid,
- }
- BASE_CONTEXT.update(config.get('JINJA_CONTEXT_ADDONS', {}))
- def url_param(param, default=None):
- """Get a url or post data parameter
- :param param: the parameter to lookup
- :type param: str
- :param default: the value to return in the absence of the parameter
- :type default: str
- """
- if request.args.get(param):
- return request.args.get(param, default)
- # Supporting POST as well as get
- if request.form.get('form_data'):
- form_data = json.loads(request.form.get('form_data'))
- url_params = form_data['url_params'] or {}
- return url_params.get(param, default)
- return default
- def current_user_id():
- """The id of the user who is currently logged in"""
- if hasattr(g, 'user') and g.user:
- return g.user.id
- def current_username():
- """The username of the user who is currently logged in"""
- if g.user:
- return g.user.username
- def filter_values(column, default=None):
- """ Gets a values for a particular filter as a list
- This is useful if:
- - you want to use a filter box to filter a query where the name of filter box
- column doesn't match the one in the select statement
- - you want to have the ability for filter inside the main query for speed purposes
- This searches for "filters" and "extra_filters" in form_data for a match
- Usage example:
- SELECT action, count(*) as times
- FROM logs
- WHERE action in ( {{ "'" + "','".join(filter_values('action_type')) + "'" )
- GROUP BY 1
- :param column: column/filter name to lookup
- :type column: str
- :param default: default value to return if there's no matching columns
- :type default: str
- :return: returns a list of filter values
- :type: list
- """
- form_data = json.loads(request.form.get('form_data', '{}'))
- return_val = []
- for filter_type in ['filters', 'extra_filters']:
- if filter_type not in form_data:
- continue
- for f in form_data[filter_type]:
- if f['col'] == column:
- for v in f['val']:
- return_val.append(v)
- if return_val:
- return return_val
- if default:
- return [default]
- else:
- return []
- class BaseTemplateProcessor(object):
- """Base class for database-specific jinja context
- There's this bit of magic in ``process_template`` that instantiates only
- the database context for the active database as a ``models.Database``
- object binds it to the context object, so that object methods
- have access to
- that context. This way, {{ hive.latest_partition('mytable') }} just
- knows about the database it is operating in.
- This means that object methods are only available for the active database
- and are given access to the ``models.Database`` object and schema
- name. For globally available methods use ``@classmethod``.
- """
- engine = None
- def __init__(self, database=None, query=None, table=None, **kwargs):
- self.database = database
- self.query = query
- self.schema = None
- if query and query.schema:
- self.schema = query.schema
- elif table:
- self.schema = table.schema
- self.context = {
- 'url_param': url_param,
- 'current_user_id': current_user_id,
- 'current_username': current_username,
- 'filter_values': filter_values,
- 'form_data': {},
- }
- self.context.update(kwargs)
- self.context.update(BASE_CONTEXT)
- if self.engine:
- self.context[self.engine] = self
- self.env = SandboxedEnvironment()
- def process_template(self, sql, **kwargs):
- """Processes a sql template
- >>> sql = "SELECT '{{ datetime(2017, 1, 1).isoformat() }}'"
- >>> process_template(sql)
- "SELECT '2017-01-01T00:00:00'"
- """
- template = self.env.from_string(sql)
- kwargs.update(self.context)
- return template.render(kwargs)
- class PrestoTemplateProcessor(BaseTemplateProcessor):
- """Presto Jinja context
- The methods described here are namespaced under ``presto`` in the
- jinja context as in ``SELECT '{{ presto.some_macro_call() }}'``
- """
- engine = 'presto'
- @staticmethod
- def _schema_table(table_name, schema):
- if '.' in table_name:
- schema, table_name = table_name.split('.')
- return table_name, schema
- def latest_partition(self, table_name):
- table_name, schema = self._schema_table(table_name, self.schema)
- return self.database.db_engine_spec.latest_partition(
- table_name, schema, self.database)[1]
- def latest_sub_partition(self, table_name, **kwargs):
- table_name, schema = self._schema_table(table_name, self.schema)
- return self.database.db_engine_spec.latest_sub_partition(
- table_name=table_name,
- schema=schema,
- database=self.database,
- **kwargs)
- class HiveTemplateProcessor(PrestoTemplateProcessor):
- engine = 'hive'
- template_processors = {}
- keys = tuple(globals().keys())
- for k in keys:
- o = globals()[k]
- if o and inspect.isclass(o) and issubclass(o, BaseTemplateProcessor):
- template_processors[o.engine] = o
- def get_template_processor(database, table=None, query=None, **kwargs):
- TP = template_processors.get(database.backend, BaseTemplateProcessor)
- return TP(database=database, table=table, query=query, **kwargs)
|