Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better expose the yaml config files from python #1099

Merged
merged 1 commit into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
109 changes: 108 additions & 1 deletion girder/girder_large_image/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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": <dict>, "admin": <dict>}, the base values are updated
based on the user's access level. If the root of the config contains
"group": {<group-name>: <dict>, ...}, 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({
Expand Down
106 changes: 4 additions & 102 deletions girder/girder_large_image/rest/__init__.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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": <dict>, "admin": <dict>}, the base values are updated
based on the user's access level. If the root of the config contains
"group": {<group-name>: <dict>, ...}, 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.')
Expand All @@ -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)