Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-cfgv for openSUSE:Factory checked in at 2026-02-17 16:47:21 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-cfgv (Old) and /work/SRC/openSUSE:Factory/.python-cfgv.new.1977 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-cfgv" Tue Feb 17 16:47:21 2026 rev:8 rq:1333397 version:3.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-cfgv/python-cfgv.changes 2023-09-20 13:31:53.132440418 +0200 +++ /work/SRC/openSUSE:Factory/.python-cfgv.new.1977/python-cfgv.changes 2026-02-17 16:48:10.993528884 +0100 @@ -1,0 +2,6 @@ +Mon Feb 16 16:49:53 UTC 2026 - Dirk Müller <[email protected]> + +- update to 3.5.0: + * CI updates and minor bugfixes + +------------------------------------------------------------------- Old: ---- cfgv-3.4.0.tar.gz New: ---- cfgv-3.5.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-cfgv.spec ++++++ --- /var/tmp/diff_new_pack.BQ1u9E/_old 2026-02-17 16:48:12.625597083 +0100 +++ /var/tmp/diff_new_pack.BQ1u9E/_new 2026-02-17 16:48:12.629597250 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-cfgv # -# Copyright (c) 2023 SUSE LLC +# 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 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-cfgv -Version: 3.4.0 +Version: 3.5.0 Release: 0 Summary: Configuration validator producing human readable error messages License: MIT ++++++ cfgv-3.4.0.tar.gz -> cfgv-3.5.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cfgv-3.4.0/.github/workflows/main.yml new/cfgv-3.5.0/.github/workflows/main.yml --- old/cfgv-3.4.0/.github/workflows/main.yml 2023-08-12 22:38:08.000000000 +0200 +++ new/cfgv-3.5.0/.github/workflows/main.yml 2025-11-19 21:55:30.000000000 +0100 @@ -8,6 +8,6 @@ jobs: main: - uses: asottile/workflows/.github/workflows/[email protected] + uses: asottile/workflows/.github/workflows/[email protected] with: - env: '["py38", "py39", "py310", "py311"]' + env: '["py310", "py311", "py312", "py313"]' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cfgv-3.4.0/.pre-commit-config.yaml new/cfgv-3.5.0/.pre-commit-config.yaml --- old/cfgv-3.4.0/.pre-commit-config.yaml 2023-08-12 22:38:08.000000000 +0200 +++ new/cfgv-3.5.0/.pre-commit-config.yaml 2025-11-19 21:55:30.000000000 +0100 @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -10,28 +10,28 @@ - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.4.0 + rev: v3.1.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports - rev: v3.10.0 + rev: v3.16.0 hooks: - id: reorder-python-imports - args: [--py38-plus, --add-import, 'from __future__ import annotations'] + args: [--py310-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v3.0.1 + rev: v4.0.0 hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 + rev: v3.21.1 hooks: - id: pyupgrade - args: [--py38-plus] -- repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v2.0.2 + args: [--py310-plus] +- repo: https://github.com/hhatto/autopep8 + rev: v2.3.2 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 + rev: 7.3.0 hooks: - id: flake8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cfgv-3.4.0/README.md new/cfgv-3.5.0/README.md --- old/cfgv-3.4.0/README.md 2023-08-12 22:38:08.000000000 +0200 +++ new/cfgv-3.5.0/README.md 2025-11-19 21:55:30.000000000 +0100 @@ -116,6 +116,15 @@ When validated, this will check that each element adheres to the sub-schema. +### `KeyValueMap(object_name, key_check_fn, value_schema)` + +Used to make a schema representing a homogenous mapping where the keys are +of a specific type and the values match a schema + +- `object_name`: will be displayed in error messages +- `key_check_fn`: a [check function](#check-functions) for the key +- `value_schema`: a `Map` / `Array` or other sub-schema. + ## Validator objects Validator objects are used to validate key-value-pairs of a `Map`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cfgv-3.4.0/cfgv.py new/cfgv-3.5.0/cfgv.py --- old/cfgv-3.4.0/cfgv.py 2023-08-12 22:38:08.000000000 +0200 +++ new/cfgv-3.5.0/cfgv.py 2025-11-19 21:55:30.000000000 +0100 @@ -258,6 +258,40 @@ return ret +class KeyValueMap( + collections.namedtuple( + 'KeyValueMap', + ('object_name', 'check_key_fn', 'value_schema'), + ), +): + __slots__ = () + + def check(self, v): + if not isinstance(v, dict): + raise ValidationError( + f'Expected a {self.object_name} map but got a ' + f'{type(v).__name__}', + ) + with validate_context(f'At {self.object_name}()'): + for k, val in v.items(): + with validate_context(f'For key: {k}'): + self.check_key_fn(k) + with validate_context(f'At key: {k}'): + validate(val, self.value_schema) + + def apply_defaults(self, v): + return { + k: apply_defaults(val, self.value_schema) + for k, val in v.items() + } + + def remove_defaults(self, v): + return { + k: remove_defaults(val, self.value_schema) + for k, val in v.items() + } + + class Array(collections.namedtuple('Array', ('of', 'allow_empty'))): __slots__ = () diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cfgv-3.4.0/setup.cfg new/cfgv-3.5.0/setup.cfg --- old/cfgv-3.4.0/setup.cfg 2023-08-12 22:38:08.000000000 +0200 +++ new/cfgv-3.5.0/setup.cfg 2025-11-19 21:55:30.000000000 +0100 @@ -1,6 +1,6 @@ [metadata] name = cfgv -version = 3.4.0 +version = 3.5.0 description = Validate configuration and produce human readable error messages. long_description = file: README.md long_description_content_type = text/markdown @@ -10,7 +10,6 @@ license = MIT license_files = LICENSE classifiers = - License :: OSI Approved :: MIT License Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython @@ -18,7 +17,7 @@ [options] py_modules = cfgv -python_requires = >=3.8 +python_requires = >=3.10 [bdist_wheel] universal = True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cfgv-3.4.0/tests/cfgv_test.py new/cfgv-3.5.0/tests/cfgv_test.py --- old/cfgv-3.4.0/tests/cfgv_test.py 2023-08-12 22:38:08.000000000 +0200 +++ new/cfgv-3.5.0/tests/cfgv_test.py 2025-11-19 21:55:30.000000000 +0100 @@ -11,13 +11,16 @@ from cfgv import check_any from cfgv import check_array from cfgv import check_bool +from cfgv import check_int from cfgv import check_one_of from cfgv import check_regex +from cfgv import check_string from cfgv import check_type from cfgv import Conditional from cfgv import ConditionalOptional from cfgv import ConditionalRecurse from cfgv import In +from cfgv import KeyValueMap from cfgv import load_from_filename from cfgv import Map from cfgv import MISSING @@ -728,3 +731,79 @@ def test_warn_additional_keys_when_no_extra_keys(warn_additional_keys): validate({True: True}, warn_additional_keys.schema) assert not warn_additional_keys.record.called + + +key_value_map_schema = KeyValueMap( + 'Container', + check_string, + Map( + 'Object', 'name', + Required('name', check_string), + Optional('setting', check_bool, False), + ), +) +key_value_map_ints_schema = KeyValueMap( + 'Container', + check_int, + Array(Map('Object', 'nane', Required('name', check_string))), +) + + +def test_key_value_map_schema_ok(): + validate( + {'hello': {'name': 'hello'}, 'world': {'name': 'world'}}, + key_value_map_schema, + ) + validate( + {1: [{'name': 'hello'}], 2: [{'name': 'world'}]}, + key_value_map_ints_schema, + ) + + +def test_key_value_map_apply_defaults(): + orig = {'hello': {'name': 'hello'}} + ret = apply_defaults(orig, key_value_map_schema) + assert orig == {'hello': {'name': 'hello'}} + assert ret == {'hello': {'name': 'hello', 'setting': False}} + + +def test_key_value_map_remove_defaults(): + orig = {'hello': {'name': 'hello', 'setting': False}} + ret = remove_defaults(orig, key_value_map_schema) + assert orig == {'hello': {'name': 'hello', 'setting': False}} + assert ret == {'hello': {'name': 'hello'}} + + +def test_key_value_map_not_a_map(): + with pytest.raises(ValidationError) as excinfo: + validate([], key_value_map_schema) + expected = ( + 'Expected a Container map but got a list', + ) + _assert_exception_trace(excinfo.value, expected) + + +def test_key_value_map_wrong_key_type(): + with pytest.raises(ValidationError) as excinfo: + val = {1: {'name': 'hello'}} + validate(val, key_value_map_schema) + expected = ( + 'At Container()', + 'For key: 1', + 'Expected string got int', + ) + _assert_exception_trace(excinfo.value, expected) + + +def test_key_value_map_error_in_child_schema(): + with pytest.raises(ValidationError) as excinfo: + val = {'hello': {'name': 1}} + validate(val, key_value_map_schema) + expected = ( + 'At Container()', + 'At key: hello', + 'At Object(name=1)', + 'At key: name', + 'Expected string got int', + ) + _assert_exception_trace(excinfo.value, expected)
