Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add: API docstrings #1546

Merged
merged 3 commits into from
Jan 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions redash/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def to_url(self, value):
return value


def create_app():
def create_app(load_admin=True):
from redash import handlers
from redash.admin import init_admin
from redash.models import db
Expand Down Expand Up @@ -118,10 +118,11 @@ def create_app():
provision_app(app)
db.init_app(app)
migrate.init_app(app, db)
init_admin(app)
if load_admin:
init_admin(app)
mail.init_app(app)
setup_authentication(app)
handlers.init_app(app)
limiter.init_app(app)
handlers.init_app(app)

return app
81 changes: 81 additions & 0 deletions redash/handlers/dashboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
class RecentDashboardsResource(BaseResource):
@require_permission('list_dashboards')
def get(self):
"""
Lists dashboards modified in the last 7 days.
"""
recent = [d.to_dict() for d in models.Dashboard.recent(self.current_org, self.current_user.group_ids, self.current_user.id, for_user=True)]

global_recent = []
Expand All @@ -27,11 +30,21 @@ def get(self):
class DashboardListResource(BaseResource):
@require_permission('list_dashboards')
def get(self):
"""
Lists all accessible dashboards.
"""
results = models.Dashboard.all(self.current_org, self.current_user.group_ids, self.current_user.id)
return [q.to_dict() for q in results]

@require_permission('create_dashboard')
def post(self):
"""
Creates a new dashboard.

:<json string name: Dashboard name

Responds with a :ref:`dashboard <dashboard-response-label>`.
"""
dashboard_properties = request.get_json(force=True)
dashboard = models.Dashboard(name=dashboard_properties['name'],
org=self.current_org,
Expand All @@ -46,6 +59,39 @@ def post(self):
class DashboardResource(BaseResource):
@require_permission('list_dashboards')
def get(self, dashboard_slug=None):
"""
Retrieves a dashboard.

:qparam string slug: Slug of dashboard to retrieve.

.. _dashboard-response-label:

:>json number id: Dashboard ID
:>json string name:
:>json string slug:
:>json number user_id: ID of the dashboard creator
:>json string created_at: ISO format timestamp for dashboard creation
:>json string updated_at: ISO format timestamp for last dashboard modification
:>json number version: Revision number of dashboard
:>json boolean dashboard_filters_enabled: Whether filters are enabled or not
:>json boolean is_archived: Whether this dashboard has been removed from the index or not
:>json boolean is_draft: Whether this dashboard is a draft or not.
:>json array layout: Array of arrays containing widget IDs, corresponding to the rows and columns the widgets are displayed in
:>json array widgets: Array of arrays containing :ref:`widget <widget-response-label>` data

.. _widget-response-label:

Widget structure:

:>json number widget.id: Widget ID
:>json number widget.width: Widget size
:>json object widget.options: Widget options
:>json number widget.dashboard_id: ID of dashboard containing this widget
:>json string widget.text: Widget contents, if this is a text-box widget
:>json object widget.visualization: Widget contents, if this is a visualization widget
:>json string widget.created_at: ISO format timestamp for widget creation
:>json string widget.updated_at: ISO format timestamp for last widget modification
"""
dashboard = get_object_or_404(models.Dashboard.get_by_slug_and_org, dashboard_slug, self.current_org)
response = dashboard.to_dict(with_widgets=True, user=self.current_user)

Expand All @@ -60,6 +106,16 @@ def get(self, dashboard_slug=None):

@require_permission('edit_dashboard')
def post(self, dashboard_slug):
"""
Modifies a dashboard.

:qparam string slug: Slug of dashboard to retrieve.

Responds with the updated :ref:`dashboard <dashboard-response-label>`.

:status 200: success
:status 409: Version conflict -- dashboard modified since last read
"""
dashboard_properties = request.get_json(force=True)
# TODO: either convert all requests to use slugs or ids
dashboard = models.Dashboard.get_by_id_and_org(dashboard_slug, self.current_org)
Expand Down Expand Up @@ -89,6 +145,13 @@ def post(self, dashboard_slug):

@require_permission('edit_dashboard')
def delete(self, dashboard_slug):
"""
Archives a dashboard.

:qparam string slug: Slug of dashboard to retrieve.

Responds with the archived :ref:`dashboard <dashboard-response-label>`.
"""
dashboard = models.Dashboard.get_by_slug_and_org(dashboard_slug, self.current_org)
dashboard.is_archived = True
dashboard.record_changes(changed_by=self.current_user)
Expand All @@ -100,6 +163,12 @@ def delete(self, dashboard_slug):

class PublicDashboardResource(BaseResource):
def get(self, token):
"""
Retrieve a public dashboard.

:param token: An API key for a public dashboard.
:>json array widgets: An array of arrays of :ref:`public widgets <public-widget-label>`, corresponding to the rows and columns the widgets are displayed in
"""
if not isinstance(self.current_user, models.ApiUser):
api_key = get_object_or_404(models.ApiKey.get_by_api_key, token)
dashboard = api_key.object
Expand All @@ -111,6 +180,13 @@ def get(self, token):

class DashboardShareResource(BaseResource):
def post(self, dashboard_id):
"""
Allow anonymous access to a dashboard.

:param dashboard_id: The numeric ID of the dashboard to share.
:>json string public_url: The URL for anonymous access to the dashboard.
:>json api_key: The API key to use when accessing it.
"""
dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)
require_admin_or_owner(dashboard.user_id)
api_key = models.ApiKey.create_for_object(dashboard, self.current_user)
Expand All @@ -128,6 +204,11 @@ def post(self, dashboard_id):
return {'public_url': public_url, 'api_key': api_key.api_key}

