def sprint(obj, indent=4, bullet='* '): ''' Display JSON-like objects in a compact way. Differently to pretty print, it does not format objects using a valid dict/list syntax. Examples -------- >>> obj = {'foo': {'foobar': 1, 'bar': 'null'}, 'bar': ['null', 1, 2, 3]} >>> print(sprint(obj)) bar: * null * 1 * 2 * 3 foo: bar: null foobar: 1 ''' if is_object(obj): if obj: items = [] obj_items = obj.items() obj_items.sort() for key, value in obj_items: if is_object(value) or is_array(value): value = '\n' + _indent_f(sprint(value), indent) else: value = ' ' + sprint(value) items.append('%s:%s' % (key, value)) return '\n'.join(items) return '<empty>' elif is_array(obj): if obj: return itemize(map(sprint, obj)) else: return '<empty>' else: data = unicode(obj) if '\n' in data: data = '\n' + _indent_f(data, indent) return data
def validate(self, obj, lazy=True, path=[]): # Check if obj is a dict if not pyson_t.is_object(obj): msg = "object of type '%s' is not a mapping." % type(obj).__name__ raise errors.ValidationError((msg, path)) # Validate root obj_keys = set(obj) valid_root = False if self.root_key is not None: try: root = obj[self.root_key] except KeyError: raise errors.MissingKeyError((None, path + [self.root_key])) else: self._root_schema.validate(root, lazy, path) obj_keys.remove(self.root_key) valid_root = True # Catch errors and re-raise exception as PartialValidation if # valid_root is True try: # Validate all keys for key, validator in self._schemas.items(): # Get value from object and check if key exists try: value = obj[key] obj_keys.remove(key) except KeyError: if not validator.can_be_empty(lazy): raise errors.MissingKeyError((None, path + [key])) else: # Validate value validator.validate(value, lazy, path + [key]) # Assert obj do not have extra keys if obj_keys: first_key = iter(obj_keys).next() raise errors.InvalidKeyError((None, path + [first_key])) except Exception as ex: if valid_root: raise errors.PartialValidationError(ex) else: raise ex
def adapt(self, obj, inplace=False, path=[]): # Check if obj is a dict if not pyson_t.is_object(obj): raise errors.AdaptationError(('%(path)s: not a mapping', path)) new_obj = obj.copy() # Test the root key root_value = None if self.root_key is not None: try: root_value = new_obj.pop(self.root_key) except KeyError: raise errors.AdaptationError((None, path)) else: root_value = self._root_schema.adapt(root_value, inplace, path) # Catches AdaptationError and raises PartialAdaptationError if root # exists try: for k, v in new_obj.items(): try: adapter = self[k].adapt except KeyError: raise errors.AdaptationError((None, path)) else: new_obj[k] = adapter(v, inplace, path + [k]) except errors.AdaptationError as ex: if self.root_key is not None: raise errors.PartialAdaptationError(ex.args[0], ex) # Writes root key back to the dictionary if self.root_key is not None: new_obj[self.root_key] = root_value # Checks validity if self.is_valid(new_obj): if inplace: obj.update(new_obj) return obj else: return new_obj
def setitem(obj, path, value, newmap=None): ''' Updates ``obj``'s ``path`` node to given ``value``. Recursively creates and updates new keys for mapping containers. Parameters ---------- obj : JSON-like JSON-like structure. path : str or list path Any valid JSON path. value : object Any value to be assigned to the given path node. newmap : callable Factory function for creating new mappings. By default it tries to use the same type as the innermost dictionary. This function, called with no arguments, should return a new dictionary-like object. Raises ------ IndexKeyError If a node in a sequence container is empty. ''' path = as_path(path) curr_obj = obj # Find the first empty node idx = 0 objs = [obj] for node in path[:-1]: try: curr_obj = curr_obj[node] objs.append(curr_obj) idx += 1 except KeyError: break_node = node break_obj = curr_obj break except IndexError: path = as_str_path(path[:idx + 1]) raise IndexKeyError('empty node %s in sequence.' % path) else: # There is no empty node: assign value try: curr_obj[path[-1]] = value return except IndexError: raise IndexKeyError('empty node %s in sequence.' % as_str_path(path)) # An empty node was found: inspect the missing structures empty_path = path[idx:] # Asserts that no integer index exists for the new nodes: it only fills # new dictionaries empty_tt = map(type, empty_path) if int in empty_tt: raise IndexKeyError('attempt to create sequence') # Obtain the factory function for creating new mappings if newmap is None: objs.reverse() for obj in objs: if is_object(obj): newmap = type(obj) break else: newmap = dict # Fill dictionary values curr = empty = newmap() for node in empty_path[1:-1]: curr[node] = newmap() curr = curr[node] curr[empty_path[-1]] = value # Commit new dictionary to 'obj' break_obj[break_node] = empty
def writeitem(obj, path, value, newmap=None, newseq=None, newitem=None): ''' In most cases, it behaves like the __setitem__ iterface: setitem(obj, key, value) <==> obj[key] = value. The two optional arguments 'fill' and 'fill_value' defines how list-like sequences are handled if 'key' is an invalid index. If 'fill' is True (default) and key == len(obj), thus indices are [0, 1, ..., len(obj) - 1], 'value' is appended to the end of the list. This behavior creates the new entry that is equivalent to 'obj[key] == value'. If 'fill' is True and key > len(obj), the function checks if the user had defined the 'fill_value' argument. The list is then filled with this value until the obj[key] is reached, and finally value is appended to the list. ''' #TODO: support completion in array objects raise NotImplementedError path = as_path(path) curr_obj = obj # Find the first empty node idx = 0 objs = [obj] for node in path[:-1]: try: curr_obj = curr_obj[node] objs.append(curr_obj) idx += 1 except KeyError: break_node = node break_obj = curr_obj break except IndexError: path = as_str_path(path[:idx + 1]) raise IndexKeyError('empty node %s in sequence.' % path) else: # There is no empty node: assign value try: curr_obj[path[-1]] = value return except IndexError: raise IndexKeyError('empty node %s in sequence.' % as_str_path(path)) # An empty node was found: inspect the missing structures empty_path = path[idx:] # Asserts that no integer index exists for the new nodes: it only fills # new dictionaries empty_tt = map(type, empty_path) if int in empty_tt: raise IndexKeyError('attempt to create sequence') # Obtain the factory function for creating new mappings if newmap is None: objs.reverse() for obj in objs: if is_object(obj): newmap = type(obj) break else: newmap = dict # Fill dictionary values curr = empty = newmap() for node in empty_path[1:-1]: curr[node] = newmap() curr = curr[node] curr[empty_path[-1]] = value # Commit new dictionary to 'obj' break_obj[break_node] = empty