Skip to content

Commit b2140bd

Browse files
committed
Add basic struture - JSON-RPC
1 parent f5fb832 commit b2140bd

14 files changed

+757
-0
lines changed

AUTHORS

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# This is the official list of cenobit.es authors for copyright purposes.
2+
# This file is distinct from the CONTRIBUTORS files.
3+
# See the latter for an explanation.
4+
5+
# Names should be added to this file alphabetically as
6+
# Name or Organization <email address>
7+
# The email address is not required for organizations.
8+
9+
Cenobit Technologies, Inc. http://cenobit.es/
10+
Nycholas de Oliveira e Oliveira <[email protected]>

CONTRIBUTORS

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# This is the official list of people who can contribute
2+
# (and typically have contributed) code to the cenobit.es repository.
3+
# The AUTHORS file lists the copyright holders; this file
4+
# lists people. For example, cenobit.es employees are listed here
5+
# but not in AUTHORS, because cenobit.es holds the copyright.
6+
#
7+
# The submission process automatically checks to make sure
8+
# that people submitting code are listed in this file (by email address).
9+
#
10+
# Names should be added to this file alphabetically like so:
11+
# Name <email address>
12+
13+
Nycholas de Oliveira e Oliveira <[email protected]>

COPYING

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Copyright (c) 2012, Cenobit Technologies, Inc. http://cenobit.es/
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
* Redistributions of source code must retain the above copyright notice,
8+
this list of conditions and the following disclaimer.
9+
* Redistributions in binary form must reproduce the above copyright notice,
10+
this list of conditions and the following disclaimer in the documentation
11+
and/or other materials provided with the distribution.
12+
* Neither the name of the Cenobit Technologies, Inc. nor the names of
13+
its contributors may be used to endorse or promote products derived from
14+
this software without specific prior written permission.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
POSSIBILITY OF SUCH DAMAGE.

ChangeLog

Whitespace-only changes.

FAQ

Whitespace-only changes.

INSTALL

Whitespace-only changes.

THANKS

Whitespace-only changes.

TODO

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
3+
BYE!
4+
/\___/\ /
5+
( )
6+
( o )
7+
--_-_--