def delete(self, dashboard_id):
"""
Disable anonymous access to a dashboard.

:param dashboard_id: The numeric ID of the dashboard to unshare.
"""
dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)
require_admin_or_owner(dashboard.user_id)
api_key = models.ApiKey.get_by_object(dashboard)
Expand Down
107 changes: 107 additions & 0 deletions redash/handlers/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
@routes.route(org_scoped_rule('/api/queries/format'), methods=['POST'])
@login_required
def format_sql_query(org_slug=None):
"""
Formats an SQL query using the Python ``sqlparse`` formatter.

:<json string query: The SQL text to format
:>json string query: Formatted SQL text
"""
arguments = request.get_json(force=True)
query = arguments.get("query", "")

Expand All @@ -30,6 +36,13 @@ def format_sql_query(org_slug=None):
class QuerySearchResource(BaseResource):
@require_permission('view_query')
def get(self):
"""
Search query text, titles, and descriptions.

:qparam string q: Search term

Responds with a list of :ref:`query <query-response-label>` objects.
"""
term = request.args.get('q', '')
include_drafts = request.args.get('include_drafts') is not None

Expand All @@ -39,6 +52,11 @@ def get(self):
class QueryRecentResource(BaseResource):
@require_permission('view_query')
def get(self):
"""
Retrieve up to 20 queries modified in the last 7 days.

Responds with a list of :ref:`query <query-response-label>` objects.
"""
queries = models.Query.recent(self.current_user.group_ids, self.current_user.id)
recent = [d.to_dict(with_last_modified_by=False) for d in queries]

