diff --git a/CHANGELOG.md b/CHANGELOG.md index f8c56074f..b1b4b2d74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Changes - On the large image item page, guard against overflow ([#1096](../../pull/1096)) - Specify version of simplejpeg for older python ([#1098](../../pull/1098)) +- Better expose the Girder yaml config files from python ([#1099](../../pull/1099)) ## 1.20.3 diff --git a/girder/girder_large_image/__init__.py b/girder/girder_large_image/__init__.py index 8338f4c38..e3bef642d 100644 --- a/girder/girder_large_image/__init__.py +++ b/girder/girder_large_image/__init__.py @@ -19,16 +19,18 @@ import re import warnings +import yaml from girder_jobs.constants import JobStatus from girder_jobs.models.job import Job import girder import large_image from girder import events, logger -from girder.constants import AccessType +from girder.constants import AccessType, SortDir from girder.exceptions import RestException, ValidationException from girder.models.file import File from girder.models.folder import Folder +from girder.models.group import Group from girder.models.item import Item from girder.models.notification import Notification from girder.models.setting import Setting @@ -367,6 +369,111 @@ def metadataSearchHandler( # noqa return result +def _mergeDictionaries(a, b): + """ + Merge two dictionaries recursively. If the second dictionary (or any + sub-dictionary) has a special key, value of '__all__': True, the updated + dictionary only contains values from the second dictionary and excludes + the __all__ key. + + :param a: the first dictionary. Modified. + :param b: the second dictionary that gets added to the first. + :returns: the modified first dictionary. + """ + if b.get('__all__') is True: + a.clear() + for key in b: + if isinstance(a.get(key), dict) and isinstance(b[key], dict): + _mergeDictionaries(a[key], b[key]) + elif key != '__all__' or b[key] is not True: + a[key] = b[key] + return a + + +def adjustConfigForUser(config, user): + """ + Given the current user, adjust the config so that only relevant and + combined values are used. If the root of the config dictionary contains + "access": {"user": , "admin": }, the base values are updated + based on the user's access level. If the root of the config contains + "group": {: , ...}, the base values are updated for + every group the user is a part of. + + The order of update is groups in C-sort alphabetical order followed by + access/user and then access/admin as they apply. + + :param config: a config dictionary. + """ + if not isinstance(config, dict): + return config + if isinstance(config.get('groups'), dict): + groups = config.pop('groups') + if user: + for group in Group().find( + {'_id': {'$in': user['groups']}}, sort=[('name', SortDir.ASCENDING)]): + if isinstance(groups.get(group['name']), dict): + config = _mergeDictionaries(config, groups[group['name']]) + if isinstance(config.get('access'), dict): + accessList = config.pop('access') + if user and isinstance(accessList.get('user'), dict): + config = _mergeDictionaries(config, accessList['user']) + if user and user.get('admin') and isinstance(accessList.get('admin'), dict): + config = _mergeDictionaries(config, accessList['admin']) + return config + + +def yamlConfigFile(folder, name, user): + """ + Get a resolved named config file based on a folder and user. + + :param folder: a Girder folder model. + :param name: the name of the config file. + :param user: the user that the response if adjusted for. + :returns: either None if no config file, or a yaml record. + """ + addConfig = None + last = False + while folder: + item = Item().findOne({'folderId': folder['_id'], 'name': name}) + if item: + for file in Item().childFiles(item): + if file['size'] > 10 * 1024 ** 2: + logger.info('Not loading %s -- too large' % file['name']) + continue + with File().open(file) as fptr: + config = yaml.safe_load(fptr) + if isinstance(config, list) and len(config) == 1: + config = config[0] + # combine and adjust config values based on current user + if isinstance(config, dict) and 'access' in config or 'group' in config: + config = adjustConfigForUser(config, user) + if addConfig and isinstance(config, dict): + config = _mergeDictionaries(config, addConfig) + if not isinstance(config, dict) or config.get('__inherit__') is not True: + return config + config.pop('__inherit__') + addConfig = config + if last: + break + if folder['parentCollection'] != 'folder': + if folder['name'] != '.config': + folder = Folder().findOne({ + 'parentId': folder['parentId'], + 'parentCollection': folder['parentCollection'], + 'name': '.config'}) + else: + last = 'setting' + if not folder or last == 'setting': + folderId = Setting().get(constants.PluginSettings.LARGE_IMAGE_CONFIG_FOLDER) + if not folderId: + break + folder = Folder().load(folderId, force=True) + last = True + else: + folder = Folder().load(folder['parentId'], user=user, level=AccessType.READ) + return addConfig + + # Validators @setting_utilities.validator({ diff --git a/girder/girder_large_image/rest/__init__.py b/girder/girder_large_image/rest/__init__.py index 1c833ad48..80262acd9 100644 --- a/girder/girder_large_image/rest/__init__.py +++ b/girder/girder_large_image/rest/__init__.py @@ -1,19 +1,12 @@ import json -import yaml - from girder import logger from girder.api import access from girder.api.describe import Description, autoDescribeRoute from girder.api.rest import boundHandler -from girder.constants import AccessType, SortDir, TokenScope -from girder.models.file import File +from girder.constants import AccessType, TokenScope from girder.models.folder import Folder -from girder.models.group import Group from girder.models.item import Item -from girder.models.setting import Setting - -from .. import constants def addSystemEndpoints(apiRoot): @@ -103,59 +96,6 @@ def _itemFindRecursive(self, origItemFind, folderId, text, name, limit, offset, return origItemFind(folderId, text, name, limit, offset, sort, filters) -def _mergeDictionaries(a, b): - """ - Merge two dictionaries recursively. If the second dictionary (or any - sub-dictionary) has a special key, value of '__all__': True, the updated - dictionary only contains values from the second dictionary and excludes - the __all__ key. - - :param a: the first dictionary. Modified. - :param b: the second dictionary that gets added to the first. - :returns: the modified first dictionary. - """ - if b.get('__all__') is True: - a.clear() - for key in b: - if isinstance(a.get(key), dict) and isinstance(b[key], dict): - _mergeDictionaries(a[key], b[key]) - elif key != '__all__' or b[key] is not True: - a[key] = b[key] - return a - - -def adjustConfigForUser(config, user): - """ - Given the current user, adjust the config so that only relevant and - combined values are used. If the root of the config dictionary contains - "access": {"user": , "admin": }, the base values are updated - based on the user's access level. If the root of the config contains - "group": {: , ...}, the base values are updated for - every group the user is a part of. - - The order of update is groups in C-sort alphabetical order followed by - access/user and then access/admin as they apply. - - :param config: a config dictionary. - """ - if not isinstance(config, dict): - return config - if isinstance(config.get('groups'), dict): - groups = config.pop('groups') - if user: - for group in Group().find( - {'_id': {'$in': user['groups']}}, sort=[('name', SortDir.ASCENDING)]): - if isinstance(groups.get(group['name']), dict): - config = _mergeDictionaries(config, groups[group['name']]) - if isinstance(config.get('access'), dict): - accessList = config.pop('access') - if user and isinstance(accessList.get('user'), dict): - config = _mergeDictionaries(config, accessList['user']) - if user and user.get('admin') and isinstance(accessList.get('admin'), dict): - config = _mergeDictionaries(config, accessList['admin']) - return config - - @access.public(scope=TokenScope.DATA_READ) @autoDescribeRoute( Description('Get a config file.') @@ -179,45 +119,7 @@ def adjustConfigForUser(config, user): ) @boundHandler() def getYAMLConfigFile(self, folder, name): - addConfig = None + from .. import yamlConfigFile + user = self.getCurrentUser() - last = False - while folder: - item = Item().findOne({'folderId': folder['_id'], 'name': name}) - if item: - for file in Item().childFiles(item): - if file['size'] > 10 * 1024 ** 2: - logger.info('Not loading %s -- too large' % file['name']) - continue - with File().open(file) as fptr: - config = yaml.safe_load(fptr) - if isinstance(config, list) and len(config) == 1: - config = config[0] - # combine and adjust config values based on current user - if isinstance(config, dict) and 'access' in config or 'group' in config: - config = adjustConfigForUser(config, user) - if addConfig and isinstance(config, dict): - config = _mergeDictionaries(config, addConfig) - if not isinstance(config, dict) or config.get('__inherit__') is not True: - return config - config.pop('__inherit__') - addConfig = config - if last: - break - if folder['parentCollection'] != 'folder': - if folder['name'] != '.config': - folder = Folder().findOne({ - 'parentId': folder['parentId'], - 'parentCollection': folder['parentCollection'], - 'name': '.config'}) - else: - last = 'setting' - if not folder or last == 'setting': - folderId = Setting().get(constants.PluginSettings.LARGE_IMAGE_CONFIG_FOLDER) - if not folderId: - break - folder = Folder().load(folderId, force=True) - last = True - else: - folder = Folder().load(folder['parentId'], user=user, level=AccessType.READ) - return addConfig + return yamlConfigFile(folder, name, user)