Commit 1b5fecc3 authored by Christophe de Vienne's avatar Christophe de Vienne
Browse files

[doc] Add a documentation

Closes #4873126
parent 73e8d3c1abfe
......@@ -7,4 +7,5 @@ syntax: glob
.tox
docs/_build
test/data/database
Summary
-------
Easily build a webservice API on top of a cubic web database
Easily build a webservice API on top of a cubic web database.
Once activated, the cube provides new controllers (their regid is
`webservices`) which respond on the rest path of entities, if the requests
matches one of the following condition:
Types
-----
- Content-Type == 'application/json'
- Accept == 'application/json'
To build a wsme structure type that match an entity type::
The following API is automatically provided for all the entities:
from cubes.wsme.types import Base, wsattr
.. csv-table::
class CWUser(Base):
login = wsattr('login', datatype=wsme.types.text)
password = wsattr('upassword', datatype=wsme.types.text)
HTTP, action
`GET /etype?filter=xxx`, return a list of entities matching the filter
`POST /etype`, create a new entity
`GET /etype/1234`, return a particular entity
`PUT /etype/1234`, update an entity
`DELETE /etype/1234`, delete an entity
in_group = wsattr('in_group', datatype=['CWGroup'])
class CWGroup(Base):
name = wsattr('name', datatype=wsme.types.text)
users = wsattr('in_group', role='subject', datatype=[CWUser])
def register_callback(vreg):
CWUser.reginit(vreg)
CWGroup.reginit(vreg)
# ...
user = req.find('CWUser', login=u"admin")
ws_user = CWUser(user, fetch=['in_group'])
assert ws_user.in_group[0].name == user.in_group[0].name
Query
-----
Filter format
~~~~~~~~~~~~~
The filter format is partially inspired by https://www.parse.com/docs/rest#queries
operators
'''''''''
Key Operation
$lt Less Than
$lte Less Than Or Equal To
$gt Greater Than
$gte Greater Than Or Equal To
$ne Not Equal To
$in Contained In
$nin Not Contained in
$or Or
$and And
Filter attribute
''''''''''''''''
Exact match::
{'attrname': value}
Other comparisons::
{'attrname': {'$op': value, '$op2': othervalue}}
Use and/or::
{'$or': {'attrname': value, 'attr2name': value}}
{'$or': [
{'attrname': value},
{'attrname': {
'$in': [1, 2, 3]}}]}
Filter relations
''''''''''''''''
If comparing by eid, same as attribute
Exact match::
{'relname': eid}
Other::
{"relname": {"$op": eid}}
Filter on relation target attributes/relations::
{"relname": <entity filter>}
{"relname": {"attrname": value}}
{"relname": {"$or": {"attrname": value, "attr2name": ovalue}}}
......@@ -13,7 +13,7 @@ author_email = 'christophe@unlish.com'
description = 'Easily build a webservice API on top of a cubic web database"'
web = 'http://www.cubicweb.org/project/%s' % distname
__depends__ = {'cubicweb': '>= 3.19.0', 'wsme': None}
__depends__ = {'cubicweb': '>= 3.19.0', 'wsme': None, 'rqlquery': None}
__recommends__ = {}
classifiers = [
......
""" 'webservice' controller implementation
"""
import inspect
import sys
......@@ -25,6 +27,10 @@ restformats = {
class ParamsAdapter(dict):
""" Adapter for passing cubicweb request params to
:func:`wsme.rest.args.get_args`
"""
def getall(self, path):
v = self[path]
if not isinstance(v, list):
......@@ -33,11 +39,18 @@ class ParamsAdapter(dict):
class WSController(Controller):
"""
A controller that rely on WSME to provide webservice API for an entity.
"""
__regid__ = 'webservice'
__abstract__ = True
@classmethod
def resolve_types(cls, registry):
""" Late-resolve the types of the function signatures.
This function is called at regitering time (by :meth:`__registered__`).
"""
for name, attr in inspect.getmembers(cls, wsme.api.iswsmefunction):
funcdef = wsme.api.FunctionDefinition.get(attr)
funcdef.resolve_types(registry)
......@@ -47,6 +60,26 @@ class WSController(Controller):
cls.resolve_types(reg.vreg.wsme_registry)
def publish(self, rset):
""" Main entry-point of the controller.
Will dispatch the request to the adequate function depending on the
http method and the form/rset content.
It also takes care of converting the inputs (form & body) to call
arguments using the WSME api, based on the function signatures.
The following `form` values are used, which are normaly set by
:class:`cubes.wsme.views.RestPathEvaluator`:
- `_ws_method`: the HTTP method
- `_ws_etype`: the etype (ignored, only used by the selector)
- `_ws_rtype`: the relation type if provided
- `_ws_rtype_target`: An option relation target id
If the :arg:`rset` contains an entity, it will be considered as the
target of the API call.
"""
content_type = self._cw.get_header('Content-Type')
accept = self._cw.get_header('Accept')
......@@ -146,11 +179,31 @@ class WSController(Controller):
class WSCRUDController(WSController):
"""
An entity type CRUD controller
The displatch is summarized in this table, where 'entity' means that an
entity exists in the rset:
.. csv-table::
form-rset / verb, GET, POST, PUT, DELETE
, :meth:`_get`, :meth:`_post`, ,
entity, :meth:`_entity_get`, , :meth:`_entity_put`, :meth:`_entity_delete`
"entity, _ws_rtype", :meth:`_entity_rtype_get`, :meth:`_entity_rtype_post`
"entity, _ws_rtype, _ws_rtype_target", , , , :meth:`_entity_rtype_target_delete`
"""
#: The entity type
__cwetype__ = None
__select__ = yes()
def _get_entity(self, data):
"""
Get an entity and update/create it and its related entities all along.
:param data: A webservice type instance
"""
eid, values, relation_values = self._handle_data(data)
if eid:
entity = self._cw.entity_from_eid(eid)
......@@ -195,9 +248,23 @@ class WSCRUDController(WSController):
return entity
def _get_entities(self, datalist):
"""
Get a list of entities from a list a webservice data
"""
return [self._get_entity(data) for data in datalist]
def _handle_data(self, data):
"""
Handle webservice data.
It returns a tuple `(eid, values, relation_values)`, where eid can be
None if the data had none, `values` contains the final and inlined
values, and `relation_values` the relation values. These variables are
dictionnaries that can be fed cw_set().
While handling the entity data, the related entities present in the
data will be updated/create (via :meth:`_get_entity`).
"""
eid = data.eid if data.eid else None
values = {}
relation_values = {}
......@@ -225,10 +292,12 @@ class WSCRUDController(WSController):
return eid, values, relation_values
def _update(self, data):
""" Update an existing entity from ws data"""
assert data.eid, "missing eid on data"
return self._get_entity(data)
def _create(self, data):
""" Create an entity from ws data"""
if data.eid:
raise ValueError(
"Cannot create with a fixed eid. Please remove it")
......@@ -288,6 +357,8 @@ class WSCRUDController(WSController):
keyonly=False):
"""List entities with an optional filter.
Default implementation of `GET /etype`.
:param filter:
:param fetch: A list of relations and subrelations of which the target
entities will be returned.
......@@ -328,6 +399,7 @@ class WSCRUDController(WSController):
for e in q.all(self._cw.cnx)]
def _post(self, fetch=[], keyonly=False, data=None):
""" Default implementation of `POST /etype`."""
try:
entity = self._create(data)
except:
......@@ -340,9 +412,11 @@ class WSCRUDController(WSController):
return self.__cwetype__(entity, keyonly=keyonly, fetch=fetch)
def _entity_get(self, entity, fetch=[]):
""" Default implementation of `GET /etype/eid`."""
return self.__cwetype__(entity, fetch=fetch)
def _entity_put(self, entity, fetch=[], data=None):
""" Default implementation of `PUT /etype/eid`."""
if not data.eid:
data.eid = entity.eid
entity = self._update(data)
......@@ -354,16 +428,19 @@ class WSCRUDController(WSController):
return self.__cwetype__(entity, fetch=fetch)
def _entity_delete(self, entity):
""" Default implementation of `DELETE /etype/eid`."""
entity.cw_delete()
return wsme.api.Response(None, 204)
def _entity_rtype_post(self, entity, rtype, eid):
""" Default implementation of `POST /etype/eid/rtype`."""
if rtype.startswith('<'):
rtype = 'reverse_' + rtype[1:]
entity.cw_set(**{rtype: eid})
def _entity_rtype_get(self, entity, rtype, orderby=None, limit=None,
offset=None, keyonly=False):
""" Default implementation of `GET /etype/eid/rtype`."""
if rtype.startswith('<'):
rtype, role = rtype[1:], 'object'
else:
......@@ -381,6 +458,7 @@ class WSCRUDController(WSController):
]
def _entity_rtype_target_delete(self, entity, rtype, eid):
""" Default implementation of `DELETE /etype/eid/rtype/eid`."""
if rtype.startswith('<'):
rtype = rtype[1:]
relation = 'X %s E'
......
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyramidCubicweb.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyramidCubicweb.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/PyramidCubicweb"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyramidCubicweb"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
API
===
:mod:`cubes.wsme.types`
'''''''''''''''''''''''
.. automodule:: cubes.wsme.types
.. autoclass:: PassThroughType
:members:
.. autodata:: JsonData
.. autodata:: binary
.. autoclass:: wsattr
.. autofunction:: iswsattr
.. autoclass:: Base
:show-inheritance:
:members:
.. autoclass:: Any
.. autofunction:: scan
:mod:`cubes.wsme.controller`
''''''''''''''''''''''''''''
.. automodule:: cubes.wsme.controller
.. autoclass:: WSController
:show-inheritance:
:members:
.. autoclass:: WSCRUDController
:show-inheritance:
:members:
:private-members:
# -*- coding: utf-8 -*-
#
# Pyramid Cubicweb documentation build configuration file, created by
# sphinx-quickstart on Sat Jan 3 21:00:05 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
import os
import sys
sys.path.insert(0, os.path.join(sys.prefix, 'share', 'cubicweb'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinx.ext.extlinks',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Pyramid Cubicweb'
copyright = u'2015, Christophe de Vienne'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
import pkg_resources
d = pkg_resources.require('cubicweb-wsme')
version = d[0].version.split('.')[:2]
# The full version, including alpha/beta/rc tags.
release = d[0].version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.