Skip to content

Commit

Permalink
Added self-documenting capability to the API.
Browse files Browse the repository at this point in the history
 * Run the generate_docs.sh script to generate a man page describing all of the
   api commands. The man page is appropriate to distribute to end users, and is
   in fact geared for that.
 * The documentation is pulled from the galah.api.commands module. Modify the
   docstrings for functions in that module to modify the documentation.
 * An index.rst file was created that gives some summary info. This
   documentation structure is simple to understand, read up on Sphinx to learn
   more.
 * galah.api.commands.api_call was renamed to galah.api.commands._api_call to
   ensure it does not appear in the documentation.
  • Loading branch information
John Sullivan committed Oct 24, 2012
1 parent ff37f3b commit 568f03b
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 20 deletions.
50 changes: 50 additions & 0 deletions docs/src/galah.api/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
#
# Galah documentation build configuration file, created by
# sphinx-quickstart on Sun Oct 14 01:32:18 2012. Since modified (see git log)
#
# This file is execfile()d with the current directory set to its containing dir.

import sys, os

sys.path.insert(0, os.path.abspath('../../../'))

# -- General configuration -----------------------------------------------------

extensions = ['sphinx.ext.autodoc']

# The suffix of source filenames.
source_suffix = '.rst'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'Galah'
copyright = u'2012, John Sullivan'

# The version info for the project you're documenting.
version = release = '0.2'

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# -- Options for manual page output --------------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'galah.api.commands', u'Galah\'s API Documentation',
[u'John Sullivan'], 1)
]

# -- Autodoc signal handlers ---------------------------------------------------

# Will skip all documentable objects that aren't exposed API functions
def should_skip(app, what, name, obj, skip, options):
from types import FunctionType

return name.startswith("_") or type(obj) is not FunctionType

def setup(app):
app.connect('autodoc-skip-member', should_skip)
29 changes: 29 additions & 0 deletions docs/src/galah.api/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Galah API
=========

Summary
-------
This is a complete reference to Galah's outward facing API. If you are using
api_client.py to access the API, you should treat each parameter given for
the functions below as an argument to pass to the command.

For example, to create a new user, you could execute...

api_client.py create_user john.doe@college.edu StRongPassWOrd

Notice I leave out the last two arguments (account_type and send_receipt). I
can do this because both has default parameters. You could specify a value for
each of them if you desire...

api_client.py create_user jane.doe@college.edu DifferentPassWoRD teacher

You will be given the results of your command in a human-readable format.

If you are accessing the API via a Python interpreter, the only thing to note
is that every API function will always, and only, return a string. This may be
changed soon.

Reference
---------
.. automodule:: galah.api.commands
:members:
45 changes: 25 additions & 20 deletions galah/api/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,22 @@ def __call__(self, current_user, *args, **kwargs):
# Only pass the current user to the function if the function wants it
if len(self.argspec[0]) != 0 and self.argspec[0][0] == "current_user":
return str(self.wrapped_function(current_user, *args, **kwargs))
else:
else:
return str(self.wrapped_function(*args, **kwargs))

def api_call(allowed = None):
from decorator import decorator

def _api_call(allowed = None):
"""Decorator that wraps a function with the :class: APICall class."""

if isinstance(allowed, basestring):
allowed = (allowed, )

def wrapped(func):
return APICall(func, allowed)

@decorator
def wrapped(func, *args, **kwargs):
_api_call = APICall(func, allowed)

return _api_call(*args, **kwargs)

return wrapped

Expand Down Expand Up @@ -151,7 +156,7 @@ def _to_datetime(time):
)

## Below are the actual API calls ##
@api_call()
@_api_call()
def get_api_info():
# This function should be memoized

Expand Down Expand Up @@ -180,7 +185,7 @@ def get_api_info():

from galah.db.crypto.passcrypt import serialize_seal, seal
from mongoengine import OperationError
@api_call("admin")
@_api_call("admin")
def create_user(email, password, account_type = "student",
send_receipt = False):
"""Creates a user with the given credentials. Be very careful executing this
Expand Down Expand Up @@ -223,7 +228,7 @@ def create_user(email, password, account_type = "student",

return "Success! %s created." % _user_to_str(new_user)

@api_call(("admin", "teacher"))
@_api_call(("admin", "teacher"))
def find_user(current_user, email_contains = "", account_type = "",
enrolled_in = ""):
query = {}
Expand Down Expand Up @@ -255,7 +260,7 @@ def find_user(current_user, email_contains = "", account_type = "",
return "%d user(s) found matching query {%s}.\n\t%s" % \
(len(matches), query_description, result_string)

@api_call(("admin", "teacher"))
@_api_call(("admin", "teacher"))
def user_info(current_user, email):
user = _get_user(email, current_user)

Expand All @@ -268,7 +273,7 @@ def user_info(current_user, email):
else:
return "%s is enrolled in:\n\t%s" % (_user_to_str(user), class_list)

@api_call("admin")
@_api_call("admin")
def delete_user(current_user, email):
"""Deletes a user with the given email. **This is irreversable.**
Expand All @@ -284,7 +289,7 @@ def delete_user(current_user, email):

