Source code for magpie.api.management.resource.resource_formats
from typing import TYPE_CHECKING
from pyramid.httpexceptions import HTTPInternalServerError
from ziggurat_foundations.models.services.resource import ResourceService
from magpie.api.exception import evaluate_call
from magpie.permissions import PermissionType, format_permissions
from magpie.services import SERVICE_TYPE_DICT
if TYPE_CHECKING:
from typing import Collection, List, Optional
from sqlalchemy.orm.session import Session
from magpie.models import Resource, Service
from magpie.typedefs import (
JSON,
AnyPermissionType,
NestedResourceNodes,
NestingKeyType,
ResourcePermissionMap,
ServiceOrResourceType,
Str
)
[docs]
def format_resource(resource, permissions=None, permission_type=None, basic_info=False, dotted=False):
# type: (Resource, Optional[Collection[AnyPermissionType]], Optional[PermissionType], bool, bool) -> JSON
"""
Formats a :term:`Resource` information into JSON.
:param resource: :term:`Resource` to be formatted.
:param permissions:
Permissions to list along with the :paramref:`resource`.
By default, these are the applicable permissions for that corresponding resource type.
:param permission_type:
Override indication of provenance to apply to :paramref:`permissions`. Only applicable when they are provided.
:param basic_info:
If ``True``, return only sufficient details to identify the resource, without any additional
:paramref:`permissions` detail, nor hierarchical :paramref:`resource` information is returned.
:param dotted:
Employ a dot (``.``) instead of underscore (``_``) to separate :term:`Resource` from its basic information.
.. seealso::
:func:`magpie.api.management.service.service_formats.format_service`
"""
def fmt_res(): # type: () -> JSON
sep = "." if dotted else "_"
result = {
"resource{}name".format(sep): str(resource.resource_name),
"resource{}display_name".format(sep): str(resource.resource_display_name or resource.resource_name),
"resource{}type".format(sep): str(resource.resource_type),
"resource{}id".format(sep): resource.resource_id
}
if not basic_info:
result.update({
"parent_id": resource.parent_id,
"root_service_id": resource.root_service_id,
})
result.update(format_permissions(permissions, permission_type))
return result
return evaluate_call(
lambda: fmt_res(),
http_error=HTTPInternalServerError,
msg_on_fail="Failed to format resource.",
content={"resource": repr(resource), "permissions": repr(permissions), "basic_info": basic_info}
)
[docs]
def format_resource_tree(
nested_resources, # type: NestedResourceNodes
db_session, # type: Session
resources_perms_dict=None, # type: Optional[ResourcePermissionMap]
permission_type=None, # type: Optional[PermissionType]
nesting_key="children", # type: NestingKeyType
): # type: (...) -> JSON
"""
Generates the formatted resource tree under the provided nested resources.
For all of the nested resources, formatting is applied by calling :func:`format_resource` recursively on them.
Apply specific resource permissions as defined by :paramref:`resources_perms_dict` if provided.
:param nested_resources: Service or resource for which to generate the formatted resource tree.
:param db_session: Connection to database.
:param resources_perms_dict:
Any pre-established :term:`Applied Permission` to set to corresponding resources by ID.
When provided, these will define the :term:`User`, :term:`Group` or both
(i.e.: :term:`Inherited Permissions <Inherited Permission>`)
actual permissions, or even the :term:`Effective Permissions <Effective Permission>`, according to parent
caller function's context.
Otherwise (``None``), defaults to extracting :term:`Allowed Permissions <Allowed Permission>` for the given
:term:`Resource` scoped under the corresponding root :term:`Service`.
:param permission_type:
Override :term:`Permission` type to indicate its provenance.
Type is applied recursively for all resources in the generated nested resource tree.
:param nesting_key:
Key to employ for nesting the formatted sub-tree resources according to the provided nested resources.
:return: Formatted nested resource tree with their details and permissions.
"""
# optimization to avoid re-lookup of 'allowed permissions' when already fetched
# unused when parsing 'applied permissions'
__internal_svc_res_perm_dict = {}
def recursive_fmt_res_tree(nested_dict): # type: (NestedResourceNodes) -> JSON
fmt_res_tree = {}
for child_id, child_dict in nested_dict.items():
resource = child_dict["node"]
# nested nodes always use 'children' regardless of nested-key
# nested-key employed in the generated format will indicate the real resource parents/children relationship
new_nested = child_dict["children"]
perms = []
# case of pre-specified user/group-specific permissions
if resources_perms_dict is not None:
if resource.resource_id in resources_perms_dict.keys():
perms = resources_perms_dict[resource.resource_id]
# case of full fetch (allowed resource permissions)
else:
# directly access the resource if it is a service
service = None # type: Optional[Service]
if resource.root_service_id is None:
service = resource
service_id = resource.resource_id
# obtain corresponding top-level service resource if not already available,
# get resource permissions allowed under the top service's scope
else:
service_id = resource.root_service_id
if service_id not in __internal_svc_res_perm_dict:
service = ResourceService.by_resource_id(service_id, db_session=db_session)
# add to dict only if not already added
if service is not None and service_id not in __internal_svc_res_perm_dict:
__internal_svc_res_perm_dict[service_id] = {
res_type.resource_type_name: res_perms # use str key to match below 'resource_type' field
for res_type, res_perms in SERVICE_TYPE_DICT[service.type].resource_types_permissions.items()
}
# in case of inverse nesting, service could be at "bottom"
# retrieve its permissions directly since its type is never expected nested under itself
res_type_name = resource.resource_type # type: Str
if res_type_name == "service":
perms = SERVICE_TYPE_DICT[service.type].permissions
else:
perms = __internal_svc_res_perm_dict[service_id][resource.resource_type]
fmt_res_tree[child_id] = format_resource(resource, perms, permission_type)
fmt_res_tree[child_id][nesting_key] = recursive_fmt_res_tree(new_nested)
return fmt_res_tree
return recursive_fmt_res_tree(nested_resources)
[docs]
def format_resources_listed(resources, db_session):
# type: (List[ServiceOrResourceType], Session) -> List[JSON]
"""
Obtains the formatted :term:`Resource` list with their applicable permissions.
"""
from magpie.api.management.resource import resource_utils as ru
res_list = []
for res in resources:
res_perms = ru.get_resource_permissions(res, db_session=db_session)
res_json = format_resource(res, permissions=res_perms)
res_list.append(res_json)
return res_list
[docs]
def format_resources_nested(resource, nested_resources, nesting_key, db_session):
# type: (ServiceOrResourceType, NestedResourceNodes, NestingKeyType, Session) -> JSON
"""
Obtains the formatted :term:`Resource` tree with all its formatted children hierarchy.
"""
from magpie.api.management.resource import resource_utils as ru
resource_permissions = ru.get_resource_permissions(resource, db_session=db_session)
resource_formatted = format_resource(resource, permissions=resource_permissions)
resource_formatted[nesting_key] = format_resource_tree(
nested_resources, nesting_key=nesting_key, db_session=db_session
)
return resource_formatted