-
Notifications
You must be signed in to change notification settings - Fork 42
Refactored jsonpointer.py, added tox.ini #11
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,12 +11,12 @@ | |
# are met: | ||
# | ||
# 1. Redistributions of source code must retain the above copyright | ||
# notice, this list of conditions and the following disclaimer. | ||
# 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. | ||
# 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. | ||
# 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 | ||
|
@@ -30,14 +30,9 @@ | |
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
# | ||
|
||
from __future__ import unicode_literals | ||
|
||
""" Identify specific nodes in a JSON document (RFC 6901) """ | ||
|
||
try: | ||
from collections.abc import Mapping, Sequence | ||
except ImportError: | ||
from collections import Mapping, Sequence | ||
from __future__ import unicode_literals | ||
|
||
# Will be parsed by setup.py to determine package metadata | ||
__author__ = 'Stefan Kögl <stefan@skoegl.net>' | ||
|
@@ -49,43 +44,48 @@ | |
try: | ||
from urllib import unquote | ||
from itertools import izip | ||
except ImportError: # Python 3 | ||
except ImportError: # Python 3 | ||
from urllib.parse import unquote | ||
izip = zip | ||
|
||
try: | ||
from collections.abc import Mapping, Sequence | ||
except ImportError: # Python 3 | ||
from collections import Mapping, Sequence | ||
|
||
from itertools import tee | ||
import re | ||
import copy | ||
|
||
|
||
# array indices must not contain leading zeros, signs, spaces, decimals, etc | ||
RE_ARRAY_INDEX=re.compile('0|[1-9][0-9]*$') | ||
|
||
|
||
class JsonPointerException(Exception): | ||
pass | ||
_nothing = object() | ||
|
||
|
||
class EndOfList(object): | ||
""" Result of accessing element "-" of a list """ | ||
def set_pointer(doc, pointer, value, inplace=True): | ||
"""Resolves pointer against doc and sets the value of the target within doc. | ||
|
||
def __init__(self, list_): | ||
self.list_ = list_ | ||
With inplace set to true, doc is modified as long as pointer is not the | ||
root. | ||
|
||
>>> obj = {'foo': {'anArray': [ {'prop': 44}], 'another prop': {'baz': 'A string' }}} | ||
|
||
def __repr__(self): | ||
return '{cls}({lst})'.format(cls=self.__class__.__name__, | ||
lst=repr(self.list_)) | ||
>>> set_pointer(obj, '/foo/anArray/0/prop', 55) == \ | ||
{'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}} | ||
True | ||
|
||
>>> set_pointer(obj, '/foo/yet%20another%20prop', 'added prop') == \ | ||
{'foo': {'another prop': {'baz': 'A string'}, 'yet another prop': 'added prop', 'anArray': [{'prop': 55}]}} | ||
True | ||
""" | ||
|
||
_nothing = object() | ||
pointer = JsonPointer(pointer) | ||
return pointer.set(doc, value, inplace) | ||
|
||
|
||
def resolve_pointer(doc, pointer, default=_nothing): | ||
""" | ||
Resolves pointer against doc and returns the referenced object | ||
"""Resolves pointer against doc and returns the referenced object | ||
|
||
>>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}} | ||
>>> obj = {'foo': {'anArray': [ {'prop': 44}], 'another prop': {'baz': 'A string' }}} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why did you change the quotes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code had a mixed use of quotes, in my opinion, one set of quotes should be used consistenly (in cases which allow it), and this type of quotes are more common in python code |
||
|
||
>>> resolve_pointer(obj, '') == obj | ||
True | ||
|
@@ -104,37 +104,53 @@ def resolve_pointer(doc, pointer, default=_nothing): | |
|
||
>>> resolve_pointer(obj, '/some/path', None) == None | ||
True | ||
|
||
""" | ||
|
||
pointer = JsonPointer(pointer) | ||
return pointer.resolve(doc, default) | ||
|
||
def set_pointer(doc, pointer, value, inplace=True): | ||
""" | ||
Resolves pointer against doc and sets the value of the target within doc. | ||
|
||
With inplace set to true, doc is modified as long as pointer is not the | ||
root. | ||
def pairwise(iterable): | ||
""" Transforms a list to a list of tuples of adjacent items | ||
|
||
>>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}} | ||
s -> (s0,s1), (s1,s2), (s2, s3), ... | ||
|
||
>>> set_pointer(obj, '/foo/anArray/0/prop', 55) == \ | ||
{'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}} | ||
True | ||
>>> list(pairwise([])) | ||
[] | ||
|
||
>>> set_pointer(obj, '/foo/yet%20another%20prop', 'added prop') == \ | ||
{'foo': {'another prop': {'baz': 'A string'}, 'yet another prop': 'added prop', 'anArray': [{'prop': 55}]}} | ||
True | ||
>>> list(pairwise([1])) | ||
[] | ||
|
||
>>> list(pairwise([1, 2, 3, 4])) | ||
[(1, 2), (2, 3), (3, 4)] | ||
""" | ||
a, b = tee(iterable) | ||
for _ in b: | ||
break | ||
return izip(a, b) | ||
|
||
pointer = JsonPointer(pointer) | ||
return pointer.set(doc, value, inplace) | ||
|
||
class JsonPointerException(Exception): | ||
pass | ||
|
||
|
||
class EndOfList(object): | ||
"""Result of accessing element "-" of a list""" | ||
|
||
def __init__(self, list_): | ||
self.list_ = list_ | ||
|
||
def __repr__(self): | ||
return '{cls}({lst})'.format(cls=self.__class__.__name__, | ||
lst=repr(self.list_)) | ||
|
||
|
||
class JsonPointer(object): | ||
""" A JSON Pointer that can reference parts of an JSON document """ | ||
"""A JSON Pointer that can reference parts of an JSON document""" | ||
|
||
# Array indices must not contain: | ||
# leading zeros, signs, spaces, decimals, etc | ||
_RE_ARRAY_INDEX = re.compile('0|[1-9][0-9]*$') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why did you move this here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only use of this re is made inside the class, thus it's a const not a global/ |
||
|
||
def __init__(self, pointer): | ||
parts = pointer.split('/') | ||
|
@@ -146,9 +162,8 @@ def __init__(self, pointer): | |
parts = [part.replace('~0', '~') for part in parts] | ||
self.parts = parts | ||
|
||
|
||
def to_last(self, doc): | ||
""" Resolves ptr until the last step, returns (sub-doc, last-step) """ | ||
"""Resolves ptr until the last step, returns (sub-doc, last-step)""" | ||
|
||
if not self.parts: | ||
return doc, None | ||
|
@@ -158,7 +173,6 @@ def to_last(self, doc): | |
|
||
return doc, self.get_part(doc, self.parts[-1]) | ||
|
||
|
||
def resolve(self, doc, default=_nothing): | ||
"""Resolves the pointer against doc and returns the referenced object""" | ||
|
||
|
@@ -174,11 +188,10 @@ def resolve(self, doc, default=_nothing): | |
|
||
return doc | ||
|
||
|
||
get = resolve | ||
|
||
def set(self, doc, value, inplace=True): | ||
""" Resolve the pointer against the doc and replace the target with value. """ | ||
"""Resolve the pointer against the doc and replace the target with value.""" | ||
|
||
if len(self.parts) == 0: | ||
if inplace: | ||
|
@@ -194,7 +207,7 @@ def set(self, doc, value, inplace=True): | |
return doc | ||
|
||
def get_part(self, doc, part): | ||
""" Returns the next step in the correct type """ | ||
"""Returns the next step in the correct type""" | ||
|
||
if isinstance(doc, Mapping): | ||
return part | ||
|
@@ -204,26 +217,27 @@ def get_part(self, doc, part): | |
if part == '-': | ||
return part | ||
|
||
if not RE_ARRAY_INDEX.match(str(part)): | ||
raise JsonPointerException("'%s' is not a valid list index" % (part, )) | ||
if not self._RE_ARRAY_INDEX.match(str(part)): | ||
raise JsonPointerException("'%s' is not a valid sequence index" % part) | ||
|
||
return int(part) | ||
|
||
elif hasattr(doc, '__getitem__'): | ||
# Allow indexing via ducktyping if the target has defined __getitem__ | ||
# Allow indexing via ducktyping | ||
# if the target has defined __getitem__ | ||
return part | ||
|
||
else: | ||
raise JsonPointerException("Document '%s' does not support indexing, " | ||
"must be dict/list or support __getitem__" % type(doc)) | ||
|
||
raise JsonPointerException("document '%s' does not support indexing, " | ||
"must be mapping/sequence or support __getitem__" % type(doc)) | ||
|
||
def walk(self, doc, part): | ||
""" Walks one step in doc and returns the referenced part """ | ||
"""Walks one step in doc and returns the referenced part""" | ||
|
||
part = self.get_part(doc, part) | ||
|
||
assert (type(doc) in (dict, list) or hasattr(doc, '__getitem__')), "invalid document type %s" % (type(doc),) | ||
assert hasattr(doc, '__getitem__'), \ | ||
'invalid document type %s' % type(doc) | ||
|
||
if isinstance(doc, Mapping): | ||
try: | ||
|
@@ -248,13 +262,14 @@ def walk(self, doc, part): | |
return doc[part] | ||
|
||
def contains(self, ptr): | ||
""" Returns True if self contains the given ptr """ | ||
"""Returns True if self contains the given ptr""" | ||
|
||
return len(self.parts) > len(ptr.parts) and \ | ||
self.parts[:len(ptr.parts)] == ptr.parts | ||
self.parts[:len(ptr.parts)] == ptr.parts | ||
|
||
@property | ||
def path(self): | ||
""" Returns the string representation of the pointer | ||
"""Returns the string representation of the pointer | ||
|
||
>>> ptr = JsonPointer('/~0/0/~1').path == '/~0/0/~1' | ||
""" | ||
|
@@ -263,24 +278,24 @@ def path(self): | |
return ''.join('/' + part for part in parts) | ||
|
||
def __eq__(self, other): | ||
""" compares a pointer to another object | ||
"""Compares a pointer to another object | ||
|
||
Pointers can be compared by comparing their strings (or splitted | ||
strings), because no two different parts can point to the same | ||
structure in an object (eg no different number representations) """ | ||
structure in an object (eg no different number representations) | ||
""" | ||
|
||
if not isinstance(other, JsonPointer): | ||
return False | ||
|
||
return self.parts == other.parts | ||
|
||
|
||
def __hash__(self): | ||
return hash(tuple(self.parts)) | ||
|
||
@classmethod | ||
def from_parts(cls, parts): | ||
""" Constructs a JsonPointer from a list of (unescaped) paths | ||
"""Constructs a JsonPointer from a list of (unescaped) paths | ||
|
||
>>> JsonPointer.from_parts(['a', '~', '/', 0]).path == '/a/~0/~1/0' | ||
True | ||
|
@@ -290,22 +305,3 @@ def from_parts(cls, parts): | |
parts = [part.replace('/', '~1') for part in parts] | ||
ptr = cls(''.join('/' + part for part in parts)) | ||
return ptr | ||
|
||
|
||
|
||
def pairwise(iterable): | ||
""" s -> (s0,s1), (s1,s2), (s2, s3), ... | ||
|
||
>>> list(pairwise([])) | ||
[] | ||
|
||
>>> list(pairwise([1])) | ||
[] | ||
|
||
>>> list(pairwise([1, 2, 3, 4])) | ||
[(1, 2), (2, 3), (3, 4)] | ||
""" | ||
a, b = tee(iterable) | ||
for _ in b: | ||
break | ||
return izip(a, b) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why did you rearrange the methods and classes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks much more clearer and nicer, more organized