return "Success! %s deleted." % _user_to_str(to_delete)

@api_call(("admin", "teacher"))
@_api_call(("admin", "teacher"))
def find_class(current_user, name_contains = "", instructor = ""):
"""
Finds a classes or classes that match the given query and displays
Expand Down Expand Up @@ -324,7 +329,7 @@ def find_class(current_user, name_contains = "", instructor = ""):
return "%s teaching %d class(es) with '%s' in their name.\n\t%s" % \
(instructor_string, len(matches), name_contains, result_string)

@api_call(("admin", "teacher"))
@_api_call(("admin", "teacher"))
def enroll_student(current_user, email, enroll_in):
"""Enrolls a student in a given class. May be used on teachers to assign
them to classes.
Expand Down Expand Up @@ -358,7 +363,7 @@ class to enroll the student in.
_user_to_str(user), _class_to_str(the_class)
)

@api_call("admin")
@_api_call("admin")
def assign_teacher(current_user, email, assign_to):
"""Assigns a teacher to teach a particular course. Alias of enroll_student.
Expand All @@ -370,7 +375,7 @@ def assign_teacher(current_user, email, assign_to):

return enroll_student(current_user, email, assign_to)

@api_call(("admin", "teacher"))
@_api_call(("admin", "teacher"))
def drop_student(current_user, email, drop_from):
"""Drops a student (or un-enrolls) a student from a given class. May be
used on teachers to un-assign them from classes.
Expand Down Expand Up @@ -404,7 +409,7 @@ def drop_student(current_user, email, drop_from):
_user_to_str(user), _class_to_str(the_class)
)

@api_call("admin")
@_api_call("admin")
def create_class(name):
"""Creates a class with the given name.
Expand All @@ -421,7 +426,7 @@ def create_class(name):

return "Success! %s created." % _class_to_str(new_class)

@api_call("admin")
@_api_call("admin")
def delete_class(to_delete):
"""Deletes a class and all assignments assigned to it.
Expand Down Expand Up @@ -453,7 +458,7 @@ def delete_class(to_delete):
% _class_to_str(the_class)) + \
"\n\t".join(_assignment_to_str(i) for i in assignments)

@api_call(("admin", "teacher"))
@_api_call(("admin", "teacher"))
def create_assignment(current_user, name, due, for_class):
"""Creates an assignment.
Expand Down Expand Up @@ -488,7 +493,7 @@ def create_assignment(current_user, name, due, for_class):

return "Success! %s created." % _assignment_to_str(new_assignment)

@api_call(("admin", "teacher"))
@_api_call(("admin", "teacher"))
def modify_assignment(current_user, id, name = "", due = "", for_class = ""):
"""Modifies an existing assignment.
Expand Down Expand Up @@ -544,7 +549,7 @@ def modify_assignment(current_user, id, name = "", due = "", for_class = ""):
return "Success! %s changed to %s." % \
(old_assignment_string, _assignment_to_str(assignment))

@api_call(("admin", "teacher"))
@_api_call(("admin", "teacher"))
def delete_assignment(current_user, id):
"""Deletes an assignment.
Expand Down Expand Up @@ -631,7 +636,7 @@ def tar_tasks():
archive.save()


@api_call(("admin", "teacher"))
@_api_call(("admin", "teacher"))
def get_submissions(current_user, assignment, email = None):
"""Creates an archive of students' submissions that a teacher or admin
can download.
Expand Down
21 changes: 21 additions & 0 deletions generate_docs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash

# This will ensure that the script exits if a failure occurs
set -e

# This will ensure the user is visually prompted upon failure
trap "echo FAILURE: An error has occured! >&2" EXIT

# The directory that will contain all of the generated documentation
DOCS_PATH=./docs

## Create the API documentation for shell users ################################
echo Building API documentation...

sphinx-build -q -a -b man ./docs/src/galah.api/ $DOCS_PATH

echo "SUCCESS: MAN page 'galah.api.commands.1' created in $DOCS_PATH"

# Unset the trap so we don't freak the user out by telling them an error has
# occured when everything went fine.
trap - EXIT

0 comments on commit 568f03b

Please sign in to comment.