1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-03-20 06:50:23 +03:00

Add HelpMethod and HelpMethodInfo classes for improved API documentation

This commit is contained in:
Adolfo Gómez García 2025-02-03 04:49:04 +01:00
parent b41a1afd43
commit 4c59a25092
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
3 changed files with 111 additions and 59 deletions

View File

@ -30,6 +30,7 @@
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import dataclasses
import enum
import logging
import typing
@ -49,6 +50,49 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger(__name__)
@dataclasses.dataclass
class HelpMethodInfo:
method: str
params: list[str]
text: str
@property
def methods(self) -> typing.Generator[str, None, None]:
for param in self.params:
if param == '':
yield self.method
else:
if (self.method + ' ')[0] == '-':
yield f'<{param}>/{self.method[1:]}'
elif self.method:
yield f'{self.method}/<{param}>'
else:
yield f'<{param}>'
def __str__(self) -> str:
return ', '.join(self.methods)
def __repr__(self) -> str:
return self.__str__()
class HelpMethod(enum.Enum):
ITEM = HelpMethodInfo('', ['uuid'], 'Retrieves an item by its UUID')
LOG = HelpMethodInfo('-' + consts.rest.LOG, ['uuid'], 'Retrieves the log of an item')
OVERVIEW = HelpMethodInfo(consts.rest.OVERVIEW, [''], 'General Overview of all items (a list')
TABLEINFO = HelpMethodInfo(consts.rest.TABLEINFO, [''], 'Table visualization information (types, etc..)')
TYPES = HelpMethodInfo(consts.rest.TYPES, ['', 'type'], 'Types information')
GUI = HelpMethodInfo(consts.rest.GUI, ['', 'type'], 'GUI information')
@dataclasses.dataclass
class HelpInfo:
level: int
path: str
text: str
methods: list[HelpMethod]
class Documentation(View):
def dispatch(
@ -61,21 +105,29 @@ class Documentation(View):
if not request.user.get_role().can_access(consts.UserRole.STAFF):
return auth.weblogout(request)
@dataclasses.dataclass
class HelpInfo:
level: int
path: str
text: str
help_data: list[HelpInfo] = []
def _process_node(node: 'types.rest.HelpNode', path: str, level: int) -> None:
help_data.append(HelpInfo(level, path, node.help.text))
if node.kind == types.rest.HelpNode.HelpNodeType.MODEL:
methods = [
HelpMethod.OVERVIEW,
HelpMethod.GUI,
HelpMethod.TABLEINFO,
HelpMethod.TYPES,
HelpMethod.ITEM,
HelpMethod.LOG,
]
elif node.kind == types.rest.HelpNode.HelpNodeType.DETAIL:
methods = []
else:
methods = []
help_data.append(HelpInfo(level, path, node.help.text, methods))
for child in node.children:
_process_node(
child,
path + '/' + child.help.path,
child.help.path,
level + (0 if node.kind == types.rest.HelpNode.HelpNodeType.PATH else 1),
)

View File

@ -232,13 +232,13 @@ class ModelHandler(BaseModelHandler):
self, *args: typing.Any, **kwargs: typing.Any
) -> typing.Generator[types.rest.ItemDictType, None, None]:
if 'overview' in kwargs:
overview = kwargs['overview']
overview: bool = kwargs['overview']
del kwargs['overview']
else:
overview = True
if 'prefetch' in kwargs:
prefetch = kwargs['prefetch']
prefetch: list[str] = kwargs['prefetch']
logger.debug('Prefetching %s', prefetch)
del kwargs['prefetch']
else:
@ -327,61 +327,53 @@ class ModelHandler(BaseModelHandler):
return operation()
if number_of_args == 1:
if self._args[0] == consts.rest.OVERVIEW:
return list(self.get_items())
if self._args[0] == consts.rest.TYPES:
return list(self.get_types())
if self._args[0] == consts.rest.TABLEINFO:
match self._args[0]:
case consts.rest.OVERVIEW:
if number_of_args == 1:
return list(self.get_items())
raise self.invalid_request_response()
case consts.rest.TABLEINFO:
if number_of_args != 1:
raise self.invalid_request_response()
return self.process_table_fields(
self.table_title,
self.table_fields,
self.table_row_style,
self.table_subtitle,
)
if self._args[0] == consts.rest.GUI:
return self.get_gui('')
case consts.rest.TYPES:
if number_of_args == 1:
return list(self.get_types())
if number_of_args != 2:
raise self.invalid_request_response()
return self.get_type(self._args[1])
case consts.rest.GUI:
if number_of_args == 1:
return self.get_gui('')
if number_of_args != 2:
raise self.invalid_request_response()
return sorted(self.get_gui(self._args[1]), key=lambda f: f['gui']['order'])
case _: # Maybe an item or a detail
if number_of_args == 1:
try:
item = self.model.objects.get(uuid__iexact=self._args[0].lower())
self.ensure_has_access(item, types.permissions.PermissionType.READ)
res = self.item_as_dict(item)
self.fill_instance_fields(item, res)
return res
except Exception as e:
logger.exception('Got Exception looking for item')
raise self.invalid_item_response() from e
elif number_of_args == 2:
if self._args[1] == consts.rest.LOG:
try:
item = self.model.objects.get(uuid__iexact=self._args[0].lower())
return self.get_logs(item)
except Exception as e:
raise self.invalid_item_response() from e
# get item ID
try:
item = self.model.objects.get(uuid__iexact=self._args[0].lower())
self.ensure_has_access(item, types.permissions.PermissionType.READ)
res = self.item_as_dict(item)
self.fill_instance_fields(item, res)
return res
except Exception as e:
logger.exception('Got Exception looking for item')
raise self.invalid_item_response() from e
# nArgs > 1
# Request type info or gui, or detail
if self._args[0] == consts.rest.OVERVIEW:
if number_of_args != 2:
raise self.invalid_request_response()
elif self._args[0] == consts.rest.TYPES:
if number_of_args != 2:
raise self.invalid_request_response()
return self.get_type(self._args[1])
elif self._args[0] == consts.rest.GUI:
if number_of_args != 2:
raise self.invalid_request_response()
gui = self.get_gui(self._args[1])
return sorted(gui, key=lambda f: f['gui']['order'])
elif self._args[1] == consts.rest.LOG:
if number_of_args != 2:
raise self.invalid_request_response()
try:
# DB maybe case sensitive??, anyway, uuids are stored in lowercase
item = self.model.objects.get(uuid__iexact=self._args[0].lower())
return self.get_logs(item)
except Exception as e:
raise self.invalid_item_response() from e
# If has detail and is requesting detail
if self.detail is not None:
return self.process_detail()
if self.detail is not None:
return self.process_detail()
raise self.invalid_request_response() # Will not return

View File

@ -28,7 +28,15 @@
<div class="doc">
{% for h in help %}
<div class="doc-item">
{{ h }}
{% if h.methods %}
<div class="doc-item-methods">
{% for m in h.methods %}
{% for mm in m.value.methods %}
<div class="doc-item-method">{{ h.path }}/{{ mm }}</div>
{% endfor %}
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}