Added documentation for REST API layer.
The goal of this patch is to setup infrastructure and organization of docs in the project. Changes: * Added documentation for REST objects along with examples * Added documentation for REST endpoints * Added auto-generation of REST API docs Note(1): New API endpoint will not be auto-added to the docs. It is responibility of committer to add new endpoint to the docs. Note(2): Usage examples still need to be added manually. Change-Id: I080a6daba2fdb8a1a6a18087c7b3929da5b4bf1a
This commit is contained in:
parent
5a0143a698
commit
fff51b88cc
@ -23,9 +23,15 @@ sys.path.insert(0, os.path.abspath('../..'))
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinxcontrib.httpdomain',
|
||||
'sphinxcontrib.pecanwsme.rest',
|
||||
'wsmeext.sphinxext',
|
||||
'oslo.sphinx'
|
||||
]
|
||||
|
||||
wsme_protocols = ['restjson', 'restxml']
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
@ -36,6 +36,14 @@ Table of contents
|
||||
contributing
|
||||
|
||||
|
||||
Client API Reference
|
||||
--------------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
webapi/v1
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
|
87
doc/source/webapi/v1.rst
Normal file
87
doc/source/webapi/v1.rst
Normal file
@ -0,0 +1,87 @@
|
||||
============
|
||||
V1 Web API
|
||||
============
|
||||
|
||||
###
|
||||
API
|
||||
###
|
||||
|
||||
Projects
|
||||
========
|
||||
.. rest-controller:: storyboard.api.v1.projects:ProjectsController
|
||||
:webprefix: /v1/projects
|
||||
|
||||
Project Groups
|
||||
==============
|
||||
.. rest-controller:: storyboard.api.v1.project_groups:ProjectGroupsController
|
||||
:webprefix: /v1/projects
|
||||
|
||||
Stories
|
||||
=======
|
||||
.. rest-controller:: storyboard.api.v1.stories:StoriesController
|
||||
:webprefix: /v1/projects
|
||||
|
||||
Tasks
|
||||
=====
|
||||
.. rest-controller:: storyboard.api.v1.tasks:TasksController
|
||||
:webprefix: /v1/projects
|
||||
|
||||
Teams
|
||||
=====
|
||||
.. rest-controller:: storyboard.api.v1.teams:TeamsController
|
||||
:webprefix: /v1/projects
|
||||
|
||||
Users
|
||||
=====
|
||||
.. rest-controller:: storyboard.api.v1.users:UsersController
|
||||
:webprefix: /v1/projects
|
||||
|
||||
|
||||
############
|
||||
Object model
|
||||
############
|
||||
|
||||
Comment
|
||||
=======
|
||||
.. autotype:: storyboard.api.v1.wsme_models.Comment
|
||||
:members:
|
||||
|
||||
Permission
|
||||
==========
|
||||
.. autotype:: storyboard.api.v1.wsme_models.Permission
|
||||
:members:
|
||||
|
||||
Project
|
||||
=======
|
||||
.. autotype:: storyboard.api.v1.wsme_models.Project
|
||||
:members:
|
||||
|
||||
ProjectGroup
|
||||
============
|
||||
.. autotype:: storyboard.api.v1.wsme_models.ProjectGroup
|
||||
:members:
|
||||
|
||||
Story
|
||||
=====
|
||||
.. autotype:: storyboard.api.v1.wsme_models.Story
|
||||
:members:
|
||||
|
||||
StoryTag
|
||||
========
|
||||
.. autotype:: storyboard.api.v1.wsme_models.StoryTag
|
||||
:members:
|
||||
|
||||
Task
|
||||
====
|
||||
.. autotype:: storyboard.api.v1.wsme_models.Task
|
||||
:members:
|
||||
|
||||
Team
|
||||
====
|
||||
.. autotype:: storyboard.api.v1.wsme_models.Team
|
||||
:members:
|
||||
|
||||
User
|
||||
====
|
||||
.. autotype:: storyboard.api.v1.wsme_models.User
|
||||
:members:
|
@ -21,9 +21,17 @@ import storyboard.api.v1.wsme_models as wsme_models
|
||||
|
||||
|
||||
class ProjectGroupsController(rest.RestController):
|
||||
"""REST controller for Project Groups.
|
||||
|
||||
At this moment it provides read-only operations.
|
||||
"""
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.ProjectGroup, int)
|
||||
def get_one(self, id):
|
||||
"""Retrieve information about the given project group.
|
||||
|
||||
:param name: project group name.
|
||||
"""
|
||||
group = wsme_models.ProjectGroup.get(id=id)
|
||||
if not group:
|
||||
raise ClientSideError("Project Group %s not found" % id,
|
||||
@ -32,12 +40,17 @@ class ProjectGroupsController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose([wsme_models.ProjectGroup])
|
||||
def get(self):
|
||||
"""Retrieve a list of projects groups."""
|
||||
groups = wsme_models.ProjectGroup.get_all()
|
||||
return groups
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.ProjectGroup,
|
||||
body=wsme_models.ProjectGroup)
|
||||
def post(self, group):
|
||||
"""Create a new project group.
|
||||
|
||||
:param group: a project group within the request body.
|
||||
"""
|
||||
created_group = wsme_models.ProjectGroup.create(wsme_entry=group)
|
||||
if not created_group:
|
||||
raise ClientSideError("Could not create ProjectGroup")
|
||||
@ -46,6 +59,11 @@ class ProjectGroupsController(rest.RestController):
|
||||
@wsme_pecan.wsexpose(wsme_models.ProjectGroup, int,
|
||||
body=wsme_models.ProjectGroup)
|
||||
def put(self, id, group):
|
||||
"""Modify this project group.
|
||||
|
||||
:param id: An ID of the project group.
|
||||
:param group: a project group within the request body.
|
||||
"""
|
||||
updated_group = wsme_models.ProjectGroup.update("id", id, group)
|
||||
if not updated_group:
|
||||
raise ClientSideError("Could not update group %s" % id)
|
||||
|
@ -21,9 +21,17 @@ import storyboard.api.v1.wsme_models as wsme_models
|
||||
|
||||
|
||||
class ProjectsController(rest.RestController):
|
||||
"""REST controller for Projects.
|
||||
|
||||
At this moment it provides read-only operations.
|
||||
"""
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.Project, unicode)
|
||||
def get_one(self, name):
|
||||
"""Retrieve information about the given project.
|
||||
|
||||
:param name: project name.
|
||||
"""
|
||||
project = wsme_models.Project.get(name=name)
|
||||
if not project:
|
||||
raise ClientSideError("Project %s not found" % name,
|
||||
@ -32,5 +40,7 @@ class ProjectsController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose([wsme_models.Project])
|
||||
def get(self):
|
||||
"""Retrieve a list of projects.
|
||||
"""
|
||||
projects = wsme_models.Project.get_all()
|
||||
return projects
|
||||
|
@ -21,6 +21,7 @@ import storyboard.api.v1.wsme_models as wsme_models
|
||||
|
||||
|
||||
class StoriesController(rest.RestController):
|
||||
"""Manages operations on stories."""
|
||||
|
||||
_custom_actions = {
|
||||
"add_task": ["POST"],
|
||||
@ -29,6 +30,10 @@ class StoriesController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.Story, unicode)
|
||||
def get_one(self, id):
|
||||
"""Retrieve details about one story.
|
||||
|
||||
:param id: An ID of the story.
|
||||
"""
|
||||
story = wsme_models.Story.get(id=id)
|
||||
if not story:
|
||||
raise ClientSideError("Story %s not found" % id,
|
||||
@ -37,11 +42,16 @@ class StoriesController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose([wsme_models.Story])
|
||||
def get(self):
|
||||
"""Retrieve definitions of all of the stories."""
|
||||
stories = wsme_models.Story.get_all()
|
||||
return stories
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.Story, wsme_models.Story)
|
||||
def post(self, story):
|
||||
"""Create a new story.
|
||||
|
||||
:param story: a story within the request body.
|
||||
"""
|
||||
created_story = wsme_models.Story.create(wsme_entry=story)
|
||||
if not created_story:
|
||||
raise ClientSideError("Could not create a story")
|
||||
@ -49,6 +59,11 @@ class StoriesController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.Story, unicode, wsme_models.Story)
|
||||
def put(self, story_id, story):
|
||||
"""Modify this story.
|
||||
|
||||
:param story_id: An ID of the story.
|
||||
:param story: a story within the request body.
|
||||
"""
|
||||
updated_story = wsme_models.Story.update("id", story_id, story)
|
||||
if not updated_story:
|
||||
raise ClientSideError("Could not update story %s" % story_id)
|
||||
@ -56,6 +71,11 @@ class StoriesController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.Story, unicode, wsme_models.Task)
|
||||
def add_task(self, story_id, task):
|
||||
"""Associate a task with a story.
|
||||
|
||||
:param story_id: An ID of the story.
|
||||
:param task: a task within the request body.
|
||||
"""
|
||||
updated_story = wsme_models.Story.add_task(story_id, task)
|
||||
if not updated_story:
|
||||
raise ClientSideError("Could not add task to story %s" % story_id)
|
||||
@ -63,6 +83,11 @@ class StoriesController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.Story, unicode, wsme_models.Comment)
|
||||
def add_comment(self, story_id, comment):
|
||||
"""Add a comment with a story.
|
||||
|
||||
:param story_id: An ID of the story.
|
||||
:param comment: a comment within the request body.
|
||||
"""
|
||||
updated_story = wsme_models.Story.add_comment(story_id, comment)
|
||||
if not updated_story:
|
||||
raise ClientSideError("Could not add comment to story %s"
|
||||
|
@ -21,9 +21,14 @@ import storyboard.api.v1.wsme_models as wsme_models
|
||||
|
||||
|
||||
class TasksController(rest.RestController):
|
||||
"""Manages tasks."""
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.Task, unicode)
|
||||
def get_one(self, id):
|
||||
"""Retrieve details about one task.
|
||||
|
||||
:param id: An ID of the task.
|
||||
"""
|
||||
task = wsme_models.Task.get(id=id)
|
||||
if not task:
|
||||
raise ClientSideError("Task %s not found" % id,
|
||||
@ -32,11 +37,17 @@ class TasksController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose([wsme_models.Task])
|
||||
def get(self):
|
||||
"""Retrieve definitions of all of the tasks."""
|
||||
tasks = wsme_models.Task.get_all()
|
||||
return tasks
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.Task, unicode, wsme_models.Task)
|
||||
def put(self, task_id, task):
|
||||
"""Modify this task.
|
||||
|
||||
:param task_id: An ID of the task.
|
||||
:param task: a task within the request body.
|
||||
"""
|
||||
updated_task = wsme_models.Task.update("id", task_id, task)
|
||||
if not updated_task:
|
||||
raise ClientSideError("Could not update story %s" % task_id)
|
||||
|
@ -22,6 +22,7 @@ import storyboard.api.v1.wsme_models as wsme_models
|
||||
|
||||
|
||||
class TeamsController(rest.RestController):
|
||||
"""Manages teams."""
|
||||
|
||||
_custom_actions = {
|
||||
"add_user": ["POST"]
|
||||
@ -29,6 +30,10 @@ class TeamsController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.Team, unicode)
|
||||
def get_one(self, name):
|
||||
"""Retrieve details about one team.
|
||||
|
||||
:param name: unique name to identify the team.
|
||||
"""
|
||||
team = wsme_models.Team.get(name=name)
|
||||
if not team:
|
||||
raise ClientSideError("Team %s not found" % name,
|
||||
@ -37,11 +42,16 @@ class TeamsController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose([wsme_models.Team])
|
||||
def get(self):
|
||||
"""Retrieve definitions of all of the teams."""
|
||||
teams = wsme_models.Team.get_all()
|
||||
return teams
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.Team, wsme_models.Team)
|
||||
def post(self, team):
|
||||
"""Create a new team.
|
||||
|
||||
:param team: a team within the request body.
|
||||
"""
|
||||
created_team = wsme_models.Team.create(wsme_entry=team)
|
||||
if not created_team:
|
||||
raise ClientSideError("Could not create a team")
|
||||
@ -49,6 +59,11 @@ class TeamsController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.Team, unicode, unicode)
|
||||
def add_user(self, team_name, username):
|
||||
"""Associate a user with the team.
|
||||
|
||||
:param team_name: unique name to identify the team.
|
||||
:param username: unique name to identify the user.
|
||||
"""
|
||||
updated_team = wsme_models.Team.add_user(team_name, username)
|
||||
if not updated_team:
|
||||
raise ClientSideError("Could not add user %s to team %s"
|
||||
|
@ -21,14 +21,20 @@ import storyboard.api.v1.wsme_models as wsme_models
|
||||
|
||||
|
||||
class UsersController(rest.RestController):
|
||||
"""Manages users."""
|
||||
|
||||
@wsme_pecan.wsexpose([wsme_models.User])
|
||||
def get(self):
|
||||
"""Retrieve definitions of all of the users."""
|
||||
users = wsme_models.User.get_all()
|
||||
return users
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.User, unicode)
|
||||
def get_one(self, username):
|
||||
"""Retrieve details about one user.
|
||||
|
||||
:param username: unique name to identify the user.
|
||||
"""
|
||||
user = wsme_models.User.get(username=username)
|
||||
if not user:
|
||||
raise ClientSideError("User %s not found" % username,
|
||||
@ -37,6 +43,10 @@ class UsersController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.User, wsme_models.User)
|
||||
def post(self, user):
|
||||
"""Create a new user.
|
||||
|
||||
:param user: a user within the request body.
|
||||
"""
|
||||
created_user = wsme_models.User.create(wsme_entry=user)
|
||||
if not created_user:
|
||||
raise ClientSideError("Could not create User")
|
||||
@ -44,6 +54,11 @@ class UsersController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(wsme_models.User, unicode, wsme_models.User)
|
||||
def put(self, username, user):
|
||||
"""Modify this user.
|
||||
|
||||
:param username: unique name to identify the user.
|
||||
:param user: a user within the request body.
|
||||
"""
|
||||
updated_user = wsme_models.User.update("username", username, user)
|
||||
if not updated_user:
|
||||
raise ClientSideError("Could not update user %s" % username)
|
||||
|
@ -191,46 +191,121 @@ def update_db_model(cls, db_entry, wsme_entry):
|
||||
|
||||
|
||||
class Project(_Base):
|
||||
"""The Storyboard Registry describes the open source world as ProjectGroups
|
||||
and Products. Each ProjectGroup may be responsible for several Projects.
|
||||
For example, the OpenStack Infrastructure Project has Zuul, Nodepool,
|
||||
Storyboard as Projects, among others.
|
||||
"""
|
||||
|
||||
name = wtypes.text
|
||||
"""At least one lowercase letter or number, followed by letters, numbers,
|
||||
dots, hyphens or pluses. Keep this name short; it is used in URLs.
|
||||
"""
|
||||
|
||||
description = wtypes.text
|
||||
"""Details about the project's work, highlights, goals, and how to
|
||||
contribute. Use plain text, paragraphs are preserved and URLs are
|
||||
linked in pages.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
return cls(
|
||||
name="Storyboard",
|
||||
description="Awesome project")
|
||||
|
||||
|
||||
class ProjectGroup(_Base):
|
||||
"""Represents a group of projects."""
|
||||
|
||||
name = wtypes.text
|
||||
"""A unique name, used in URLs, identifying the project group. All
|
||||
lowercase, no special characters. Examples: infra, compute.
|
||||
"""
|
||||
|
||||
title = wtypes.text
|
||||
"""The full name of the project group, which can contain spaces, special
|
||||
characters, etc.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
return cls(
|
||||
name="Infra",
|
||||
title="Awesome project")
|
||||
|
||||
|
||||
class Permission(_Base):
|
||||
"""Permissions can be associated with users and teams."""
|
||||
pass
|
||||
|
||||
|
||||
class Task(_Base):
|
||||
"""Represents a task within a story."""
|
||||
pass
|
||||
|
||||
|
||||
class StoryTag(_Base):
|
||||
"""Tags are used classifying user-stories."""
|
||||
pass
|
||||
|
||||
|
||||
# TODO(ruhe): clarify and document what are 'action' and 'type' for
|
||||
class Comment(_Base):
|
||||
"""Represents a comment."""
|
||||
|
||||
#todo(nkonovalov): replace with a enum
|
||||
action = wtypes.text
|
||||
"""Comment action. Allowed values: unknown."""
|
||||
|
||||
comment_type = wtypes.text
|
||||
"""Comment type. Allowed values: unknown."""
|
||||
|
||||
content = wtypes.text
|
||||
"""All the text/plain chunks joined together as a unicode string."""
|
||||
|
||||
story_id = int
|
||||
"""ID of corresponding user-story."""
|
||||
|
||||
author_id = int
|
||||
"""Comment author ID."""
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
return cls(
|
||||
action="action",
|
||||
comment_type="type1",
|
||||
content="comment content goes here",
|
||||
story_id=42,
|
||||
author_id=67)
|
||||
|
||||
|
||||
class Story(_Base):
|
||||
"""Represents a user-story."""
|
||||
|
||||
title = wtypes.text
|
||||
"""A descriptive label for this tracker to show in listings."""
|
||||
|
||||
description = wtypes.text
|
||||
"""A brief introduction or overview of this bug tracker instance."""
|
||||
|
||||
is_bug = bool
|
||||
"""Is this a bug or a feature :)"""
|
||||
|
||||
#todo(nkonovalov): replace with a enum
|
||||
priority = wtypes.text
|
||||
"""Priority.
|
||||
Allowed values: ['Undefined', 'Low', 'Medium', 'High', 'Critical'].
|
||||
"""
|
||||
|
||||
tasks = wtypes.ArrayType(Task)
|
||||
"""List of linked tasks."""
|
||||
|
||||
comments = wtypes.ArrayType(Comment)
|
||||
"""List of linked comments."""
|
||||
|
||||
tags = wtypes.ArrayType(StoryTag)
|
||||
"""List of linked tags."""
|
||||
|
||||
@classmethod
|
||||
def add_task(cls, story_id, task):
|
||||
@ -241,25 +316,76 @@ class Story(_Base):
|
||||
return cls.create_and_add_item("id", story_id, Comment, comment,
|
||||
"comments")
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
return cls(
|
||||
title="Use Storyboard to manage Storyboard",
|
||||
description="We should use Storyboard to manage Storyboard",
|
||||
is_bug=False,
|
||||
priority='Critical',
|
||||
tasks=[],
|
||||
comments=[],
|
||||
tags=[])
|
||||
|
||||
|
||||
class User(_Base):
|
||||
"""Represents a user."""
|
||||
|
||||
username = wtypes.text
|
||||
"""A short unique name, beginning with a lower-case letter or number, and
|
||||
containing only letters, numbers, dots, hyphens, or plus signs"""
|
||||
|
||||
first_name = wtypes.text
|
||||
"""First name."""
|
||||
|
||||
last_name = wtypes.text
|
||||
"""Last name."""
|
||||
|
||||
email = wtypes.text
|
||||
"""Email Address."""
|
||||
|
||||
# TODO(ruhe): clarify and document what are these fields for
|
||||
is_staff = bool
|
||||
is_active = bool
|
||||
is_superuser = bool
|
||||
|
||||
last_login = datetime
|
||||
"""Date of the last login."""
|
||||
|
||||
#teams = wtypes.ArrayType(Team)
|
||||
|
||||
permissions = wtypes.ArrayType(Permission)
|
||||
"""List of associated permissions"""
|
||||
|
||||
#tasks = wtypes.ArrayType(Task)
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
return cls(
|
||||
username="elbarto",
|
||||
first_name="Bart",
|
||||
last_name="Simpson",
|
||||
email="skinnerstinks@springfield.net",
|
||||
is_staff=False,
|
||||
is_active=True,
|
||||
is_superuser=True,
|
||||
last_login=datetime(2014, 1, 1, 16, 42),
|
||||
permissions=[])
|
||||
|
||||
|
||||
class Team(_Base):
|
||||
"""A group of people and other teams."""
|
||||
|
||||
name = wtypes.text
|
||||
"""A short unique name, beginning with a lower-case letter or number,
|
||||
and containing only letters, numbers, dots, hyphens, or plus signs.
|
||||
"""
|
||||
|
||||
users = wtypes.ArrayType(User)
|
||||
"""List of direct members."""
|
||||
|
||||
permissions = wtypes.ArrayType(Permission)
|
||||
"""Collection of associated permissions."""
|
||||
|
||||
@classmethod
|
||||
def add_user(cls, team_name, username):
|
||||
|
Loading…
Reference in New Issue
Block a user