Improved documentation (docstrings and sphinx)
Added docstrings to every controllers and types. Added samples for every types. Improved CloudKitty's developper documentation. Change-Id: Idcd25777f67c61c096fbc52962bc173a86d614ba
This commit is contained in:
		| @@ -28,50 +28,111 @@ LOG = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class APILink(wtypes.Base): | ||||
|     """API link description. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     type = wtypes.text | ||||
|     """Type of link.""" | ||||
|  | ||||
|     rel = wtypes.text | ||||
|     """Relationship with this link.""" | ||||
|  | ||||
|     href = wtypes.text | ||||
|     """URL of the link.""" | ||||
|  | ||||
|     @classmethod | ||||
|     def sample(cls): | ||||
|         version = 'v1' | ||||
|         sample = cls( | ||||
|             rel='self', | ||||
|             type='text/html', | ||||
|             href='http://127.0.0.1:8888/{id}'.format( | ||||
|                 id=version)) | ||||
|         return sample | ||||
|  | ||||
|  | ||||
| class APIMediaType(wtypes.Base): | ||||
|     """Media type description. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     base = wtypes.text | ||||
|     """Base type of this media type.""" | ||||
|  | ||||
|     type = wtypes.text | ||||
|     """Type of this media type.""" | ||||
|  | ||||
|     @classmethod | ||||
|     def sample(cls): | ||||
|         sample = cls( | ||||
|             base='application/json', | ||||
|             type='application/vnd.openstack.cloudkitty-v1+json') | ||||
|         return sample | ||||
|  | ||||
|  | ||||
| VERSION_STATUS = wtypes.Enum(wtypes.text, 'EXPERIMENTAL', 'STABLE') | ||||
|  | ||||
|  | ||||
| class APIVersion(wtypes.Base): | ||||
|     """API Version description. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     id = wtypes.text | ||||
|     """ID of the version.""" | ||||
|  | ||||
|     status = wtypes.text | ||||
|     status = VERSION_STATUS | ||||
|     """Status of the version.""" | ||||
|  | ||||
|     updated = wtypes.text | ||||
|     "Last update in iso8601 format." | ||||
|  | ||||
|     links = [APILink] | ||||
|     """List of links to API resources.""" | ||||
|  | ||||
|     media_types = [APIMediaType] | ||||
|     """Types accepted by this API.""" | ||||
|  | ||||
|     @classmethod | ||||
|     def sample(cls): | ||||
|         version = 'v1' | ||||
|         updated = '2014-08-11T16:00:00Z' | ||||
|         links = [APILink.sample()] | ||||
|         media_types = [APIMediaType.sample()] | ||||
|         sample = cls(id=version, | ||||
|                      status='STABLE', | ||||
|                      updated=updated, | ||||
|                      links=links, | ||||
|                      media_types=media_types) | ||||
|         return sample | ||||
|  | ||||
|  | ||||
| class RootController(rest.RestController): | ||||
|     """Root REST Controller exposing versions of the API. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     v1 = v1.V1Controller() | ||||
|  | ||||
|     @wsme_pecan.wsexpose([APIVersion]) | ||||
|     def get(self): | ||||
|         """Return the version list | ||||
|  | ||||
|         """ | ||||
|         # TODO(sheeprine): Maybe we should store all the API version | ||||
|         # informations in every API modules | ||||
|         ver1 = APIVersion( | ||||
|             id='v1', | ||||
|             status='EXPERIMENTAL', | ||||
|             updated='2014-06-02T00:00:00Z', | ||||
|             updated='2014-08-11T16:00:00Z', | ||||
|             links=[ | ||||
|                 APILink( | ||||
|                     rel='self', | ||||
|                     href='{scheme}://{host}/v1'.format( | ||||
|                     href='{scheme}://{host}:{port}/v1'.format( | ||||
|                         scheme=pecan.request.scheme, | ||||
|                         host=pecan.request.host | ||||
|                         host=pecan.request.host, | ||||
|                         port=pecan.request.port | ||||
|                     ) | ||||
|                 ) | ||||
|             ], | ||||
|   | ||||
| @@ -33,14 +33,20 @@ CLOUDKITTY_SERVICES = wtypes.Enum(wtypes.text, | ||||
|  | ||||
|  | ||||
| class ResourceDescriptor(wtypes.Base): | ||||
|     """Type describing a resource in CloudKitty. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     service = CLOUDKITTY_SERVICES | ||||
|     """Name of the service.""" | ||||
|  | ||||
|     # FIXME(sheeprine): values should be dynamic | ||||
|     # Testing with ironic dynamic type | ||||
|     desc = {wtypes.text: cktypes.MultiType(wtypes.text, int, float, dict)} | ||||
|     """Description of the resources parameters.""" | ||||
|  | ||||
|     volume = int | ||||
|     """Number of resources.""" | ||||
|  | ||||
|     def to_json(self): | ||||
|         res_dict = {} | ||||
| @@ -50,8 +56,20 @@ class ResourceDescriptor(wtypes.Base): | ||||
|                                    }] | ||||
|         return res_dict | ||||
|  | ||||
|     @classmethod | ||||
|     def sample(cls): | ||||
|         sample = cls(service='compute', | ||||
|                      desc={ | ||||
|                          'image_id': 'a41fba37-2429-4f15-aa00-b5bc4bf557bf' | ||||
|                      }, | ||||
|                      volume=1) | ||||
|         return sample | ||||
|  | ||||
|  | ||||
| class ModulesController(rest.RestController): | ||||
|     """REST Controller managing billing modules. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.extensions = extension.ExtensionManager( | ||||
| @@ -72,6 +90,10 @@ class ModulesController(rest.RestController): | ||||
|  | ||||
|     @wsme_pecan.wsexpose([wtypes.text]) | ||||
|     def get(self): | ||||
|         """Return the list of loaded modules. | ||||
|  | ||||
|         :return: Name of every loaded modules. | ||||
|         """ | ||||
|         return [ext for ext in self.extensions.names()] | ||||
|  | ||||
|  | ||||
| @@ -85,6 +107,12 @@ class BillingController(rest.RestController): | ||||
|  | ||||
|     @wsme_pecan.wsexpose(float, body=[ResourceDescriptor]) | ||||
|     def quote(self, res_data): | ||||
|         """Get an instant quote based on multiple resource descriptions. | ||||
|  | ||||
|         :param res_data: List of resource descriptions. | ||||
|         :return: Total price for these descriptions. | ||||
|         """ | ||||
|  | ||||
|         # TODO(sheeprine): Send RPC request for quote | ||||
|         from cloudkitty import extension_manager | ||||
|         b_processors = {} | ||||
| @@ -115,6 +143,9 @@ class BillingController(rest.RestController): | ||||
|  | ||||
|  | ||||
| class ReportController(rest.RestController): | ||||
|     """REST Controller managing the reporting. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     _custom_actions = { | ||||
|         'total': ['GET'] | ||||
| @@ -122,11 +153,17 @@ class ReportController(rest.RestController): | ||||
|  | ||||
|     @wsme_pecan.wsexpose(float) | ||||
|     def total(self): | ||||
|         """Return the amount to pay for the current month. | ||||
|  | ||||
|         """ | ||||
|         # TODO(sheeprine): Get current total from DB | ||||
|         return 10.0 | ||||
|  | ||||
|  | ||||
| class V1Controller(rest.RestController): | ||||
|     """API version 1 controller. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     billing = BillingController() | ||||
|     report = ReportController() | ||||
|   | ||||
| @@ -39,19 +39,37 @@ class ExtensionSummary(wtypes.Base): | ||||
|     """ | ||||
|  | ||||
|     name = wtypes.wsattr(wtypes.text, mandatory=True) | ||||
|     """Name of the extension.""" | ||||
|  | ||||
|     description = wtypes.text | ||||
|     """Short description of the extension.""" | ||||
|  | ||||
|     enabled = wtypes.wsattr(bool, default=False) | ||||
|     """Extension status.""" | ||||
|  | ||||
|     hot_config = wtypes.wsattr(bool, default=False, name='hot-config') | ||||
|     """On-the-fly configuration support.""" | ||||
|  | ||||
|     @classmethod | ||||
|     def sample(cls): | ||||
|         sample = cls(name='example', | ||||
|                      description='Sample extension.', | ||||
|                      enabled=True, | ||||
|                      hot_config=False) | ||||
|         return sample | ||||
|  | ||||
|  | ||||
| @six.add_metaclass(abc.ABCMeta) | ||||
| class BillingEnableController(rest.RestController): | ||||
|     """REST Controller to enable or disable a billing module. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     @wsme_pecan.wsexpose(bool) | ||||
|     def get(self): | ||||
|         """Get module status | ||||
|  | ||||
|         """ | ||||
|         api = db_api.get_instance() | ||||
|         module = pecan.request.path.rsplit('/', 2)[-2] | ||||
|         module_db = api.get_module_enable_state() | ||||
| @@ -59,6 +77,11 @@ class BillingEnableController(rest.RestController): | ||||
|  | ||||
|     @wsme_pecan.wsexpose(bool, body=bool) | ||||
|     def put(self, state): | ||||
|         """Set module status | ||||
|  | ||||
|         :param state: State to set. | ||||
|         :return: New state set for the module. | ||||
|         """ | ||||
|         api = db_api.get_instance() | ||||
|         module = pecan.request.path.rsplit('/', 2)[-2] | ||||
|         module_db = api.get_module_enable_state() | ||||
| @@ -67,18 +90,37 @@ class BillingEnableController(rest.RestController): | ||||
|  | ||||
| @six.add_metaclass(abc.ABCMeta) | ||||
| class BillingConfigController(rest.RestController): | ||||
|     """REST Controller managing internal configuration of billing modules. | ||||
|  | ||||
|     @wsme_pecan.wsexpose() | ||||
|     def get(self): | ||||
|     """ | ||||
|  | ||||
|     def _not_configurable(self): | ||||
|         try: | ||||
|             module = pecan.request.path.rsplit('/', 1)[-1] | ||||
|             raise BillingModuleNotConfigurable(module) | ||||
|         except BillingModuleNotConfigurable as e: | ||||
|             pecan.abort(400, str(e)) | ||||
|  | ||||
|     @wsme_pecan.wsexpose() | ||||
|     def get(self): | ||||
|         """Get current module configuration | ||||
|  | ||||
|         """ | ||||
|         self._not_configurable() | ||||
|  | ||||
|     @wsme_pecan.wsexpose() | ||||
|     def put(self): | ||||
|         """Set current module configuration | ||||
|  | ||||
|         """ | ||||
|         self._not_configurable() | ||||
|  | ||||
|  | ||||
| @six.add_metaclass(abc.ABCMeta) | ||||
| class BillingController(rest.RestController): | ||||
|     """REST Controller used to manage billing system. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     config = BillingConfigController() | ||||
|     enabled = BillingEnableController() | ||||
|   | ||||
| @@ -34,8 +34,15 @@ MAP_TYPE = wtypes.Enum(wtypes.text, 'flat', 'rate') | ||||
| class Mapping(wtypes.Base): | ||||
|  | ||||
|     map_type = wtypes.wsattr(MAP_TYPE, default='rate', name='type') | ||||
|     """Type of the mapping.""" | ||||
|  | ||||
|     value = wtypes.wsattr(float, mandatory=True) | ||||
|     """Value of the mapping.""" | ||||
|  | ||||
|     @classmethod | ||||
|     def sample(cls): | ||||
|         sample = cls(value=4.2) | ||||
|         return sample | ||||
|  | ||||
|  | ||||
| class BasicHashMapConfigController(billing.BillingConfigController): | ||||
| @@ -56,7 +63,7 @@ class BasicHashMapConfigController(billing.BillingConfigController): | ||||
|  | ||||
|     @wsme_pecan.wsexpose(Mapping, wtypes.text, wtypes.text, wtypes.text) | ||||
|     def get_mapping(self, service, field, key): | ||||
|         """Return the list of every mappings. | ||||
|         """Get a mapping from full path. | ||||
|  | ||||
|         """ | ||||
|         hashmap = api.get_instance() | ||||
| @@ -67,6 +74,10 @@ class BasicHashMapConfigController(billing.BillingConfigController): | ||||
|  | ||||
|     @wsme_pecan.wsexpose([wtypes.text]) | ||||
|     def get(self): | ||||
|         """Get the service list | ||||
|  | ||||
|         :return: List of every services' name. | ||||
|         """ | ||||
|         hashmap = api.get_instance() | ||||
|         return [service.name for service in hashmap.list_services()] | ||||
|  | ||||
| @@ -74,6 +85,8 @@ class BasicHashMapConfigController(billing.BillingConfigController): | ||||
|     def get_one(self, service=None, field=None): | ||||
|         """Return the list of every sub keys. | ||||
|  | ||||
|         :param service: (Optional) Filter on this service. | ||||
|         :param field: (Optional) Filter on this field. | ||||
|         """ | ||||
|         hashmap = api.get_instance() | ||||
|         if field: | ||||
| @@ -95,6 +108,13 @@ class BasicHashMapConfigController(billing.BillingConfigController): | ||||
|     @wsme_pecan.wsexpose(None, wtypes.text, wtypes.text, wtypes.text, | ||||
|                          body=Mapping) | ||||
|     def post(self, service, field=None, key=None, mapping=None): | ||||
|         """Create hashmap fields. | ||||
|  | ||||
|         :param service: Name of the service to create. | ||||
|         :param field: (Optional) Name of the field to create. | ||||
|         :param key: (Optional) Name of the key to create. | ||||
|         :param mapping: (Optional) Mapping object to create. | ||||
|         """ | ||||
|         hashmap = api.get_instance() | ||||
|         if field: | ||||
|             if key: | ||||
| @@ -131,6 +151,13 @@ class BasicHashMapConfigController(billing.BillingConfigController): | ||||
|     @wsme_pecan.wsexpose(None, wtypes.text, wtypes.text, wtypes.text, | ||||
|                          body=Mapping) | ||||
|     def put(self, service, field, key, mapping): | ||||
|         """Modify hashmap fields | ||||
|  | ||||
|         :param service: Filter on this service. | ||||
|         :param field: Filter on this field. | ||||
|         :param key: Modify the content of this key. | ||||
|         :param mapping: Mapping object to update. | ||||
|         """ | ||||
|         hashmap = api.get_instance() | ||||
|         try: | ||||
|             hashmap.update_mapping( | ||||
| @@ -149,6 +176,9 @@ class BasicHashMapConfigController(billing.BillingConfigController): | ||||
|     def delete(self, service, field=None, key=None): | ||||
|         """Delete the parent and all the sub keys recursively. | ||||
|  | ||||
|         :param service: Name of the service to delete. | ||||
|         :param field: (Optional) Name of the field to delete. | ||||
|         :param key: (Optional) Name of the key to delete. | ||||
|         """ | ||||
|         hashmap = api.get_instance() | ||||
|         try: | ||||
|   | ||||
							
								
								
									
										47
									
								
								doc/source/arch.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								doc/source/arch.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| ========================= | ||||
| CloudKitty's Architecture | ||||
| ========================= | ||||
|  | ||||
| CloudKitty can be cut in four big parts: | ||||
|  | ||||
| * API | ||||
| * collector | ||||
| * billing processor | ||||
| * writer pipeline | ||||
|  | ||||
|  | ||||
| Module loading and extensions | ||||
| ============================= | ||||
|  | ||||
| Nearly every part of CloudKitty makes use of stevedore to load extensions | ||||
| dynamically. | ||||
|  | ||||
| Every billing module is loaded at runtime and can be enabled/disabled directly | ||||
| via CloudKitty's API. The billing module is responsible of its own API to ease | ||||
| the management of its configuration. | ||||
|  | ||||
| Collectors and writers are loaded with stevedore but configured in CloudKitty's | ||||
| configuration file. | ||||
|  | ||||
|  | ||||
| Collector | ||||
| ========= | ||||
|  | ||||
| This part is responsible of the information gathering. It consists of a python | ||||
| module that load data from a backend and return them in a format that | ||||
| CloudKitty can handle. | ||||
|  | ||||
| Processor | ||||
| ========= | ||||
|  | ||||
| This is where every pricing calculations is done. The data gathered by | ||||
| the collector is pushed in a pipeline of billing processors. Every | ||||
| processor does its calculations and updates the data. | ||||
|  | ||||
|  | ||||
| Writer | ||||
| ====== | ||||
|  | ||||
| In the same way as the processor pipeline, the writing is handled with a | ||||
| pipeline. The data is pushed to every writer in the pipeline which is | ||||
| responsible of the writing. | ||||
| @@ -12,8 +12,8 @@ | ||||
| # All configuration values have a default; values that are commented out | ||||
| # serve to show the default. | ||||
|  | ||||
| import sys | ||||
| import os | ||||
| #import sys | ||||
| #import os | ||||
|  | ||||
| # 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 | ||||
| @@ -28,7 +28,18 @@ import os | ||||
| # Add any Sphinx extension module names here, as strings. They can be | ||||
| # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom | ||||
| # ones. | ||||
| extensions = [] | ||||
| extensions = [ | ||||
|     'sphinx.ext.autodoc', | ||||
|     'sphinx.ext.intersphinx', | ||||
|     'sphinx.ext.viewcode', | ||||
|     'wsmeext.sphinxext', | ||||
|     'sphinxcontrib.docbookrestapi.setup', | ||||
|     'sphinxcontrib.pecanwsme.rest', | ||||
|     'sphinxcontrib.httpdomain', | ||||
|     'oslosphinx', | ||||
| ] | ||||
|  | ||||
| wsme_protocols = ['restjson', 'restxml'] | ||||
|  | ||||
| # Add any paths that contain templates here, relative to this directory. | ||||
| templates_path = ['_templates'] | ||||
| @@ -74,11 +85,11 @@ exclude_patterns = [] | ||||
| #default_role = None | ||||
|  | ||||
| # If true, '()' will be appended to :func: etc. cross-reference text. | ||||
| #add_function_parentheses = True | ||||
| 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 | ||||
| add_module_names = True | ||||
|  | ||||
| # If true, sectionauthor and moduleauthor directives will be shown in the | ||||
| # output. They are ignored by default. | ||||
| @@ -88,7 +99,7 @@ exclude_patterns = [] | ||||
| pygments_style = 'sphinx' | ||||
|  | ||||
| # A list of ignored prefixes for module index sorting. | ||||
| #modindex_common_prefix = [] | ||||
| modindex_common_prefix = ['cloudkitty.'] | ||||
|  | ||||
| # If true, keep warnings as "system message" paragraphs in the built documents. | ||||
| #keep_warnings = False | ||||
|   | ||||
| @@ -3,14 +3,44 @@ | ||||
|    You can adapt this file completely to your liking, but it should at least | ||||
|    contain the root `toctree` directive. | ||||
|  | ||||
| Welcome to cloudkitty's documentation! | ||||
| ====================================== | ||||
| ================================================= | ||||
| Welcome to CloudKitty's developper documentation! | ||||
| ================================================= | ||||
|  | ||||
| Contents: | ||||
| Introduction | ||||
| ============ | ||||
|  | ||||
| CloudKitty is a PricingAsAService project aimed at translating Ceilometer | ||||
| metrics to prices. | ||||
|  | ||||
|  | ||||
| Architecture | ||||
| ============ | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 2 | ||||
|    :maxdepth: 1 | ||||
|  | ||||
|    arch | ||||
|  | ||||
|  | ||||
| API References | ||||
| ============== | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 1 | ||||
|  | ||||
|    webapi/root | ||||
|    webapi/v1 | ||||
|  | ||||
|  | ||||
| Modules | ||||
| ======= | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 1 | ||||
|    :glob: | ||||
|  | ||||
|    webapi/billing/* | ||||
|  | ||||
|  | ||||
| Indices and tables | ||||
| @@ -19,4 +49,3 @@ Indices and tables | ||||
| * :ref:`genindex` | ||||
| * :ref:`modindex` | ||||
| * :ref:`search` | ||||
|  | ||||
|   | ||||
							
								
								
									
										30
									
								
								doc/source/webapi/billing/hashmap.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								doc/source/webapi/billing/hashmap.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| ======================= | ||||
| HashMap Module REST API | ||||
| ======================= | ||||
|  | ||||
| .. rest-controller:: cloudkitty.billing.hash:BasicHashMapController | ||||
|    :webprefix: /v1/billing/modules/hashmap | ||||
|  | ||||
| .. rest-controller:: cloudkitty.billing.hash:BasicHashMapConfigController | ||||
|    :webprefix: /v1/billing/modules/hashmap/config | ||||
|  | ||||
| .. http:get:: /v1/billing/hashmap/modules/config/(service)/(field)/(key) | ||||
|  | ||||
|    Get a mapping from full path | ||||
|  | ||||
|    :param service: Filter on this service. | ||||
|    :param field: Filter on this field. | ||||
|    :param key: Filter on this key. | ||||
|    :type service: :class:`unicode` | ||||
|    :type field: :class:`unicode` | ||||
|    :type key: :class:`unicode` | ||||
|    :type mapping: :class:`Mapping` | ||||
|    :return: A mapping | ||||
|  | ||||
|    :return type: :class:`Mapping` | ||||
|  | ||||
|  | ||||
| .. autotype:: cloudkitty.billing.hash.Mapping | ||||
|    :members: | ||||
|  | ||||
|  | ||||
							
								
								
									
										16
									
								
								doc/source/webapi/root.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								doc/source/webapi/root.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| ========================== | ||||
| CloudKitty REST API (root) | ||||
| ========================== | ||||
|  | ||||
| .. rest-controller:: cloudkitty.api.controllers.root:RootController | ||||
|    :webprefix: / / | ||||
| .. Dirty hack till the bug is fixed so we can specify root path | ||||
|  | ||||
| .. autotype:: cloudkitty.api.controllers.root.APILink | ||||
|    :members: | ||||
|  | ||||
| .. autotype:: cloudkitty.api.controllers.root.APIMediaType | ||||
|    :members: | ||||
|  | ||||
| .. autotype:: cloudkitty.api.controllers.root.APIVersion | ||||
|    :members: | ||||
							
								
								
									
										31
									
								
								doc/source/webapi/v1.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								doc/source/webapi/v1.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| ======================== | ||||
| CloudKitty REST API (v1) | ||||
| ======================== | ||||
|  | ||||
| Billing | ||||
| ======= | ||||
|  | ||||
| .. rest-controller:: cloudkitty.billing:BillingEnableController | ||||
|    :webprefix: /v1/billing/modules/(module)/enabled | ||||
|  | ||||
| .. rest-controller:: cloudkitty.billing:BillingConfigController | ||||
|    :webprefix: /v1/billing/modules/(module)/config | ||||
|  | ||||
| .. rest-controller:: cloudkitty.api.controllers.v1:ModulesController | ||||
|    :webprefix: /v1/billing/modules | ||||
|  | ||||
| .. rest-controller:: cloudkitty.api.controllers.v1:BillingController | ||||
|    :webprefix: /v1/billing | ||||
|  | ||||
| .. autotype:: cloudkitty.billing.ExtensionSummary | ||||
|    :members: | ||||
|  | ||||
| .. autotype:: cloudkitty.api.controllers.v1.ResourceDescriptor | ||||
|    :members: | ||||
|  | ||||
|  | ||||
| Report | ||||
| ====== | ||||
|  | ||||
| .. rest-controller:: cloudkitty.api.controllers.v1:ReportController | ||||
|    :webprefix: /v1/report | ||||
| @@ -6,7 +6,6 @@ mock>=1.0 | ||||
| sphinx>=1.1.2,<1.2 | ||||
| oslosphinx | ||||
| oslotest | ||||
|  | ||||
| sphinxcontrib-docbookrestapi | ||||
| sphinxcontrib-httpdomain | ||||
| sphinxcontrib-pecanwsme>=0.8 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Stéphane Albert
					Stéphane Albert