Added JSON API.

This commit is contained in:
mitsuhiko 2008-11-29 11:37:46 +01:00
parent d5d704d012
commit 010447f9f9
15 changed files with 315 additions and 183 deletions

View File

@ -1 +1,2 @@
\.py[co]$
\.DS_Store$

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
"""
lodgeit.controllers.json
~~~~~~~~~~~~~~~~~~~~~~~~
The JSON controller
:copyright: 2008 by Armin Ronacher.
:license: BSD
"""
from lodgeit import local
from lodgeit.lib.webapi import json
from lodgeit.utils import render_to_response
class JSONController(object):
def handle_request(self):
if local.request.args.get('method'):
return json.handle_request()
return render_to_response('json.html')
controller = JSONController

View File

@ -10,17 +10,17 @@
"""
from werkzeug.exceptions import NotFound
from lodgeit import local
from lodgeit.i18n import _
from lodgeit.i18n import lazy_gettext
from lodgeit.utils import render_to_response
from lodgeit.lib.xmlrpc import xmlrpc
from lodgeit.lib.webapi import get_public_methods
from lodgeit.lib.highlighting import LANGUAGES
HELP_PAGES = [
('pasting', _('Pasting')),
('advanced', _('Advanced Features')),
('xmlrpc', _('Using the XMLRPC Interface')),
('integration', _('Scripts and Editor Integration'))
('pasting', lazy_gettext('Pasting')),
('advanced', lazy_gettext('Advanced Features')),
('api', lazy_gettext('Using the LodgeIt API')),
('integration', lazy_gettext('Scripts and Editor Integration'))
]
known_help_pages = set(x[0] for x in HELP_PAGES)
@ -45,10 +45,9 @@ class StaticController(object):
tmpl_name,
help_topics=HELP_PAGES,
current_topic=topic,
xmlrpc_url='http://%s/xmlrpc/' %
local.request.environ['SERVER_NAME'],
pastebin_url=local.request.host_url,
formatters=LANGUAGES,
xmlrpc_methods=xmlrpc.get_public_methods()
xmlrpc_methods=get_public_methods()
)

View File

@ -10,13 +10,10 @@
"""
from lodgeit import local
from lodgeit.utils import render_to_response
from lodgeit.database import session, Paste
from lodgeit.lib.xmlrpc import xmlrpc, exported
from lodgeit.lib.highlighting import STYLES, LANGUAGES, get_style, \
get_language_for
from lodgeit.lib.webapi import xmlrpc
class XmlRpcController(object):
class XMLRPCController(object):
def handle_request(self):
if local.request.method == 'POST':
@ -24,85 +21,4 @@ class XmlRpcController(object):
return render_to_response('xmlrpc.html')
@exported('pastes.newPaste')
def pastes_new_paste(language, code, parent_id=None,
filename='', mimetype='', private=False):
"""Create a new paste. Return the new ID.
`language` can be None, in which case the language will be
guessed from `filename` and/or `mimetype`.
"""
if not language:
language = get_language_for(filename or '', mimetype or '')
parent = None
if parent_id:
parent = Paste.get(parent_id)
if parent is None:
raise ValueError('parent paste not found')
paste = Paste(code, language, parent, private=private)
session.flush()
return paste.identifier
@exported('pastes.getPaste')
def pastes_get_paste(paste_id):
"""Get all known information about a paste by a given paste id.
Return a dictionary with these keys:
`paste_id`, `code`, `parsed_code`, `pub_date`, `language`,
`parent_id`, `url`.
"""
paste = Paste.get(paste_id)
if paste is None:
return False
return paste.to_xmlrpc_dict()
@exported('pastes.getDiff')
def pastes_get_diff(old_id, new_id):
"""Compare the two pastes and return an unified diff."""
old = Paste.get(old_id)
new = Paste.get(new_id)
if old is None or new is None:
raise ValueError('argument error, paste not found')
return old.compare_to(new)
@exported('pastes.getRecent')
def pastes_get_recent(amount=5):
"""Return information dict (see `getPaste`) about the last
`amount` pastes.
"""
amount = min(amount, 20)
return [x.to_xmlrpc_dict() for x in Paste.find_all().limit(amount)]
@exported('pastes.getLast')
def pastes_get_last():
"""Get information dict (see `getPaste`) for the most recent paste."""
rv = pastes_get_recent(1)
if rv:
return rv[0]
return {}
@exported('pastes.getLanguages')
def pastes_get_languages():
"""Get a list of supported languages."""
return LANGUAGES.items()
@exported('styles.getStyles')
def styles_get_styles():
"""Get a list of supported styles."""
return STYLES.items()
@exported('styles.getStylesheet')
def styles_get_stylesheet(name):
"""Return the stylesheet for a given style."""
return get_style(name)
controller = XmlRpcController
controller = XMLRPCController

