mirror of
https://github.com/dkmstr/openuds.git
synced 2025-03-20 06:50:23 +03:00
Refactor HelpDoc class to support function-based help documentation and improve return type handling
This commit is contained in:
parent
f9a0026d5d
commit
94249decfb
@ -88,21 +88,6 @@ class Dispatcher(View):
|
||||
# # Guess content type from content type header (post) or ".xxx" to method
|
||||
content_type: str = request.META.get('CONTENT_TYPE', 'application/json').split(';')[0]
|
||||
|
||||
# while path:
|
||||
# clean_path = path[0]
|
||||
# # Skip empty path elements, so /x/y == /x////y for example (due to some bugs detected on some clients)
|
||||
# if not clean_path:
|
||||
# path = path[1:]
|
||||
# continue
|
||||
|
||||
# if clean_path in service.children: # if we have a node for this path, walk down
|
||||
# service = service.children[clean_path]
|
||||
# full_path_lst.append(path[0]) # Add this path to full path
|
||||
# path = path[1:] # Remove first part of path
|
||||
# else:
|
||||
# break # If we don't have a node for this path, we are done
|
||||
|
||||
# full_path = '/'.join(full_path_lst)
|
||||
handler_node = Dispatcher.base_handler_node.find_path(path)
|
||||
if not handler_node:
|
||||
return http.HttpResponseNotFound('Service not found', content_type="text/plain")
|
||||
|
@ -72,7 +72,7 @@ class TypedResponse(abc.ABC):
|
||||
list: '<list>',
|
||||
typing.Any: '<any>',
|
||||
}
|
||||
|
||||
|
||||
def _as_help(obj: typing.Any) -> typing.Union[str, dict[str, typing.Any]]:
|
||||
if dataclasses.is_dataclass(obj):
|
||||
return {field.name: _as_help(field.type) for field in dataclasses.fields(obj)}
|
||||
@ -170,19 +170,19 @@ class HelpDoc:
|
||||
"""
|
||||
Help helper class
|
||||
"""
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ArgumentInfo:
|
||||
name: str
|
||||
type: str
|
||||
description: str
|
||||
|
||||
|
||||
path: str
|
||||
description: str
|
||||
arguments: list[ArgumentInfo] = dataclasses.field(default_factory=list)
|
||||
# Result is always a json ressponse, so we can describe it as a dict
|
||||
# Note that this dict can be nested
|
||||
returns: dict[str, typing.Any] = dataclasses.field(default_factory=dict)
|
||||
returns: typing.Any = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -218,15 +218,19 @@ class HelpDoc:
|
||||
"""
|
||||
self.description = ''
|
||||
self.arguments = []
|
||||
self.returns = {}
|
||||
self.returns = None
|
||||
|
||||
match = API_RE.search(help)
|
||||
if match:
|
||||
self.description = help[: match.start()].strip()
|
||||
|
||||
if annotations:
|
||||
if 'return' in annotations and issubclass(annotations['return'], TypedResponse):
|
||||
self.returns = annotations['return'].as_help()
|
||||
if 'return' in annotations:
|
||||
t = annotations['return']
|
||||
if isinstance(t, collections.abc.Iterable):
|
||||
pass
|
||||
# if issubclass(annotations['return'], TypedResponse):
|
||||
# self.returns = annotations['return'].as_help()
|
||||
|
||||
@staticmethod
|
||||
def from_typed_response(path: str, help: str, TR: type[TypedResponse]) -> 'HelpDoc':
|
||||
@ -239,6 +243,28 @@ class HelpDoc:
|
||||
returns=TR.as_help(),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def from_fnc(path: str, help: str, fnc: typing.Callable[..., typing.Any]) -> 'HelpDoc|None':
|
||||
"""
|
||||
Returns a HelpDoc from a function that returns a list of TypedResponses
|
||||
"""
|
||||
return_type: typing.Any = fnc.__annotations__.get('return')
|
||||
|
||||
if isinstance(return_type, TypedResponse):
|
||||
return HelpDoc.from_typed_response(path, help, typing.cast(type[TypedResponse], return_type))
|
||||
elif (
|
||||
isinstance(return_type, collections.abc.Iterable)
|
||||
and len(typing.cast(typing.Any, return_type).__args__) == 1
|
||||
and issubclass(typing.cast(typing.Any, return_type).__args__[0], TypedResponse)
|
||||
):
|
||||
hd = HelpDoc.from_typed_response(
|
||||
path, help, typing.cast(type[TypedResponse], typing.cast(typing.Any, return_type).__args__[0])
|
||||
)
|
||||
hd.returns = [hd.returns] # We need to return a list of returns
|
||||
return hd
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class HelpNode:
|
||||
|
@ -30,7 +30,10 @@
|
||||
Author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import dataclasses
|
||||
import collections.abc
|
||||
import logging
|
||||
|
||||
import typing
|
||||
from unittest import TestCase
|
||||
|
||||
from uds.core.types import rest
|
||||
@ -81,25 +84,27 @@ class TestHelpDoc(TestCase):
|
||||
self.assertEqual(h.description, 'help_text')
|
||||
self.assertEqual(h.arguments, arguments)
|
||||
self.assertEqual(h.returns, returns)
|
||||
|
||||
|
||||
|
||||
def test_help_doc_from_typed_response(self) -> None:
|
||||
@dataclasses.dataclass
|
||||
class TestResponse(rest.TypedResponse):
|
||||
name: str = 'test_name'
|
||||
age: int = 0
|
||||
money: float = 0.0
|
||||
|
||||
|
||||
h = rest.HelpDoc.from_typed_response('path', 'help', TestResponse)
|
||||
|
||||
|
||||
self.assertEqual(h.path, 'path')
|
||||
self.assertEqual(h.description, 'help')
|
||||
self.assertEqual(h.arguments, [])
|
||||
self.assertEqual(h.returns, {
|
||||
'name': '<string>',
|
||||
'age': '<integer>',
|
||||
'money': '<float>',
|
||||
})
|
||||
self.assertEqual(
|
||||
h.returns,
|
||||
{
|
||||
'name': '<string>',
|
||||
'age': '<integer>',
|
||||
'money': '<float>',
|
||||
},
|
||||
)
|
||||
|
||||
def test_help_doc_from_typed_response_nested_dataclass(self) -> None:
|
||||
@dataclasses.dataclass
|
||||
@ -107,26 +112,103 @@ class TestHelpDoc(TestCase):
|
||||
name: str = 'test_name'
|
||||
age: int = 0
|
||||
money: float = 0.0
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class TestResponse2(rest.TypedResponse):
|
||||
name: str
|
||||
age: int
|
||||
money: float
|
||||
nested: TestResponse
|
||||
|
||||
|
||||
h = rest.HelpDoc.from_typed_response('path', 'help', TestResponse2)
|
||||
|
||||
|
||||
self.assertEqual(h.path, 'path')
|
||||
self.assertEqual(h.description, 'help')
|
||||
self.assertEqual(h.arguments, [])
|
||||
self.assertEqual(h.returns, {
|
||||
'name': '<string>',
|
||||
'age': '<integer>',
|
||||
'money': '<float>',
|
||||
'nested': {
|
||||
self.assertEqual(
|
||||
h.returns,
|
||||
{
|
||||
'name': '<string>',
|
||||
'age': '<integer>',
|
||||
'money': '<float>',
|
||||
}
|
||||
})
|
||||
'nested': {
|
||||
'name': '<string>',
|
||||
'age': '<integer>',
|
||||
'money': '<float>',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def test_help_doc_from_fnc(self) -> None:
|
||||
@dataclasses.dataclass
|
||||
class TestResponse(rest.TypedResponse):
|
||||
name: str = 'test_name'
|
||||
age: int = 0
|
||||
money: float = 0.0
|
||||
|
||||
def testing_fnc() -> TestResponse:
|
||||
"""
|
||||
This is a test function
|
||||
"""
|
||||
return []
|
||||
|
||||
h = rest.HelpDoc.from_fnc('path', 'help', testing_fnc)
|
||||
|
||||
if h is None:
|
||||
self.fail('HelpDoc is None')
|
||||
|
||||
self.assertEqual(h.path, 'path')
|
||||
self.assertEqual(h.description, 'help')
|
||||
self.assertEqual(h.arguments, [])
|
||||
self.assertEqual(
|
||||
h.returns,
|
||||
{
|
||||
'name': '<string>',
|
||||
'age': '<integer>',
|
||||
'money': '<float>',
|
||||
},
|
||||
)
|
||||
|
||||
def test_help_doc_from_non_typed_response(self) -> None:
|
||||
def testing_fnc() -> dict[str, typing.Any]:
|
||||
"""
|
||||
This is a test function
|
||||
"""
|
||||
return {}
|
||||
|
||||
h = rest.HelpDoc.from_fnc('path', 'help', testing_fnc)
|
||||
|
||||
self.assertIsNone(h)
|
||||
|
||||
|
||||
def test_help_doc_from_fnc_list(self) -> None:
|
||||
@dataclasses.dataclass
|
||||
class TestResponse(rest.TypedResponse):
|
||||
name: str = 'test_name'
|
||||
age: int = 0
|
||||
money: float = 0.0
|
||||
|
||||
def testing_fnc() -> list[TestResponse]:
|
||||
"""
|
||||
This is a test function
|
||||
"""
|
||||
return []
|
||||
|
||||
h = rest.HelpDoc.from_fnc('path', 'help', testing_fnc)
|
||||
|
||||
if h is None:
|
||||
self.fail('HelpDoc is None')
|
||||
|
||||
self.assertEqual(h.path, 'path')
|
||||
self.assertEqual(h.description, 'help')
|
||||
self.assertEqual(h.arguments, [])
|
||||
self.assertEqual(
|
||||
h.returns,
|
||||
[
|
||||
{
|
||||
'name': '<string>',
|
||||
'age': '<integer>',
|
||||
'money': '<float>',
|
||||
}
|
||||
],
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user