Skip to content

Commit

Permalink
Fix func annotations by signarure (#509)
Browse files Browse the repository at this point in the history
  • Loading branch information
nycholas authored Jun 21, 2024
1 parent 2d13a9d commit 9f29158
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 11 deletions.
15 changes: 15 additions & 0 deletions examples/wba/minimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,20 @@ def fails(_string: Optional[str]) -> NoReturn:
raise ValueError('example of fail')


@jsonrpc.method('App.notValidate', validate=False)
def not_validate(s='Oops!'): # noqa: ANN001,ANN202,ANN201
return f'Not validate: {s}'


@jsonrpc.method('App.mixinNotValidate', validate=False)
def mixin_not_validate(s, t: int, u, v: str, x, z): # noqa: ANN001,ANN202,ANN201
return f'Not validate: {s} {t} {u} {v} {x} {z}'


@jsonrpc.method('App.mixinNotValidateReturn', validate=False)
def mixin_not_validate_with_no_return(_s, _t: int, _u, _v: str, _x, _z): # noqa: ANN001,ANN202,ANN201
pass


if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ backend-path = ["src/buildtools"]

[project]
name = "Flask-JSONRPC"
version = "3.0.1"
version = "4.0.0a0"
description = "Adds JSONRPC support to Flask."
readme = {file = "README.md", content-type = "text/markdown"}
license = {file = "LICENSE"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,12 @@
}

if (param.type === 'Object') {
return eval('('+ param.value + ')');
try {
return eval('('+ param.value + ')');
} catch (e) {
console.error('Failed to evaluate the object:', param.value);
return param.value;
}
} else if (param.type === 'Number') {
if (param.value.indexOf('.') !== -1) {
return parseFloat(param.value);
Expand Down
17 changes: 16 additions & 1 deletion src/flask_jsonrpc/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import typing as t
from inspect import ismethod, signature, isfunction
from inspect import _empty, ismethod, signature, isfunction
from collections import OrderedDict

from typeguard import typechecked

Expand Down Expand Up @@ -76,6 +77,18 @@ def _get_function(self: Self, fn: t.Callable[..., t.Any]) -> t.Callable[..., t.A
return fn.__func__ # pytype: disable=attribute-error,bad-return-type
raise ValueError('the view function must be either a function or a method')

def _get_type_hints_by_signature(
self: Self, fn: t.Callable[..., t.Any], fn_annotations: t.Dict[str, t.Any]
) -> t.Dict[str, t.Any]:
sig = signature(fn)
parameters = OrderedDict()
for name in sig.parameters:
parameters[name] = fn_annotations.get(name, t.Any)
parameters['return'] = fn_annotations.get(
'return', t.Any if sig.return_annotation is _empty else sig.return_annotation
)
return parameters

def get_jsonrpc_site(self: Self) -> 'JSONRPCSite':
raise NotImplementedError('.get_jsonrpc_site must be overridden')

Expand All @@ -88,6 +101,8 @@ def register_view_function(
fn = self._get_function(view_func)
fn_options = self._method_options(options)
fn_annotations = t.get_type_hints(fn)
if not fn_options['validate']:
fn_annotations = self._get_type_hints_by_signature(fn, fn_annotations)
method_name = name if name else getattr(fn, '__name__', '<noname>')
view_func_wrapped = typechecked(view_func) if fn_options['validate'] else view_func
setattr(view_func_wrapped, 'jsonrpc_method_name', method_name) # noqa: B010
Expand Down
43 changes: 41 additions & 2 deletions tests/integration/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,31 @@ def test_not_validate_method(self: Self) -> None:
self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Not validate: OK'}, rv.json())
self.assertEqual(200, rv.status_code)

def test_mixin_not_validate_method(self: Self) -> None:
rv = self.requests.post(
API_URL,
json={
'id': 1,
'jsonrpc': '2.0',
'method': 'jsonrpc.mixin_not_validate',
'params': [':)', 1, 3.2, ':D', [1, 2, 3], {1: 1}],
},
)
self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Not validate: :) 1 3.2 :D [1, 2, 3] {1: 1}'}, rv.json())
self.assertEqual(200, rv.status_code)

rv = self.requests.post(
API_URL,
json={
'id': 1,
'jsonrpc': '2.0',
'method': 'jsonrpc.mixin_not_validate',
'params': {'s': ':)', 't': 1, 'u': 3.2, 'v': ':D', 'x': [1, 2, 3], 'z': {1: 1}},
},
)
self.assertEqual({'id': 1, 'jsonrpc': '2.0', 'result': 'Not validate: :) 1 3.2 :D [1, 2, 3] {1: 1}'}, rv.json())
self.assertEqual(200, rv.status_code)

def test_no_return_method(self: Self) -> None:
rv = self.requests.post(API_URL, json={'id': 1, 'jsonrpc': '2.0', 'method': 'jsonrpc.noReturn', 'params': []})
self.assertEqual(
Expand Down Expand Up @@ -668,8 +693,22 @@ def test_system_describe(self: Self) -> None:
'jsonrpc.not_validate': {
'type': 'method',
'options': {'notification': True, 'validate': False},
'params': [],
'returns': {'type': 'Null'},
'params': [{'name': 's', 'nullable': False, 'required': False, 'type': 'Object'}],
'returns': {'type': 'Object'},
'description': None,
},
'jsonrpc.mixin_not_validate': {
'type': 'method',
'options': {'notification': True, 'validate': False},
'params': [
{'name': 's', 'type': 'Object', 'required': False, 'nullable': False},
{'name': 't', 'type': 'Number', 'required': False, 'nullable': False},
{'name': 'u', 'type': 'Object', 'required': False, 'nullable': False},
{'name': 'v', 'type': 'String', 'required': False, 'nullable': False},
{'name': 'x', 'type': 'Object', 'required': False, 'nullable': False},
{'name': 'z', 'type': 'Object', 'required': False, 'nullable': False},
],
'returns': {'type': 'Object'},
'description': None,
},
'jsonrpc.noReturn': {
Expand Down
5 changes: 5 additions & 0 deletions tests/test_apps/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ def return_status_code_and_headers(s: str) -> t.Tuple[str, int, t.Dict[str, t.An
def not_validate(s='Oops!'): # noqa: ANN001,ANN202
return f'Not validate: {s}'

# pylint: disable=W0612
@jsonrpc.method('jsonrpc.mixin_not_validate', validate=False)
def mixin_not_validate(s, t: int, u, v: str, x, z): # noqa: ANN001,ANN202
return f'Not validate: {s} {t} {u} {v} {x} {z}'

@jsonrpc.method('jsonrpc.noReturn')
def no_return(_string: t.Optional[str] = None) -> t.NoReturn:
raise ValueError('no return')
Expand Down
6 changes: 6 additions & 0 deletions tests/test_apps/async_app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ async def not_validate(s='Oops!'): # noqa: ANN001,ANN202
await asyncio.sleep(0)
return f'Not validate: {s}'

# pylint: disable=W0612
@jsonrpc.method('jsonrpc.mixin_not_validate', validate=False)
async def mixin_not_validate(s, t: int, u, v: str, x, z): # noqa: ANN001,ANN202
await asyncio.sleep(0)
return f'Not validate: {s} {t} {u} {v} {x} {z}'

@jsonrpc.method('jsonrpc.noReturn')
async def no_return(_string: t.Optional[str] = None) -> t.NoReturn:
await asyncio.sleep(0)
Expand Down
43 changes: 41 additions & 2 deletions tests/unit/contrib/test_browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ def fn2(s: str) -> str:
def fn3(s: str) -> str:
return f'Foo {s}'

# pylint: disable=W0612
@jsonrpc.method('app.fn4', validate=False)
def fn4(s, t: int, u, v: str, x, z): # noqa: ANN001,ANN202
return f'Not validate: {s} {t} {u} {v} {x} {z}'

with app.test_client() as client:
rv = client.post('/api', json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn1', 'params': [1]})
assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': 'Foo 1'}
Expand Down Expand Up @@ -79,6 +84,25 @@ def fn3(s: str) -> str:
}
assert rv.status_code == 400

rv = client.post(
'/api',
json={'id': 1, 'jsonrpc': '2.0', 'method': 'app.fn4', 'params': [':)', 1, 3.2, ':D', [1, 2, 3], {1: 1}]},
)
assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': "Not validate: :) 1 3.2 :D [1, 2, 3] {'1': 1}"}
assert rv.status_code == 200

rv = client.post(
'/api',
json={
'id': 1,
'jsonrpc': '2.0',
'method': 'app.fn4',
'params': {'s': ':)', 't': 1, 'u': 3.2, 'v': ':D', 'x': [1, 2, 3], 'z': {1: 1}},
},
)
assert rv.json == {'id': 1, 'jsonrpc': '2.0', 'result': "Not validate: :) 1 3.2 :D [1, 2, 3] {'1': 1}"}
assert rv.status_code == 200

rv = client.get('/api/browse')
assert rv.status_code == 308

Expand All @@ -94,8 +118,8 @@ def fn3(s: str) -> str:
'name': 'app.fn1',
'type': 'method',
'options': {'notification': True, 'validate': False},
'params': [],
'returns': {'type': 'Null'},
'params': [{'name': 's', 'type': 'Object', 'required': False, 'nullable': False}],
'returns': {'type': 'Object'},
'description': 'Function app.fn1',
},
{
Expand All @@ -114,6 +138,21 @@ def fn3(s: str) -> str:
'returns': {'type': 'String'},
'description': None,
},
{
'name': 'app.fn4',
'type': 'method',
'options': {'notification': True, 'validate': False},
'params': [
{'name': 's', 'type': 'Object', 'required': False, 'nullable': False},
{'name': 't', 'type': 'Number', 'required': False, 'nullable': False},
{'name': 'u', 'type': 'Object', 'required': False, 'nullable': False},
{'name': 'v', 'type': 'String', 'required': False, 'nullable': False},
{'name': 'x', 'type': 'Object', 'required': False, 'nullable': False},
{'name': 'z', 'type': 'Object', 'required': False, 'nullable': False},
],
'returns': {'type': 'Object'},
'description': None,
},
]
}
assert rv.status_code == 200
Expand Down
18 changes: 16 additions & 2 deletions tests/unit/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,8 +593,22 @@ def test_app_system_describe(async_client: 'FlaskClient') -> None:
'jsonrpc.not_validate': {
'type': 'method',
'options': {'notification': True, 'validate': False},
'params': [],
'returns': {'type': 'Null'},
'params': [{'name': 's', 'nullable': False, 'required': False, 'type': 'Object'}],
'returns': {'type': 'Object'},
'description': None,
},
'jsonrpc.mixin_not_validate': {
'type': 'method',
'options': {'notification': True, 'validate': False},
'params': [
{'name': 's', 'type': 'Object', 'required': False, 'nullable': False},
{'name': 't', 'type': 'Number', 'required': False, 'nullable': False},
{'name': 'u', 'type': 'Object', 'required': False, 'nullable': False},
{'name': 'v', 'type': 'String', 'required': False, 'nullable': False},
{'name': 'x', 'type': 'Object', 'required': False, 'nullable': False},
{'name': 'z', 'type': 'Object', 'required': False, 'nullable': False},
],
'returns': {'type': 'Object'},
'description': None,
},
'jsonrpc.noReturn': {
Expand Down
18 changes: 16 additions & 2 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,8 +581,22 @@ def test_app_system_describe(client: 'FlaskClient') -> None:
'jsonrpc.not_validate': {
'type': 'method',
'options': {'notification': True, 'validate': False},
'params': [],
'returns': {'type': 'Null'},
'params': [{'name': 's', 'nullable': False, 'required': False, 'type': 'Object'}],
'returns': {'type': 'Object'},
'description': None,
},
'jsonrpc.mixin_not_validate': {
'type': 'method',
'options': {'notification': True, 'validate': False},
'params': [
{'name': 's', 'type': 'Object', 'required': False, 'nullable': False},
{'name': 't', 'type': 'Number', 'required': False, 'nullable': False},
{'name': 'u', 'type': 'Object', 'required': False, 'nullable': False},
{'name': 'v', 'type': 'String', 'required': False, 'nullable': False},
{'name': 'x', 'type': 'Object', 'required': False, 'nullable': False},
{'name': 'z', 'type': 'Object', 'required': False, 'nullable': False},
],
'returns': {'type': 'Object'},
'description': None,
},
'jsonrpc.noReturn': {
Expand Down

0 comments on commit 9f29158

Please sign in to comment.