Permalink
Browse files

Add basic struture - JSON-RPC

  • Loading branch information...
1 parent f5fb832 commit b2140bd00f05564e76f53e003eeddc3c4e0209ba @nycholas nycholas committed Dec 14, 2012
Showing with 757 additions and 0 deletions.
  1. +10 −0 AUTHORS
  2. +13 −0 CONTRIBUTORS
  3. +26 −0 COPYING
  4. 0 ChangeLog
  5. 0 FAQ
  6. 0 INSTALL
  7. 0 THANKS
  8. +7 −0 TODO
  9. +169 −0 flask_jsonrpc/__init__.py
  10. +113 −0 flask_jsonrpc/exceptions.py
  11. +284 −0 flask_jsonrpc/site.py
  12. +74 −0 flask_jsonrpc/types.py
  13. +17 −0 run_test.py
  14. +44 −0 setup.py
View
@@ -0,0 +1,10 @@
+# This is the official list of cenobit.es authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+
+# Names should be added to this file alphabetically as
+# Name or Organization <email address>
+# The email address is not required for organizations.
+
+Cenobit Technologies, Inc. http://cenobit.es/
+Nycholas de Oliveira e Oliveira <nycholas@gmail.com>
View
@@ -0,0 +1,13 @@
+# This is the official list of people who can contribute
+# (and typically have contributed) code to the cenobit.es repository.
+# The AUTHORS file lists the copyright holders; this file
+# lists people. For example, cenobit.es employees are listed here
+# but not in AUTHORS, because cenobit.es holds the copyright.
+#
+# The submission process automatically checks to make sure
+# that people submitting code are listed in this file (by email address).
+#
+# Names should be added to this file alphabetically like so:
+# Name <email address>
+
+Nycholas de Oliveira e Oliveira <nycholas@gmail.com>
View
@@ -0,0 +1,26 @@
+Copyright (c) 2012, Cenobit Technologies, Inc. http://cenobit.es/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of the Cenobit Technologies, Inc. nor the names of
+ its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
View
No changes.
View
No changes.
View
No changes.
View
No changes.
View
@@ -0,0 +1,7 @@
+
+
+ BYE!
+ /\___/\ /
+ ( )
+ ( o )
+ --_-_--
@@ -0,0 +1,169 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2012, Cenobit Technologies, Inc. http://cenobit.es/
+# All rights reserved.
+import re
+import StringIO
+from inspect import getargspec
+from functools import wraps
+
+from collections import OrderedDict
+
+from flask import request, jsonify
+
+from flask_jsonrpc.site import jsonrpc_site
+from flask_jsonrpc.types import Object, Number, Boolean, String, Array, Nil, Any
+from flask_jsonrpc.exceptions import (Error, ParseError, InvalidRequestError,
+ MethodNotFoundError, InvalidParamsError,
+ ServerError, RequestPostError,
+ InvalidCredentialsError, OtherError)
+
+default_site = jsonrpc_site
+KWARG_RE = re.compile(
+ r'\s*(?P<arg_name>[a-zA-Z0-9_]+)\s*=\s*(?P<arg_type>[a-zA-Z]+)\s*$')
+SIG_RE = re.compile(
+ r'\s*(?P<method_name>[a-zA-Z0-9._]+)\s*(\((?P<args_sig>[^)].*)?\)'
+ r'\s*(\->\s*(?P<return_sig>.*))?)?\s*$')
+
+
+class JSONRPCTypeCheckingUnavailable(Exception):
+ pass
+
+def _type_checking_available(sig='', validate=False):
+ if not hasattr(type, '__eq__') and validate: # and False:
+ raise JSONRPCTypeCheckingUnavailable(
+ 'Type checking is not available in your version of Python '
+ 'which is only available in Python 2.6 or later. Use Python 2.6 '
+ 'or later or disable type checking in %s' % sig)
+
+def _validate_arg(value, expected):
+ """Returns whether or not ``value`` is the ``expected`` type.
+ """
+ if type(value) == expected:
+ return True
+ return False
+
+def _eval_arg_type(arg_type, T=Any, arg=None, sig=None):
+ """Returns a type from a snippit of python source. Should normally be
+ something just like 'str' or 'Object'.
+
+ arg_type the source to be evaluated
+ T the default type
+ arg context of where this type was extracted
+ sig context from where the arg was extracted
+
+ Returns a type or a Type
+ """
+ try:
+ T = eval(arg_type)
+ except Exception, e:
+ raise ValueError('The type of %s could not be evaluated in %s for %s: %s' %
+ (arg_type, arg, sig, str(e)))
+ else:
+ if type(T) not in (type, Type):
+ raise TypeError('%s is not a valid type in %s for %s' %
+ (repr(T), arg, sig))
+ return T
+
+def _parse_sig(sig, arg_names, validate=False):
+ """Parses signatures into a ``OrderedDict`` of paramName => type.
+ Numerically-indexed arguments that do not correspond to an argument
+ name in python (ie: it takes a variable number of arguments) will be
+ keyed as the stringified version of it's index.
+
+ sig the signature to be parsed
+ arg_names a list of argument names extracted from python source
+
+ Returns a tuple of (method name, types dict, return type)
+ """
+ d = SIG_RE.match(sig)
+ if not d:
+ raise ValueError('Invalid method signature %s' % sig)
+ d = d.groupdict()
+ ret = [(n, Any) for n in arg_names]
+ if 'args_sig' in d and type(d['args_sig']) is str and d['args_sig'].strip():
+ for i, arg in enumerate(d['args_sig'].strip().split(',')):
+ _type_checking_available(sig, validate)
+ if '=' in arg:
+ if not type(ret) is OrderedDict:
+ ret = OrderedDict(ret)
+ dk = KWARG_RE.match(arg)
+ if not dk:
+ raise ValueError('Could not parse arg type %s in %s' % (arg, sig))
+ dk = dk.groupdict()
+ if not sum([(k in dk and type(dk[k]) is str and bool(dk[k].strip()))
+ for k in ('arg_name', 'arg_type')]):
+ raise ValueError('Invalid kwarg value %s in %s' % (arg, sig))
+ ret[dk['arg_name']] = _eval_arg_type(dk['arg_type'], None, arg, sig)
+ else:
+ if type(ret) is OrderedDict:
+ raise ValueError('Positional arguments must occur '
+ 'before keyword arguments in %s' % sig)
+ if len(ret) < i + 1:
+ ret.append((str(i), _eval_arg_type(arg, None, arg, sig)))
+ else:
+ ret[i] = (ret[i][0], _eval_arg_type(arg, None, arg, sig))
+ if not type(ret) is OrderedDict:
+ ret = OrderedDict(ret)
+ return (d['method_name'],
+ ret,
+ (_eval_arg_type(d['return_sig'], Any, 'return', sig)
+ if d['return_sig'] else Any))
+
+def _inject_args(sig, types):
+ """A function to inject arguments manually into a method signature before
+ it's been parsed. If using keyword arguments use 'kw=type' instead in
+ the types array.
+
+ sig the string signature
+ types a list of types to be inserted
+
+ Returns the altered signature.
+ """
+ if '(' in sig:
+ parts = sig.split('(')
+ sig = '%s(%s%s%s' % (
+ parts[0], ', '.join(types),
+ (', ' if parts[1].index(')') > 0 else ''), parts[1]
+ )
+ else:
+ sig = '%s(%s)' % (sig, ', '.join(types))
+ return sig
+
+def _site_api(method=''):
+ response_dict = default_site.dispatch(request, method)
+ return jsonify(response_dict)
+
+
+class JSONRPC(object):
+
+ def __init__(self, app=None, rule='/api', site=default_site):
+ self.rule = rule
+ self.site = site
+ if app is not None:
+ self.app = app
+ self.init_app(self.app)
+ else:
+ self.app = None
+
+ def init_app(self, app):
+ app.add_url_rule(self.rule + '/<method>', '', _site_api, methods=['POST'])
+
+ def method(self, name, authenticated=False, safe=False, validate=False, **options):
+ def decorator(f):
+ arg_names = getargspec(f)[0][1:]
+ X = {'name': name, 'arg_names': arg_names}
+ if authenticated:
+ raise Exception('Not implement')
+ else:
+ _f = f
+ method, arg_types, return_type = _parse_sig(X['name'], X['arg_names'], validate)
+ _f.json_args = X['arg_names']
+ _f.json_arg_types = arg_types
+ _f.json_return_type = return_type
+ _f.json_method = method
+ _f.json_safe = safe
+ _f.json_sig = X['name']
+ _f.json_validate = validate
+ self.site.register(method, _f)
+ return _f
+ return decorator
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2012, Cenobit Technologies, Inc. http://cenobit.es/
+# All rights reserved.
+
+try:
+ from flaskext.babel import gettext as _
+ _("You're lazy...") # this function lazy-loads settings
+except (ImportError, NameError):
+ _ = lambda t, *a, **k: t
+
+class Error(Exception):
+ """Error class based on the JSON-RPC 2.0 specs
+ http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal
+
+ code - number
+ message - string
+ data - object
+
+ status - number from http://groups.google.com/group/json-rpc/web/json-rpc-over-http JSON-RPC over HTTP Errors section
+ """
+
+ code = 0
+ message = None
+ data = None
+ status = 200
+
+ def __init__(self, message=None):
+ """Setup the Exception and overwrite the default message
+ """
+ if message is not None:
+ self.message = message
+
+ @property
+ def json_rpc_format(self):
+ """Return the Exception data in a format for JSON-RPC
+ """
+
+ error = {
+ 'name': str(self.__class__.__name__),
+ 'code': self.code,
+ 'message': "%s: %s" % (str(self.__class__.__name__), str(self.message)),
+ 'data': self.data
+ }
+
+ # TODO: Add debug env flask
+ # from flask import current_app
+
+ # if settings.DEBUG:
+ # import sys, traceback
+ # error['stack'] = traceback.format_exc()
+ # error['executable'] = sys.executable
+
+ return error
+
+# Exceptions
+# from http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal
+
+# The error-codes -32768 .. -32000 (inclusive) are reserved for pre-defined errors.
+# Any error-code within this range not defined explicitly below is reserved for future use
+
+class ParseError(Error):
+ """Invalid JSON. An error occurred on the server while parsing the JSON text.
+ """
+ code = -32700
+ message = _('Parse error.')
+
+class InvalidRequestError(Error):
+ """The received JSON is not a valid JSON-RPC Request.
+ """
+ code = -32600
+ message = _('Invalid Request.')
+
+class MethodNotFoundError(Error):
+ """The requested remote-procedure does not exist / is not available.
+ """
+ code = -32601
+ message = _('Method not found.')
+
+class InvalidParamsError(Error):
+ """Invalid method parameters.
+ """
+ code = -32602
+ message = _('Invalid params.')
+
+class ServerError(Error):
+ """Internal JSON-RPC error.
+ """
+ code = -32603
+ message = _('Internal error.')
+
+# -32099..-32000 Server error.
+# Reserved for implementation-defined server-errors.
+
+# The remainder of the space is available for application defined errors.
+
+class RequestPostError(InvalidRequestError):
+ """JSON-RPC requests must be POST
+ """
+ message = _('JSON-RPC requests must be POST')
+
+class InvalidCredentialsError(Error):
+ """Invalid login credentials
+ """
+ code = 401
+ message = _('Invalid login credentials')
+ status = 401
+
+class OtherError(Error):
+ """catchall error
+ """
+ code = 500
+ message = _('Error missed by other execeptions')
+ status = 200
Oops, something went wrong. Retry.

0 comments on commit b2140bd

Please sign in to comment.