diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 554c462..fa172c1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,34 +1,58 @@ name: Python package -on: [push] +on: + push: + branches: + - master + pull_request: + branches: + - master jobs: - build: + lint: + name: "flake8 on code" + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: 3.12 + allow-prereleases: true + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 + - name: Run flake8 + shell: bash + run: | + flake8 + test: + needs: [ lint ] runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: ["2.7", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11", "3.12" ] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest - pip install coveralls -# - name: Lint with flake8 -# run: | - # stop the build if there are Python syntax errors or undefined names - # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + pip install -r requirements-dev.txt + - name: Test run: | make coverage diff --git a/.gitignore b/.gitignore index ce09cfb..861d6b2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dist *.swp doc/_build *.egg-info +.idea diff --git a/bin/jsonpointer b/bin/jsonpointer index d577d01..ba2117c 100755 --- a/bin/jsonpointer +++ b/bin/jsonpointer @@ -1,14 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import print_function -import sys -import os.path -import json -import jsonpointer import argparse +import json +import sys +import jsonpointer parser = argparse.ArgumentParser( description='Resolve a JSON pointer on JSON files') @@ -20,7 +18,7 @@ ptr_group.add_argument('-f', '--pointer-file', type=argparse.FileType('r'), nargs='?', help='File containing a JSON pointer expression') -ptr_group.add_argument('POINTER', type=str, nargs='?', +ptr_group.add_argument('POINTER', type=str, nargs='?', help='A JSON pointer expression') parser.add_argument('FILE', type=argparse.FileType('r'), nargs='+', diff --git a/jsonpointer.py b/jsonpointer.py index 47d90ad..3e97add 100644 --- a/jsonpointer.py +++ b/jsonpointer.py @@ -32,38 +32,22 @@ """ 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.4' +__version__ = '3.0.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 - -try: - from collections.abc import Mapping, Sequence -except ImportError: # Python 3 - from collections import Mapping, Sequence - -from itertools import tee, chain -import re import copy - +import re +from collections.abc import Mapping, Sequence +from itertools import tee, chain _nothing = object() def set_pointer(doc, pointer, value, inplace=True): - """Resolves pointer against doc and sets the value of the target within doc. + """Resolves a 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. @@ -145,7 +129,7 @@ def pairwise(iterable): a, b = tee(iterable) for _ in b: break - return izip(a, b) + return zip(a, b) class JsonPointerException(Exception): @@ -259,12 +243,11 @@ def get_part(cls, doc, part): else: 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 """ @@ -281,7 +264,7 @@ def walk(self, doc, part): 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,7 +273,6 @@ def walk(self, doc, part): except KeyError: raise JsonPointerException("member '%s' not found in %s" % (part, doc)) - def contains(self, ptr): """ Returns True if self contains the given ptr """ return self.parts[:len(ptr.parts)] == ptr.parts @@ -309,12 +291,11 @@ def join(self, suffix): suffix_parts = suffix try: return JsonPointer.from_parts(chain(self.parts, suffix_parts)) - except: + except: # noqa E722 raise JsonPointerException("Invalid suffix") - def __truediv__(self, suffix): # Python 3 + def __truediv__(self, suffix): # Python 3 return self.join(suffix) - __div__ = __truediv__ # Python 2 @property def path(self): @@ -342,10 +323,10 @@ def __hash__(self): return hash(tuple(self.parts)) def __str__(self): - return encode_str(self.path) + return self.path def __repr__(self): - return "JsonPointer(" + repr(self.path) + ")" + return type(self).__name__ + "(" + repr(self.path) + ")" @classmethod def from_parts(cls, parts): @@ -362,5 +343,6 @@ def from_parts(cls, parts): def escape(s): return s.replace('~', '~0').replace('/', '~1') + def unescape(s): return s.replace('~1', '/').replace('~0', '~') diff --git a/requirements-dev.txt b/requirements-dev.txt index 9fcb076..239fcca 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,4 @@ wheel -twine>=1.11.0 -setuptools>=38.6.0 +setuptools +coverage +flake8 diff --git a/setup.cfg b/setup.cfg index 2a9acf1..ab8f354 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,6 @@ [bdist_wheel] universal = 1 + +[flake8] +max-line-length = 120 +exclude = .git,.tox,dist,doc,*egg,build,.venv \ No newline at end of file diff --git a/setup.py b/setup.py index 37d3657..3e87a4c 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,10 @@ #!/usr/bin/env python -from setuptools import setup -import re import io import os.path +import re + +from setuptools import setup dirname = os.path.dirname(os.path.abspath(__file__)) filename = os.path.join(dirname, 'jsonpointer.py') @@ -14,7 +15,7 @@ PACKAGE = 'jsonpointer' MODULES = ( - 'jsonpointer', + 'jsonpointer', ) AUTHOR_EMAIL = metadata['author'] @@ -26,10 +27,8 @@ # Extract name and e-mail ("Firstname Lastname ") AUTHOR, EMAIL = re.match(r'(.*) <(.*)>', AUTHOR_EMAIL).groups() - with open('README.md') as readme: - long_description = readme.read() - + long_description = readme.read() CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', @@ -38,8 +37,6 @@ '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.7', 'Programming Language :: Python :: 3.8', @@ -65,5 +62,5 @@ py_modules=MODULES, scripts=['bin/jsonpointer'], classifiers=CLASSIFIERS, - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*', -) + python_requires='>=3.7', + ) diff --git a/tests.py b/tests.py index 6b4b8cc..7b1cdac 100755 --- a/tests.py +++ b/tests.py @@ -3,20 +3,21 @@ from __future__ import unicode_literals +import copy import doctest -import unittest import sys -import copy +import unittest + import jsonpointer from jsonpointer import resolve_pointer, EndOfList, JsonPointerException, \ - JsonPointer, set_pointer + JsonPointer, set_pointer class SpecificationTests(unittest.TestCase): """ Tests all examples from the JSON Pointer specification """ def test_example(self): - doc = { + doc = { "foo": ["bar", "baz"], "": 0, "a/b": 1, @@ -42,7 +43,6 @@ def test_example(self): self.assertEqual(resolve_pointer(doc, "/ "), 7) self.assertEqual(resolve_pointer(doc, "/m~0n"), 8) - def test_eol(self): doc = { "foo": ["bar", "baz"] @@ -165,19 +165,16 @@ def test_eq_hash(self): self.assertFalse(p1 == "/something/1/b") def test_contains(self): - self.assertTrue(self.ptr1.contains(self.ptr2)) self.assertTrue(self.ptr1.contains(self.ptr1)) self.assertFalse(self.ptr1.contains(self.ptr3)) def test_contains_magic(self): - self.assertTrue(self.ptr2 in self.ptr1) self.assertTrue(self.ptr1 in self.ptr1) self.assertFalse(self.ptr3 in self.ptr1) def test_join(self): - ptr12a = self.ptr1.join(self.ptr2) self.assertEqual(ptr12a.path, "/a/b/c/a/b") @@ -196,7 +193,6 @@ def test_join(self): self.assertRaises(JsonPointerException, self.ptr1.join, 0) def test_join_magic(self): - ptr12a = self.ptr1 / self.ptr2 self.assertEqual(ptr12a.path, "/a/b/c/a/b") @@ -212,6 +208,7 @@ def test_join_magic(self): ptr12e = self.ptr1 / ["a", "b"] self.assertEqual(ptr12e.path, "/a/b/c/a/b") + class WrongInputTests(unittest.TestCase): def test_no_start_slash(self): @@ -244,7 +241,6 @@ def test_empty_path(self): 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') @@ -256,7 +252,7 @@ def test_path(self): class SetTests(unittest.TestCase): def test_set(self): - doc = { + doc = { "foo": ["bar", "baz"], "": 0, "a/b": 1, @@ -285,7 +281,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) @@ -307,14 +303,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 @@ -349,13 +344,13 @@ 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'}}}) Node = Node - def test_alttypes(self): Node = self.Node