def test_deep(self): data = {"a": [{"b": "c"}], "d": "e"} update_merge_dict( data, {"q": [{"b": "c"}], "a": [{"z": "x"}], "r": "e"} ) assert data == { "q": [{"b": "c"}], "a": [{"b": "c", "z": "x"}], "r": "e", "d": "e", }
def test_deep(self): data = {'a': [{'b': 'c'}], 'd': 'e'} update_merge_dict(data, { 'q': [{ 'b': 'c' }], 'a': [{ 'z': 'x' }], 'r': 'e' }) assert data == { 'q': [{ 'b': 'c' }], 'a': [{ 'b': 'c', 'z': 'x' }], 'r': 'e', 'd': 'e' }
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_dict_expected(self): data = {'a': [{'b': []}], 'd': 'e'} with pytest.raises(DataError) as de: update_merge_dict(data, {'a': [['b']]}) assert de.value.error == 'Expected dict for a__0'
def test_simple_list(self): data = {'a': ['q', 'w', 'e', 'r', 't']} update_merge_dict(data, {'a': ['z', 'x', 'c']}) assert data == {'a': ['z', 'x', 'c', 'r', 't']}
def test_replace_parent(self): data = {'a': [{'b': 'c'}], 'd': 'e'} update_merge_dict(data, {'d': 'd'}) assert data == {'a': [{'b': 'c'}], 'd': 'd'}
def test_replace_child(self): data = {'a': [{'b': 'c'}], 'd': 'e'} update_merge_dict(data, {'a': [{'b': 'z'}]}) assert data == {'a': [{'b': 'z'}], 'd': 'e'}
def test_dict_expected(self): data = {"a": [{"b": []}], "d": "e"} with pytest.raises(DataError) as de: update_merge_dict(data, {"a": [["b"]]}) assert de.value.error == "Expected dict for a__0"
def test_simple_list(self): data = {"a": ["q", "w", "e", "r", "t"]} update_merge_dict(data, {"a": ["z", "x", "c"]}) assert data == {"a": ["z", "x", "c", "r", "t"]}
def test_replace_parent(self): data = {"a": [{"b": "c"}], "d": "e"} update_merge_dict(data, {"d": "d"}) assert data == {"a": [{"b": "c"}], "d": "d"}
def test_replace_child(self): data = {"a": [{"b": "c"}], "d": "e"} update_merge_dict(data, {"a": [{"b": "z"}]}) assert data == {"a": [{"b": "z"}], "d": "e"}