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-07-21 22:55:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-ijson (Old) and /work/SRC/openSUSE:Factory/.python-ijson.new.24530 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-ijson" Tue Jul 21 22:55:46 2026 rev:10 rq:1365759 version:3.5.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-ijson/python-ijson.changes 2026-03-16 14:20:00.499958685 +0100 +++ /work/SRC/openSUSE:Factory/.python-ijson.new.24530/python-ijson.changes 2026-07-21 22:58:14.945313114 +0200 @@ -1,0 +2,14 @@ +Tue Jul 14 19:54:06 UTC 2026 - Dirk Müller <[email protected]> + +- update to 3.5.1: + * The internal `ijson.common.ObjectBuilder` no longer creates + internal reference cycles, so discarded builders are freed by + reference counting instead of waiting for a cyclic garbage + collection pass. This lowers peak memory usage for code that + builds and discards one large object at a time. As a side + effect, `builder.value` is now `None` before any + event is processed (previously accessing it raised + `AttributeError`), and the undocumented `containers` attribute + holds the in-progress containers rather than setter closures. + +------------------------------------------------------------------- Old: ---- ijson-3.5.0.tar.gz New: ---- ijson-3.5.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-ijson.spec ++++++ --- /var/tmp/diff_new_pack.cm1U6X/_old 2026-07-21 22:58:15.469330971 +0200 +++ /var/tmp/diff_new_pack.cm1U6X/_new 2026-07-21 22:58:15.469330971 +0200 @@ -20,7 +20,7 @@ %{?sle15_python_module_pythons} Name: python-ijson -Version: 3.5.0 +Version: 3.5.1 Release: 0 Summary: Iterative JSON parser with a standard Python iterator interface License: BSD-3-Clause ++++++ ijson-3.5.0.tar.gz -> ijson-3.5.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.5.0/CHANGELOG.md new/ijson-3.5.1/CHANGELOG.md --- old/ijson-3.5.0/CHANGELOG.md 2026-02-24 04:43:16.000000000 +0100 +++ new/ijson-3.5.1/CHANGELOG.md 2026-07-06 19:24:12.000000000 +0200 @@ -2,6 +2,17 @@ ## Development +## [3.5.1] + +* The internal `ijson.common.ObjectBuilder` no longer creates internal reference + cycles, so discarded builders are freed by reference counting instead + of waiting for a cyclic garbage collection pass. This lowers peak + memory usage for code that builds and discards one large object at a + time. As a side effect, `builder.value` is now `None` before any + event is processed (previously accessing it raised `AttributeError`), + and the undocumented `containers` attribute holds the in-progress + containers rather than setter closures. + ## [3.5.0] * Added input iterator support via the new `ijson.from_iter` adapter. @@ -398,3 +409,4 @@ [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 +[3.5.1]: https://github.com/ICRAR/ijson/releases/tag/v3.5.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.5.0/PKG-INFO new/ijson-3.5.1/PKG-INFO --- old/ijson-3.5.0/PKG-INFO 2026-02-24 04:43:22.860385000 +0100 +++ new/ijson-3.5.1/PKG-INFO 2026-07-06 19:24:17.241391400 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ijson -Version: 3.5.0 +Version: 3.5.1 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.5.0/pyproject.toml new/ijson-3.5.1/pyproject.toml --- old/ijson-3.5.0/pyproject.toml 2026-02-24 04:43:16.000000000 +0100 +++ new/ijson-3.5.1/pyproject.toml 2026-07-06 19:24:12.000000000 +0200 @@ -72,3 +72,21 @@ description = "Converts README.rst to HTML" commands = [['rst2html5', "--strict", "README.rst", "--output=readme.html"]] deps = ["docutils", "Pygments"] + +[tool.cibuildwheel] +before-all = "bash -c 'cd \"{project}\"; sh .github/tools/install_yajl.sh'" +before-test = "pip install -r test-requirements.txt" +build-verbosity = 1 +enable = [ + "pypy", +] +# Our C extension made PyPy < 7.3.13 crash (a bug on their end) +# it doesn't make sense to build wheels for platforms not supported by that version +skip = "pp39*" +test-command = "pytest -vv {project}/tests" + +[tool.cibuildwheel.macos.environment] +IJSON_EMBED_YAJL = "1" + +[tool.cibuildwheel.windows.environment] +IJSON_EMBED_YAJL = "1" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.5.0/src/ijson/benchmark.py new/ijson-3.5.1/src/ijson/benchmark.py --- old/ijson-3.5.0/src/ijson/benchmark.py 2026-02-24 04:43:16.000000000 +0100 +++ new/ijson-3.5.1/src/ijson/benchmark.py 2026-07-06 19:24:12.000000000 +0200 @@ -215,41 +215,36 @@ def main(): - DEFAULT_N = 100000 - DEFAULT_ITERATIONS = 1 - DEFAULT_BUFSIZE = 64 * 1024 - ALL_BENCHMARKS = ','.join(_benchmarks) - ALL_BACKENDS = ','.join(_backends) parser = argparse.ArgumentParser() parser.add_argument('-s', '--size', type=int, - help='Size of JSON content; actual size in bytes might differ, defaults to %d' % DEFAULT_N, - default=DEFAULT_N) + help='Size of JSON content; actual size in bytes might differ, defaults to %(default)s', + default=100000) parser.add_argument('-I', '--iterations', type=int, - help='How many times each method should be tested, defaults to %d' % DEFAULT_ITERATIONS, - default=DEFAULT_ITERATIONS) + help='How many times each method should be tested, defaults to %(default)s', + default=1) parser.add_argument('-S', '--bufsize', type=int, - help='Buffer size used during parsing; defaults to %d' % DEFAULT_BUFSIZE, - default=DEFAULT_BUFSIZE) + help='Buffer size used during parsing; defaults to %(default)s', + default=64 * 1024) parser.add_argument('-b', '--benchmarks', type=parse_benchmarks, - help='Comma-separated list of benchmarks to include, defaults to %s' % ALL_BENCHMARKS, - default=ALL_BENCHMARKS) + help='Comma-separated list of benchmarks to include, defaults to %(default)s', + default=','.join(_benchmarks)) parser.add_argument('-B', '--backends', type=parse_backends, - help='Comma-separated list of backends to include, defaults to %s' % ALL_BACKENDS, - default=ALL_BACKENDS) + help='Comma-separated list of backends to include, defaults to %(default)s', + default=','.join(_backends)) parser.add_argument('-l', '--list', action='store_true', help='List available benchmarks and backends') parser.add_argument('inputs', nargs='*', help='File to use for benchmarks rather than built-in benchmarking functions') - parser.add_argument('-M', '--multiple-values', action='store_true', default=False, + parser.add_argument('-M', '--multiple-values', action='store_true', help='Content has multiple JSON values, useful when used with -i') - parser.add_argument('-f', '--use-float', action='store_true', default=False, + parser.add_argument('-f', '--use-float', action='store_true', help='Parse non-integer numbers as float instead of Decimal') parser.add_argument('-m', '--method', choices=['basic_parse', 'parse', 'kvitems', 'items'], - help='The method to benchmark', default='basic_parse') - parser.add_argument('-c', '--coro', action='store_true', default=False, - dest='run_coro', help='Benchmark coroutine methods') - parser.add_argument('-a', '--async', action='store_true', default=False, - dest='run_async', help='Benchmark asyncio-enabled methods') + help='The method to benchmark, defaults to %(default)s', default='basic_parse') + parser.add_argument('-c', '--coro', action='store_true', + dest='run_coro', help='Benchmark coroutine methods') + parser.add_argument('-a', '--async', action='store_true', + dest='run_async', help='Benchmark asyncio-enabled methods') parser.add_argument('-p', '--prefix', help='Prefix (used with -M items|kvitems)', default='') args = parser.parse_args() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.5.0/src/ijson/common.py new/ijson-3.5.1/src/ijson/common.py --- old/ijson-3.5.0/src/ijson/common.py 2026-02-24 04:43:16.000000000 +0100 +++ new/ijson-3.5.1/src/ijson/common.py 2026-07-06 19:24:12.000000000 +0200 @@ -113,28 +113,38 @@ ''' def __init__(self, map_type=None): - def initial_set(value): - self.value = value - self.containers = [initial_set] + self.value = None + self.containers = [] self.map_type = map_type or dict + def _add(self, value): + # Storing the in-progress containers themselves (rather than setter + # closures, which would reference back to self and create reference + # cycles) keeps the builder collectable by plain refcounting + if not self.containers: + self.value = value + else: + top = self.containers[-1] + if isinstance(top, list): + top.append(value) + else: + top[self.key] = value + def event(self, event, value): if event == 'map_key': self.key = value elif event == 'start_map': mappable = self.map_type() - self.containers[-1](mappable) - def setter(value): - mappable[self.key] = value - self.containers.append(setter) + self._add(mappable) + self.containers.append(mappable) elif event == 'start_array': array = [] - self.containers[-1](array) - self.containers.append(array.append) + self._add(array) + self.containers.append(array) elif event == 'end_array' or event == 'end_map': self.containers.pop() else: - self.containers[-1](value) + self._add(value) @utils.coroutine @@ -156,7 +166,6 @@ object_depth += 1 elif event in ('end_map', 'end_array'): object_depth -= 1 - del builder.containers[:] target.send(builder.value) else: target.send(value) @@ -187,7 +196,6 @@ object_depth += 1 elif event == 'end_map': object_depth -= 1 - del builder.containers[:] target.send((key, builder.value)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.5.0/src/ijson/dump.py new/ijson-3.5.1/src/ijson/dump.py --- old/ijson-3.5.0/src/ijson/dump.py 2026-02-24 04:43:16.000000000 +0100 +++ new/ijson-3.5.1/src/ijson/dump.py 2026-07-06 19:24:12.000000000 +0200 @@ -6,7 +6,7 @@ import ijson -HEADERS = { +METHODS_WITH_COLUMN_HEADERS = { 'basic_parse': 'name, value', 'parse': 'path, name, value', 'kvitems': 'key, value', @@ -19,10 +19,14 @@ return str(o) def dump(): - parser = argparse.ArgumentParser(description='Dump ijson events') - parser.add_argument('-m', '--method', choices=['basic_parse', 'parse', 'kvitems', 'items'], - help='The method to use for dumping', default='basic_parse') - parser.add_argument('-p', '--prefix', help='Prefix (used with -M items|kvitems)', default='') + description = ''' + Read JSON data from standard input, and dump + ijson events (using methods basic_parse or parse), + or content (using methods kvitems or items).''' + parser = argparse.ArgumentParser(description=description) + parser.add_argument('-m', '--method', choices=METHODS_WITH_COLUMN_HEADERS, + help='The method to use for dumping. Defaults to %(default)s', default='basic_parse') + parser.add_argument('-p', '--prefix', help='Prefix (used with -m items|kvitems)', default='') parser.add_argument('-M', '--multiple-values', help='Allow multiple values', action='store_true') args = parser.parse_args() @@ -33,7 +37,7 @@ method_args = args.prefix, if args.multiple_values: method_kwargs['multiple_values'] = True - header = '#: ' + HEADERS[args.method] + header = '#: ' + METHODS_WITH_COLUMN_HEADERS[args.method] print(header) print('-' * len(header)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.5.0/src/ijson/version.py new/ijson-3.5.1/src/ijson/version.py --- old/ijson-3.5.0/src/ijson/version.py 2026-02-24 04:43:16.000000000 +0100 +++ new/ijson-3.5.1/src/ijson/version.py 2026-07-06 19:24:12.000000000 +0200 @@ -1 +1 @@ -__version__ = '3.5.0' +__version__ = '3.5.1' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.5.0/src/ijson.egg-info/PKG-INFO new/ijson-3.5.1/src/ijson.egg-info/PKG-INFO --- old/ijson-3.5.0/src/ijson.egg-info/PKG-INFO 2026-02-24 04:43:22.000000000 +0100 +++ new/ijson-3.5.1/src/ijson.egg-info/PKG-INFO 2026-07-06 19:24:17.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ijson -Version: 3.5.0 +Version: 3.5.1 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ijson-3.5.0/tests/test_misc.py new/ijson-3.5.1/tests/test_misc.py --- old/ijson-3.5.0/tests/test_misc.py 2026-02-24 04:43:16.000000000 +0100 +++ new/ijson-3.5.1/tests/test_misc.py 2026-07-06 19:24:12.000000000 +0200 @@ -1,9 +1,14 @@ +import collections +import gc import importlib.util import io +import sys +import weakref import pytest from ijson import common +from ijson.common import ObjectBuilder from tests.test_base import JSON, JSON_EVENTS, JSON_PARSE_EVENTS, JSON_OBJECT,\ JSON_KVITEMS @@ -23,6 +28,96 @@ importlib.util.module_from_spec(spec) +class TestObjectBuilder: + """Unit tests for the pure-python ObjectBuilder class""" + + def test_initial_value_is_none(self): + assert ObjectBuilder().value is None + + def test_value_available_during_construction(self): + builder = ObjectBuilder() + builder.event('start_map', None) + assert builder.value == {} + builder.event('map_key', 'a') + builder.event('string', 'value') + assert builder.value == {'a': 'value'} + builder.event('map_key', 'b') + builder.event('number', 1) + assert builder.value == {'a': 'value', 'b': 1} + + def test_builds_expected_value(self): + builder = ObjectBuilder() + for event, value in JSON_EVENTS: + builder.event(event, value) + assert builder.value == JSON_OBJECT + + def test_custom_map_type(self): + builder = ObjectBuilder(map_type=collections.OrderedDict) + events = [ + ('start_map', None), + ('map_key', 'a'), + ('start_map', None), + ('map_key', 'b'), + ('number', 1), + ('end_map', None), + ('map_key', 'c'), + ('start_array', None), + ('number', 2), + ('end_array', None), + ('end_map', None), + ] + for event, value in events: + builder.event(event, value) + assert builder.value == {'a': {'b': 1}, 'c': [2]} + assert type(builder.value) is collections.OrderedDict + assert type(builder.value['a']) is collections.OrderedDict + + @pytest.mark.skipif(sys.implementation.name != "cpython", + reason="deterministic refcounting is CPython-only") + @pytest.mark.parametrize("events", [ + pytest.param([ + ('start_map', None), + ('map_key', 'key'), + ('start_map', None), + ('map_key', 'inner'), + ('string', 'value'), + ('end_map', None), + ('end_map', None), + ], id="nested_map"), + pytest.param([ + ('start_array', None), + ('number', 1), + ('start_array', None), + ('number', 2), + ('end_array', None), + ('end_array', None), + ], id="nested_array"), + pytest.param([ + ('string', 'scalar'), + ], id="scalar_only"), + pytest.param([], id="no_events"), + ]) + def test_builder_freed_without_cyclic_gc(self, events): + """A discarded builder must be freed by refcounting alone. + + The previous implementation stored setter closures in + self.containers; each closure referenced the builder, creating a + reference cycle that kept the builder (and the value being built) + alive until a cyclic GC pass.""" + builder = ObjectBuilder() + for event, value in events: + builder.event(event, value) + ref = weakref.ref(builder) + gc_was_enabled = gc.isenabled() + gc.disable() + try: + del builder + assert ref() is None + finally: + if gc_was_enabled: + gc.enable() + + class TestMainEntryPoints: """Tests that main API entry points work against different types of inputs automatically"""
