def test_del_protect_del_list(self): data = [{'id': 'hello'}, {'id': 'world'}, {'id': 'people'}] filter_glob_match(data, ['world', '+*', 'hello']) assert data == [{'id': 'hello'}, {'id': 'people'}]
def package_revise(context, data_dict): '''Revise a dataset (package) selectively with match, filter and update parameters. You must be authorized to edit the dataset and the groups that it belongs to. :param match: a dict containing "id" or "name" values of the dataset to update, all values provided must match the current dataset values or a ValidationError will be raised. e.g. ``{"name": "my-data", "resources": {["name": "big.csv"]}}`` would abort if the my-data dataset's first resource name is not "big.csv". :type match: dict :param filter: a list of string patterns of fields to remove from the current dataset. e.g. ``"-resources__1"`` would remove the second resource, ``"+title, +resources, -*"`` would remove all fields at the dataset level except title and all resources (default: ``[]``) :type filter: comma-separated string patterns or list of string patterns :param update: a dict with values to update/create after filtering e.g. ``{"resources": [{"description": "file here"}]}`` would update the description for the first resource :type update: dict :param include: a list of string pattern of fields to include in response e.g. ``"-*"`` to return nothing (default: ``[]`` all fields returned) :type include: comma-separated-string patterns or list of string patterns ``match`` and ``update`` parameters may also be passed as "flattened keys", using either the item numeric index or its unique id (with a minimum of 5 characters), e.g. ``update__resource__1f9ab__description="guidebook"`` would set the description of the resource with id starting with "1f9ab" to "guidebook", and ``update__resource__-1__description="guidebook"`` would do the same on the last resource in the dataset. The ``extend`` suffix can also be used on the update parameter to add a new item to a list, e.g. ``update__resources__extend=[{"name": "new resource", "url": "https://example.com"}]`` will add a new resource to the dataset with the provided ``name`` and ``url``. Usage examples: * Change description in dataset, checking for old description:: match={"notes": "old notes", "name": "xyz"} update={"notes": "new notes"} * Identical to above, but using flattened keys:: match__name="xyz" match__notes="old notes" update__notes="new notes" * Replace all fields at dataset level only, keep resources (note: dataset id and type fields can't be deleted) :: match={"id": "1234abc-1420-cbad-1922"} filter=["+resources", "-*"] update={"name": "fresh-start", "title": "Fresh Start"} * Add a new resource (__extend on flattened key):: match={"id": "abc0123-1420-cbad-1922"} update__resources__extend=[{"name": "new resource", "url": "http://example.com"}] * Update a resource by its index:: match={"name": "my-data"} update__resources__0={"name": "new name, first resource"} * Update a resource by its id (provide at least 5 characters):: match={"name": "their-data"} update__resources__19cfad={"description": "right one for sure"} * Replace all fields of a resource:: match={"id": "34a12bc-1420-cbad-1922"} filter=["+resources__1492a__id", "-resources__1492a__*"] update__resources__1492a={"name": "edits here", "url": "http://example.com"} :returns: a dict containing 'package':the updated dataset with fields filtered by include parameter :rtype: dictionary ''' model = context['model'] schema = schema_.package_revise_schema() data, errors = _validate(data_dict, schema, context) if errors: model.Session.rollback() raise ValidationError(errors) name_or_id = ( data['match__'].get('id') or data.get('match', {}).get('id') or data['match__'].get('name') or data.get('match', {}).get('name')) if name_or_id is None: raise ValidationError({'match__id': _('Missing value')}) package_show_context = dict( context, for_update=True) orig = _get_action('package_show')( package_show_context, {'id': name_or_id}) pkg = package_show_context['package'] # side-effect of package_show unmatched = [] if 'match' in data: unmatched.extend(dfunc.check_dict(orig, data['match'])) for k, v in sorted(data['match__'].items()): unmatched.extend(dfunc.check_string_key(orig, k, v)) if unmatched: model.Session.rollback() raise ValidationError([{'match': [ '__'.join(str(p) for p in unm) for unm in unmatched ]}]) if 'filter' in data: orig_id = orig['id'] dfunc.filter_glob_match(orig, data['filter']) orig['id'] = orig_id if 'update' in data: try: dfunc.update_merge_dict(orig, data['update']) except dfunc.DataError as de: model.Session.rollback() raise ValidationError([{'update': [de.error]}]) # update __extend keys before __#__* so that files may be # attached to newly added resources in the same call try: for k, v in sorted( data['update__'].items(), key=lambda s: s[0][-6] if s[0].endswith('extend') else s[0]): dfunc.update_merge_string_key(orig, k, v) except dfunc.DataError as de: model.Session.rollback() raise ValidationError([{k: [de.error]}]) _check_access('package_revise', context, {"update": orig}) # future-proof return dict by putting package data under # "package". We will want to return activity info # on update or "nothing changed" status once possible rval = { 'package': _get_action('package_update')( dict(context, package=pkg), orig)} if 'include' in data_dict: dfunc.filter_glob_match(rval, data_dict['include']) return rval
def test_protect_dict_key(self): data = {'q': [{'b': 'c'}, {'z': 'x'}], 'r': 'e'} filter_glob_match(data, ['+q', '*']) assert data == {'q': [{'b': 'c'}, {'z': 'x'}]}
def test_del_protect_del_dict(self): data = {'q': 'b', 'c': 'z', 'r': 'e'} filter_glob_match(data, ['q', '+*', 'r']) assert data == {'c': 'z', 'r': 'e'}
def test_remove_list_item(self): data = {'q': [{'b': 'c'}, {'z': 'x'}], 'r': 'e'} filter_glob_match(data, ['q__0']) assert data == {'q': [{'z': 'x'}], 'r': 'e'}
def test_protect_list_item(self): data = {'q': [{'b': 'c'}, {'z': 'x'}], 'r': 'e'} filter_glob_match(data, ['+q__1', 'q__*']) assert data == {'q': [{'z': 'x'}], 'r': 'e'}
def test_del_protect_del_list(self): data = [{"id": "hello"}, {"id": "world"}, {"id": "people"}] filter_glob_match(data, ["world", "+*", "hello"]) assert data == [{"id": "hello"}, {"id": "people"}]
def test_remove_leaves(self): data = {'q': [{'b': 'c'}, {'z': 'x'}], 'r': 'e'} filter_glob_match(data, ['q__0__b', 'q__1__z', 'r']) assert data == {'q': [{}, {}]}
def test_del_protect_del_dict(self): data = {"q": "b", "c": "z", "r": "e"} filter_glob_match(data, ["q", "+*", "r"]) assert data == {"c": "z", "r": "e"}
def test_protect_dict_key(self): data = {"q": [{"b": "c"}, {"z": "x"}], "r": "e"} filter_glob_match(data, ["+q", "*"]) assert data == {"q": [{"b": "c"}, {"z": "x"}]}
def test_protect_list_item(self): data = {"q": [{"b": "c"}, {"z": "x"}], "r": "e"} filter_glob_match(data, ["+q__1", "q__*"]) assert data == {"q": [{"z": "x"}], "r": "e"}
def test_remove_list_item(self): data = {"q": [{"b": "c"}, {"z": "x"}], "r": "e"} filter_glob_match(data, ["q__0"]) assert data == {"q": [{"z": "x"}], "r": "e"}
def test_remove_leaves(self): data = {"q": [{"b": "c"}, {"z": "x"}], "r": "e"} filter_glob_match(data, ["q__0__b", "q__1__z", "r"]) assert data == {"q": [{}, {}]}