diff --git a/couchpotato/api.py b/couchpotato/api.py index d1aa6a4..77f24a5 100644 --- a/couchpotato/api.py +++ b/couchpotato/api.py @@ -1,21 +1,30 @@ -from couchpotato.core.helpers.request import jsonified from flask.blueprints import Blueprint +from flask.templating import render_template api = Blueprint('api', __name__) +api_docs = {} +api_docs_missing = [] -def addApiView(route, func, static = False): - api.add_url_rule(route + ('' if static else '/'), endpoint = route.replace('.', '-') if route else 'index', view_func = func) +def addApiView(route, func, static = False, docs = None): + api.add_url_rule(route + ('' if static else '/'), endpoint = route.replace('.', '::') if route else 'index', view_func = func) + if docs: + api_docs[route[4:] if route[0:4] == 'api.' else route] = docs + else: + api_docs_missing.append(route) """ Api view """ def index(): - from couchpotato import app + from couchpotato import app routes = [] for route, x in sorted(app.view_functions.iteritems()): if route[0:4] == 'api.': - routes += [route[4:]] + routes += [route[4:].replace('::', '.')] - return jsonified({'routes': routes}) + if api_docs.get(''): + del api_docs[''] + del api_docs_missing[''] + return render_template('api.html', routes = sorted(routes), api_docs = api_docs, api_docs_missing = sorted(api_docs_missing)) addApiView('', index) addApiView('default', index) diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py index 7a14208..f1846cd 100644 --- a/couchpotato/core/_base/_core/main.py +++ b/couchpotato/core/_base/_core/main.py @@ -24,9 +24,17 @@ class Core(Plugin): shutdown_started = False def __init__(self): - addApiView('app.shutdown', self.shutdown) - addApiView('app.restart', self.restart) - addApiView('app.available', self.available) + addApiView('app.shutdown', self.shutdown, docs = { + 'desc': 'Shutdown the app.', + 'return': {'type': 'string: shutdown'} + }) + addApiView('app.restart', self.restart, docs = { + 'desc': 'Restart the app.', + 'return': {'type': 'string: restart'} + }) + addApiView('app.available', self.available, docs = { + 'desc': 'Check if app available.' + }) addEvent('app.crappy_shutdown', self.crappyShutdown) addEvent('app.crappy_restart', self.crappyRestart) diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py index 6e9236f..57a98c6 100644 --- a/couchpotato/core/_base/updater/main.py +++ b/couchpotato/core/_base/updater/main.py @@ -29,9 +29,25 @@ class Updater(Plugin): addEvent('app.load', self.check) - addApiView('updater.info', self.getInfo) + addApiView('updater.info', self.getInfo, docs = { + 'desc': 'Get updater information', + 'return': { + 'type': 'object', + 'example': """ + { + 'repo_name': "Name of used repository", + 'last_check': "last checked for update", + 'update_version': "available update version or empty", + 'version': current_cp_version + } + """ + } + }) addApiView('updater.update', self.doUpdateView) - addApiView('updater.check', self.checkView) + addApiView('updater.check', self.checkView, docs = { + 'desc': 'Check for available update', + 'return': {'type': 'see updater.info'} + }) def getInfo(self): diff --git a/couchpotato/core/plugins/browser/main.py b/couchpotato/core/plugins/browser/main.py index ca7e313..3c3e6e8 100644 --- a/couchpotato/core/plugins/browser/main.py +++ b/couchpotato/core/plugins/browser/main.py @@ -11,7 +11,18 @@ if os.name == 'nt': class FileBrowser(Plugin): def __init__(self): - addApiView('directory.list', self.view) + addApiView('directory.list', self.view, docs = { + 'desc': 'Return the directory list of a given directory', + 'params': { + 'path': {'desc': 'The directory to scan'}, + 'show_hidden': {'desc': 'Also show hidden files'} + }, + 'return': {'type': 'object', 'example': """{ + 'is_root': bool, //is top most folder + 'empty': bool, //directory is empty + 'dirs': array, //directory names +}"""} + }) def getDirectories(self, path = '/', show_hidden = True): diff --git a/couchpotato/core/plugins/file/main.py b/couchpotato/core/plugins/file/main.py index ba84baa..1ec7281 100644 --- a/couchpotato/core/plugins/file/main.py +++ b/couchpotato/core/plugins/file/main.py @@ -19,7 +19,13 @@ class FileManager(Plugin): addEvent('file.download', self.download) addEvent('file.types', self.getTypes) - addApiView('file.cache/', self.showCacheFile, static = True) + addApiView('file.cache/', self.showCacheFile, static = True, docs = { + 'desc': 'Return a file from the cp_data/cache directory', + 'params': { + 'filename': {'desc': 'path/filename of the wanted file'} + }, + 'return': {'type': 'file'} + }) def showCacheFile(self, filename = ''): diff --git a/couchpotato/core/plugins/log/main.py b/couchpotato/core/plugins/log/main.py index 00d2baf..2a2c022 100644 --- a/couchpotato/core/plugins/log/main.py +++ b/couchpotato/core/plugins/log/main.py @@ -12,9 +12,27 @@ log = CPLog(__name__) class Logging(Plugin): def __init__(self): - addApiView('logging.get', self.get) - addApiView('logging.clear', self.clear) - addApiView('logging.log', self.log) + addApiView('logging.get', self.get, docs = { + 'desc': 'Get the full log file by number', + 'params': { + 'nr': {'desc': 'Number of the log to get.'} + }, + 'return': {'type': 'object', 'example': """{ + 'success': True, + 'log': string, //Log file + 'total': int, //Total log files available +}"""} + }) + addApiView('logging.clear', self.clear, docs = { + 'desc': 'Remove all the log files' + }) + addApiView('logging.log', self.log, docs = { + 'desc': 'Get the full log file by number', + 'params': { + 'type': {'desc': 'Type of logging, default "error"'}, + '**kwargs': {'type':'object', 'desc': 'All other params will be printed in the log string.'}, + } + }) def get(self): diff --git a/couchpotato/core/plugins/manage/main.py b/couchpotato/core/plugins/manage/main.py index 6810199..20fdd4c 100644 --- a/couchpotato/core/plugins/manage/main.py +++ b/couchpotato/core/plugins/manage/main.py @@ -21,7 +21,12 @@ class Manage(Plugin): fireEvent('scheduler.interval', identifier = 'manage.update_library', handle = self.updateLibrary, hours = 2) addEvent('manage.update', self.updateLibrary) - addApiView('manage.update', self.updateLibraryView) + addApiView('manage.update', self.updateLibraryView, docs = { + 'desc': 'Update the library by scanning for new movies', + 'params': { + 'full': {'desc': 'Do a full update or just recently changed/added movies.'}, + } + }) if not Env.get('dev'): addEvent('app.load', self.updateLibrary) diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index 44045ba..cb9ffd9 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -26,9 +26,37 @@ class MoviePlugin(Plugin): } def __init__(self): - addApiView('movie.search', self.search) - addApiView('movie.list', self.listView) - addApiView('movie.refresh', self.refresh) + addApiView('movie.search', self.search, docs = { + 'desc': 'Search the movie providers for a movie', + 'params': { + 'q': {'desc': 'The (partial) movie name you want to search for'}, + }, + 'return': {'type': 'object', 'example': """{ + 'success': True, + 'empty': bool, any movies returned or not, + 'movies': array, movies found, +}"""} + }) + addApiView('movie.list', self.listView, docs = { + 'desc': 'List movies in wanted list', + 'params': { + 'status': {'type': 'array or csv', 'desc': 'Filter movie by status. Example:"active,done"'}, + 'limit_offset': {'desc': 'Limit the movie list. Examples: "50", "50,30"'}, + 'starts_with': {'desc': 'Starts with these characters. Example: "a" returns all movies starting with the letter "a"'}, + 'search': {'desc': 'Search movie title'}, + }, + 'return': {'type': 'object', 'example': """{ + 'success': True, + 'empty': bool, any movies returned or not, + 'movies': array, movies found, +}"""} + }) + addApiView('movie.refresh', self.refresh, docs = { + 'desc': 'Refresh a movie by id', + 'params': { + 'id': {'desc': 'The id of the movie that needs to be refreshed'}, + } + }) addApiView('movie.available_chars', self.charView) addApiView('movie.add', self.addView) diff --git a/couchpotato/core/settings/__init__.py b/couchpotato/core/settings/__init__.py index 6bed3dd..29b6b05 100644 --- a/couchpotato/core/settings/__init__.py +++ b/couchpotato/core/settings/__init__.py @@ -16,8 +16,41 @@ class Settings(object): def __init__(self): - addApiView('settings', self.view) - addApiView('settings.save', self.saveView) + addApiView('settings', self.view, docs = { + 'desc': 'Return the options and its values of settings.conf. Including the default values and group ordering used on the settings page.', + 'return': {'type': 'object', 'example': """{ + // objects like in __init__.py of plugin + "options": { + "moovee" : { + "groups" : [{ + "description" : "SD movies only", + "name" : "#alt.binaries.moovee", + "options" : [{ + "default" : false, + "name" : "enabled", + "type" : "enabler" + }], + "tab" : "providers" + }], + "name" : "moovee" + } + }, + // object structured like settings.conf + "values": { + "moovee": { + "enabled": false + } + } +}"""} + }) + addApiView('settings.save', self.saveView, docs = { + 'desc': 'Save setting to config file (settings.conf)', + 'params': { + 'section': {'desc': 'The section name in settings.conf'}, + 'option': {'desc': 'The option name'}, + 'value': {'desc': 'The value you want to save'}, + } + }) def setFile(self, config_file): self.file = config_file diff --git a/couchpotato/static/style/api.css b/couchpotato/static/style/api.css new file mode 100644 index 0000000..15bddd3 --- /dev/null +++ b/couchpotato/static/style/api.css @@ -0,0 +1,75 @@ +html { + font-size: 12px; + line-height: 1.5; + font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; + font-size: 14px; +} + +h1, h2, h3, h4, h5 { + clear: both; + padding: 0; + margin: 0; + font-size: 14px; +} + +h1 { + font-size: 25px; +} + +h2 { + font-size: 20px; +} + +pre { + background: #ccc; + font-family: monospace; + margin: 0; + padding: 2%; + width: 96%; + display: block; +} + +.api { + margin-bottom: 20px; + overflow: hidden; +} + + .api .description { + color: #333; + padding: 0 0 5px; + } + + .api .params { + background: #f5f5f5; + width: 100%; + } + .api h3 { + clear: both; + float: left; + width: 100px; + } + + .api .params { + float: left; + width: 700px; + } + + .api .params .param { + vertical-align: top; + } + + .api .params .param th { + text-align: left; + width: 100px; + } + + .api .param .type { + font-style: italic; + margin-right: 10px; + width: 100px; + } + + .api .return { + float: left; + width: 700px; + } diff --git a/couchpotato/templates/api.html b/couchpotato/templates/api.html new file mode 100644 index 0000000..9852ccb --- /dev/null +++ b/couchpotato/templates/api.html @@ -0,0 +1,49 @@ + + + + + API documentation + + + +

API

+ {% for route in routes %} + {% if api_docs.get(route) %} +
+

{{route}}

+
{{api_docs[route].get('desc', '')}}
+ + {% if api_docs[route].get('params') %} +

Params

+ + {% for param in api_docs[route]['params'] %} + + + + + + {% endfor %} +
{{param}}{{ api_docs[route]['params'][param].get('type', 'string') }}{{ api_docs[route]['params'][param]['desc'] }}
+ {% endif %} + + {% if api_docs[route].get('return') %} +

Return

+
+
{{ api_docs[route]['return'].get('type', '{"success": True}') }}
+ {% if api_docs[route]['return'].get('example') %} +
+

Example

+
{{ api_docs[route]['return'].get('example', '')|safe }}
+
+ {% endif %} +
+ {% endif %} +
+ {% endif %} + {% endfor %} + +

Missing documentation

+
{{', '.join(api_docs_missing)}}
+ + + \ No newline at end of file