flask_jsonrpc/__init__.py

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright (c) 2012, Cenobit Technologies, Inc. http://cenobit.es/
3+
# All rights reserved.
4+
import re
5+
import StringIO
6+
from inspect import getargspec
7+
from functools import wraps
8+
9+
from collections import OrderedDict
10+
11+
from flask import request, jsonify
12+
13+
from flask_jsonrpc.site import jsonrpc_site
14+
from flask_jsonrpc.types import Object, Number, Boolean, String, Array, Nil, Any
15+
from flask_jsonrpc.exceptions import (Error, ParseError, InvalidRequestError,
16+
MethodNotFoundError, InvalidParamsError,
17+
ServerError, RequestPostError,
18+
InvalidCredentialsError, OtherError)
19+
20+
default_site = jsonrpc_site
21+
KWARG_RE = re.compile(
22+
r'\s*(?P<arg_name>[a-zA-Z0-9_]+)\s*=\s*(?P<arg_type>[a-zA-Z]+)\s*$')
23+
SIG_RE = re.compile(
24+
r'\s*(?P<method_name>[a-zA-Z0-9._]+)\s*(\((?P<args_sig>[^)].*)?\)'
25+
r'\s*(\->\s*(?P<return_sig>.*))?)?\s*$')
26+
27+
28+
class JSONRPCTypeCheckingUnavailable(Exception):
29+
pass
30+
31+
def _type_checking_available(sig='', validate=False):
32+
if not hasattr(type, '__eq__') and validate: # and False:
33+
raise JSONRPCTypeCheckingUnavailable(
34+
'Type checking is not available in your version of Python '
35+
'which is only available in Python 2.6 or later. Use Python 2.6 '
36+
'or later or disable type checking in %s' % sig)
37+
38+
def _validate_arg(value, expected):
39+
"""Returns whether or not ``value`` is the ``expected`` type.
40+
"""
41+
if type(value) == expected:
42+
return True
43+
return False
44+
45+
def _eval_arg_type(arg_type, T=Any, arg=None, sig=None):
46+
"""Returns a type from a snippit of python source. Should normally be
47+
something just like 'str' or 'Object'.
48+
49+
arg_type the source to be evaluated
50+
T the default type
51+
arg context of where this type was extracted
52+
sig context from where the arg was extracted
53+
54+
Returns a type or a Type
55+
"""
56+
try:
57+
T = eval(arg_type)
58+
except Exception, e:
59+
raise ValueError('The type of %s could not be evaluated in %s for %s: %s' %
60+
(arg_type, arg, sig, str(e)))
61+
else:
62+
if type(T) not in (type, Type):
63+
raise TypeError('%s is not a valid type in %s for %s' %
64+
(repr(T), arg, sig))
65+
return T
66+
67+
def _parse_sig(sig, arg_names, validate=False):
68+
"""Parses signatures into a ``OrderedDict`` of paramName => type.
69+
Numerically-indexed arguments that do not correspond to an argument
70+
name in python (ie: it takes a variable number of arguments) will be
71+
keyed as the stringified version of it's index.
72+
73+
sig the signature to be parsed
74+
arg_names a list of argument names extracted from python source
75+
76+
Returns a tuple of (method name, types dict, return type)
77+
"""
78+
d = SIG_RE.match(sig)
79+
if not d:
80+
raise ValueError('Invalid method signature %s' % sig)
81+
d = d.groupdict()
82+
ret = [(n, Any) for n in arg_names]
83+
if 'args_sig' in d and type(d['args_sig']) is str and d['args_sig'].strip():
84+
for i, arg in enumerate(d['args_sig'].strip().split(',')):
85+
_type_checking_available(sig, validate)
86+
if '=' in arg:
87+
if not type(ret) is OrderedDict:
88+
ret = OrderedDict(ret)
89+
dk = KWARG_RE.match(arg)
90+
if not dk:
91+
raise ValueError('Could not parse arg type %s in %s' % (arg, sig))
92+
dk = dk.groupdict()
93+
if not sum([(k in dk and type(dk[k]) is str and bool(dk[k].strip()))
94+
for k in ('arg_name', 'arg_type')]):
95+
raise ValueError('Invalid kwarg value %s in %s' % (arg, sig))
96+
ret[dk['arg_name']] = _eval_arg_type(dk['arg_type'], None, arg, sig)
97+
else:
98+
if type(ret) is OrderedDict:
99+
raise ValueError('Positional arguments must occur '
100+
'before keyword arguments in %s' % sig)
101+
if len(ret) < i + 1:
102+
ret.append((str(i), _eval_arg_type(arg, None, arg, sig)))
103+
else:
104+
ret[i] = (ret[i][0], _eval_arg_type(arg, None, arg, sig))
105+
if not type(ret) is OrderedDict:
106+
ret = OrderedDict(ret)
107+
return (d['method_name'],
108+
ret,
109+
(_eval_arg_type(d['return_sig'], Any, 'return', sig)
110+
if d['return_sig'] else Any))
111+
112+
def _inject_args(sig, types):
113+
"""A function to inject arguments manually into a method signature before
114+
it's been parsed. If using keyword arguments use 'kw=type' instead in
115+
the types array.
116+
117+
sig the string signature
118+
types a list of types to be inserted
119+
120+
Returns the altered signature.
121+
"""
122+
if '(' in sig:
123+
parts = sig.split('(')
124+
sig = '%s(%s%s%s' % (
125+
parts[0], ', '.join(types),
126+
(', ' if parts[1].index(')') > 0 else ''), parts[1]
127+
)
128+
else:
129+
sig = '%s(%s)' % (sig, ', '.join(types))
130+
return sig
131+
132+
def _site_api(method=''):
133+
response_dict = default_site.dispatch(request, method)
134+
return jsonify(response_dict)
135+
136+
137+
class JSONRPC(object):
138+
139+
def __init__(self, app=None, rule='/api', site=default_site):
140+
self.rule = rule
141+
self.site = site
142+
if app is not None:
143+
self.app = app
144+
self.init_app(self.app)
145+
else:
146+
self.app = None
147+
148+
def init_app(self, app):
149+
app.add_url_rule(self.rule + '/<method>', '', _site_api, methods=['POST'])
150+
151+
def method(self, name, authenticated=False, safe=False, validate=False, **options):
152+
def decorator(f):
153+
arg_names = getargspec(f)[0][1:]
154+
X = {'name': name, 'arg_names': arg_names}
155+
if authenticated:
156+
raise Exception('Not implement')
157+
else:
158+
_f = f
159+
method, arg_types, return_type = _parse_sig(X['name'], X['arg_names'], validate)
160+
_f.json_args = X['arg_names']
161+
_f.json_arg_types = arg_types
162+
_f.json_return_type = return_type
163+
_f.json_method = method
164+
_f.json_safe = safe
165+
_f.json_sig = X['name']
166+
_f.json_validate = validate
167+
self.site.register(method, _f)
168+
return _f
169+
return decorator

