async def view_get(self, request, database, hash, correct_hash_provided, **kwargs): _format, kwargs = await self.get_format(request, database, kwargs) if _format == "csv": return await self.as_csv(request, database, hash, **kwargs) if _format is None: # HTML views default to expanding all foreign key labels kwargs["default_labels"] = True extra_template_data = {} start = time.time() status_code = 200 templates = [] try: response_or_template_contexts = await self.data( request, database, hash, **kwargs) if isinstance(response_or_template_contexts, Response): return response_or_template_contexts else: data, extra_template_data, templates = response_or_template_contexts except QueryInterrupted: raise DatasetteError( """ SQL query took too long. The time limit is controlled by the <a href="https://datasette.readthedocs.io/en/stable/config.html#sql-time-limit-ms">sql_time_limit_ms</a> configuration option. """, title="SQL Interrupted", status=400, messagge_is_html=True, ) except (sqlite3.OperationalError, InvalidSql) as e: raise DatasetteError(str(e), title="Invalid SQL", status=400) except (sqlite3.OperationalError) as e: raise DatasetteError(str(e)) except DatasetteError: raise end = time.time() data["query_ms"] = (end - start) * 1000 for key in ("source", "source_url", "license", "license_url"): value = self.ds.metadata(key) if value: data[key] = value # Special case for .jsono extension - redirect to _shape=objects if _format == "jsono": return self.redirect( request, path_with_added_args( request, {"_shape": "objects"}, path=request.path.rsplit(".jsono", 1)[0] + ".json", ), forward_querystring=False, ) if _format in self.ds.renderers.keys(): # Dispatch request to the correct output format renderer # (CSV is not handled here due to streaming) result = self.ds.renderers[_format](request.args, data, self.name) if result is None: raise NotFound("No data") r = Response( body=result.get("body"), status=result.get("status_code", 200), content_type=result.get("content_type", "text/plain"), ) else: extras = {} if callable(extra_template_data): extras = extra_template_data() if asyncio.iscoroutine(extras): extras = await extras else: extras = extra_template_data url_labels_extra = {} if data.get("expandable_columns"): url_labels_extra = {"_labels": "on"} renderers = { key: path_with_format(request, key, {**url_labels_extra}) for key in self.ds.renderers.keys() } url_csv_args = {"_size": "max", **url_labels_extra} url_csv = path_with_format(request, "csv", url_csv_args) url_csv_path = url_csv.split("?")[0] context = { **data, **extras, **{ "renderers": renderers, "url_csv": url_csv, "url_csv_path": url_csv_path, "url_csv_hidden_args": [(key, value) for key, value in urllib.parse.parse_qsl(request.query_string) if key not in ("_labels", "_facet", "_size")] + [("_size", "max")], "datasette_version": __version__, "config": self.ds.config_dict(), }, } if "metadata" not in context: context["metadata"] = self.ds.metadata r = await self.render(templates, request=request, context=context) r.status = status_code ttl = request.args.get("_ttl", None) if ttl is None or not ttl.isdigit(): if correct_hash_provided: ttl = self.ds.config("default_cache_ttl_hashed") else: ttl = self.ds.config("default_cache_ttl") return self.set_response_headers(r, ttl)
async def get(self, request): database_route = tilde_decode(request.url_vars["database"]) try: db = self.ds.get_database(route=database_route) except KeyError: raise NotFound("Database not found: {}".format(database_route)) database = db.name _format = request.url_vars["format"] data_kwargs = {} if _format == "csv": return await self.as_csv(request, database_route) if _format is None: # HTML views default to expanding all foreign key labels data_kwargs["default_labels"] = True extra_template_data = {} start = time.perf_counter() status_code = None templates = [] try: response_or_template_contexts = await self.data( request, **data_kwargs) if isinstance(response_or_template_contexts, Response): return response_or_template_contexts # If it has four items, it includes an HTTP status code if len(response_or_template_contexts) == 4: ( data, extra_template_data, templates, status_code, ) = response_or_template_contexts else: data, extra_template_data, templates = response_or_template_contexts except QueryInterrupted: raise DatasetteError( """ SQL query took too long. The time limit is controlled by the <a href="https://docs.datasette.io/en/stable/settings.html#sql-time-limit-ms">sql_time_limit_ms</a> configuration option. """, title="SQL Interrupted", status=400, message_is_html=True, ) except (sqlite3.OperationalError, InvalidSql) as e: raise DatasetteError(str(e), title="Invalid SQL", status=400) except sqlite3.OperationalError as e: raise DatasetteError(str(e)) except DatasetteError: raise end = time.perf_counter() data["query_ms"] = (end - start) * 1000 for key in ("source", "source_url", "license", "license_url"): value = self.ds.metadata(key) if value: data[key] = value # Special case for .jsono extension - redirect to _shape=objects if _format == "jsono": return self.redirect( request, path_with_added_args( request, {"_shape": "objects"}, path=request.path.rsplit(".jsono", 1)[0] + ".json", ), forward_querystring=False, ) if _format in self.ds.renderers.keys(): # Dispatch request to the correct output format renderer # (CSV is not handled here due to streaming) result = call_with_supported_arguments( self.ds.renderers[_format][0], datasette=self.ds, columns=data.get("columns") or [], rows=data.get("rows") or [], sql=data.get("query", {}).get("sql", None), query_name=data.get("query_name"), database=database, table=data.get("table"), request=request, view_name=self.name, # These will be deprecated in Datasette 1.0: args=request.args, data=data, ) if asyncio.iscoroutine(result): result = await result if result is None: raise NotFound("No data") if isinstance(result, dict): r = Response( body=result.get("body"), status=result.get("status_code", status_code or 200), content_type=result.get("content_type", "text/plain"), headers=result.get("headers"), ) elif isinstance(result, Response): r = result if status_code is not None: # Over-ride the status code r.status = status_code else: assert False, f"{result} should be dict or Response" else: extras = {} if callable(extra_template_data): extras = extra_template_data() if asyncio.iscoroutine(extras): extras = await extras else: extras = extra_template_data url_labels_extra = {} if data.get("expandable_columns"): url_labels_extra = {"_labels": "on"} renderers = {} for key, (_, can_render) in self.ds.renderers.items(): it_can_render = call_with_supported_arguments( can_render, datasette=self.ds, columns=data.get("columns") or [], rows=data.get("rows") or [], sql=data.get("query", {}).get("sql", None), query_name=data.get("query_name"), database=database, table=data.get("table"), request=request, view_name=self.name, ) it_can_render = await await_me_maybe(it_can_render) if it_can_render: renderers[key] = self.ds.urls.path( path_with_format(request=request, format=key, extra_qs={**url_labels_extra})) url_csv_args = {"_size": "max", **url_labels_extra} url_csv = self.ds.urls.path( path_with_format(request=request, format="csv", extra_qs=url_csv_args)) url_csv_path = url_csv.split("?")[0] context = { **data, **extras, **{ "renderers": renderers, "url_csv": url_csv, "url_csv_path": url_csv_path, "url_csv_hidden_args": [(key, value) for key, value in urllib.parse.parse_qsl(request.query_string) if key not in ("_labels", "_facet", "_size")] + [("_size", "max")], "datasette_version": __version__, "settings": self.ds.settings_dict(), }, } if "metadata" not in context: context["metadata"] = self.ds.metadata r = await self.render(templates, request=request, context=context) if status_code is not None: r.status = status_code ttl = request.args.get("_ttl", None) if ttl is None or not ttl.isdigit(): ttl = self.ds.setting("default_cache_ttl") return self.set_response_headers(r, ttl)