##// END OF EJS Templates
update notebook api tests...
update notebook api tests with creation URL changes also use unicode as the default name in tests, to increase likelihood of catching unicode bugs.

File last commit:

r8673:7fe4928b
r13130:76f76016
Show More
_jsonpointer.py
228 lines | 6.4 KiB | text/x-python | PythonLexer
# -*- coding: utf-8 -*-
#
# python-json-pointer - An implementation of the JSON Pointer syntax
# https://github.com/stefankoegl/python-json-pointer
#
# Copyright (c) 2011 Stefan Kögl <stefan@skoegl.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
""" Identify specific nodes in a JSON document according to
http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-04 """
# Will be parsed by setup.py to determine package metadata
__author__ = 'Stefan Kögl <stefan@skoegl.net>'
__version__ = '0.3'
__website__ = 'https://github.com/stefankoegl/python-json-pointer'
__license__ = 'Modified BSD License'
try:
from urllib import unquote
from itertools import izip
except ImportError: # Python 3
from urllib.parse import unquote
izip = zip
from itertools import tee
class JsonPointerException(Exception):
pass
_nothing = object()
def resolve_pointer(doc, pointer, default=_nothing):
"""
Resolves pointer against doc and returns the referenced object
>>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}}
>>> resolve_pointer(obj, '') == obj
True
>>> resolve_pointer(obj, '/foo') == obj['foo']
True
>>> resolve_pointer(obj, '/foo/another%20prop') == obj['foo']['another prop']
True
>>> resolve_pointer(obj, '/foo/another%20prop/baz') == obj['foo']['another prop']['baz']
True
>>> resolve_pointer(obj, '/foo/anArray/0') == obj['foo']['anArray'][0]
True
>>> resolve_pointer(obj, '/some/path', None) == None
True
"""
pointer = JsonPointer(pointer)
return pointer.resolve(doc, default)
def set_pointer(doc, pointer, value):
"""
Set a field to a given value
The field is indicates by a base location that is given in the constructor,
and an optional relative location in the call to set. If the path doesn't
exist, it is created if possible
>>> obj = {"foo": 2}
>>> pointer = JsonPointer('/bar')
>>> pointer.set(obj, 'one', '0')
>>> pointer.set(obj, 'two', '1')
>>> obj
{'foo': 2, 'bar': ['one', 'two']}
>>> obj = {"foo": 2, "bar": []}
>>> pointer = JsonPointer('/bar')
>>> pointer.set(obj, 5, '0/x')
>>> obj
{'foo': 2, 'bar': [{'x': 5}]}
>>> obj = {'foo': 2, 'bar': [{'x': 5}]}
>>> pointer = JsonPointer('/bar/0')
>>> pointer.set(obj, 10, 'y/0')
>>> obj == {'foo': 2, 'bar': [{'y': [10], 'x': 5}]}
True
"""
pointer = JsonPointer(pointer)
pointer.set(doc, value)
class JsonPointer(object):
""" A JSON Pointer that can reference parts of an JSON document """
def __init__(self, pointer):
parts = pointer.split('/')
if parts.pop(0) != '':
raise JsonPointerException('location must starts with /')
parts = map(unquote, parts)
parts = [part.replace('~1', '/') for part in parts]
parts = [part.replace('~0', '~') for part in parts]
self.parts = parts
def resolve(self, doc, default=_nothing):
"""Resolves the pointer against doc and returns the referenced object"""
for part in self.parts:
try:
doc = self.walk(doc, part)
except JsonPointerException:
if default is _nothing:
raise
else:
return default
return doc
get = resolve
def set(self, doc, value, path=None):
""" Sets a field of doc to value
The location of the field is given by the pointers base location and
the optional path which is relative to the base location """
fullpath = list(self.parts)
if path:
fullpath += path.split('/')
for part, nextpart in pairwise(fullpath):
try:
doc = self.walk(doc, part)
except JsonPointerException:
step_val = [] if nextpart.isdigit() else {}
doc = self._set_value(doc, part, step_val)
self._set_value(doc, fullpath[-1], value)
@staticmethod
def _set_value(doc, part, value):
part = int(part) if part.isdigit() else part
if isinstance(doc, dict):
doc[part] = value
if isinstance(doc, list):
if len(doc) < part:
doc[part] = value
if len(doc) == part:
doc.append(value)
else:
raise IndexError
return doc[part]
def walk(self, doc, part):
""" Walks one step in doc and returns the referenced part """
# Its not clear if a location "1" should be considered as 1 or "1"
# We prefer the integer-variant if possible
part_variants = self._try_parse(part) + [part]
for variant in part_variants:
try:
return doc[variant]
except:
continue
raise JsonPointerException("'%s' not found in %s" % (part, doc))
@staticmethod
def _try_parse(val, cls=int):
try:
return [cls(val)]
except:
return []
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
for _ in b:
break
return izip(a, b)