flask_jsonrpc/exceptions.py

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright (c) 2012, Cenobit Technologies, Inc. http://cenobit.es/
3+
# All rights reserved.
4+
5+
try:
6+
from flaskext.babel import gettext as _
7+
_("You're lazy...") # this function lazy-loads settings
8+
except (ImportError, NameError):
9+
_ = lambda t, *a, **k: t
10+
11+
class Error(Exception):
12+
"""Error class based on the JSON-RPC 2.0 specs
13+
http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal
14+
15+
code - number
16+
message - string
17+
data - object
18+
19+
status - number from http://groups.google.com/group/json-rpc/web/json-rpc-over-http JSON-RPC over HTTP Errors section
20+
"""
21+
22+
code = 0
23+
message = None
24+
data = None
25+
status = 200
26+
27+
def __init__(self, message=None):
28+
"""Setup the Exception and overwrite the default message
29+
"""
30+
if message is not None:
31+
self.message = message
32+
33+
@property
34+
def json_rpc_format(self):
35+
"""Return the Exception data in a format for JSON-RPC
36+
"""
37+
38+
error = {
39+
'name': str(self.__class__.__name__),
40+
'code': self.code,
41+
'message': "%s: %s" % (str(self.__class__.__name__), str(self.message)),
42+
'data': self.data
43+
}
44+
45+
# TODO: Add debug env flask
46+
# from flask import current_app
47+
48+
# if settings.DEBUG:
49+
# import sys, traceback
50+
# error['stack'] = traceback.format_exc()
51+
# error['executable'] = sys.executable
52+
53+
return error
54+
55+
# Exceptions
56+
# from http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal
57+
58+
# The error-codes -32768 .. -32000 (inclusive) are reserved for pre-defined errors.
59+
# Any error-code within this range not defined explicitly below is reserved for future use
60+
61+
class ParseError(Error):
62+
"""Invalid JSON. An error occurred on the server while parsing the JSON text.
63+
"""
64+
code = -32700
65+
message = _('Parse error.')
66+
67+
class InvalidRequestError(Error):
68+
"""The received JSON is not a valid JSON-RPC Request.
69+
"""
70+
code = -32600
71+
message = _('Invalid Request.')
72+
73+
class MethodNotFoundError(Error):
74+
"""The requested remote-procedure does not exist / is not available.
75+
"""
76+
code = -32601
77+
message = _('Method not found.')
78+
79+
class InvalidParamsError(Error):
80+
"""Invalid method parameters.
81+
"""
82+
code = -32602
83+
message = _('Invalid params.')
84+
85+
class ServerError(Error):
86+
"""Internal JSON-RPC error.
87+
"""
88+
code = -32603
89+
message = _('Internal error.')
90+
91+
# -32099..-32000 Server error.
92+
# Reserved for implementation-defined server-errors.
93+
94+
# The remainder of the space is available for application defined errors.
95+
96+
class RequestPostError(InvalidRequestError):
97+
"""JSON-RPC requests must be POST
98+
"""
99+
message = _('JSON-RPC requests must be POST')
100+
101+
class InvalidCredentialsError(Error):
102+
"""Invalid login credentials
103+
"""
104+
code = 401
105+
message = _('Invalid login credentials')
106+
status = 401
107+
108+
class OtherError(Error):
109+
"""catchall error
110+
"""
111+
code = 500
112+
message = _('Error missed by other execeptions')
113+
status = 200

0 commit comments

Comments
 (0)