jazzband / geojson

Python bindings and utilities for GeoJSON
https://pypi.python.org/pypi/geojson/
BSD 3-Clause "New" or "Revised" License
913 stars 121 forks source link

encoding parameter to json.loads has been deprecated and removed in Python 3.9 #150

Closed tirkarthi closed 4 years ago

tirkarthi commented 4 years ago

encoding parameter has been deprecated and removed in Python 3.9. geojson.loads internally uses json.loads. This will cause test failure.

https://github.com/jazzband/geojson/blob/3cef47cf977320aace537bf4b154f5c61ef9afb2/tests/test_features.py#L85

Ref : https://github.com/python/cpython/pull/18075

Sample fa

pytest -v tests/test_features.py
=============================== test session starts ===============================
platform linux -- Python 3.10.0a0, pytest-6.0.1, py-1.9.0, pluggy-0.13.1 -- /root/pydev-venv/bin/python
cachedir: .pytest_cache
rootdir: /root/checked_repos/geojson
collected 4 items                                                                 

tests/test_features.py::FeaturesTest::test_feature_class FAILED             [ 25%]
tests/test_features.py::FeaturesTest::test_geo_interface PASSED             [ 50%]
tests/test_features.py::FeaturesTest::test_protocol PASSED                  [ 75%]
tests/test_features.py::FeaturesTest::test_unicode_properties PASSED        [100%]

==================================== FAILURES =====================================
_________________________ FeaturesTest.test_feature_class _________________________

self = <tests.test_features.FeaturesTest testMethod=test_feature_class>

    def test_feature_class(self):
        """
        Test the Feature class
        """

        from geojson.examples import SimpleWebFeature
        feature = SimpleWebFeature(
            id='1',
            geometry={'type': 'Point', 'coordinates': [53.0, -4.0]},
            title='Feature 1', summary='The first feature',
            link='http://example.org/features/1'
        )

        # It satisfies the feature protocol
        self.assertEqual(feature.id, '1')
        self.assertEqual(feature.properties['title'], 'Feature 1')
        self.assertEqual(feature.properties['summary'], 'The first feature')
        self.assertEqual(feature.properties['link'],
                         'http://example.org/features/1')
        self.assertEqual(geojson.dumps(feature.geometry, sort_keys=True),
                         '{"coordinates": [53.0, -4.0], "type": "Point"}')

        # Encoding
        json = ('{"geometry": {"coordinates": [53.0, -4.0],'
                ' "type": "Point"},'
                ' "id": "1",'
                ' "properties":'
                ' {"link": "http://example.org/features/1",'
                ' "summary": "The first feature",'
                ' "title": "Feature 1"},'
                ' "type": "Feature"}')
        self.assertEqual(geojson.dumps(feature, sort_keys=True), json)

        # Decoding
        factory = geojson.examples.create_simple_web_feature
        json = ('{"geometry": {"type": "Point",'
                ' "coordinates": [53.0, -4.0]},'
                ' "id": "1",'
                ' "properties": {"summary": "The first feature",'
                ' "link": "http://example.org/features/1",'
                ' "title": "Feature 1"}}')
>       feature = geojson.loads(json, object_hook=factory, encoding="utf-8")

tests/test_features.py:85: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
geojson/codec.py:51: in loads
    return json.loads(s,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

s = '{"geometry": {"type": "Point", "coordinates": [53.0, -4.0]}, "id": "1", "properties": {"summary": "The first feature", "link": "http://example.org/features/1", "title": "Feature 1"}}'
cls = <class 'json.decoder.JSONDecoder'>
object_hook = <function create_simple_web_feature at 0x7f711ac7dd30>
parse_float = None, parse_int = None
parse_constant = <function _enforce_strict_numbers at 0x7f711acff1f0>
object_pairs_hook = None
kw = {'encoding': 'utf-8', 'object_hook': <function create_simple_web_feature at 0x7f711ac7dd30>, 'parse_constant': <function _enforce_strict_numbers at 0x7f711acff1f0>}

    def loads(s, *, cls=None, object_hook=None, parse_float=None,
            parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
        """Deserialize ``s`` (a ``str``, ``bytes`` or ``bytearray`` instance
        containing a JSON document) to a Python object.

        ``object_hook`` is an optional function that will be called with the
        result of any object literal decode (a ``dict``). The return value of
        ``object_hook`` will be used instead of the ``dict``. This feature
        can be used to implement custom decoders (e.g. JSON-RPC class hinting).

        ``object_pairs_hook`` is an optional function that will be called with the
        result of any object literal decoded with an ordered list of pairs.  The
        return value of ``object_pairs_hook`` will be used instead of the ``dict``.
        This feature can be used to implement custom decoders.  If ``object_hook``
        is also defined, the ``object_pairs_hook`` takes priority.

        ``parse_float``, if specified, will be called with the string
        of every JSON float to be decoded. By default this is equivalent to
        float(num_str). This can be used to use another datatype or parser
        for JSON floats (e.g. decimal.Decimal).

        ``parse_int``, if specified, will be called with the string
        of every JSON int to be decoded. By default this is equivalent to
        int(num_str). This can be used to use another datatype or parser
        for JSON integers (e.g. float).

        ``parse_constant``, if specified, will be called with one of the
        following strings: -Infinity, Infinity, NaN.
        This can be used to raise an exception if invalid JSON numbers
        are encountered.

        To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
        kwarg; otherwise ``JSONDecoder`` is used.
        """
        if isinstance(s, str):
            if s.startswith('\ufeff'):
                raise JSONDecodeError("Unexpected UTF-8 BOM (decode using utf-8-sig)",
                                      s, 0)
        else:
            if not isinstance(s, (bytes, bytearray)):
                raise TypeError(f'the JSON object must be str, bytes or bytearray, '
                                f'not {s.__class__.__name__}')
            s = s.decode(detect_encoding(s), 'surrogatepass')

        if (cls is None and object_hook is None and
                parse_int is None and parse_float is None and
                parse_constant is None and object_pairs_hook is None and not kw):
            return _default_decoder.decode(s)
        if cls is None:
            cls = JSONDecoder
        if object_hook is not None:
            kw['object_hook'] = object_hook
        if object_pairs_hook is not None:
            kw['object_pairs_hook'] = object_pairs_hook
        if parse_float is not None:
            kw['parse_float'] = parse_float
        if parse_int is not None:
            kw['parse_int'] = parse_int
        if parse_constant is not None:
            kw['parse_constant'] = parse_constant
>       return cls(**kw).decode(s)
E       TypeError: JSONDecoder.__init__() got an unexpected keyword argument 'encoding'

/root/cpython/Lib/json/__init__.py:359: TypeError
============================= short test summary info =============================
FAILED tests/test_features.py::FeaturesTest::test_feature_class - TypeError: JSO...
=========================== 1 failed, 3 passed in 0.09s ===========================
rayrrr commented 4 years ago

@tirkarthi thank you for submitting this issue and PR. I think it should be fine but I am going to take a bit of time this weekend to consider "Chesterton's Fence" and make sure removing the utf-8 encoding thing is all good and then will merge or circle back.