We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Access current user id/details from Python data source 
First of all, thanks to the authors and contributors for a very interesting and promising tool.  
### Question:
Is it possible to access current user id/details from Python data source?
I would like to be able to control access to the viewed data at row level without a need of creating and maintaining multiple (in my case about 80) data sources and dashboards. My desired scenario:
    1. Store A logs into their account and accesses dashboard with KPI/Metrics.
    2. Dashboard calls Python data source(s).
    3. Python code loads data from a db table with all stores, identifies current user and filters out all records where store != Store A.
    4. Dashboard is dynamically loaded with entries for Store A only.

Also if there are any other ways to achieve the above, please advise.   

### Technical details:
* Redash Version: 0.12.0

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `redash/query_runner/python.py`
Content:
```
1 import datetime
2 import importlib
3 import logging
4 import sys
5 
6 from redash.query_runner import *
7 from redash.utils import json_dumps, json_loads
8 from redash import models
9 from RestrictedPython import compile_restricted
10 from RestrictedPython.Guards import safe_builtins
11 
12 
13 logger = logging.getLogger(__name__)
14 
15 
16 class CustomPrint(object):
17     """CustomPrint redirect "print" calls to be sent as "log" on the result object."""
18     def __init__(self):
19         self.enabled = True
20         self.lines = []
21 
22     def write(self, text):
23         if self.enabled:
24             if text and text.strip():
25                 log_line = "[{0}] {1}".format(datetime.datetime.utcnow().isoformat(), text)
26                 self.lines.append(log_line)
27 
28     def enable(self):
29         self.enabled = True
30 
31     def disable(self):
32         self.enabled = False
33 
34     def __call__(self):
35         return self
36 
37 
38 class Python(BaseQueryRunner):
39     safe_builtins = (
40         'sorted', 'reversed', 'map', 'reduce', 'any', 'all',
41         'slice', 'filter', 'len', 'next', 'enumerate',
42         'sum', 'abs', 'min', 'max', 'round', 'cmp', 'divmod',
43         'str', 'unicode', 'int', 'float', 'complex',
44         'tuple', 'set', 'list', 'dict', 'bool',
45     )
46 
47     @classmethod
48     def configuration_schema(cls):
49         return {
50             'type': 'object',
51             'properties': {
52                 'allowedImportModules': {
53                     'type': 'string',
54                     'title': 'Modules to import prior to running the script'
55                 },
56                 'additionalModulesPaths': {
57                     'type': 'string'
58                 }
59             },
60         }
61 
62     @classmethod
63     def enabled(cls):
64         return True
65 
66     @classmethod
67     def annotate_query(cls):
68         return False
69 
70     def __init__(self, configuration):
71         super(Python, self).__init__(configuration)
72 
73         self.syntax = "python"
74 
75         self._allowed_modules = {}
76         self._script_locals = {"result": {"rows": [], "columns": [], "log": []}}
77         self._enable_print_log = True
78         self._custom_print = CustomPrint()
79 
80         if self.configuration.get("allowedImportModules", None):
81             for item in self.configuration["allowedImportModules"].split(","):
82                 self._allowed_modules[item] = None
83 
84         if self.configuration.get("additionalModulesPaths", None):
85             for p in self.configuration["additionalModulesPaths"].split(","):
86                 if p not in sys.path:
87                     sys.path.append(p)
88 
89     def custom_import(self, name, globals=None, locals=None, fromlist=(), level=0):
90         if name in self._allowed_modules:
91             m = None
92             if self._allowed_modules[name] is None:
93                 m = importlib.import_module(name)
94                 self._allowed_modules[name] = m
95             else:
96                 m = self._allowed_modules[name]
97 
98             return m
99 
100         raise Exception("'{0}' is not configured as a supported import module".format(name))
101 
102     @staticmethod
103     def custom_write(obj):
104         """
105         Custom hooks which controls the way objects/lists/tuples/dicts behave in
106         RestrictedPython
107         """
108         return obj
109 
110     @staticmethod
111     def custom_get_item(obj, key):
112         return obj[key]
113 
114     @staticmethod
115     def custom_get_iter(obj):
116         return iter(obj)
117 
118     @staticmethod
119     def add_result_column(result, column_name, friendly_name, column_type):
120         """Helper function to add columns inside a Python script running in Redash in an easier way
121 
122         Parameters:
123         :result dict: The result dict
124         :column_name string: Name of the column, which should be consisted of lowercase latin letters or underscore.
125         :friendly_name string: Name of the column for display
126         :column_type string: Type of the column. Check supported data types for details.
127         """
128         if column_type not in SUPPORTED_COLUMN_TYPES:
129             raise Exception("'{0}' is not a supported column type".format(column_type))
130 
131         if "columns" not in result:
132             result["columns"] = []
133 
134         result["columns"].append({
135             "name": column_name,
136             "friendly_name": friendly_name,
137             "type": column_type
138         })
139 
140     @staticmethod
141     def add_result_row(result, values):
142         """Helper function to add one row to results set.
143 
144         Parameters:
145         :result dict: The result dict
146         :values dict: One row of result in dict. The key should be one of the column names. The value is the value of the column in this row.
147         """
148         if "rows" not in result:
149             result["rows"] = []
150 
151         result["rows"].append(values)
152 
153     @staticmethod
154     def execute_query(data_source_name_or_id, query):
155         """Run query from specific data source.
156 
157         Parameters:
158         :data_source_name_or_id string|integer: Name or ID of the data source
159         :query string: Query to run
160         """
161         try:
162             if type(data_source_name_or_id) == int:
163                 data_source = models.DataSource.get_by_id(data_source_name_or_id)
164             else:
165                 data_source = models.DataSource.get_by_name(data_source_name_or_id)
166         except models.NoResultFound:
167             raise Exception("Wrong data source name/id: %s." % data_source_name_or_id)
168 
169         # TODO: pass the user here...
170         data, error = data_source.query_runner.run_query(query, None)
171         if error is not None:
172             raise Exception(error)
173 
174         # TODO: allow avoiding the JSON dumps/loads in same process
175         return json_loads(data)
176 
177     @staticmethod
178     def get_source_schema(data_source_name_or_id):
179         """Get schema from specific data source.
180 
181         :param data_source_name_or_id: string|integer: Name or ID of the data source
182         :return:
183         """
184         try:
185             if type(data_source_name_or_id) == int:
186                 data_source = models.DataSource.get_by_id(data_source_name_or_id)
187             else:
188                 data_source = models.DataSource.get_by_name(data_source_name_or_id)
189         except models.NoResultFound:
190             raise Exception("Wrong data source name/id: %s." % data_source_name_or_id)
191         schema = data_source.query_runner.get_schema()
192         return schema
193 
194     @staticmethod
195     def get_query_result(query_id):
196         """Get result of an existing query.
197 
198         Parameters:
199         :query_id integer: ID of existing query
200         """
201         try:
202             query = models.Query.get_by_id(query_id)
203         except models.NoResultFound:
204             raise Exception("Query id %s does not exist." % query_id)
205 
206         if query.latest_query_data is None:
207             raise Exception("Query does not have results yet.")
208 
209         if query.latest_query_data.data is None:
210             raise Exception("Query does not have results yet.")
211 
212         return json_loads(query.latest_query_data.data)
213 
214     def test_connection(self):
215         pass
216 
217     def run_query(self, query, user):
218         try:
219             error = None
220 
221             code = compile_restricted(query, '<string>', 'exec')
222 
223             builtins = safe_builtins.copy()
224             builtins["_write_"] = self.custom_write
225             builtins["__import__"] = self.custom_import
226             builtins["_getattr_"] = getattr
227             builtins["getattr"] = getattr
228             builtins["_setattr_"] = setattr
229             builtins["setattr"] = setattr
230             builtins["_getitem_"] = self.custom_get_item
231             builtins["_getiter_"] = self.custom_get_iter
232             builtins["_print_"] = self._custom_print
233 
234             # Layer in our own additional set of builtins that we have
235             # considered safe.
236             for key in self.safe_builtins:
237                 builtins[key] = __builtins__[key]
238 
239             restricted_globals = dict(__builtins__=builtins)
240             restricted_globals["get_query_result"] = self.get_query_result
241             restricted_globals["get_source_schema"] = self.get_source_schema
242             restricted_globals["execute_query"] = self.execute_query
243             restricted_globals["add_result_column"] = self.add_result_column
244             restricted_globals["add_result_row"] = self.add_result_row
245             restricted_globals["disable_print_log"] = self._custom_print.disable
246             restricted_globals["enable_print_log"] = self._custom_print.enable
247 
248             # Supported data types
249             restricted_globals["TYPE_DATETIME"] = TYPE_DATETIME
250             restricted_globals["TYPE_BOOLEAN"] = TYPE_BOOLEAN
251             restricted_globals["TYPE_INTEGER"] = TYPE_INTEGER
252             restricted_globals["TYPE_STRING"] = TYPE_STRING
253             restricted_globals["TYPE_DATE"] = TYPE_DATE
254             restricted_globals["TYPE_FLOAT"] = TYPE_FLOAT
255 
256 
257             # TODO: Figure out the best way to have a timeout on a script
258             #       One option is to use ETA with Celery + timeouts on workers
259             #       And replacement of worker process every X requests handled.
260 
261             exec((code), restricted_globals, self._script_locals)
262 
263             result = self._script_locals['result']
264             result['log'] = self._custom_print.lines
265             json_data = json_dumps(result)
266         except KeyboardInterrupt:
267             error = "Query cancelled by user."
268             json_data = None
269         except Exception as e:
270             error = str(type(e)) + " " + str(e)
271             json_data = None
272 
273         return json_data, error
274 
275 
276 register(Python)
277
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/redash/query_runner/python.py b/redash/query_runner/python.py
--- a/redash/query_runner/python.py
+++ b/redash/query_runner/python.py
@@ -211,10 +211,15 @@
 
         return json_loads(query.latest_query_data.data)
 
+    def get_current_user(self):
+        return self._current_user.to_dict()
+
     def test_connection(self):
         pass
 
     def run_query(self, query, user):
+        self._current_user = user
+
         try:
             error = None
 
@@ -239,6 +244,7 @@
             restricted_globals = dict(__builtins__=builtins)
             restricted_globals["get_query_result"] = self.get_query_result
             restricted_globals["get_source_schema"] = self.get_source_schema
+            restricted_globals["get_current_user"] = self.get_current_user
             restricted_globals["execute_query"] = self.execute_query
             restricted_globals["add_result_column"] = self.add_result_column
             restricted_globals["add_result_row"] = self.add_result_row

{"golden_diff": "diff --git a/redash/query_runner/python.py b/redash/query_runner/python.py\n--- a/redash/query_runner/python.py\n+++ b/redash/query_runner/python.py\n@@ -211,10 +211,15 @@\n \n         return json_loads(query.latest_query_data.data)\n \n+    def get_current_user(self):\n+        return self._current_user.to_dict()\n+\n     def test_connection(self):\n         pass\n \n     def run_query(self, query, user):\n+        self._current_user = user\n+\n         try:\n             error = None\n \n@@ -239,6 +244,7 @@\n             restricted_globals = dict(__builtins__=builtins)\n             restricted_globals[\"get_query_result\"] = self.get_query_result\n             restricted_globals[\"get_source_schema\"] = self.get_source_schema\n+            restricted_globals[\"get_current_user\"] = self.get_current_user\n             restricted_globals[\"execute_query\"] = self.execute_query\n             restricted_globals[\"add_result_column\"] = self.add_result_column\n             restricted_globals[\"add_result_row\"] = self.add_result_row\n", "issue": "Access current user id/details from Python data source \nFirst of all, thanks to the authors and contributors for a very interesting and promising tool.  \r\n### Question:\r\nIs it possible to access current user id/details from Python data source?\r\nI would like to be able to control access to the viewed data at row level without a need of creating and maintaining multiple (in my case about 80) data sources and dashboards. My desired scenario:\r\n    1. Store A logs into their account and accesses dashboard with KPI/Metrics.\r\n    2. Dashboard calls Python data source(s).\r\n    3. Python code loads data from a db table with all stores, identifies current user and filters out all records where store != Store A.\r\n    4. Dashboard is dynamically loaded with entries for Store A only.\r\n\r\nAlso if there are any other ways to achieve the above, please advise.   \r\n\r\n### Technical details:\r\n* Redash Version: 0.12.0\n", "before_files": [{"content": "import datetime\nimport importlib\nimport logging\nimport sys\n\nfrom redash.query_runner import *\nfrom redash.utils import json_dumps, json_loads\nfrom redash import models\nfrom RestrictedPython import compile_restricted\nfrom RestrictedPython.Guards import safe_builtins\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass CustomPrint(object):\n    \"\"\"CustomPrint redirect \"print\" calls to be sent as \"log\" on the result object.\"\"\"\n    def __init__(self):\n        self.enabled = True\n        self.lines = []\n\n    def write(self, text):\n        if self.enabled:\n            if text and text.strip():\n                log_line = \"[{0}] {1}\".format(datetime.datetime.utcnow().isoformat(), text)\n                self.lines.append(log_line)\n\n    def enable(self):\n        self.enabled = True\n\n    def disable(self):\n        self.enabled = False\n\n    def __call__(self):\n        return self\n\n\nclass Python(BaseQueryRunner):\n    safe_builtins = (\n        'sorted', 'reversed', 'map', 'reduce', 'any', 'all',\n        'slice', 'filter', 'len', 'next', 'enumerate',\n        'sum', 'abs', 'min', 'max', 'round', 'cmp', 'divmod',\n        'str', 'unicode', 'int', 'float', 'complex',\n        'tuple', 'set', 'list', 'dict', 'bool',\n    )\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            'type': 'object',\n            'properties': {\n                'allowedImportModules': {\n                    'type': 'string',\n                    'title': 'Modules to import prior to running the script'\n                },\n                'additionalModulesPaths': {\n                    'type': 'string'\n                }\n            },\n        }\n\n    @classmethod\n    def enabled(cls):\n        return True\n\n    @classmethod\n    def annotate_query(cls):\n        return False\n\n    def __init__(self, configuration):\n        super(Python, self).__init__(configuration)\n\n        self.syntax = \"python\"\n\n        self._allowed_modules = {}\n        self._script_locals = {\"result\": {\"rows\": [], \"columns\": [], \"log\": []}}\n        self._enable_print_log = True\n        self._custom_print = CustomPrint()\n\n        if self.configuration.get(\"allowedImportModules\", None):\n            for item in self.configuration[\"allowedImportModules\"].split(\",\"):\n                self._allowed_modules[item] = None\n\n        if self.configuration.get(\"additionalModulesPaths\", None):\n            for p in self.configuration[\"additionalModulesPaths\"].split(\",\"):\n                if p not in sys.path:\n                    sys.path.append(p)\n\n    def custom_import(self, name, globals=None, locals=None, fromlist=(), level=0):\n        if name in self._allowed_modules:\n            m = None\n            if self._allowed_modules[name] is None:\n                m = importlib.import_module(name)\n                self._allowed_modules[name] = m\n            else:\n                m = self._allowed_modules[name]\n\n            return m\n\n        raise Exception(\"'{0}' is not configured as a supported import module\".format(name))\n\n    @staticmethod\n    def custom_write(obj):\n        \"\"\"\n        Custom hooks which controls the way objects/lists/tuples/dicts behave in\n        RestrictedPython\n        \"\"\"\n        return obj\n\n    @staticmethod\n    def custom_get_item(obj, key):\n        return obj[key]\n\n    @staticmethod\n    def custom_get_iter(obj):\n        return iter(obj)\n\n    @staticmethod\n    def add_result_column(result, column_name, friendly_name, column_type):\n        \"\"\"Helper function to add columns inside a Python script running in Redash in an easier way\n\n        Parameters:\n        :result dict: The result dict\n        :column_name string: Name of the column, which should be consisted of lowercase latin letters or underscore.\n        :friendly_name string: Name of the column for display\n        :column_type string: Type of the column. Check supported data types for details.\n        \"\"\"\n        if column_type not in SUPPORTED_COLUMN_TYPES:\n            raise Exception(\"'{0}' is not a supported column type\".format(column_type))\n\n        if \"columns\" not in result:\n            result[\"columns\"] = []\n\n        result[\"columns\"].append({\n            \"name\": column_name,\n            \"friendly_name\": friendly_name,\n            \"type\": column_type\n        })\n\n    @staticmethod\n    def add_result_row(result, values):\n        \"\"\"Helper function to add one row to results set.\n\n        Parameters:\n        :result dict: The result dict\n        :values dict: One row of result in dict. The key should be one of the column names. The value is the value of the column in this row.\n        \"\"\"\n        if \"rows\" not in result:\n            result[\"rows\"] = []\n\n        result[\"rows\"].append(values)\n\n    @staticmethod\n    def execute_query(data_source_name_or_id, query):\n        \"\"\"Run query from specific data source.\n\n        Parameters:\n        :data_source_name_or_id string|integer: Name or ID of the data source\n        :query string: Query to run\n        \"\"\"\n        try:\n            if type(data_source_name_or_id) == int:\n                data_source = models.DataSource.get_by_id(data_source_name_or_id)\n            else:\n                data_source = models.DataSource.get_by_name(data_source_name_or_id)\n        except models.NoResultFound:\n            raise Exception(\"Wrong data source name/id: %s.\" % data_source_name_or_id)\n\n        # TODO: pass the user here...\n        data, error = data_source.query_runner.run_query(query, None)\n        if error is not None:\n            raise Exception(error)\n\n        # TODO: allow avoiding the JSON dumps/loads in same process\n        return json_loads(data)\n\n    @staticmethod\n    def get_source_schema(data_source_name_or_id):\n        \"\"\"Get schema from specific data source.\n\n        :param data_source_name_or_id: string|integer: Name or ID of the data source\n        :return:\n        \"\"\"\n        try:\n            if type(data_source_name_or_id) == int:\n                data_source = models.DataSource.get_by_id(data_source_name_or_id)\n            else:\n                data_source = models.DataSource.get_by_name(data_source_name_or_id)\n        except models.NoResultFound:\n            raise Exception(\"Wrong data source name/id: %s.\" % data_source_name_or_id)\n        schema = data_source.query_runner.get_schema()\n        return schema\n\n    @staticmethod\n    def get_query_result(query_id):\n        \"\"\"Get result of an existing query.\n\n        Parameters:\n        :query_id integer: ID of existing query\n        \"\"\"\n        try:\n            query = models.Query.get_by_id(query_id)\n        except models.NoResultFound:\n            raise Exception(\"Query id %s does not exist.\" % query_id)\n\n        if query.latest_query_data is None:\n            raise Exception(\"Query does not have results yet.\")\n\n        if query.latest_query_data.data is None:\n            raise Exception(\"Query does not have results yet.\")\n\n        return json_loads(query.latest_query_data.data)\n\n    def test_connection(self):\n        pass\n\n    def run_query(self, query, user):\n        try:\n            error = None\n\n            code = compile_restricted(query, '<string>', 'exec')\n\n            builtins = safe_builtins.copy()\n            builtins[\"_write_\"] = self.custom_write\n            builtins[\"__import__\"] = self.custom_import\n            builtins[\"_getattr_\"] = getattr\n            builtins[\"getattr\"] = getattr\n            builtins[\"_setattr_\"] = setattr\n            builtins[\"setattr\"] = setattr\n            builtins[\"_getitem_\"] = self.custom_get_item\n            builtins[\"_getiter_\"] = self.custom_get_iter\n            builtins[\"_print_\"] = self._custom_print\n\n            # Layer in our own additional set of builtins that we have\n            # considered safe.\n            for key in self.safe_builtins:\n                builtins[key] = __builtins__[key]\n\n            restricted_globals = dict(__builtins__=builtins)\n            restricted_globals[\"get_query_result\"] = self.get_query_result\n            restricted_globals[\"get_source_schema\"] = self.get_source_schema\n            restricted_globals[\"execute_query\"] = self.execute_query\n            restricted_globals[\"add_result_column\"] = self.add_result_column\n            restricted_globals[\"add_result_row\"] = self.add_result_row\n            restricted_globals[\"disable_print_log\"] = self._custom_print.disable\n            restricted_globals[\"enable_print_log\"] = self._custom_print.enable\n\n            # Supported data types\n            restricted_globals[\"TYPE_DATETIME\"] = TYPE_DATETIME\n            restricted_globals[\"TYPE_BOOLEAN\"] = TYPE_BOOLEAN\n            restricted_globals[\"TYPE_INTEGER\"] = TYPE_INTEGER\n            restricted_globals[\"TYPE_STRING\"] = TYPE_STRING\n            restricted_globals[\"TYPE_DATE\"] = TYPE_DATE\n            restricted_globals[\"TYPE_FLOAT\"] = TYPE_FLOAT\n\n\n            # TODO: Figure out the best way to have a timeout on a script\n            #       One option is to use ETA with Celery + timeouts on workers\n            #       And replacement of worker process every X requests handled.\n\n            exec((code), restricted_globals, self._script_locals)\n\n            result = self._script_locals['result']\n            result['log'] = self._custom_print.lines\n            json_data = json_dumps(result)\n        except KeyboardInterrupt:\n            error = \"Query cancelled by user.\"\n            json_data = None\n        except Exception as e:\n            error = str(type(e)) + \" \" + str(e)\n            json_data = None\n\n        return json_data, error\n\n\nregister(Python)\n", "path": "redash/query_runner/python.py"}], "after_files": [{"content": "import datetime\nimport importlib\nimport logging\nimport sys\n\nfrom redash.query_runner import *\nfrom redash.utils import json_dumps, json_loads\nfrom redash import models\nfrom RestrictedPython import compile_restricted\nfrom RestrictedPython.Guards import safe_builtins\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass CustomPrint(object):\n    \"\"\"CustomPrint redirect \"print\" calls to be sent as \"log\" on the result object.\"\"\"\n    def __init__(self):\n        self.enabled = True\n        self.lines = []\n\n    def write(self, text):\n        if self.enabled:\n            if text and text.strip():\n                log_line = \"[{0}] {1}\".format(datetime.datetime.utcnow().isoformat(), text)\n                self.lines.append(log_line)\n\n    def enable(self):\n        self.enabled = True\n\n    def disable(self):\n        self.enabled = False\n\n    def __call__(self):\n        return self\n\n\nclass Python(BaseQueryRunner):\n    safe_builtins = (\n        'sorted', 'reversed', 'map', 'reduce', 'any', 'all',\n        'slice', 'filter', 'len', 'next', 'enumerate',\n        'sum', 'abs', 'min', 'max', 'round', 'cmp', 'divmod',\n        'str', 'unicode', 'int', 'float', 'complex',\n        'tuple', 'set', 'list', 'dict', 'bool',\n    )\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            'type': 'object',\n            'properties': {\n                'allowedImportModules': {\n                    'type': 'string',\n                    'title': 'Modules to import prior to running the script'\n                },\n                'additionalModulesPaths': {\n                    'type': 'string'\n                }\n            },\n        }\n\n    @classmethod\n    def enabled(cls):\n        return True\n\n    @classmethod\n    def annotate_query(cls):\n        return False\n\n    def __init__(self, configuration):\n        super(Python, self).__init__(configuration)\n\n        self.syntax = \"python\"\n\n        self._allowed_modules = {}\n        self._script_locals = {\"result\": {\"rows\": [], \"columns\": [], \"log\": []}}\n        self._enable_print_log = True\n        self._custom_print = CustomPrint()\n\n        if self.configuration.get(\"allowedImportModules\", None):\n            for item in self.configuration[\"allowedImportModules\"].split(\",\"):\n                self._allowed_modules[item] = None\n\n        if self.configuration.get(\"additionalModulesPaths\", None):\n            for p in self.configuration[\"additionalModulesPaths\"].split(\",\"):\n                if p not in sys.path:\n                    sys.path.append(p)\n\n    def custom_import(self, name, globals=None, locals=None, fromlist=(), level=0):\n        if name in self._allowed_modules:\n            m = None\n            if self._allowed_modules[name] is None:\n                m = importlib.import_module(name)\n                self._allowed_modules[name] = m\n            else:\n                m = self._allowed_modules[name]\n\n            return m\n\n        raise Exception(\"'{0}' is not configured as a supported import module\".format(name))\n\n    @staticmethod\n    def custom_write(obj):\n        \"\"\"\n        Custom hooks which controls the way objects/lists/tuples/dicts behave in\n        RestrictedPython\n        \"\"\"\n        return obj\n\n    @staticmethod\n    def custom_get_item(obj, key):\n        return obj[key]\n\n    @staticmethod\n    def custom_get_iter(obj):\n        return iter(obj)\n\n    @staticmethod\n    def add_result_column(result, column_name, friendly_name, column_type):\n        \"\"\"Helper function to add columns inside a Python script running in Redash in an easier way\n\n        Parameters:\n        :result dict: The result dict\n        :column_name string: Name of the column, which should be consisted of lowercase latin letters or underscore.\n        :friendly_name string: Name of the column for display\n        :column_type string: Type of the column. Check supported data types for details.\n        \"\"\"\n        if column_type not in SUPPORTED_COLUMN_TYPES:\n            raise Exception(\"'{0}' is not a supported column type\".format(column_type))\n\n        if \"columns\" not in result:\n            result[\"columns\"] = []\n\n        result[\"columns\"].append({\n            \"name\": column_name,\n            \"friendly_name\": friendly_name,\n            \"type\": column_type\n        })\n\n    @staticmethod\n    def add_result_row(result, values):\n        \"\"\"Helper function to add one row to results set.\n\n        Parameters:\n        :result dict: The result dict\n        :values dict: One row of result in dict. The key should be one of the column names. The value is the value of the column in this row.\n        \"\"\"\n        if \"rows\" not in result:\n            result[\"rows\"] = []\n\n        result[\"rows\"].append(values)\n\n    @staticmethod\n    def execute_query(data_source_name_or_id, query):\n        \"\"\"Run query from specific data source.\n\n        Parameters:\n        :data_source_name_or_id string|integer: Name or ID of the data source\n        :query string: Query to run\n        \"\"\"\n        try:\n            if type(data_source_name_or_id) == int:\n                data_source = models.DataSource.get_by_id(data_source_name_or_id)\n            else:\n                data_source = models.DataSource.get_by_name(data_source_name_or_id)\n        except models.NoResultFound:\n            raise Exception(\"Wrong data source name/id: %s.\" % data_source_name_or_id)\n\n        # TODO: pass the user here...\n        data, error = data_source.query_runner.run_query(query, None)\n        if error is not None:\n            raise Exception(error)\n\n        # TODO: allow avoiding the JSON dumps/loads in same process\n        return json_loads(data)\n\n    @staticmethod\n    def get_source_schema(data_source_name_or_id):\n        \"\"\"Get schema from specific data source.\n\n        :param data_source_name_or_id: string|integer: Name or ID of the data source\n        :return:\n        \"\"\"\n        try:\n            if type(data_source_name_or_id) == int:\n                data_source = models.DataSource.get_by_id(data_source_name_or_id)\n            else:\n                data_source = models.DataSource.get_by_name(data_source_name_or_id)\n        except models.NoResultFound:\n            raise Exception(\"Wrong data source name/id: %s.\" % data_source_name_or_id)\n        schema = data_source.query_runner.get_schema()\n        return schema\n\n    @staticmethod\n    def get_query_result(query_id):\n        \"\"\"Get result of an existing query.\n\n        Parameters:\n        :query_id integer: ID of existing query\n        \"\"\"\n        try:\n            query = models.Query.get_by_id(query_id)\n        except models.NoResultFound:\n            raise Exception(\"Query id %s does not exist.\" % query_id)\n\n        if query.latest_query_data is None:\n            raise Exception(\"Query does not have results yet.\")\n\n        if query.latest_query_data.data is None:\n            raise Exception(\"Query does not have results yet.\")\n\n        return json_loads(query.latest_query_data.data)\n\n    def get_current_user(self):\n        return self._current_user.to_dict()\n\n    def test_connection(self):\n        pass\n\n    def run_query(self, query, user):\n        self._current_user = user\n\n        try:\n            error = None\n\n            code = compile_restricted(query, '<string>', 'exec')\n\n            builtins = safe_builtins.copy()\n            builtins[\"_write_\"] = self.custom_write\n            builtins[\"__import__\"] = self.custom_import\n            builtins[\"_getattr_\"] = getattr\n            builtins[\"getattr\"] = getattr\n            builtins[\"_setattr_\"] = setattr\n            builtins[\"setattr\"] = setattr\n            builtins[\"_getitem_\"] = self.custom_get_item\n            builtins[\"_getiter_\"] = self.custom_get_iter\n            builtins[\"_print_\"] = self._custom_print\n\n            # Layer in our own additional set of builtins that we have\n            # considered safe.\n            for key in self.safe_builtins:\n                builtins[key] = __builtins__[key]\n\n            restricted_globals = dict(__builtins__=builtins)\n            restricted_globals[\"get_query_result\"] = self.get_query_result\n            restricted_globals[\"get_source_schema\"] = self.get_source_schema\n            restricted_globals[\"get_current_user\"] = self.get_current_user\n            restricted_globals[\"execute_query\"] = self.execute_query\n            restricted_globals[\"add_result_column\"] = self.add_result_column\n            restricted_globals[\"add_result_row\"] = self.add_result_row\n            restricted_globals[\"disable_print_log\"] = self._custom_print.disable\n            restricted_globals[\"enable_print_log\"] = self._custom_print.enable\n\n            # Supported data types\n            restricted_globals[\"TYPE_DATETIME\"] = TYPE_DATETIME\n            restricted_globals[\"TYPE_BOOLEAN\"] = TYPE_BOOLEAN\n            restricted_globals[\"TYPE_INTEGER\"] = TYPE_INTEGER\n            restricted_globals[\"TYPE_STRING\"] = TYPE_STRING\n            restricted_globals[\"TYPE_DATE\"] = TYPE_DATE\n            restricted_globals[\"TYPE_FLOAT\"] = TYPE_FLOAT\n\n\n            # TODO: Figure out the best way to have a timeout on a script\n            #       One option is to use ETA with Celery + timeouts on workers\n            #       And replacement of worker process every X requests handled.\n\n            exec((code), restricted_globals, self._script_locals)\n\n            result = self._script_locals['result']\n            result['log'] = self._custom_print.lines\n            json_data = json_dumps(result)\n        except KeyboardInterrupt:\n            error = \"Query cancelled by user.\"\n            json_data = None\n        except Exception as e:\n            error = str(type(e)) + \" \" + str(e)\n            json_data = None\n\n        return json_data, error\n\n\nregister(Python)\n", "path": "redash/query_runner/python.py"}]}