Skip to content

Commit

Permalink
Merge pull request getredash#1546 from washort/api-docstrings
Browse files Browse the repository at this point in the history
Add: API docstrings
  • Loading branch information
arikfr authored Jan 26, 2017
2 parents 4460306 + 0d7c5db commit a6e8ce2
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 4 deletions.
7 changes: 4 additions & 3 deletions __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 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 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

0 comments on commit a6e8ce2

Please sign in to comment.