From d907b1e71c42efc0e54843877d4233d7e88d5f56 Mon Sep 17 00:00:00 2001 From: Dmitrii Safronov Date: Tue, 9 Dec 2025 17:50:28 +0400 Subject: [PATCH 01/13] feat: add type annotations and mypy support - Updated `pyproject.toml` to include "Typing :: Typed" in classifiers. - Introduced a new `py.typed` file to indicate type checking support for mypy. - Enhanced type annotations in `schema_applier.py` for better type safety. - Added comprehensive tests in `test_mypy_support.py` to verify mypy type checking and ensure correct type annotations in `SchemaLogger`. These changes improve type safety and support for static type checking in the logging framework. Signed-off-by: Dmitrii Safronov --- pyproject.toml | 3 +- src/logging_objects_with_schema/py.typed | 0 .../schema_applier.py | 2 +- tests/test_mypy_support.py | 107 ++++++++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 src/logging_objects_with_schema/py.typed create mode 100644 tests/test_mypy_support.py diff --git a/pyproject.toml b/pyproject.toml index 485997c..93bff8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,8 @@ classifiers = [ "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3 :: Only", "Intended Audience :: Developers", - "Topic :: System :: Logging" + "Topic :: System :: Logging", + "Typing :: Typed" ] [[project.authors]] diff --git a/src/logging_objects_with_schema/py.typed b/src/logging_objects_with_schema/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/logging_objects_with_schema/schema_applier.py b/src/logging_objects_with_schema/schema_applier.py index 2f30e86..74ff80b 100644 --- a/src/logging_objects_with_schema/schema_applier.py +++ b/src/logging_objects_with_schema/schema_applier.py @@ -42,7 +42,7 @@ def _create_validation_error_json(field: str, error: str, value: Any) -> str: def _validate_list_value( - value: list, + value: list[Any], source: str, item_expected_type: type | None, ) -> _DataProblem | None: diff --git a/tests/test_mypy_support.py b/tests/test_mypy_support.py new file mode 100644 index 0000000..2c79a2a --- /dev/null +++ b/tests/test_mypy_support.py @@ -0,0 +1,107 @@ +"""Tests for mypy type checking support.""" + +from __future__ import annotations + +import subprocess # nosec B404 +import sys +from pathlib import Path + +import pytest + +from logging_objects_with_schema import SchemaLogger +from tests.helpers import _write_schema + + +def test_py_typed_file_exists() -> None: + """Verify that py.typed marker file exists in the package.""" + package_dir = Path(__file__).parent.parent / "src" / "logging_objects_with_schema" + py_typed = package_dir / "py.typed" + + assert py_typed.exists(), "py.typed marker file must exist for mypy support" + + +def test_mypy_type_checking() -> None: + """Verify that mypy can type-check the package without errors.""" + package_dir = Path(__file__).parent.parent / "src" / "logging_objects_with_schema" + + # Run mypy on the package directory + result = subprocess.run( # nosec B603 + [ + sys.executable, + "-m", + "mypy", + str(package_dir), + "--no-error-summary", + ], + capture_output=True, + text=True, + cwd=Path(__file__).parent.parent, + ) + + assert ( + result.returncode == 0 + ), f"mypy found type errors:\n{result.stdout}\n{result.stderr}" + + +def test_mypy_strict_type_checking() -> None: + """Verify that mypy can type-check the package in strict mode without errors.""" + package_dir = Path(__file__).parent.parent / "src" / "logging_objects_with_schema" + + # Run mypy in strict mode on the package directory + result = subprocess.run( # nosec B603 + [ + sys.executable, + "-m", + "mypy", + str(package_dir), + "--strict", + "--no-error-summary", + ], + capture_output=True, + text=True, + cwd=Path(__file__).parent.parent, + ) + + assert ( + result.returncode == 0 + ), f"mypy found type errors in strict mode:\n{result.stdout}\n{result.stderr}" + + +def test_schema_logger_type_annotations( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Verify that SchemaLogger has correct type annotations for mypy.""" + # This test verifies that type annotations are correct by using them + # If mypy is run on this file, it should not find any errors + + monkeypatch.chdir(tmp_path) + _write_schema(tmp_path, {"ServicePayload": {}}) + + # Test that SchemaLogger can be typed correctly + logger: SchemaLogger = SchemaLogger("test-logger", forbidden_keys=None) + + # Test that __init__ signature accepts correct types + logger2: SchemaLogger = SchemaLogger( + name="test-logger-2", + level=10, + forbidden_keys={"key1", "key2"}, + ) + + # Test that SchemaLogger is compatible with logging.Logger + import logging + + def accept_logger(logger: logging.Logger) -> logging.Logger: + return logger + + # This should work without type errors + result: logging.Logger = accept_logger(logger) + assert isinstance(result, SchemaLogger) + + # Test that logger methods are accessible and typed + logger.info("test message") + logger.debug("debug message", extra={"key": "value"}) + logger.warning("warning message") + + # Verify logger2 was created successfully (test __init__ signature) + assert logger2.name == "test-logger-2" From d98a9490362d512d685feccf86675e8c73847835 Mon Sep 17 00:00:00 2001 From: Dmitrii Safronov Date: Tue, 9 Dec 2025 17:52:27 +0400 Subject: [PATCH 02/13] chore: update excluded directories in Bandit configuration - Modified the `exclude_dirs` in the Bandit tool configuration within `pyproject.toml` to include the `tests` directory, ensuring that test files are not analyzed for security issues. This change helps streamline security checks by focusing on relevant source code directories. Signed-off-by: Dmitrii Safronov --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 93bff8f..a86a2f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,8 +94,7 @@ warn_unreachable = true strict_equality = true [tool.bandit] -skips = [ "B101", "B601" ] -exclude_dirs = [ ".venv", "__pycache__", ".git", "htmlcov" ] +exclude_dirs = [ ".venv", "__pycache__", ".git", "htmlcov", "tests" ] [tool.coverage.run] source = [ "src" ] From afff55d4e9bb06b6319426c66cfafdaf575cf5f0 Mon Sep 17 00:00:00 2001 From: Dmitrii Safronov Date: Tue, 9 Dec 2025 17:53:03 +0400 Subject: [PATCH 03/13] test: clean up subprocess calls in mypy tests - Removed unnecessary comments from subprocess calls in `test_mypy_support.py` to improve code readability. - Ensured consistent formatting for subprocess invocations, enhancing maintainability of the test code. This change streamlines the test code while maintaining its functionality. Signed-off-by: Dmitrii Safronov --- tests/test_mypy_support.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_mypy_support.py b/tests/test_mypy_support.py index 2c79a2a..d342088 100644 --- a/tests/test_mypy_support.py +++ b/tests/test_mypy_support.py @@ -2,7 +2,7 @@ from __future__ import annotations -import subprocess # nosec B404 +import subprocess import sys from pathlib import Path @@ -25,7 +25,7 @@ def test_mypy_type_checking() -> None: package_dir = Path(__file__).parent.parent / "src" / "logging_objects_with_schema" # Run mypy on the package directory - result = subprocess.run( # nosec B603 + result = subprocess.run( [ sys.executable, "-m", @@ -48,7 +48,7 @@ def test_mypy_strict_type_checking() -> None: package_dir = Path(__file__).parent.parent / "src" / "logging_objects_with_schema" # Run mypy in strict mode on the package directory - result = subprocess.run( # nosec B603 + result = subprocess.run( [ sys.executable, "-m", From 4a21caf4f6ee5068e4e47a52b317dca18630ec7e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 9 Dec 2025 15:21:47 +0000 Subject: [PATCH 04/13] chore(release): 0.3.0-rc.1 ## [0.3.0-rc.1](https://github.com/disafronov/python-logging-objects-with-schema/compare/v0.2.0...v0.3.0-rc.1) (2025-12-09) ### Features * add type annotations and mypy support ([d907b1e](https://github.com/disafronov/python-logging-objects-with-schema/commit/d907b1e71c42efc0e54843877d4233d7e88d5f56)) Signed-off-by: semantic-release-bot --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- uv.lock | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9863707..b252ff5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [0.3.0-rc.1](https://github.com/disafronov/python-logging-objects-with-schema/compare/v0.2.0...v0.3.0-rc.1) (2025-12-09) + +### Features + +* add type annotations and mypy support ([d907b1e](https://github.com/disafronov/python-logging-objects-with-schema/commit/d907b1e71c42efc0e54843877d4233d7e88d5f56)) + ## [0.2.0](https://github.com/disafronov/python-logging-objects-with-schema/compare/v0.1.4...v0.2.0) (2025-12-09) ### Features diff --git a/pyproject.toml b/pyproject.toml index a86a2f1..4ab6a91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "uv_build" [project] name = "logging-objects-with-schema" -version = "0.2.0" +version = "0.3.0rc1" description = "Proxy logging wrapper that validates extra fields against a JSON schema." readme = "README.md" requires-python = ">=3.10" diff --git a/uv.lock b/uv.lock index 316b18b..9d108cf 100644 --- a/uv.lock +++ b/uv.lock @@ -1394,7 +1394,7 @@ version = "0.6.3" [[package]] name = "logging-objects-with-schema" -version = "0.2.0" +version = "0.3.0rc1" [package.source] editable = "." From cd42c16341ccb5169f699766454c299f75624976 Mon Sep 17 00:00:00 2001 From: Dmitrii Safronov Date: Tue, 9 Dec 2025 19:18:54 +0400 Subject: [PATCH 05/13] refactor(schema_loader): enhance _is_leaf_node logic and add tests - Updated the _is_leaf_node function to accurately determine leaf nodes based on the presence of 'type' or 'source' fields as primitive values, rather than objects. - Added comprehensive unit tests to validate the new logic, covering various scenarios including cases where 'type' or 'source' are objects or strings. This change improves the accuracy of schema node classification in the logging framework. Signed-off-by: Dmitrii Safronov --- .../schema_loader.py | 22 +++++++-- tests/private/test_schema_loader.py | 45 +++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/logging_objects_with_schema/schema_loader.py b/src/logging_objects_with_schema/schema_loader.py index d717042..f64d46b 100644 --- a/src/logging_objects_with_schema/schema_loader.py +++ b/src/logging_objects_with_schema/schema_loader.py @@ -386,8 +386,12 @@ def _is_empty_or_none(value: Any) -> bool: def _is_leaf_node(value_dict: dict[str, Any]) -> bool: """Check if a schema node is a leaf node. - A leaf node is identified by having either 'type' or 'source' field. - Inner nodes have neither of these fields. + A leaf node is identified by having at least one of 'type' or 'source' fields + that is a primitive value (not a Mapping/object). If 'type' or 'source' are + themselves objects, they are child nodes, not leaf properties. + + Inner nodes have either no 'type'/'source' fields, or have these fields as + objects (child nodes) rather than primitive values. We use `.get()` with `is not None` check instead of `in` operator because: - A field might be present but have a None value (which indicates an error) @@ -400,7 +404,19 @@ def _is_leaf_node(value_dict: dict[str, Any]) -> bool: Returns: True if the node is a leaf, False if it's an inner node. """ - return value_dict.get("type") is not None or value_dict.get("source") is not None + type_value = value_dict.get("type") + source_value = value_dict.get("source") + + # Check if type is present and is a primitive (not a Mapping/object) + if type_value is not None and not isinstance(type_value, Mapping): + return True + + # Check if source is present and is a primitive (not a Mapping/object) + if source_value is not None and not isinstance(source_value, Mapping): + return True + + # Neither field is present as a primitive - this is an inner node + return False def _validate_and_create_leaf( diff --git a/tests/private/test_schema_loader.py b/tests/private/test_schema_loader.py index 744f0ed..954f07f 100644 --- a/tests/private/test_schema_loader.py +++ b/tests/private/test_schema_loader.py @@ -1497,6 +1497,51 @@ def test_is_leaf_node_with_both_none() -> None: assert is_leaf_node(value_dict) is False +def test_is_leaf_node_with_type_as_object() -> None: + """_is_leaf_node should return False when type is an object (child node).""" + value_dict = { + "type": {"type": "str", "source": "some.type"}, + "other": "value", + } + assert is_leaf_node(value_dict) is False + + +def test_is_leaf_node_with_source_as_object() -> None: + """_is_leaf_node should return False when source is an object (child node).""" + value_dict = { + "source": {"type": "str", "source": "some.source"}, + "other": "value", + } + assert is_leaf_node(value_dict) is False + + +def test_is_leaf_node_with_both_as_objects() -> None: + """_is_leaf_node should return False when both type and source are objects.""" + value_dict = { + "type": {"type": "str", "source": "some.type"}, + "source": {"type": "str", "source": "some.source"}, + } + assert is_leaf_node(value_dict) is False + + +def test_is_leaf_node_with_type_as_object_source_as_string() -> None: + """_is_leaf_node should return True when type is object but source is string.""" + value_dict = { + "type": {"type": "str", "source": "some.type"}, + "source": "request_id", + } + assert is_leaf_node(value_dict) is True + + +def test_is_leaf_node_with_source_as_object_type_as_string() -> None: + """_is_leaf_node should return True when source is object but type is string.""" + value_dict = { + "type": "str", + "source": {"type": "str", "source": "some.source"}, + } + assert is_leaf_node(value_dict) is True + + def test_get_schema_path_uses_cached_missing_file_path( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, From c7a1bba9cf78d956b926d73daf63674669b098d6 Mon Sep 17 00:00:00 2001 From: Dmitrii Safronov Date: Tue, 9 Dec 2025 19:27:32 +0400 Subject: [PATCH 06/13] refactor(schema_loader): refine leaf node detection logic - Updated the _is_leaf_node function to check for 'type' and 'source' fields as strings instead of primitive values, enhancing the accuracy of leaf node identification. - Adjusted comments to reflect the new logic, ensuring clarity in the codebase. This change improves the schema node classification process in the logging framework. Signed-off-by: Dmitrii Safronov --- src/logging_objects_with_schema/schema_loader.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/logging_objects_with_schema/schema_loader.py b/src/logging_objects_with_schema/schema_loader.py index f64d46b..830f382 100644 --- a/src/logging_objects_with_schema/schema_loader.py +++ b/src/logging_objects_with_schema/schema_loader.py @@ -387,11 +387,11 @@ def _is_leaf_node(value_dict: dict[str, Any]) -> bool: """Check if a schema node is a leaf node. A leaf node is identified by having at least one of 'type' or 'source' fields - that is a primitive value (not a Mapping/object). If 'type' or 'source' are - themselves objects, they are child nodes, not leaf properties. + that is a string. If 'type' or 'source' are themselves objects (not strings), + they are child nodes, not leaf properties. Inner nodes have either no 'type'/'source' fields, or have these fields as - objects (child nodes) rather than primitive values. + objects (child nodes) rather than strings. We use `.get()` with `is not None` check instead of `in` operator because: - A field might be present but have a None value (which indicates an error) @@ -407,15 +407,15 @@ def _is_leaf_node(value_dict: dict[str, Any]) -> bool: type_value = value_dict.get("type") source_value = value_dict.get("source") - # Check if type is present and is a primitive (not a Mapping/object) - if type_value is not None and not isinstance(type_value, Mapping): + # Check if type is present and is a string + if isinstance(type_value, str): return True - # Check if source is present and is a primitive (not a Mapping/object) - if source_value is not None and not isinstance(source_value, Mapping): + # Check if source is present and is a string + if isinstance(source_value, str): return True - # Neither field is present as a primitive - this is an inner node + # Neither field is present as a string - this is an inner node return False From ff319295a4021b0e6e82627bcc278bb32abd659b Mon Sep 17 00:00:00 2001 From: Dmitrii Safronov Date: Tue, 9 Dec 2025 20:20:26 +0400 Subject: [PATCH 07/13] refactor(schema_loader): enhance node validation logic and update documentation - Introduced a new function, _determine_node_type_and_validate, to accurately classify nodes as leaf or inner based on their structure, ensuring that nodes cannot simultaneously have properties and children. - Updated the README.md to clarify the definitions and validation rules for inner and leaf nodes. - Added unit tests to cover various scenarios, including cases of mixed nodes, empty nodes, and validation errors, improving overall test coverage. These changes enhance the schema validation process in the logging framework, ensuring stricter adherence to node structure rules. Signed-off-by: Dmitrii Safronov --- README.md | 22 +++- .../schema_loader.py | 116 +++++++++++++++-- tests/private/test_schema_loader.py | 121 +++++++++++++++++- 3 files changed, 241 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 602fe3a..45679b0 100644 --- a/README.md +++ b/README.md @@ -180,11 +180,27 @@ An example of a valid empty schema (no leaves, no problems): **Schema structure:** -- **Inner nodes**: Objects without `type` and `source` fields (used for nesting). -- **Leaf nodes**: Objects with both `type` and `source` fields. A valid leaf - must have both fields present and non-empty. +- **Inner nodes**: Objects that contain child nodes (any fields that are objects). + Inner nodes cannot have `type` or `source` as strings (they can only have + these fields as objects, which are child nodes). Inner nodes must have at + least one child node. +- **Leaf nodes**: Objects with `type` and `source` fields as strings (properties). + A valid leaf must have both `type` and `source` present and non-empty. Leaf + nodes cannot have child nodes (any fields that are objects). +- **Node validation rules**: + - A node cannot be both a leaf and an inner node (cannot have both properties + and children simultaneously). + - A node cannot be empty (must be either a leaf with properties or an inner + node with children). + - Fields `type`, `source`, and `item_type` can be used as child node names + in inner nodes (as objects), but then they are treated as children, not + as leaf properties. - **`type`**: One of `"str"`, `"int"`, `"float"`, `"bool"`, or `"list"`. + Must be a string for leaf nodes. - **`source`**: The name of the field in `extra` from which the value is taken. + Must be a string for leaf nodes. +- **`item_type`**: Optional field for list-typed leaves. Must be a string if + present. See "List-typed fields" section below. - **Root key restrictions**: Root keys cannot conflict with standard `logging` module fields (e.g., `name`, `levelno`, `pathname`). Such conflicts cause schema validation to fail. diff --git a/src/logging_objects_with_schema/schema_loader.py b/src/logging_objects_with_schema/schema_loader.py index 830f382..fe2a24c 100644 --- a/src/logging_objects_with_schema/schema_loader.py +++ b/src/logging_objects_with_schema/schema_loader.py @@ -16,7 +16,7 @@ from collections.abc import Iterable, Mapping, MutableMapping from dataclasses import dataclass from pathlib import Path -from typing import Any +from typing import Any, Literal from .errors import _SchemaProblem @@ -383,6 +383,90 @@ def _is_empty_or_none(value: Any) -> bool: return value is None or (isinstance(value, str) and value.strip() == "") +def _determine_node_type_and_validate( + value_dict: dict[str, Any], + path: tuple[str, ...], + key: str, + problems: list[_SchemaProblem], +) -> tuple[Literal["leaf", "inner"] | None, bool]: + """Determine node type (leaf/inner) and validate node structure. + + A node can be either: + - A leaf node: has 'type' and 'source' as strings (properties), no children + - An inner node: has children (any fields that are objects), no leaf properties + + A node cannot have both properties and children, and cannot be empty. + + Args: + value_dict: Dictionary containing node data. + path: Current path in the schema tree. + key: Current key being processed. + problems: List to collect validation problems. Validation errors are + automatically appended to this list when an invalid node is detected. + + Returns: + Tuple of (node_type, is_valid) where: + - node_type: "leaf" for valid leaf nodes, "inner" for valid inner nodes, + or None if the node is invalid + - is_valid: True if node is valid, False if there are validation errors + + When is_valid is False: + - node_type is always None + - A validation problem has been added to the problems list + - The caller should skip processing this node (e.g., use continue) + """ + type_value = value_dict.get("type") + source_value = value_dict.get("source") + item_type_value = value_dict.get("item_type") + + # Check if node has leaf properties + # Leaf properties are: type (required, string), source (required, string), + # item_type (optional, string). If type/source/item_type are objects, + # they are children, not properties + has_leaf_properties = ( + isinstance(type_value, str) + or isinstance(source_value, str) + or isinstance(item_type_value, str) + ) + + # Check if node has children (any field that is an object/Mapping) + # Children can have ANY names, including type, source, item_type - this is + # valid for inner nodes. If type, source, or item_type are objects, + # they count as children + has_children = any( + isinstance(field_value, Mapping) for field_value in value_dict.values() + ) + + # Validate node structure + if has_leaf_properties and has_children: + # Node cannot have both properties and children + problems.append( + _SchemaProblem( + f"Invalid node at {_format_path(path, key)}: " + f"node cannot have both properties (type/source as strings) " + f"and children (object fields)" + ), + ) + return (None, False) + + if not has_leaf_properties and not has_children: + # Node must be either a leaf or have children + problems.append( + _SchemaProblem( + f"Invalid node at {_format_path(path, key)}: " + f"node must be either a leaf (with type/source as strings) " + f"or have children (object fields)" + ), + ) + return (None, False) + + # Node is valid - determine type + if has_leaf_properties: + return ("leaf", True) + else: # has_children + return ("inner", True) + + def _is_leaf_node(value_dict: dict[str, Any]) -> bool: """Check if a schema node is a leaf node. @@ -407,12 +491,12 @@ def _is_leaf_node(value_dict: dict[str, Any]) -> bool: type_value = value_dict.get("type") source_value = value_dict.get("source") - # Check if type is present and is a string - if isinstance(type_value, str): - return True + # If either field is an object (Mapping), this is an inner node, not a leaf + if isinstance(type_value, Mapping) or isinstance(source_value, Mapping): + return False - # Check if source is present and is a string - if isinstance(source_value, str): + # Check if at least one field is present and is a string + if isinstance(type_value, str) or isinstance(source_value, str): return True # Neither field is present as a string - this is an inner node @@ -462,6 +546,9 @@ def _validate_and_create_leaf( if type_invalid or source_invalid: return None + # Note: Check for children is done in _determine_node_type_and_validate() + # before this function is called, so we don't need to check here. + # Convert to string before lookup to handle cases where the JSON parser # might return non-string types (though this shouldn't happen with valid JSON). # This ensures type safety and consistent behavior. @@ -572,12 +659,23 @@ def _compile_schema_tree( # might be a read-only Mapping (e.g., from JSON parsing). value_dict = dict(value) - if _is_leaf_node(value_dict): + # Determine node type and validate structure + # (checks for mixed nodes, empty nodes, etc.) + node_type, is_valid = _determine_node_type_and_validate( + value_dict, path, key, problems + ) + + if not is_valid: + # Node has validation errors, skip processing but continue with other nodes + continue + + if node_type == "leaf": + # Process as leaf node leaf = _validate_and_create_leaf(value_dict, path, key, problems) if leaf is not None: yield leaf - else: - # This is an inner node; recurse into children. + elif node_type == "inner": + # Process as inner node - recurse into children for child_leaf in _compile_schema_tree(value_dict, path + (key,), problems): yield child_leaf diff --git a/tests/private/test_schema_loader.py b/tests/private/test_schema_loader.py index 954f07f..dc2ecdc 100644 --- a/tests/private/test_schema_loader.py +++ b/tests/private/test_schema_loader.py @@ -212,8 +212,12 @@ def test_schema_with_only_inner_nodes_produces_empty_compiled_schema( compiled, problems = compile_schema_internal() assert isinstance(compiled, _CompiledSchema) assert compiled.is_empty - # Schema with only inner nodes is valid, just empty (no problems) - assert problems == [] + # Schema with empty inner node should produce problem + assert len(problems) > 0 + assert any( + "must be either a leaf" in p.message and "or have children" in p.message + for p in problems + ) def test_deeply_nested_schema_compiles_correctly( @@ -1525,21 +1529,126 @@ def test_is_leaf_node_with_both_as_objects() -> None: def test_is_leaf_node_with_type_as_object_source_as_string() -> None: - """_is_leaf_node should return True when type is object but source is string.""" + """_is_leaf_node should return False when type is object (even if source is string).""" # noqa: E501 value_dict = { "type": {"type": "str", "source": "some.type"}, "source": "request_id", } - assert is_leaf_node(value_dict) is True + assert is_leaf_node(value_dict) is False def test_is_leaf_node_with_source_as_object_type_as_string() -> None: - """_is_leaf_node should return True when source is object but type is string.""" + """_is_leaf_node should return False when source is object (even if type is string).""" # noqa: E501 value_dict = { "type": "str", "source": {"type": "str", "source": "some.source"}, } - assert is_leaf_node(value_dict) is True + assert is_leaf_node(value_dict) is False + + +def test_leaf_node_with_child_nodes_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Leaf node with child nodes (objects) should produce problem.""" + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "RequestID": { + "type": "str", + "source": "request_id", + "child": {"type": "str", "source": "child"}, # Child node - error + }, + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + assert any("cannot have both properties" in p.message for p in problems) + + +def test_inner_node_with_type_source_as_strings_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Inner node with type/source as strings should produce problem.""" + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "RequestID": { + "type": { + "type": "str", + "source": "some.type", + }, # Object - makes it inner node + "source": "request_id", # String - error for inner node + "child": {"type": "str", "source": "child"}, # Child node + }, + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + # Schema is not empty because it has valid child leaves, but should have problems + assert any("cannot have both properties" in p.message for p in problems) + + +def test_empty_node_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Empty node (no properties, no children) should produce problem.""" + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "EmptyNode": {}, # Empty node - error + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + assert any( + "must be either a leaf" in p.message and "or have children" in p.message + for p in problems + ) + + +def test_mixed_node_with_properties_and_children_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Node with both properties (type/source as strings) and children should produce problem.""" # noqa: E501 + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "MixedNode": { + "type": "str", # Property + "source": "request_id", # Property + "child": {"type": "str", "source": "child"}, # Child - error + }, + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + assert any( + "cannot have both properties" in p.message and "and children" in p.message + for p in problems + ) def test_get_schema_path_uses_cached_missing_file_path( From 784c105105ca96e78c69c0f844693011981f6410 Mon Sep 17 00:00:00 2001 From: Dmitrii Safronov Date: Tue, 9 Dec 2025 20:26:56 +0400 Subject: [PATCH 08/13] fix(schema_loader): enhance validation for leaf node source type - Updated the validation logic in _validate_and_create_leaf to ensure that the source field is a string, preventing None, empty, or non-string types from being accepted. - Added unit tests to cover edge cases where the type and source fields are invalid, ensuring that appropriate problems are raised during schema compilation. These changes improve the robustness of the schema validation process in the logging framework. Signed-off-by: Dmitrii Safronov --- .../schema_loader.py | 3 +- tests/private/test_schema_loader.py | 131 ++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) diff --git a/src/logging_objects_with_schema/schema_loader.py b/src/logging_objects_with_schema/schema_loader.py index fe2a24c..787c46c 100644 --- a/src/logging_objects_with_schema/schema_loader.py +++ b/src/logging_objects_with_schema/schema_loader.py @@ -525,7 +525,8 @@ def _validate_and_create_leaf( # This is supposed to be a leaf - validate required fields first. type_invalid = _is_empty_or_none(leaf_type) - source_invalid = _is_empty_or_none(leaf_source) + # Source must be a string (not None, not empty, and not other types like bool/int) + source_invalid = _is_empty_or_none(leaf_source) or not isinstance(leaf_source, str) if type_invalid: problems.append( diff --git a/tests/private/test_schema_loader.py b/tests/private/test_schema_loader.py index dc2ecdc..3056fe5 100644 --- a/tests/private/test_schema_loader.py +++ b/tests/private/test_schema_loader.py @@ -1861,3 +1861,134 @@ def test_compile_schema_internal_caches_by_forbidden_keys( compiled3, problems3 = compile_schema_internal(forbidden_keys={"some_key"}) assert compiled3 is not compiled1 # Different cache entry assert not problems3 + + +def test_node_with_only_item_type_as_string_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Node with only item_type as string should produce problem. + + This tests the edge case where item_type is the only string field. The node + will be determined as "leaf" by _determine_node_type_and_validate, but + _validate_and_create_leaf should reject it because type and source are required. + """ + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "Tags": {"item_type": "str"}, # Only item_type, missing type and source + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + # Should have problems because type and source are required + assert any("type cannot be None or empty" in p.message for p in problems) + + +def test_node_with_type_as_number_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Node with type as number (non-string, non-object) should produce problem. + + This tests the edge case where type field has a non-string, non-object value. + The node will be determined as "leaf" (because source is a string), but + _validate_and_create_leaf should reject it because the type value is invalid. + """ + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "RequestID": {"type": 123, "source": "request_id"}, # type is number + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + # Should have problem because type '123' is unknown + assert any("Unknown type" in p.message and "123" in p.message for p in problems) + + +def test_node_with_source_as_boolean_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Node with source as boolean (non-string, non-object) should produce problem. + + This tests the edge case where source field has a non-string, non-object value. + The node will be determined as "leaf" (because type is a string), but + _validate_and_create_leaf should reject it because source must be a string. + """ + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "RequestID": {"type": "str", "source": True}, # source is boolean + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + # Should have problem because source must be a string, not boolean + assert any("source cannot be None or empty" in p.message for p in problems) + + +def test_node_with_type_and_source_as_numbers_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Node with both type and source as numbers should produce problem. + + This tests the edge case where both type and source fields have non-string, + non-object values. The node should be determined as "empty" (neither leaf nor inner) + because has_leaf_properties = False (no string fields) and has_children = False. + """ + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "RequestID": {"type": 123, "source": 456}, # Both are numbers + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + # Should have problem because node is neither leaf nor inner + assert any( + "must be either a leaf" in p.message and "or have children" in p.message + for p in problems + ) + + +def test_determine_node_type_with_item_type_only() -> None: + """_determine_node_type_and_validate identifies node with only item_type as leaf. + + This tests that a node with only item_type as string (without type and source) + is correctly identified as a leaf node by _determine_node_type_and_validate. + The validation of required fields happens later in _validate_and_create_leaf. + """ + problems: list[_SchemaProblem] = [] + value_dict = {"item_type": "str"} # Only item_type, no type or source + + node_type, is_valid = schema_loader._determine_node_type_and_validate( + value_dict, ("ServicePayload",), "Tags", problems + ) + + # Should be identified as leaf (has_leaf_properties = True) + assert node_type == "leaf" + assert is_valid is True + assert problems == [] # No problems at this stage, validation happens later From 92a5ba0a2887bf48917da2576f2ef139f4050a5a Mon Sep 17 00:00:00 2001 From: Dmitrii Safronov Date: Tue, 9 Dec 2025 20:38:06 +0400 Subject: [PATCH 09/13] fix(schema_loader): improve validation for leaf node types - Enhanced the validation logic in _validate_and_create_leaf to ensure that the 'type', 'source', and 'item_type' fields are strings, preventing non-string types from being accepted. - Added unit tests to cover various edge cases where these fields are invalid, ensuring that appropriate problems are raised during schema compilation. These changes strengthen the schema validation process in the logging framework, ensuring stricter adherence to type requirements. Signed-off-by: Dmitrii Safronov --- .../schema_loader.py | 13 +- tests/private/test_schema_loader.py | 179 +++++++++++++++++- 2 files changed, 186 insertions(+), 6 deletions(-) diff --git a/src/logging_objects_with_schema/schema_loader.py b/src/logging_objects_with_schema/schema_loader.py index 787c46c..168cb58 100644 --- a/src/logging_objects_with_schema/schema_loader.py +++ b/src/logging_objects_with_schema/schema_loader.py @@ -524,7 +524,8 @@ def _validate_and_create_leaf( leaf_source = value_dict.get("source") # This is supposed to be a leaf - validate required fields first. - type_invalid = _is_empty_or_none(leaf_type) + # Type must be a string (not None, not empty, and not other types like bool/int) + type_invalid = _is_empty_or_none(leaf_type) or not isinstance(leaf_type, str) # Source must be a string (not None, not empty, and not other types like bool/int) source_invalid = _is_empty_or_none(leaf_source) or not isinstance(leaf_source, str) @@ -567,7 +568,11 @@ def _validate_and_create_leaf( # to ensure element homogeneity (e.g. list[str], list[int]). if expected_type is list: item_type_name = value_dict.get("item_type") - item_type_invalid = _is_empty_or_none(item_type_name) + # Item type must be a string (not None, not empty, and not other types + # like bool/int) + item_type_invalid = _is_empty_or_none(item_type_name) or not isinstance( + item_type_name, str + ) if item_type_invalid: problems.append( _SchemaProblem( @@ -675,8 +680,10 @@ def _compile_schema_tree( leaf = _validate_and_create_leaf(value_dict, path, key, problems) if leaf is not None: yield leaf - elif node_type == "inner": + else: # node_type == "inner" # Process as inner node - recurse into children + # Note: node_type can only be "leaf" or "inner" when is_valid is True. + # If is_valid is False, we already continue above. for child_leaf in _compile_schema_tree(value_dict, path + (key,), problems): yield child_leaf diff --git a/tests/private/test_schema_loader.py b/tests/private/test_schema_loader.py index 3056fe5..06dd12d 100644 --- a/tests/private/test_schema_loader.py +++ b/tests/private/test_schema_loader.py @@ -1898,7 +1898,7 @@ def test_node_with_type_as_number_produces_problem( This tests the edge case where type field has a non-string, non-object value. The node will be determined as "leaf" (because source is a string), but - _validate_and_create_leaf should reject it because the type value is invalid. + _validate_and_create_leaf should reject it because type must be a string. """ monkeypatch.chdir(tmp_path) _write_schema( @@ -1913,8 +1913,35 @@ def test_node_with_type_as_number_produces_problem( compiled, problems = compile_schema_internal() assert isinstance(compiled, _CompiledSchema) assert compiled.is_empty - # Should have problem because type '123' is unknown - assert any("Unknown type" in p.message and "123" in p.message for p in problems) + # Should have problem because type must be a string, not a number + assert any("type cannot be None or empty" in p.message for p in problems) + + +def test_node_with_type_as_boolean_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Node with type as boolean (non-string, non-object) should produce problem. + + This tests the edge case where type field has a non-string, non-object value. + The node will be determined as "leaf" (because source is a string), but + _validate_and_create_leaf should reject it because type must be a string. + """ + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "RequestID": {"type": True, "source": "request_id"}, # type is boolean + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + # Should have problem because type must be a string, not boolean + assert any("type cannot be None or empty" in p.message for p in problems) def test_node_with_source_as_boolean_produces_problem( @@ -1944,6 +1971,33 @@ def test_node_with_source_as_boolean_produces_problem( assert any("source cannot be None or empty" in p.message for p in problems) +def test_node_with_source_as_number_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Node with source as number (non-string, non-object) should produce problem. + + This tests the edge case where source field has a non-string, non-object value. + The node will be determined as "leaf" (because type is a string), but + _validate_and_create_leaf should reject it because source must be a string. + """ + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "RequestID": {"type": "str", "source": 123}, # source is number + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + # Should have problem because source must be a string, not number + assert any("source cannot be None or empty" in p.message for p in problems) + + def test_node_with_type_and_source_as_numbers_produces_problem( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, @@ -1992,3 +2046,122 @@ def test_determine_node_type_with_item_type_only() -> None: assert node_type == "leaf" assert is_valid is True assert problems == [] # No problems at this stage, validation happens later + + +def test_node_with_item_type_as_number_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Node with item_type as number (non-string) should produce problem. + + This tests the edge case where item_type field has a non-string value. + The node will be determined as "leaf" (because type and source are strings), + but _validate_and_create_leaf should reject it because item_type must be a string. + """ + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "Tags": { + "type": "list", + "source": "tags", + "item_type": 123, # item_type is number + }, + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + # Should have problem because item_type must be a string, not a number + assert any("item_type is required for list type" in p.message for p in problems) + + +def test_node_with_item_type_as_boolean_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Node with item_type as boolean (non-string) should produce problem. + + This tests the edge case where item_type field has a non-string value. + The node will be determined as "leaf" (because type and source are strings), + but _validate_and_create_leaf should reject it because item_type must be a string. + """ + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "Tags": { + "type": "list", + "source": "tags", + "item_type": True, # item_type is boolean + }, + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + # Should have problem because item_type must be a string, not a boolean + assert any("item_type is required for list type" in p.message for p in problems) + + +def test_node_with_type_as_list_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Node with type as list (non-string, non-Mapping) should produce problem. + + This tests the edge case where type field has a list value (not a Mapping). + The node will be determined as "leaf" (because source is a string), but + _validate_and_create_leaf should reject it because type must be a string. + """ + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "RequestID": {"type": ["str"], "source": "request_id"}, # type is list + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + # Should have problem because type must be a string, not a list + assert any("type cannot be None or empty" in p.message for p in problems) + + +def test_node_with_source_as_list_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Node with source as list (non-string, non-Mapping) should produce problem. + + This tests the edge case where source field has a list value (not a Mapping). + The node will be determined as "leaf" (because type is a string), but + _validate_and_create_leaf should reject it because source must be a string. + """ + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "RequestID": { + "type": "str", + "source": ["request_id"], + }, # source is list + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + # Should have problem because source must be a string, not a list + assert any("source cannot be None or empty" in p.message for p in problems) From 1567486629ec1bbb9cb54437dc1189108602bed0 Mon Sep 17 00:00:00 2001 From: Dmitrii Safronov Date: Tue, 9 Dec 2025 20:40:09 +0400 Subject: [PATCH 10/13] test(schema_loader): add test for mixed node type validation - Introduced a new test to validate the behavior of leaf nodes when the type is a string and the source is an object. This scenario should trigger an error indicating that a node cannot have both properties and children. - The test ensures that the schema validation logic correctly identifies and raises problems for mixed node types, enhancing the robustness of the validation process. These changes improve the coverage of edge cases in the schema validation tests. Signed-off-by: Dmitrii Safronov --- tests/private/test_schema_loader.py | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/private/test_schema_loader.py b/tests/private/test_schema_loader.py index 06dd12d..394de2f 100644 --- a/tests/private/test_schema_loader.py +++ b/tests/private/test_schema_loader.py @@ -1599,6 +1599,38 @@ def test_inner_node_with_type_source_as_strings_produces_problem( assert any("cannot have both properties" in p.message for p in problems) +def test_leaf_node_with_type_string_source_object_produces_problem( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Leaf node with type as string and source as object should produce problem. + + This tests the edge case where type is a string (leaf property) but source + is an object (child). This should be detected as a mixed node and produce + an error "cannot have both properties and children". + """ + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "RequestID": { + "type": "str", # String - leaf property + "source": { + "type": "str", + "source": "some.source", + }, # Object - child, error for leaf node + }, + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + assert compiled.is_empty + assert any("cannot have both properties" in p.message for p in problems) + + def test_empty_node_produces_problem( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, From 07b186b243543528053ece7dc6aceb26da54c888 Mon Sep 17 00:00:00 2001 From: Dmitrii Safronov Date: Tue, 9 Dec 2025 20:43:38 +0400 Subject: [PATCH 11/13] test(schema_loader): add test for item_type as object - Introduced a new test to verify that nodes with item_type defined as an object (Mapping) are correctly identified as inner nodes. This ensures that the schema validation logic processes nested structures appropriately. - The test checks that the compiled schema reflects the correct classification and that no validation problems are raised for valid configurations. These changes enhance the coverage of schema validation tests, particularly for complex node structures. Signed-off-by: Dmitrii Safronov --- tests/private/test_schema_loader.py | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/private/test_schema_loader.py b/tests/private/test_schema_loader.py index 394de2f..ad73f49 100644 --- a/tests/private/test_schema_loader.py +++ b/tests/private/test_schema_loader.py @@ -2142,6 +2142,40 @@ def test_node_with_item_type_as_boolean_produces_problem( assert any("item_type is required for list type" in p.message for p in problems) +def test_node_with_item_type_as_object_is_determined_as_inner( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Node with item_type as object (Mapping) should be determined as inner node. + + This tests that when item_type is an object (Mapping), it counts as a child, + not as a leaf property. The node should be determined as "inner" and the + nested structure should be processed correctly. + """ + monkeypatch.chdir(tmp_path) + _write_schema( + tmp_path, + { + "ServicePayload": { + "Tags": { + "item_type": { + "type": "str", + "source": "some.item_type", + }, # item_type is object - should be treated as child + }, + }, + }, + ) + + compiled, problems = compile_schema_internal() + assert isinstance(compiled, _CompiledSchema) + # Node should be determined as inner, not leaf + # Should have valid child leaves from the nested structure + assert not compiled.is_empty + assert any(leaf.source == "some.item_type" for leaf in compiled.leaves) + assert problems == [] + + def test_node_with_type_as_list_produces_problem( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, From ce6696619cb7b24d9e82bb2224909babc4ab6bac Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 9 Dec 2025 16:49:21 +0000 Subject: [PATCH 12/13] chore(release): 0.3.0-rc.2 ## [0.3.0-rc.2](https://github.com/disafronov/python-logging-objects-with-schema/compare/v0.3.0-rc.1...v0.3.0-rc.2) (2025-12-09) ### Bug Fixes * **schema_loader:** enhance validation for leaf node source type ([784c105](https://github.com/disafronov/python-logging-objects-with-schema/commit/784c105105ca96e78c69c0f844693011981f6410)) * **schema_loader:** improve validation for leaf node types ([92a5ba0](https://github.com/disafronov/python-logging-objects-with-schema/commit/92a5ba0a2887bf48917da2576f2ef139f4050a5a)) Signed-off-by: semantic-release-bot --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- uv.lock | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b252ff5..6b65a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.3.0-rc.2](https://github.com/disafronov/python-logging-objects-with-schema/compare/v0.3.0-rc.1...v0.3.0-rc.2) (2025-12-09) + +### Bug Fixes + +* **schema_loader:** enhance validation for leaf node source type ([784c105](https://github.com/disafronov/python-logging-objects-with-schema/commit/784c105105ca96e78c69c0f844693011981f6410)) +* **schema_loader:** improve validation for leaf node types ([92a5ba0](https://github.com/disafronov/python-logging-objects-with-schema/commit/92a5ba0a2887bf48917da2576f2ef139f4050a5a)) + ## [0.3.0-rc.1](https://github.com/disafronov/python-logging-objects-with-schema/compare/v0.2.0...v0.3.0-rc.1) (2025-12-09) ### Features diff --git a/pyproject.toml b/pyproject.toml index 4ab6a91..e894259 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "uv_build" [project] name = "logging-objects-with-schema" -version = "0.3.0rc1" +version = "0.3.0rc2" description = "Proxy logging wrapper that validates extra fields against a JSON schema." readme = "README.md" requires-python = ">=3.10" diff --git a/uv.lock b/uv.lock index 9d108cf..ca411b0 100644 --- a/uv.lock +++ b/uv.lock @@ -1394,7 +1394,7 @@ version = "0.6.3" [[package]] name = "logging-objects-with-schema" -version = "0.3.0rc1" +version = "0.3.0rc2" [package.source] editable = "." From c81e81a9bbf7314256963c88a7d4fc34fe6fdc48 Mon Sep 17 00:00:00 2001 From: Dmitrii Safronov Date: Tue, 9 Dec 2025 20:57:16 +0400 Subject: [PATCH 13/13] ci: Remove conditional check for release job execution based on commit message in GitHub workflows Signed-off-by: Dmitrii Safronov --- .github/workflows/release-stable.yaml | 1 - .github/workflows/release.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/release-stable.yaml b/.github/workflows/release-stable.yaml index 3f509ad..b7d9769 100644 --- a/.github/workflows/release-stable.yaml +++ b/.github/workflows/release-stable.yaml @@ -9,7 +9,6 @@ name: Release (stable) jobs: release: name: "Release" - if: ${{ !startsWith(github.event.head_commit.message, 'chore(release):') }} runs-on: ubuntu-latest permissions: contents: write diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 76324f3..dee15fa 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -9,7 +9,6 @@ name: Release jobs: release: name: "Release" - if: ${{ !startsWith(github.event.head_commit.message, 'chore(release):') }} runs-on: ubuntu-latest permissions: contents: write