View File

@ -170,11 +170,10 @@ class Paste(object):
def to_xmlrpc_dict(self):
"""Convert the paste into a dict for XMLRCP."""
from lodgeit.lib.xmlrpc import strip_control_chars
return {
'paste_id': self.paste_id,
'code': strip_control_chars(self.code),
'parsed_code': strip_control_chars(self.parsed_code),
'code': self.code,
'parsed_code': self.parsed_code,
'pub_date': int(time.mktime(self.pub_date.timetuple())),
'language': self.language,
'parent_id': self.parent_id,

Binary file not shown.

47
lodgeit/lib/json.py Normal file
View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
"""
lodgeit.lib.json
~~~~~~~~~~~~~~~~
This module implements a simple JSON API.
:copyright: 2008 by Armin Ronacher.
:license: BSD
"""
from simplejson import dumps, loads
from werkzeug import Response
from lodgeit import local
class JSONRequestHandler(object):
def __init__(self):
self.funcs = {}
def register_function(self, func, name=None):
self.funcs[name or func.__name__] = func
def handle_request(self):
try:
method_name = local.request.args['method']
if not local.request.data:
args = ()
kwargs = {}
else:
args = loads(local.request.data)
if isinstance(args, dict):
kwargs = dict((str(key), value) for
key, value in args.iteritems())
args = ()
elif isinstance(args, list):
kwargs = {}
else:
raise TypeError('arguments as object or list expected')
response = {
'data': self.funcs[method_name](*args, **kwargs),
'error': None
}
except Exception, e:
response = {'data': None, 'error': str(e).decode('utf-8')}
body = dumps(response, indent=local.request.is_xhr and 2 or 0)
return Response(body + '\n', mimetype='application/json')

142
lodgeit/lib/webapi.py Normal file
View File

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
"""
lodgeit.lib.webapi
~~~~~~~~~~~~~~~~~~
This module implements the web api.
:copyright: Copyright 2008 by Armin Ronacher.
:license: BSD.
"""
import inspect
from lodgeit.database import session, Paste
from lodgeit.lib.xmlrpc import XMLRPCRequestHandler
from lodgeit.lib.json import JSONRequestHandler
from lodgeit.lib.highlighting import STYLES, LANGUAGES, get_style, \
get_language_for
xmlrpc = XMLRPCRequestHandler()
json = JSONRequestHandler()
def exported(name, hidden=False):
"""Make a function external available via xmlrpc."""
def proxy(f):
xmlrpc.register_function(f, name)
json.register_function(f, name)
f.hidden = hidden
return f
return proxy
_public_methods = None
def get_public_methods():
"""Returns the public methods."""
global _public_methods
if _public_methods is None:
result = []
for name, f in json.funcs.iteritems():
if name.startswith('system.') or f.hidden:
continue
args, varargs, varkw, defaults = inspect.getargspec(f)
if args and args[0] == 'request':
args = args[1:]
result.append({
'name': name,
'doc': inspect.getdoc(f) or '',
'signature': inspect.formatargspec(
args, varargs, varkw, defaults,
formatvalue=lambda o: '=' + repr(o)
)
})
result.sort(key=lambda x: x['name'].lower())
_public_methods = result
return _public_methods
@exported('system.listMethods')
def system_list_methods():
return [x['name'] for x in get_public_methods()]
@exported('pastes.newPaste')
def pastes_new_paste(language, code, parent_id=None,
filename='', mimetype='', private=False):
"""Create a new paste. Return the new ID.
`language` can be None, in which case the language will be
guessed from `filename` and/or `mimetype`.
"""
if not language:
language = get_language_for(filename or '', mimetype or '')
parent = None
if parent_id:
parent = Paste.get(parent_id)
if parent is None:
raise ValueError('parent paste not found')
paste = Paste(code, language, parent, private=private)
session.flush()
return paste.identifier
@exported('pastes.getPaste')
def pastes_get_paste(paste_id):
"""Get all known information about a paste by a given paste id.
Return a dictionary with these keys:
`paste_id`, `code`, `parsed_code`, `pub_date`, `language`,
`parent_id`, `url`.
"""
paste = Paste.get(paste_id)
if paste is not None:
return paste.to_xmlrpc_dict()
@exported('pastes.getDiff')
def pastes_get_diff(old_id, new_id):
"""Compare the two pastes and return an unified diff."""
old = Paste.get(old_id)
new = Paste.get(new_id)
if old is None or new is None:
raise ValueError('argument error, paste not found')
return old.compare_to(new)
@exported('pastes.getRecent')
def pastes_get_recent(amount=5):
"""Return information dict (see `getPaste`) about the last
`amount` pastes.
"""
amount = min(amount, 20)
return [x.to_xmlrpc_dict() for x in Paste.find_all().limit(amount)]
@exported('pastes.getLast')
def pastes_get_last():
"""Get information dict (see `getPaste`) for the most recent paste."""
rv = pastes_get_recent(1)
if rv:
return rv[0]
return {}
@exported('pastes.getLanguages')
def pastes_get_languages():
"""Get a list of supported languages."""
# this resolves lazy translations
return dict((key, unicode(value)) for
key, value in LANGUAGES.iteritems())
@exported('styles.getStyles')
def styles_get_styles():
"""Get a list of supported styles."""
return STYLES.items()
@exported('styles.getStylesheet')
def styles_get_stylesheet(name):
"""Return the stylesheet for a given style."""
return get_style(name)

View File

@ -10,7 +10,6 @@
"""
import sys
import re
import inspect
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
from werkzeug import Response
from lodgeit import local
@ -28,54 +27,12 @@ class XMLRPCRequestHandler(SimpleXMLRPCDispatcher):
SimpleXMLRPCDispatcher.__init__(self)
else:
SimpleXMLRPCDispatcher.__init__(self, True, 'utf-8')
self.funcs['system.listMethods'] = self.list_methods
def list_methods(self, request):
return [x['name'] for x in self.get_public_methods()]
def handle_request(self):
def dispatch(method_name, params):
return self.funcs[method_name](*params)
rv = self.funcs[method_name](*params)
if rv is None:
rv = False
return rv
response = self._marshaled_dispatch(local.request.data, dispatch)
return Response(response, mimetype='text/xml')
def get_public_methods(self):
if not hasattr(self, '_public_methods'):
# make sure all callbacks are registered
import lodgeit.controllers.xmlrpc
result = []
for name, f in self.funcs.iteritems():
if name.startswith('system.'):
continue
if f.hidden:
continue
args, varargs, varkw, defaults = inspect.getargspec(f)
if args and args[0] == 'request':
args = args[1:]
result.append({
'name': name,
'doc': inspect.getdoc(f) or '',
'signature': inspect.formatargspec(
args, varargs, varkw, defaults,
formatvalue=lambda o: '=' + repr(o)
)
})
result.sort(key=lambda x: x['name'].lower())
self._public_methods = result
return self._public_methods
xmlrpc = XMLRPCRequestHandler()
def exported(name, hidden=False):
"""Make a function external available via xmlrpc."""
def proxy(f):
xmlrpc.register_function(f, name)
f.hidden = hidden
return f
return proxy
def strip_control_chars(s):
return _strip_re.sub('', s or '')
return Response(_strip_re.sub('', response), mimetype='text/xml')

View File

@ -327,6 +327,7 @@ ul.paste_list pre {
padding: 4px;
font-family: 'Bitstream Vera Sans Mono', monospace;
font-size: 13px;
overflow: auto;
}
ul.paste_list li {

View File

@ -28,8 +28,9 @@ urlmap = Map([
Rule('/all/', endpoint='pastes/show_all'),
Rule('/all/<int:page>/', endpoint='pastes/show_all'),
# xmlrpc
# xmlrpc and json
Rule('/xmlrpc/', endpoint='xmlrpc/handle_request'),
Rule('/json/', endpoint='json/handle_request'),
# static pages
Rule('/about/', endpoint='static/about'),

View File

@ -0,0 +1,60 @@
{% extends "help/layout.html" %}
{% block help_body %}
<h3>{% trans %}Using the LodegeIt API{% endtrans %}</h3>
<p>{% trans %}
LodgeIt supports currently two APIs: XMLRPC and good old JSON.
{% endtrans %}</p>
<p>{{ _('API URLs:') }}</p>
<ul>
<li><strong>XMLRPC:</strong> <tt>{{ pastebin_url }}xmlrpc/</tt></li>
<li><strong>JSON:</strong> <tt>{{ pastebin_url }}json/</tt></li>
</ul>
<p><small>{{ _('Note the trailing slash in both URLs!') }}</small></p>
<h3>{% trans %}XMLRPC Quickstart{% endtrans %}</h3>
<p>{% trans %}
You can connect to the XMLRPC interface with any XMLRPC library. If you're
using Python the following piece of code connects you to the XMLRPC service:{% endtrans %}
</p>
<pre class="sample">{% filter escape %}
>>> from xmlrpclib import ServerProxy
>>> s = ServerProxy('{{ pastebin_url|escape }}xmlrpc/')
{% endfilter %}</pre>
<p>
{% trans %}For example if you want to fetch one paste from the server you can do this:{% endtrans %}
</p>
<pre class="sample">{% filter escape %}
>>> paste = s.pastes.getPaste(23)
>>> print paste['code']
'{{ "{% if users %}" }}\n...'
{% endfilter %}</pre>
<h3>{% trans %}JSON Quickstart{% endtrans %}</h3>
<p>{% trans %}
Alternatively you can use the JSON API. Basically what you do is sending
the function arguments as a serialized JSON object or array to the JSON URL
from above with the method name as URL parameter. You can do this for
example by using the curl command line tool:
{% endtrans %}</p>
<pre class="sample">
$ curl -d '{"paste_id": 23}' -H 'Content-Type: application/json'
'http://localhost:5000/json/?method=pastes.getPaste'
{
"data": {
"code": '{{ '{% if users %}' }}\n...',
"parsed_code": ...,
"language": 'html+django',
"url": "/show/23/",
"parent_id": null,
"paste_id": 23
},
"error": null
}</pre>
<h3>{% trans %}Methods{% endtrans %}</h3>
<p>{% trans %}For a list of all supported methods see the list below.{% endtrans %}</p>
<ul class="xmlrpc-method-list">
{% for method in xmlrpc_methods %}
<li class="{{ loop.cycle('even', 'odd') }}"><p class="signature"><strong>{{
method.name|escape }}</strong><em>{{ method.signature|escape }}</em></p>
<p class="docstring">{{ method.doc|e }}</p></li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -1,34 +0,0 @@
{% extends "help/layout.html" %}
{% block help_body %}
<h3>{% trans %}Using the XMLRPC Interface{% endtrans %}</h3>
<p>{% trans xmlrpc_url=xmlrpc_url|escape %}
The XMLRPC Interface is available at <tt>{{ xmlrpc_url }}</tt>.
(Note the trailing slash!){% endtrans %}
</p>
<h3>{% trans %}Quickstart{% endtrans %}</h3>
<p>{% trans %}
You can connect to the XMLRPC interface with any XMLRPC library. If you're
using Python the following piece of code connects you to the XMLRPC service:{% endtrans %}
</p>
<pre class="sample">{% filter escape %}
>>> from xmlrpclib import ServerProxy
>>> s = ServerProxy('{{ xmlrpc_url|escape }}')
{% endfilter %}</pre>
<p>
{% trans %}For example if you want to fetch one paste from the server you can do this:{% endtrans %}
</p>
<pre class="sample">{% filter escape %}
>>> paste = s.pastes.getPaste(23)
>>> print paste['code']
'{{ "{% if users %}" }}\n...'
{% endfilter %}</pre>
<p>{% trans %}For a list of all supported methods see the list below.{% endtrans %}</p>
<h3>{% trans %}Methods{% endtrans %}</h3>
<ul class="xmlrpc-method-list">
{% for method in xmlrpc_methods %}
<li class="{{ loop.cycle('even', 'odd') }}"><p class="signature"><strong>{{
method.name|escape }}</strong><em>{{ method.signature|escape }}</em></p>
<p class="docstring">{{ method.doc|e }}</p></li>
{% endfor %}
</ul>
{% endblock %}

16
lodgeit/views/json.html Normal file
View File

@ -0,0 +1,16 @@
{% extends "layout.html" %}
{% set page_title = _('JSON') %}
{% set active_page = 'about' %}
{% block body %}
<div class="text">
<h3>{% trans %}JSON Entrypoint{% endtrans %}</h3>
<p>{% trans %}
This is the entrypoint for the JSON API. If you're interested
in using it head over to the <a href="/help/api/">API documentation</a>.
{%- endtrans %}
</p>
<p>{% trans %}
Alternatively you can also use the <a href="/xmlrpc/">XMLRPC</a> service.
{% endtrans %}</p>
</div>
{% endblock %}

View File

@ -6,8 +6,11 @@
<h3>{% trans %}XMLRPC Entrypoint{% endtrans %}</h3>
<p>{% trans %}
This is the entrypoint for the XMLRPC system. If you're interested
in using it head over to the <a href="/help/xmlrpc/">XMLRPC documentation</a>.
in using it head over to the <a href="/help/api/">API documentation</a>.
{%- endtrans %}
</p>
<p>{% trans %}
Alternatively you can also use the <a href="/json/">JSON</a> API.
{% endtrans %}</p>
</div>
{% endblock %}