Expand All @@ -52,6 +70,38 @@ def get(self):
class QueryListResource(BaseResource):
@require_permission('create_query')
def post(self):
"""
Create a new query.

:<json number data_source_id: The ID of the data source this query will run on
:<json string query: Query text
:<json string name:
:<json string description:
:<json string schedule: Schedule interval, in seconds, for repeated execution of this query
:<json object options: Query options

.. _query-response-label:

:>json number id: Query ID
:>json number latest_query_data_id: ID for latest output data from this query
:>json string name:
:>json string description:
:>json string query: Query text
:>json string query_hash: Hash of query text
:>json string schedule: Schedule interval, in seconds, for repeated execution of this query
:>json string api_key: Key for public access to this query's results.
:>json boolean is_archived: Whether this query is displayed in indexes and search results or not.
:>json boolean is_draft: Whether this query is a draft or not
:>json string updated_at: Time of last modification, in ISO format
:>json string created_at: Time of creation, in ISO format
:>json number data_source_id: ID of the data source this query will run on
:>json object options: Query options
:>json number version: Revision version (for update conflict avoidance)
:>json number user_id: ID of query creator
:>json number last_modified_by_id: ID of user who last modified this query
:>json string retrieved_at: Time when query results were last retrieved, in ISO format (may be null)
:>json number runtime: Runtime of last query execution, in seconds (may be null)
"""
query_def = request.get_json(force=True)
data_source = models.DataSource.get_by_id_and_org(query_def.pop('data_source_id'), self.current_org)
require_access(data_source.groups, self.current_user, not_view_only)
Expand All @@ -78,6 +128,15 @@ def post(self):

@require_permission('view_query')
def get(self):
"""
Retrieve a list of queries.

:qparam number page_size: Number of queries to return
:qparam number page: Page number to retrieve

Responds with an array of :ref:`query <query-response-label>` objects.
"""

results = models.Query.all_queries(self.current_user.group_ids, self.current_user.id)
page = request.args.get('page', 1, type=int)
page_size = request.args.get('page_size', 25, type=int)
Expand All @@ -87,6 +146,15 @@ def get(self):
class MyQueriesResource(BaseResource):
@require_permission('view_query')
def get(self):
"""
Retrieve a list of queries created by the current user.

:qparam number page_size: Number of queries to return
:qparam number page: Page number to retrieve

Responds with an array of :ref:`query <query-response-label>` objects.
"""
drafts = request.args.get('drafts') is not None
results = models.Query.by_user(self.current_user)
page = request.args.get('page', 1, type=int)
page_size = request.args.get('page_size', 25, type=int)
Expand All @@ -96,6 +164,19 @@ def get(self):
class QueryResource(BaseResource):
@require_permission('edit_query')
def post(self, query_id):
"""
Modify a query.

:param query_id: ID of query to update
:<json number data_source_id: The ID of the data source this query will run on
:<json string query: Query text
:<json string name:
:<json string description:
:<json string schedule: Schedule interval, in seconds, for repeated execution of this query
:<json object options: Query options

Responds with the updated :ref:`query <query-response-label>` object.
"""
query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
query_def = request.get_json(force=True)

Expand Down Expand Up @@ -125,6 +206,13 @@ def post(self, query_id):

@require_permission('view_query')
def get(self, query_id):
"""
Retrieve a query.

:param query_id: ID of query to fetch

Responds with the :ref:`query <query-response-label>` contents.
"""
q = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
require_access(q.groups, self.current_user, view_only)

Expand All @@ -134,6 +222,11 @@ def get(self, query_id):

# TODO: move to resource of its own? (POST /queries/{id}/archive)
def delete(self, query_id):
"""
Archives a query.

:param query_id: ID of query to archive
"""
query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
require_admin_or_owner(query.user_id)
query.archive(self.current_user)
Expand All @@ -143,6 +236,13 @@ def delete(self, query_id):
class QueryForkResource(BaseResource):
@require_permission('edit_query')
def post(self, query_id):
"""
Creates a new query, copying the query text from an existing one.

:param query_id: ID of query to fork

Responds with created :ref:`query <query-response-label>` object.
"""
query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
forked_query = query.fork(self.current_user)
models.db.session.commit()
Expand All @@ -151,6 +251,13 @@ def post(self, query_id):

class QueryRefreshResource(BaseResource):
def post(self, query_id):
"""
Execute a query, updating the query object with the results.

:param query_id: ID of query to execute

Responds with query task details.
"""
query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
require_access(query.groups, self.current_user, not_view_only)

Expand Down
Loading