Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-ijson for openSUSE:Factory checked in at 2026-03-16 14:16:50 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-ijson (Old) and /work/SRC/openSUSE:Factory/.python-ijson.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-ijson" Mon Mar 16 14:16:50 2026 rev:9 rq:1339146 version:3.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-ijson/python-ijson.changes 2025-11-13 17:28:57.067137556 +0100 +++ /work/SRC/openSUSE:Factory/.python-ijson.new.8177/python-ijson.changes 2026-03-16 14:20:00.499958685 +0100 @@ -1,0 +2,11 @@ +Sun Mar 15 19:12:37 UTC 2026 - Dirk Müller <[email protected]> + +- update to 3.5.0: + * Added input iterator support via the new `ijson.from_iter` + adapter. + * It allows users to easily consume iterators and async + iterators, with common examples being HTTP stream responses + as modelled by the `requests` and `httpx` libraries. + * Introdued `tox` for common task execution. + +------------------------------------------------------------------- Old: ---- ijson-3.4.0.post0.tar.gz New: ---- ijson-3.5.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-ijson.spec ++++++ --- /var/tmp/diff_new_pack.m1wq8w/_old 2026-03-16 14:20:00.911975789 +0100 +++ /var/tmp/diff_new_pack.m1wq8w/_new 2026-03-16 14:20:00.927976453 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-ijson # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -20,7 +20,7 @@ %{?sle15_python_module_pythons} Name: python-ijson -Version: 3.4.0.post0 +Version: 3.5.0 Release: 0 Summary: Iterative JSON parser with a standard Python iterator interface License: BSD-3-Clause ++++++ ijson-3.4.0.post0.tar.gz -> ijson-3.5.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.4.0.post0/CHANGELOG.md new/ijson-3.5.0/CHANGELOG.md --- old/ijson-3.4.0.post0/CHANGELOG.md 2025-10-10 07:16:50.000000000 +0200 +++ new/ijson-3.5.0/CHANGELOG.md 2026-02-24 04:43:16.000000000 +0100 @@ -2,6 +2,12 @@ ## Development +## [3.5.0] + +* Added input iterator support via the new `ijson.from_iter` adapter. + It allows users to easily consume iterators and async iterators, + with common examples being HTTP stream responses + as modelled by the `requests` and `httpx` libraries. * Introdued `tox` for common task execution. ## [3.4.0.post0] @@ -391,3 +397,4 @@ [3.3.0]: https://github.com/ICRAR/ijson/releases/tag/v3.3.0 [3.4.0]: https://github.com/ICRAR/ijson/releases/tag/v3.4.0 [3.4.0.post0]: https://github.com/ICRAR/ijson/releases/tag/v3.4.0.post0 +[3.5.0]: https://github.com/ICRAR/ijson/releases/tag/v3.5.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.4.0.post0/PKG-INFO new/ijson-3.5.0/PKG-INFO --- old/ijson-3.4.0.post0/PKG-INFO 2025-10-10 07:16:56.699156300 +0200 +++ new/ijson-3.5.0/PKG-INFO 2026-02-24 04:43:22.860385000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ijson -Version: 3.4.0.post0 +Version: 3.5.0 Summary: Iterative JSON parser with standard Python iterator interfaces Author-email: Rodrigo Tobar <[email protected]>, Ivan Sagalaev <[email protected]> License-Expression: BSD-3-Clause AND ISC @@ -243,12 +243,9 @@ In many situations the direct input users want to pass to ijson is an iterator (e.g., a generator) rather than a file-like object. -To bridge this gap users need to adapt the iterator into a file-like object. -Examples of this can be found -`here <https://github.com/ICRAR/ijson/issues/44#issuecomment-1771013830>`__ -and `here <https://github.com/ICRAR/ijson/issues/58#issuecomment-917655522>`__. -Future versions of ijson might provide built-in adapters for this, -and/or support iterators without the need to adapt them first. +ijson provides a built-in adapter to bridge this gap: + +- ``ijson.from_iter(iterable_or_async_iterable_of_bytes)`` ``asyncio`` support @@ -657,23 +654,44 @@ by passing the ``multiple_values=True`` to the ijson function in use. See the options_ section for details. -#. How do I use ijson with the ``requests`` library +#. **Q**: How do I use ijson with ``requests`` or ``httpx`` + + **A**: The ``requests`` library downloads the body of the HTTP response immediately by default. + To stream JSON into ijson, pass ``stream=True`` and adapt the byte iterator: + + .. code-block:: python + + import requests + import ijson + + with requests.get('https://..', stream=True) as resp: + resp.raise_for_status() + f = ijson.from_iter(resp.iter_content(chunk_size=64*1024)) + objects = ijson.items(f, 'earth.europe.item') + cities = (o for o in objects if o['type'] == 'city') + for city in cities: + do_something_with(city) - The ``requests`` library downloads the body of the HTTP response immediately by default. - Users wanting to feed the response into ijson - will need to override this behaviour - by using the ``requests.get(..., stream=True)`` parameter. - Then they have at least two options: - - * Wrap the ``Response.iter_content()`` iterator into a file-like object, - then give that to ijson. - - * Pass the ``Response.raw`` object (the underlying ``socket.socket``) to ijson. - - The first alternative is best, since ``requests`` will automatically decode - any HTTP transfer encodings, which doesn't happen with ``Response.raw``. - See `Iterator support`_ for how to wrap ``Response.iter_content()`` - into a file-like object. + You can also pass ``Response.raw`` directly (it's a file-like object), + but using ``iter_content`` is preferred because ``requests`` will transparently + handle HTTP transfer encodings (e.g., gzip, chunked). + + + For async usage with ``httpx``: + + .. code-block:: python + + import httpx + import ijson + + async with httpx.AsyncClient() as client: + async with client.stream('GET', 'https://..') as resp: + resp.raise_for_status() + f = ijson.from_iter(resp.aiter_bytes()) + objects = ijson.items(f, 'earth.europe.item') + cities = (o async for o in objects if o['type'] == 'city') + async for city in cities: + do_something_with(city) Acknowledgements diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.4.0.post0/README.rst new/ijson-3.5.0/README.rst --- old/ijson-3.4.0.post0/README.rst 2025-10-10 07:16:50.000000000 +0200 +++ new/ijson-3.5.0/README.rst 2026-02-24 04:43:16.000000000 +0100 @@ -221,12 +221,9 @@ In many situations the direct input users want to pass to ijson is an iterator (e.g., a generator) rather than a file-like object. -To bridge this gap users need to adapt the iterator into a file-like object. -Examples of this can be found -`here <https://github.com/ICRAR/ijson/issues/44#issuecomment-1771013830>`__ -and `here <https://github.com/ICRAR/ijson/issues/58#issuecomment-917655522>`__. -Future versions of ijson might provide built-in adapters for this, -and/or support iterators without the need to adapt them first. +ijson provides a built-in adapter to bridge this gap: + +- ``ijson.from_iter(iterable_or_async_iterable_of_bytes)`` ``asyncio`` support @@ -635,23 +632,44 @@ by passing the ``multiple_values=True`` to the ijson function in use. See the options_ section for details. -#. How do I use ijson with the ``requests`` library +#. **Q**: How do I use ijson with ``requests`` or ``httpx`` + + **A**: The ``requests`` library downloads the body of the HTTP response immediately by default. + To stream JSON into ijson, pass ``stream=True`` and adapt the byte iterator: + + .. code-block:: python + + import requests + import ijson + + with requests.get('https://..', stream=True) as resp: + resp.raise_for_status() + f = ijson.from_iter(resp.iter_content(chunk_size=64*1024)) + objects = ijson.items(f, 'earth.europe.item') + cities = (o for o in objects if o['type'] == 'city') + for city in cities: + do_something_with(city) - The ``requests`` library downloads the body of the HTTP response immediately by default. - Users wanting to feed the response into ijson - will need to override this behaviour - by using the ``requests.get(..., stream=True)`` parameter. - Then they have at least two options: - - * Wrap the ``Response.iter_content()`` iterator into a file-like object, - then give that to ijson. - - * Pass the ``Response.raw`` object (the underlying ``socket.socket``) to ijson. - - The first alternative is best, since ``requests`` will automatically decode - any HTTP transfer encodings, which doesn't happen with ``Response.raw``. - See `Iterator support`_ for how to wrap ``Response.iter_content()`` - into a file-like object. + You can also pass ``Response.raw`` directly (it's a file-like object), + but using ``iter_content`` is preferred because ``requests`` will transparently + handle HTTP transfer encodings (e.g., gzip, chunked). + + + For async usage with ``httpx``: + + .. code-block:: python + + import httpx + import ijson + + async with httpx.AsyncClient() as client: + async with client.stream('GET', 'https://..') as resp: + resp.raise_for_status() + f = ijson.from_iter(resp.aiter_bytes()) + objects = ijson.items(f, 'earth.europe.item') + cities = (o async for o in objects if o['type'] == 'city') + async for city in cities: + do_something_with(city) Acknowledgements diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.4.0.post0/src/ijson/__init__.py new/ijson-3.5.0/src/ijson/__init__.py --- old/ijson-3.4.0.post0/src/ijson/__init__.py 2025-10-10 07:16:50.000000000 +0200 +++ new/ijson-3.5.0/src/ijson/__init__.py 2026-02-24 04:43:16.000000000 +0100 @@ -13,6 +13,7 @@ also two other backends using the C library yajl in ``ijson.backends`` that have the same API and are faster under CPython. ''' +from ijson.adapters import from_iter from ijson.common import JSONError, IncompleteJSONError, ObjectBuilder from ijson.utils import coroutine, sendable_list diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.4.0.post0/src/ijson/adapters.py new/ijson-3.5.0/src/ijson/adapters.py --- old/ijson-3.4.0.post0/src/ijson/adapters.py 1970-01-01 01:00:00.000000000 +0100 +++ new/ijson-3.5.0/src/ijson/adapters.py 2026-02-24 04:43:16.000000000 +0100 @@ -0,0 +1,63 @@ +from ijson import compat + +try: + from builtins import aiter, anext +except ImportError: # Py<3.10 + _MISSING = object() + + def aiter(obj): + return obj.__aiter__() + + async def anext(ait, default=_MISSING): + try: + return await ait.__anext__() + except StopAsyncIteration: + if default is _MISSING: + raise + return default + + +def _to_bytes(chunk, warned: bool): + if isinstance(chunk, bytes): + return chunk, warned + if isinstance(chunk, str): + if not warned: + compat._warn_and_return(None) + warned = True + return chunk.encode("utf-8"), warned + raise TypeError("from_iter expects an iterable of bytes or str") + + +class IterReader: + """File-like object backed by a byte iterator.""" + + def __init__(self, byte_iter): + self._iter = byte_iter + self._warned = False + + def read(self, n: int) -> bytes: + if n == 0: + return b"" + chunk, self._warned = _to_bytes(next(self._iter, b""), self._warned) + return chunk + + +class AiterReader: + """Async file-like object backed by an async byte iterator.""" + + def __init__(self, byte_aiter): + self._aiter = byte_aiter + self._warned = False + + async def read(self, n: int) -> bytes: + if n == 0: + return b"" + chunk, self._warned = _to_bytes(await anext(self._aiter, b""), self._warned) + return chunk + + +def from_iter(byte_iter): + """Convert a byte iterable (sync or async) to a file-like object.""" + if hasattr(byte_iter, "__aiter__"): + return AiterReader(aiter(byte_iter)) + return IterReader(iter(byte_iter)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.4.0.post0/src/ijson/version.py new/ijson-3.5.0/src/ijson/version.py --- old/ijson-3.4.0.post0/src/ijson/version.py 2025-10-10 07:16:50.000000000 +0200 +++ new/ijson-3.5.0/src/ijson/version.py 2026-02-24 04:43:16.000000000 +0100 @@ -1 +1 @@ -__version__ = '3.4.0.post0' +__version__ = '3.5.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.4.0.post0/src/ijson.egg-info/PKG-INFO new/ijson-3.5.0/src/ijson.egg-info/PKG-INFO --- old/ijson-3.4.0.post0/src/ijson.egg-info/PKG-INFO 2025-10-10 07:16:56.000000000 +0200 +++ new/ijson-3.5.0/src/ijson.egg-info/PKG-INFO 2026-02-24 04:43:22.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ijson -Version: 3.4.0.post0 +Version: 3.5.0 Summary: Iterative JSON parser with standard Python iterator interfaces Author-email: Rodrigo Tobar <[email protected]>, Ivan Sagalaev <[email protected]> License-Expression: BSD-3-Clause AND ISC @@ -243,12 +243,9 @@ In many situations the direct input users want to pass to ijson is an iterator (e.g., a generator) rather than a file-like object. -To bridge this gap users need to adapt the iterator into a file-like object. -Examples of this can be found -`here <https://github.com/ICRAR/ijson/issues/44#issuecomment-1771013830>`__ -and `here <https://github.com/ICRAR/ijson/issues/58#issuecomment-917655522>`__. -Future versions of ijson might provide built-in adapters for this, -and/or support iterators without the need to adapt them first. +ijson provides a built-in adapter to bridge this gap: + +- ``ijson.from_iter(iterable_or_async_iterable_of_bytes)`` ``asyncio`` support @@ -657,23 +654,44 @@ by passing the ``multiple_values=True`` to the ijson function in use. See the options_ section for details. -#. How do I use ijson with the ``requests`` library +#. **Q**: How do I use ijson with ``requests`` or ``httpx`` + + **A**: The ``requests`` library downloads the body of the HTTP response immediately by default. + To stream JSON into ijson, pass ``stream=True`` and adapt the byte iterator: + + .. code-block:: python + + import requests + import ijson + + with requests.get('https://..', stream=True) as resp: + resp.raise_for_status() + f = ijson.from_iter(resp.iter_content(chunk_size=64*1024)) + objects = ijson.items(f, 'earth.europe.item') + cities = (o for o in objects if o['type'] == 'city') + for city in cities: + do_something_with(city) - The ``requests`` library downloads the body of the HTTP response immediately by default. - Users wanting to feed the response into ijson - will need to override this behaviour - by using the ``requests.get(..., stream=True)`` parameter. - Then they have at least two options: - - * Wrap the ``Response.iter_content()`` iterator into a file-like object, - then give that to ijson. - - * Pass the ``Response.raw`` object (the underlying ``socket.socket``) to ijson. - - The first alternative is best, since ``requests`` will automatically decode - any HTTP transfer encodings, which doesn't happen with ``Response.raw``. - See `Iterator support`_ for how to wrap ``Response.iter_content()`` - into a file-like object. + You can also pass ``Response.raw`` directly (it's a file-like object), + but using ``iter_content`` is preferred because ``requests`` will transparently + handle HTTP transfer encodings (e.g., gzip, chunked). + + + For async usage with ``httpx``: + + .. code-block:: python + + import httpx + import ijson + + async with httpx.AsyncClient() as client: + async with client.stream('GET', 'https://..') as resp: + resp.raise_for_status() + f = ijson.from_iter(resp.aiter_bytes()) + objects = ijson.items(f, 'earth.europe.item') + cities = (o async for o in objects if o['type'] == 'city') + async for city in cities: + do_something_with(city) Acknowledgements diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.4.0.post0/src/ijson.egg-info/SOURCES.txt new/ijson-3.5.0/src/ijson.egg-info/SOURCES.txt --- old/ijson-3.4.0.post0/src/ijson.egg-info/SOURCES.txt 2025-10-10 07:16:56.000000000 +0200 +++ new/ijson-3.5.0/src/ijson.egg-info/SOURCES.txt 2026-02-24 04:43:22.000000000 +0100 @@ -5,6 +5,7 @@ pyproject.toml setup.py src/ijson/__init__.py +src/ijson/adapters.py src/ijson/benchmark.py src/ijson/common.py src/ijson/compat.py @@ -60,6 +61,7 @@ src/ijson/backends/ext/_yajl2/reading_generator.h tests/__init__.py tests/conftest.py +tests/test_adapters.py tests/test_base.py tests/test_basic_parse.py tests/test_benchmark.py @@ -74,7 +76,9 @@ tests/test_subinterpreter.py tests/support/__init__.py tests/support/_async_common.py +tests/support/aiterators.py tests/support/async_.py tests/support/async_types_coroutines.py tests/support/coroutines.py -tests/support/generators.py \ No newline at end of file +tests/support/generators.py +tests/support/iterators.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.4.0.post0/tests/conftest.py new/ijson-3.5.0/tests/conftest.py --- old/ijson-3.4.0.post0/tests/conftest.py 2025-10-10 07:16:50.000000000 +0200 +++ new/ijson-3.5.0/tests/conftest.py 2026-02-24 04:43:16.000000000 +0100 @@ -22,7 +22,9 @@ class InputType(enum.Enum): ASYNC_FILE = enum.auto() ASYNC_TYPES_COROUTINES_FILE = enum.auto() + ASYNC_ITERABLE = enum.auto() FILE = enum.auto() + ITERABLE = enum.auto() SENDABLE = enum.auto() @@ -50,8 +52,10 @@ from .support.async_ import get_all as get_all_async from .support.async_types_coroutines import get_all as get_all_async_types_coroutines +from .support.aiterators import get_all as get_all_async_iterable from .support.coroutines import get_all as get_all_coro from .support.generators import get_all as get_all_gen +from .support.iterators import get_all as get_all_iterable _pull_backend_adaptors = [ backend_adaptor @@ -59,7 +63,9 @@ for backend_adaptor in [ BackendAdaptor(backend, InputType.ASYNC_FILE, "_async", get_all_async), BackendAdaptor(backend, InputType.ASYNC_TYPES_COROUTINES_FILE, "_async", get_all_async_types_coroutines), + BackendAdaptor(backend, InputType.ASYNC_ITERABLE, "_async", get_all_async_iterable), BackendAdaptor(backend, InputType.FILE, "_gen", get_all_gen), + BackendAdaptor(backend, InputType.ITERABLE, "_gen", get_all_iterable), ] ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.4.0.post0/tests/support/aiterators.py new/ijson-3.5.0/tests/support/aiterators.py --- old/ijson-3.4.0.post0/tests/support/aiterators.py 1970-01-01 01:00:00.000000000 +0100 +++ new/ijson-3.5.0/tests/support/aiterators.py 2026-02-24 04:43:16.000000000 +0100 @@ -0,0 +1,23 @@ +import asyncio + +import ijson + +from ._async_common import _aiorun + + +async def _async_chunks(json, chunk_size=1): + for i in range(0, len(json), chunk_size): + await asyncio.sleep(0) + yield json[i : i + chunk_size] + + +def get_all(routine, json_content, *args, **kwargs): + events = [] + + async def run(): + reader = ijson.from_iter(_async_chunks(json_content)) + async for event in routine(reader, *args, **kwargs): + events.append(event) + + _aiorun(run()) + return events diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.4.0.post0/tests/support/iterators.py new/ijson-3.5.0/tests/support/iterators.py --- old/ijson-3.4.0.post0/tests/support/iterators.py 1970-01-01 01:00:00.000000000 +0100 +++ new/ijson-3.5.0/tests/support/iterators.py 2026-02-24 04:43:16.000000000 +0100 @@ -0,0 +1,11 @@ +import ijson + + +def _chunks(json, chunk_size=1): + for i in range(0, len(json), chunk_size): + yield json[i : i + chunk_size] + + +def get_all(routine, json_content, *args, **kwargs): + reader = ijson.from_iter(_chunks(json_content)) + return list(routine(reader, *args, **kwargs)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.4.0.post0/tests/test_adapters.py new/ijson-3.5.0/tests/test_adapters.py --- old/ijson-3.4.0.post0/tests/test_adapters.py 1970-01-01 01:00:00.000000000 +0100 +++ new/ijson-3.5.0/tests/test_adapters.py 2026-02-24 04:43:16.000000000 +0100 @@ -0,0 +1,69 @@ +import asyncio +import ijson +import pytest + + +def test_from_iter_read0_does_not_consume(): + chunks = [b'{"key":', b'"value"}'] + file_obj = ijson.from_iter(iter(chunks)) + assert file_obj.read(0) == b"" + assert file_obj.read(1) == b'{"key":' + assert file_obj.read(1) == b'"value"}' + assert file_obj.read(1) == b"" + + [email protected]( + "chunks_factory", + [list, tuple, iter, lambda chunks: (chunk for chunk in chunks)], + ids=["list", "tuple", "iter", "generator"], +) +def test_from_iter_accepts_iterable(chunks_factory): + chunks = chunks_factory([b'{"key":', b'"value"}']) + file_obj = ijson.from_iter(chunks) + assert file_obj.read(1) == b'{"key":' + assert file_obj.read(1) == b'"value"}' + assert file_obj.read(1) == b"" + + [email protected]( + "chunks_factory", + [list, tuple, iter, lambda chunks: (chunk for chunk in chunks)], + ids=["list", "tuple", "iter", "generator"], +) +def test_from_iter_accepts_string_and_warns(chunks_factory): + chunks = chunks_factory(['{"key":', '"value"}']) + file_obj = ijson.from_iter(chunks) + with pytest.deprecated_call(): + assert file_obj.read(1) == b'{"key":' + assert file_obj.read(1) == b'"value"}' + assert file_obj.read(1) == b"" + + +def test_from_iter_accepts_aiterable(): + async def chunks(): + yield b'{"key":' + yield b'"value"}' + + async def main(): + file_obj = ijson.from_iter(chunks()) + assert await file_obj.read(0) == b"" + assert await file_obj.read(1) == b'{"key":' + assert await file_obj.read(1) == b'"value"}' + assert await file_obj.read(1) == b"" + + asyncio.run(main()) + + +def test_from_iter_accepts_async_string_chunks_and_warns(): + async def chunks(): + yield '{"key":' + yield '"value"}' + + async def main(): + file_obj = ijson.from_iter(chunks()) + with pytest.deprecated_call(): + assert await file_obj.read(1) == b'{"key":' + assert await file_obj.read(1) == b'"value"}' + assert await file_obj.read(1) == b"" + + asyncio.run(main())
