diff --git a/.travis.yml b/.travis.yml index 62ad875..d5e3fe7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,16 @@ language: python +dist: jammy python: -- '2.7' -- '3.5' -- '3.6' - '3.7' - '3.8' - '3.9' -- 3.10-dev +- '3.10' +- '3.11' +- '3.12-dev' - nightly - pypy3 install: +- travis_retry pip install --upgrade pip setuptools wheel - travis_retry pip install coveralls script: - coverage run --source=jsonpointer tests.py diff --git a/jsonpointer.py b/jsonpointer.py index b45684d..0199d1b 100644 --- a/jsonpointer.py +++ b/jsonpointer.py @@ -29,40 +29,50 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +from __future__ import annotations """ Identify specific nodes in a JSON document (RFC 6901) """ -from __future__ import unicode_literals - # Will be parsed by setup.py to determine package metadata -__author__ = 'Stefan Kögl ' -__version__ = '2.3' -__website__ = 'https://github.com/stefankoegl/python-json-pointer' -__license__ = 'Modified BSD License' +__author__ = "Stefan Kögl " +__version__ = "3.0" +__website__ = "https://github.com/stefankoegl/python-json-pointer" +__license__ = "Modified BSD License" -try: - from itertools import izip - str = unicode - encode_str = lambda u: u.encode("raw_unicode_escape") -except ImportError: # Python 3 - izip = zip - encode_str = lambda u: u +import copy +import re +from collections.abc import Mapping, Sequence +from itertools import tee, chain +from typing import * -try: - from collections.abc import Mapping, Sequence -except ImportError: # Python 3 - from collections import Mapping, Sequence +T = TypeVar("T") +TJsonPointer = TypeVar("TJsonPointer", bound="JsonPointer") +JsonType = Union[Dict[str, "JsonType"], List["JsonType"], str, bool, int, float, None] +_Parts = Union[str, int] +_Suffixes = Union["JsonPointer", str, Iterable[str]] +_Undefined = Union[T, "_Nothing"] -from itertools import tee, chain -import re -import copy +class _Singleton(type): + _instances = {} + + def __call__(cls: Type[T], *args, **kwargs) -> T: + if cls not in cls._instances: + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] -_nothing = object() + +class _Nothing(metaclass=_Singleton): + pass -def set_pointer(doc, pointer, value, inplace=True): +_nothing = _Nothing() + + +def set_pointer( + doc: JsonType, pointer: str, value: JsonType, inplace: bool = True +) -> JsonType: """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 @@ -84,12 +94,14 @@ def set_pointer(doc, pointer, value, inplace=True): True """ - pointer = JsonPointer(pointer) - return pointer.set(doc, value, inplace) + _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 +def resolve_pointer( + doc: JsonType, pointer: str, default: _Undefined[JsonType] = _nothing +) -> JsonType: + """Resolves pointer against doc and returns the referenced object >>> obj = {'foo': {'anArray': [ {'prop': 44}], 'another prop': {'baz': 'A string' }}, 'a%20b': 1, 'c d': 2} @@ -124,12 +136,12 @@ def resolve_pointer(doc, pointer, default=_nothing): True """ - pointer = JsonPointer(pointer) - return pointer.resolve(doc, default) + _pointer = JsonPointer(pointer) + return _pointer.resolve(doc, default) -def pairwise(iterable): - """ Transforms a list to a list of tuples of adjacent items +def pairwise(iterable: Iterable[T]) -> Iterable[tuple[T, T]]: + """Transforms a list to a list of tuples of adjacent items s -> (s0,s1), (s1,s2), (s2, s3), ... @@ -145,48 +157,47 @@ def pairwise(iterable): a, b = tee(iterable) for _ in b: break - return izip(a, b) + return zip(a, b) class JsonPointerException(Exception): pass -class EndOfList(object): +class EndOfList: """Result of accessing element "-" of a list""" - def __init__(self, list_): + def __init__(self, list_: list): self.list_ = list_ - def __repr__(self): - return '{cls}({lst})'.format(cls=self.__class__.__name__, - lst=repr(self.list_)) + def __repr__(self) -> str: + return "{cls}({lst})".format(cls=self.__class__.__name__, lst=repr(self.list_)) -class JsonPointer(object): +class JsonPointer: """A JSON Pointer that can reference parts of a JSON document""" # Array indices must not contain: # leading zeros, signs, spaces, decimals, etc - _RE_ARRAY_INDEX = re.compile('0|[1-9][0-9]*$') - _RE_INVALID_ESCAPE = re.compile('(~[^01]|~$)') - - def __init__(self, pointer): + _RE_ARRAY_INDEX = re.compile("0|[1-9][0-9]*$") + _RE_INVALID_ESCAPE = re.compile("(~[^01]|~$)") + def __init__(self, pointer: str): # validate escapes invalid_escape = self._RE_INVALID_ESCAPE.search(pointer) if invalid_escape: - raise JsonPointerException('Found invalid escape {}'.format( - invalid_escape.group())) + raise JsonPointerException( + "Found invalid escape {}".format(invalid_escape.group()) + ) - parts = pointer.split('/') - if parts.pop(0) != '': - raise JsonPointerException('Location must start with /') + parts = pointer.split("/") + if parts.pop(0) != "": + raise JsonPointerException("Location must start with /") parts = [unescape(part) for part in parts] self.parts = parts - def to_last(self, doc): + def to_last(self, doc: JsonType) -> tuple[JsonType, Optional[str]]: """Resolves ptr until the last step, returns (sub-doc, last-step)""" if not self.parts: @@ -195,13 +206,14 @@ def to_last(self, doc): for part in self.parts[:-1]: doc = self.walk(doc, part) - return doc, JsonPointer.get_part(doc, self.parts[-1]) + return doc, self.get_part(doc, self.parts[-1]) - def resolve(self, doc, default=_nothing): + def resolve( + self, doc: JsonType, default: _Undefined[JsonType] = _nothing + ) -> JsonType: """Resolves the pointer against doc and returns the referenced object""" for part in self.parts: - try: doc = self.walk(doc, part) except JsonPointerException: @@ -212,14 +224,16 @@ def resolve(self, doc, default=_nothing): return doc - get = resolve + def get(self, doc: JsonType, default: _Undefined[JsonType] = _nothing) -> JsonType: + """alias of resolve""" + return self.resolve(doc, default) - def set(self, doc, value, inplace=True): + def set(self, doc: JsonType, value: JsonType, inplace: bool = True) -> JsonType: """Resolve the pointer against the doc and replace the target with value.""" if len(self.parts) == 0: if inplace: - raise JsonPointerException('Cannot set root in place') + raise JsonPointerException("Cannot set root in place") return value if not inplace: @@ -227,7 +241,7 @@ def set(self, doc, value, inplace=True): (parent, part) = self.to_last(doc) - if isinstance(parent, Sequence) and part == '-': + if isinstance(parent, Sequence) and part == "-": parent.append(value) else: parent[part] = value @@ -235,7 +249,7 @@ def set(self, doc, value, inplace=True): return doc @classmethod - def get_part(cls, doc, part): + def get_part(cls, doc: JsonType, part: str) -> str: """Returns the next step in the correct type""" if isinstance(doc, Mapping): @@ -243,45 +257,46 @@ def get_part(cls, doc, part): elif isinstance(doc, Sequence): - if part == '-': + if part == "-": return part - if not JsonPointer._RE_ARRAY_INDEX.match(str(part)): + if not cls._RE_ARRAY_INDEX.match(str(part)): raise JsonPointerException("'%s' is not a valid sequence index" % part) return int(part) - elif hasattr(doc, '__getitem__'): + elif hasattr(doc, "__getitem__"): # Allow indexing via ducktyping # if the target has defined __getitem__ return part else: - raise JsonPointerException("Document '%s' does not support indexing, " - "must be mapping/sequence or support __getitem__" % type(doc)) - + raise JsonPointerException( + "Document '%s' does not support indexing, " + "must be mapping/sequence or support __getitem__" % type(doc) + ) + def get_parts(self): """Returns the list of the parts. For example, JsonPointer('/a/b').get_parts() == ['a', 'b']""" - - return self.parts + return self.parts - def walk(self, doc, part): - """ Walks one step in doc and returns the referenced part """ + def walk(self, doc: JsonType, part: str) -> JsonType: + """Walks one step in doc and returns the referenced part""" - part = JsonPointer.get_part(doc, part) + part = self.get_part(doc, part) - assert hasattr(doc, '__getitem__'), "invalid document type %s" % (type(doc),) + assert hasattr(doc, "__getitem__"), "invalid document type %s" % (type(doc),) if isinstance(doc, Sequence): - if part == '-': + if part == "-": return EndOfList(doc) try: return doc[part] except IndexError: - raise JsonPointerException("index '%s' is out of bounds" % (part, )) + raise JsonPointerException("index '%s' is out of bounds" % (part,)) # Else the object is a mapping or supports __getitem__(so assume custom indexing) try: @@ -290,17 +305,16 @@ def walk(self, doc, part): except KeyError: raise JsonPointerException("member '%s' not found in %s" % (part, doc)) + def contains(self, ptr: JsonPointer) -> bool: + """Returns True if self contains the given ptr""" + return self.parts[: len(ptr.parts)] == ptr.parts - def contains(self, ptr): - """ Returns True if self contains the given ptr """ - return self.parts[:len(ptr.parts)] == ptr.parts - - def __contains__(self, item): - """ Returns True if self contains the given ptr """ + def __contains__(self, item: JsonPointer) -> bool: + """Returns True if self contains the given ptr""" return self.contains(item) - def join(self, suffix): - """ Returns a new JsonPointer with the given suffix append to this ptr """ + def join(self: TJsonPointer, suffix: _Suffixes) -> TJsonPointer: + """Returns a new JsonPointer with the given suffix append to this ptr""" if isinstance(suffix, JsonPointer): suffix_parts = suffix.parts elif isinstance(suffix, str): @@ -308,24 +322,23 @@ def join(self, suffix): else: suffix_parts = suffix try: - return JsonPointer.from_parts(chain(self.parts, suffix_parts)) + return self.from_parts(chain(self.parts, suffix_parts)) except: raise JsonPointerException("Invalid suffix") - def __truediv__(self, suffix): # Python 3 + def __truediv__(self: TJsonPointer, suffix: _Suffixes) -> TJsonPointer: return self.join(suffix) - __div__ = __truediv__ # Python 2 @property - def path(self): + def path(self) -> str: """Returns the string representation of the pointer >>> ptr = JsonPointer('/~0/0/~1').path == '/~0/0/~1' """ parts = [escape(part) for part in self.parts] - return ''.join('/' + part for part in parts) + return "".join("/" + part for part in parts) - def __eq__(self, other): + def __eq__(self, other: Union[JsonPointer, Any]) -> bool: """Compares a pointer to another object Pointers can be compared by comparing their strings (or splitted @@ -338,29 +351,32 @@ def __eq__(self, other): return self.parts == other.parts - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self.parts)) - def __str__(self): - return encode_str(self.path) + def __str__(self) -> str: + return self.path - def __repr__(self): - return "JsonPointer(" + repr(self.path) + ")" + def __repr__(self) -> str: + return type(self).__name__ + "(" + repr(self.path) + ")" @classmethod - def from_parts(cls, parts): + def from_parts(cls: Type[TJsonPointer], parts: Iterable[_Parts]) -> TJsonPointer: """Constructs a JsonPointer from a list of (unescaped) paths >>> JsonPointer.from_parts(['a', '~', '/', 0]).path == '/a/~0/~1/0' True """ parts = [escape(str(part)) for part in parts] - ptr = cls(''.join('/' + part for part in parts)) + ptr = cls("".join("/" + part for part in parts)) return ptr -def escape(s): - return s.replace('~', '~0').replace('/', '~1') +def escape(s: str) -> str: + # Escape `~` first! https://www.rfc-editor.org/rfc/rfc6901#section-4 + return s.replace("~", "~0").replace("/", "~1") + -def unescape(s): - return s.replace('~1', '/').replace('~0', '~') +def unescape(s: str) -> str: + # Unscape `~` last! https://www.rfc-editor.org/rfc/rfc6901#section-4 + return s.replace("~1", "/").replace("~0", "~") diff --git a/setup.py b/setup.py index da27f3f..d40f90a 100644 --- a/setup.py +++ b/setup.py @@ -38,14 +38,12 @@ 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries', @@ -64,5 +62,5 @@ py_modules=MODULES, scripts=['bin/jsonpointer'], classifiers=CLASSIFIERS, - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=3.7.0', ) diff --git a/tests.py b/tests.py index 9252369..880167a 100755 --- a/tests.py +++ b/tests.py @@ -7,15 +7,22 @@ import unittest import sys import copy -from jsonpointer import resolve_pointer, EndOfList, JsonPointerException, \ - JsonPointer, set_pointer +from jsonpointer import ( + resolve_pointer, + EndOfList, + JsonPointerException, + JsonPointer, + set_pointer, + _Nothing, + _nothing, +) class SpecificationTests(unittest.TestCase): - """ Tests all examples from the JSON Pointer specification """ + """Tests all examples from the JSON Pointer specification""" def test_example(self): - doc = { + doc = { "foo": ["bar", "baz"], "": 0, "a/b": 1, @@ -23,9 +30,9 @@ def test_example(self): "e^f": 3, "g|h": 4, "i\\j": 5, - "k\"l": 6, + 'k"l': 6, " ": 7, - "m~n": 8 + "m~n": 8, } self.assertEqual(resolve_pointer(doc, ""), doc) @@ -37,15 +44,12 @@ def test_example(self): self.assertEqual(resolve_pointer(doc, "/e^f"), 3) self.assertEqual(resolve_pointer(doc, "/g|h"), 4) self.assertEqual(resolve_pointer(doc, "/i\\j"), 5) - self.assertEqual(resolve_pointer(doc, "/k\"l"), 6) + self.assertEqual(resolve_pointer(doc, '/k"l'), 6) self.assertEqual(resolve_pointer(doc, "/ "), 7) self.assertEqual(resolve_pointer(doc, "/m~0n"), 8) - def test_eol(self): - doc = { - "foo": ["bar", "baz"] - } + doc = {"foo": ["bar", "baz"]} self.assertTrue(isinstance(resolve_pointer(doc, "/foo/-"), EndOfList)) self.assertRaises(JsonPointerException, resolve_pointer, doc, "/foo/-/1") @@ -61,10 +65,10 @@ def test_round_trip(self): "/e^f", "/g|h", "/i\\j", - "/k\"l", + '/k"l', "/ ", "/m~0n", - '/\xee', + "/\xee", ] for path in paths: ptr = JsonPointer(path) @@ -86,7 +90,7 @@ def test_str_and_repr(self): ("/e^f", "/e^f", "JsonPointer({u}'/e^f')"), ("/g|h", "/g|h", "JsonPointer({u}'/g|h')"), ("/i\\j", "/i\\j", "JsonPointer({u}'/i\\\\j')"), - ("/k\"l", "/k\"l", "JsonPointer({u}'/k\"l')"), + ('/k"l', '/k"l', "JsonPointer({u}'/k\"l')"), ("/ ", "/ ", "JsonPointer({u}'/ ')"), ("/m~0n", "/m~0n", "JsonPointer({u}'/m~0n')"), ] @@ -94,21 +98,13 @@ def test_str_and_repr(self): ptr = JsonPointer(path) self.assertEqual(path, ptr.path) - if sys.version_info[0] == 2: - u_str = "u" - else: - u_str = "" + u_str = "" self.assertEqual(ptr_str, str(ptr)) self.assertEqual(ptr_repr.format(u=u_str), repr(ptr)) - if sys.version_info[0] == 2: - path = "/\xee" - ptr_str = b"/\xee" - ptr_repr = "JsonPointer(u'/\\xee')" - else: - path = "/\xee" - ptr_str = "/\xee" - ptr_repr = "JsonPointer('/\xee')" + path = "/\xee" + ptr_str = "/\xee" + ptr_repr = "JsonPointer('/\xee')" ptr = JsonPointer(path) self.assertEqual(path, ptr.path) @@ -122,18 +118,18 @@ def test_str_and_repr(self): def test_parts(self): paths = [ ("", []), - ("/foo", ['foo']), - ("/foo/0", ['foo', '0']), - ("/", ['']), - ("/a~1b", ['a/b']), - ("/c%d", ['c%d']), - ("/e^f", ['e^f']), - ("/g|h", ['g|h']), - ("/i\\j", ['i\\j']), - ("/k\"l", ['k"l']), - ("/ ", [' ']), - ("/m~0n", ['m~n']), - ('/\xee', ['\xee']), + ("/foo", ["foo"]), + ("/foo/0", ["foo", "0"]), + ("/", [""]), + ("/a~1b", ["a/b"]), + ("/c%d", ["c%d"]), + ("/e^f", ["e^f"]), + ("/g|h", ["g|h"]), + ("/i\\j", ["i\\j"]), + ('/k"l', ['k"l']), + ("/ ", [" "]), + ("/m~0n", ["m~n"]), + ("/\xee", ["\xee"]), ] for path in paths: ptr = JsonPointer(path[0]) @@ -141,7 +137,6 @@ def test_parts(self): class ComparisonTests(unittest.TestCase): - def setUp(self): self.ptr1 = JsonPointer("/a/b/c") self.ptr2 = JsonPointer("/a/b") @@ -211,51 +206,48 @@ def test_join_magic(self): ptr12e = self.ptr1 / ["a", "b"] self.assertEqual(ptr12e.path, "/a/b/c/a/b") -class WrongInputTests(unittest.TestCase): +class WrongInputTests(unittest.TestCase): def test_no_start_slash(self): # an exception is raised when the pointer string does not start with / - self.assertRaises(JsonPointerException, JsonPointer, 'some/thing') + self.assertRaises(JsonPointerException, JsonPointer, "some/thing") def test_invalid_index(self): # 'a' is not a valid list index doc = [0, 1, 2] - self.assertRaises(JsonPointerException, resolve_pointer, doc, '/a') + self.assertRaises(JsonPointerException, resolve_pointer, doc, "/a") def test_oob(self): # this list does not have 10 members doc = [0, 1, 2] - self.assertRaises(JsonPointerException, resolve_pointer, doc, '/10') + self.assertRaises(JsonPointerException, resolve_pointer, doc, "/10") def test_trailing_escape(self): - self.assertRaises(JsonPointerException, JsonPointer, '/foo/bar~') + self.assertRaises(JsonPointerException, JsonPointer, "/foo/bar~") def test_invalid_escape(self): - self.assertRaises(JsonPointerException, JsonPointer, '/foo/bar~2') + self.assertRaises(JsonPointerException, JsonPointer, "/foo/bar~2") class ToLastTests(unittest.TestCase): - def test_empty_path(self): - doc = {'a': [1, 2, 3]} - ptr = JsonPointer('') + doc = {"a": [1, 2, 3]} + ptr = JsonPointer("") last, nxt = ptr.to_last(doc) self.assertEqual(doc, last) self.assertTrue(nxt is None) - def test_path(self): - doc = {'a': [{'b': 1, 'c': 2}, 5]} - ptr = JsonPointer('/a/0/b') + doc = {"a": [{"b": 1, "c": 2}, 5]} + ptr = JsonPointer("/a/0/b") last, nxt = ptr.to_last(doc) - self.assertEqual(last, {'b': 1, 'c': 2}) - self.assertEqual(nxt, 'b') + self.assertEqual(last, {"b": 1, "c": 2}) + self.assertEqual(nxt, "b") class SetTests(unittest.TestCase): - def test_set(self): - doc = { + doc = { "foo": ["bar", "baz"], "": 0, "a/b": 1, @@ -263,9 +255,9 @@ def test_set(self): "e^f": 3, "g|h": 4, "i\\j": 5, - "k\"l": 6, + 'k"l': 6, " ": 7, - "m~n": 8 + "m~n": 8, } origdoc = copy.deepcopy(doc) @@ -284,7 +276,7 @@ def test_set(self): newdoc = set_pointer(doc, "/fud", {}, inplace=False) newdoc = set_pointer(newdoc, "/fud/gaw", [1, 2, 3], inplace=False) - self.assertEqual(resolve_pointer(newdoc, "/fud"), {'gaw' : [1, 2, 3]}) + self.assertEqual(resolve_pointer(newdoc, "/fud"), {"gaw": [1, 2, 3]}) newdoc = set_pointer(doc, "", 9, inplace=False) self.assertEqual(newdoc, 9) @@ -306,14 +298,13 @@ def test_set(self): self.assertRaises(JsonPointerException, set_pointer, doc, "/fud/gaw", 9) set_pointer(doc, "/fud", {}) - set_pointer(doc, "/fud/gaw", [1, 2, 3] ) - self.assertEqual(resolve_pointer(doc, "/fud"), {'gaw' : [1, 2, 3]}) + set_pointer(doc, "/fud/gaw", [1, 2, 3]) + self.assertEqual(resolve_pointer(doc, "/fud"), {"gaw": [1, 2, 3]}) self.assertRaises(JsonPointerException, set_pointer, doc, "", 9) class AltTypesTests(unittest.TestCase): - class Node(object): def __init__(self, name, parent=None): self.name = name @@ -330,17 +321,17 @@ def set_right(self, node): self.right = node def __getitem__(self, key): - if key == 'left': + if key == "left": return self.left - if key == 'right': + if key == "right": return self.right raise KeyError("Only left and right supported") def __setitem__(self, key, val): - if key == 'left': + if key == "left": return self.set_left(val) - if key == 'right': + if key == "right": return self.set_right(val) raise KeyError("Only left and right supported: %s" % key) @@ -348,35 +339,35 @@ def __setitem__(self, key, val): class mdict(object): def __init__(self, d): self._d = d + def __getitem__(self, item): return self._d[item] - mdict = mdict({'root': {'1': {'2': '3'}}}) + mdict = mdict({"root": {"1": {"2": "3"}}}) Node = Node - def test_alttypes(self): Node = self.Node - root = Node('root') - root.set_left(Node('a')) - root.left.set_left(Node('aa')) - root.left.set_right(Node('ab')) - root.set_right(Node('b')) - root.right.set_left(Node('ba')) - root.right.set_right(Node('bb')) + root = Node("root") + root.set_left(Node("a")) + root.left.set_left(Node("aa")) + root.left.set_right(Node("ab")) + root.set_right(Node("b")) + root.right.set_left(Node("ba")) + root.right.set_right(Node("bb")) - self.assertEqual(resolve_pointer(root, '/left').name, 'a') - self.assertEqual(resolve_pointer(root, '/left/right').name, 'ab') - self.assertEqual(resolve_pointer(root, '/right').name, 'b') - self.assertEqual(resolve_pointer(root, '/right/left').name, 'ba') + self.assertEqual(resolve_pointer(root, "/left").name, "a") + self.assertEqual(resolve_pointer(root, "/left/right").name, "ab") + self.assertEqual(resolve_pointer(root, "/right").name, "b") + self.assertEqual(resolve_pointer(root, "/right/left").name, "ba") - newroot = set_pointer(root, '/left/right', Node('AB'), inplace=False) - self.assertEqual(resolve_pointer(root, '/left/right').name, 'ab') - self.assertEqual(resolve_pointer(newroot, '/left/right').name, 'AB') + newroot = set_pointer(root, "/left/right", Node("AB"), inplace=False) + self.assertEqual(resolve_pointer(root, "/left/right").name, "ab") + self.assertEqual(resolve_pointer(newroot, "/left/right").name, "AB") - set_pointer(root, '/left/right', Node('AB')) - self.assertEqual(resolve_pointer(root, '/left/right').name, 'AB') + set_pointer(root, "/left/right", Node("AB")) + self.assertEqual(resolve_pointer(root, "/left/right").name, "AB") def test_mock_dict_sanity(self): doc = self.mdict @@ -384,9 +375,9 @@ def test_mock_dict_sanity(self): # TODO: Generate this automatically for any given object path_to_expected_value = { - '/root/1': {'2': '3'}, - '/root': {'1': {'2': '3'}}, - '/root/1/2': '3', + "/root/1": {"2": "3"}, + "/root": {"1": {"2": "3"}}, + "/root/1/2": "3", } for path, expected_value in iter(path_to_expected_value.items()): @@ -396,19 +387,27 @@ def test_mock_dict_returns_default(self): doc = self.mdict default = None - path_to_expected_value = { - '/foo': default, - '/x/y/z/d': default - } + path_to_expected_value = {"/foo": default, "/x/y/z/d": default} for path, expected_value in iter(path_to_expected_value.items()): self.assertEqual(resolve_pointer(doc, path, default), expected_value) def test_mock_dict_raises_key_error(self): doc = self.mdict - self.assertRaises(JsonPointerException, resolve_pointer, doc, '/foo') - self.assertRaises(JsonPointerException, resolve_pointer, doc, '/root/1/2/3/4') + self.assertRaises(JsonPointerException, resolve_pointer, doc, "/foo") + self.assertRaises(JsonPointerException, resolve_pointer, doc, "/root/1/2/3/4") + +class HelperTests(unittest.TestCase): + def test_singleton(self): + self.assertIs(_Nothing(), _nothing) + + def test_get_alias(self): + doc = {"foo":[2,4,8,16]} + pointer = JsonPointer("/foo/3") + self.assertEqual(pointer.get(doc), 16) + pointer2 = JsonPointer("/bar") + self.assertEqual(pointer2.get(doc, 42), 42) suite = unittest.TestSuite() @@ -418,8 +417,9 @@ def test_mock_dict_raises_key_error(self): suite.addTest(unittest.makeSuite(ToLastTests)) suite.addTest(unittest.makeSuite(SetTests)) suite.addTest(unittest.makeSuite(AltTypesTests)) +suite.addTest(unittest.makeSuite(HelperTests)) -modules = ['jsonpointer'] +modules = ["jsonpointer"] for module in modules: m = __import__(module, fromlist=[module])