From ab04bcebf2d234ea5b48f1fee3917e0c4f608cd6 Mon Sep 17 00:00:00 2001 From: Gas Town Mayor Date: Fri, 28 Aug 2026 13:40:29 -0300 Subject: [PATCH 1/5] fix: isolate model imports from dependency runtime --- src/flext_core/_runtime/__init__.py | 31 +++++++++++++++++-- src/flext_core/_utilities/collection.py | 4 +-- src/flext_core/_utilities/collection_merge.py | 18 ++++++----- tests/unit/test_lazy_exports.py | 20 ++++++++++++ 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/flext_core/_runtime/__init__.py b/src/flext_core/_runtime/__init__.py index b9b9afdd4..99a1dd1be 100644 --- a/src/flext_core/_runtime/__init__.py +++ b/src/flext_core/_runtime/__init__.py @@ -6,7 +6,32 @@ from __future__ import annotations -from ._container import FlextRuntimeContainer -from ._dependency import FlextRuntimeDependencyIntegration +from types import MappingProxyType +from typing import TYPE_CHECKING -__all__: list[str] = ["FlextRuntimeContainer", "FlextRuntimeDependencyIntegration"] +from flext_core.lazy import build_lazy_import_map, install_lazy_exports + +if TYPE_CHECKING: + from ._container import FlextRuntimeContainer + from ._dependency import FlextRuntimeDependencyIntegration + +__all__: tuple[str, ...] = ( + "FlextRuntimeContainer", + "FlextRuntimeDependencyIntegration", +) + +install_lazy_exports( + __name__, + globals(), + MappingProxyType( + build_lazy_import_map( + MappingProxyType({ + "._container": ("FlextRuntimeContainer",), + "._dependency": ("FlextRuntimeDependencyIntegration",), + }), + alias_groups=MappingProxyType({}), + sort_keys=False, + ) + ), + public_exports=__all__, +) diff --git a/src/flext_core/_utilities/collection.py b/src/flext_core/_utilities/collection.py index ed6548104..29f7202a4 100644 --- a/src/flext_core/_utilities/collection.py +++ b/src/flext_core/_utilities/collection.py @@ -17,10 +17,10 @@ FlextConstants as c, FlextProtocols as p, FlextResult as r, - FlextRuntime, FlextTypes as t, ) from flext_core._models.containers import FlextModelsContainers as mc +from flext_core._runtime._metadata import FlextRuntimeMetadata from .collection_iter import FlextUtilitiesCollectionIter from .collection_merge import FlextUtilitiesCollectionMerge @@ -44,7 +44,7 @@ def normalize_domain_event_data( for key, item in raw_source.items(): if item is None: continue - normalized[key] = FlextRuntime.normalize_to_metadata(item) + normalized[key] = FlextRuntimeMetadata.normalize_to_metadata(item) return normalized @staticmethod diff --git a/src/flext_core/_utilities/collection_merge.py b/src/flext_core/_utilities/collection_merge.py index 412577555..c4de5fa13 100644 --- a/src/flext_core/_utilities/collection_merge.py +++ b/src/flext_core/_utilities/collection_merge.py @@ -10,13 +10,9 @@ from collections.abc import Callable, Mapping from typing import ClassVar, TypeGuard -from flext_core import ( - FlextProtocols as p, - FlextResult as r, - FlextRuntime, - FlextTypes as t, -) +from flext_core import FlextProtocols as p, FlextResult as r, FlextTypes as t from flext_core._constants.cqrs import FlextConstantsCqrs as _c_cqrs +from flext_core._runtime._metadata import FlextRuntimeMetadata from flext_core._utilities.guards_type_core import FlextUtilitiesGuardsTypeCore @@ -40,7 +36,10 @@ def _merge_deep_single_key( if FlextUtilitiesCollectionMerge._is_json_mapping( current_val ) and FlextUtilitiesCollectionMerge._is_json_mapping(value): - result[key] = FlextRuntime.normalize_to_metadata({**current_val, **value}) + result[key] = FlextRuntimeMetadata.normalize_to_metadata({ + **current_val, + **value, + }) return r[bool].ok(True) result[key] = value return r[bool].ok(True) @@ -87,7 +86,10 @@ def _merge_append( if FlextUtilitiesCollectionMerge._is_json_list( current_val ) and FlextUtilitiesCollectionMerge._is_json_list(value): - result[key] = FlextRuntime.normalize_to_metadata([*current_val, *value]) + result[key] = FlextRuntimeMetadata.normalize_to_metadata([ + *current_val, + *value, + ]) continue result[key] = value return r[t.JsonMapping].ok(result) diff --git a/tests/unit/test_lazy_exports.py b/tests/unit/test_lazy_exports.py index 6b808fbf4..929c2c09f 100644 --- a/tests/unit/test_lazy_exports.py +++ b/tests/unit/test_lazy_exports.py @@ -5,6 +5,7 @@ import importlib import sys from importlib.machinery import ModuleSpec +from pathlib import Path from types import ModuleType from typing import TYPE_CHECKING @@ -16,6 +17,7 @@ lazy, lazy_attribute, ) +from tests import u if TYPE_CHECKING: from flext_core import t @@ -78,6 +80,24 @@ def test_root_package_resolves_primary_facades_via_aliases(self) -> None: assert package.u is package.FlextUtilities assert {"FlextConstants", "FlextUtilities", "u"} <= set(package.__all__) + def test_model_facade_does_not_initialize_dependency_runtime(self) -> None: + """Loading model declarations must not initialize the optional DI stack.""" + script = ( + "import sys\n" + "from flext_core import m\n" + "assert m.StrictModel\n" + "for name in sorted(sys.modules):\n" + " if name == 'fastapi' or name.startswith('fastapi.') " + "or name == 'dependency_injector' " + "or name.startswith('dependency_injector.'):\n" + " print(name)\n" + ) + + result = u.Cli.run_raw([sys.executable, "-c", script], cwd=Path.cwd()) + + assert result.success, result.error + assert result.value.stdout == "" + def test_install_without_publish_all_omits_dunder_all(self) -> None: # Arrange module_globals: t.ModuleGlobals = {} From e8dddef0514d6dd80bc2ab17ceba74158df74f69 Mon Sep 17 00:00:00 2001 From: Gas Town Mayor Date: Fri, 28 Aug 2026 15:26:02 -0300 Subject: [PATCH 2/5] fix: type private attribute factories --- src/flext_core/_models/pydantic.py | 57 ++++++++++++++++--- src/flext_core/_utilities/pydantic.py | 7 +-- .../test_utilities_pydantic_coverage_100.py | 38 +++++++++++++ 3 files changed, 89 insertions(+), 13 deletions(-) diff --git a/src/flext_core/_models/pydantic.py b/src/flext_core/_models/pydantic.py index 82481b0a6..cf9dc516b 100644 --- a/src/flext_core/_models/pydantic.py +++ b/src/flext_core/_models/pydantic.py @@ -18,7 +18,7 @@ from pathlib import Path from re import Pattern from types import EllipsisType -from typing import dataclass_transform +from typing import Literal, dataclass_transform, overload from pydantic import ( AfterValidator, @@ -36,7 +36,7 @@ JsonValue, PlainSerializer, PlainValidator, - PrivateAttr, + PrivateAttr as PydanticPrivateAttr, RootModel as PydanticRootModel, SkipValidation, TypeAdapter as PydanticTypeAdapter, @@ -82,6 +82,45 @@ def _field[DefaultT]( return field_factory(default, **kwargs) +@overload +def _private_attr[PrivateT]( + default: PrivateT | PydanticUndefinedType = PydanticUndefined, + *, + init: Literal[False] = False, +) -> PrivateT: ... + + +@overload +def _private_attr[PrivateT]( + *, + default_factory: Callable[[], PrivateT], + init: Literal[False] = False, +) -> PrivateT: ... + + +@overload +def _private_attr[PrivateT, ValidatedT]( + *, + default_factory: Callable[[dict[str, ValidatedT]], PrivateT], + init: Literal[False] = False, +) -> PrivateT: ... + + +def _private_attr[PrivateT]( + default: PrivateT | PydanticUndefinedType = PydanticUndefined, + *, + default_factory: Callable[..., PrivateT] | None = None, + init: Literal[False] = False, +) -> PrivateT: + """Typed FLEXT facade for ``pydantic.PrivateAttr``.""" + private_attr_factory: Callable[..., PrivateT] = PydanticPrivateAttr + return private_attr_factory( + default, + default_factory=default_factory, + init=init, + ) + + class FlextModelsPydantic: """Public base model classes from pydantic v2. @@ -94,19 +133,22 @@ class FlextModelsPydantic: """ @dataclass_transform( - kw_only_default=True, field_specifiers=(_field, Field, PrivateAttr) + kw_only_default=True, + field_specifiers=(_field, Field, PydanticPrivateAttr, _private_attr), ) class BaseModel(PydanticBaseModel): """Canonical BaseModel exported through the FLEXT models facade.""" @dataclass_transform( - kw_only_default=True, field_specifiers=(_field, Field, PrivateAttr) + kw_only_default=True, + field_specifiers=(_field, Field, PydanticPrivateAttr, _private_attr), ) class BaseSettings(PydanticBaseSettings): """Canonical BaseSettings exported through the FLEXT models facade.""" @dataclass_transform( - kw_only_default=True, field_specifiers=(_field, Field, PrivateAttr) + kw_only_default=True, + field_specifiers=(_field, Field, PydanticPrivateAttr, _private_attr), ) class RootModel[RootValueT](PydanticRootModel[RootValueT]): """Canonical RootModel exported through the FLEXT models facade.""" @@ -116,10 +158,7 @@ class RootModel[RootValueT](PydanticRootModel[RootValueT]): SettingsConfigDict = _PydanticSettingsConfigDict Field = staticmethod(_field) - # NOTE (multi-agent): mro-ecfu — staticmethod wrap matches Field above and - # u.PrivateAttr (_utilities/pydantic.py): pyright cannot model an unwrapped - # function class attribute called through the facade (mixins.py:59 error). - PrivateAttr = staticmethod(PrivateAttr) + PrivateAttr = staticmethod(_private_attr) SkipValidation = SkipValidation # Same unwrapped-class-attribute problem as PrivateAttr above: pyright # binds the bare decorator through the facade and infers the facade type diff --git a/src/flext_core/_utilities/pydantic.py b/src/flext_core/_utilities/pydantic.py index 196f07c78..4a9979870 100644 --- a/src/flext_core/_utilities/pydantic.py +++ b/src/flext_core/_utilities/pydantic.py @@ -14,7 +14,6 @@ AfterValidator, PlainSerializer, PlainValidator, - PrivateAttr, SkipValidation, WrapSerializer, WrapValidator, @@ -39,14 +38,14 @@ class FlextUtilitiesPydantic: Use u.* / up.* instead. """ - # Wrap field specifiers in ``staticmethod`` so pyright treats ``u.Field`` / - # ``u.PrivateAttr`` as static callables rather than instance-bound descriptors. + # Wrap field specifiers in ``staticmethod`` so type checkers treat them as + # static callables rather than instance-bound descriptors. # Why: a bare ``Field = mp.Field`` binds ``_field``'s generic first parameter # (``default: DefaultT``) to ``self`` = ``FlextUtilities`` when accessed via the # facade, so ``u.Field(default_factory=...)`` was mis-inferred as returning # ``FlextUtilities`` (bogus reportAssignmentType). ``mp.Field`` already does this. Field = staticmethod(mp.Field) - PrivateAttr = staticmethod(PrivateAttr) + PrivateAttr = staticmethod(mp.PrivateAttr) SkipValidation = SkipValidation # Same unwrapped-class-attribute problem as Field/PrivateAttr above: diff --git a/tests/unit/test_utilities_pydantic_coverage_100.py b/tests/unit/test_utilities_pydantic_coverage_100.py index 9fce51fa2..e80936bd8 100644 --- a/tests/unit/test_utilities_pydantic_coverage_100.py +++ b/tests/unit/test_utilities_pydantic_coverage_100.py @@ -2,13 +2,32 @@ from __future__ import annotations +from collections.abc import Callable + import pytest +from flext_core import m as core_m from flext_core import u from tests.models import m from tests.typings import t +def _private_label(validated_data: dict[str, str]) -> str: + return validated_data["label"] + + +def _input_reader() -> Callable[[str], str]: + return input + + +class _PrivateAttrContract(core_m.BaseModel): + label: str + _model_values: list[str] = core_m.PrivateAttr(default_factory=list) + _utility_values: list[str] = u.PrivateAttr(default_factory=list) + _label_copy: str = core_m.PrivateAttr(default_factory=_private_label) + _reader: Callable[[str], str] = u.PrivateAttr(default_factory=_input_reader) + + class TestsFlextUtilitiesPydantic: @pytest.mark.parametrize( ("raw_name", "expected_name"), @@ -110,3 +129,22 @@ def build_runtime_options( "flext.cli.jobs", ] assert resolved.settings_overrides == {"dry_run": True} + + def test_private_attr_factories_preserve_pydantic_instance_semantics( + self, + ) -> None: + first = _PrivateAttrContract(label="first") + second = _PrivateAttrContract(label="second") + + first._model_values.append("model") + first._utility_values.append("utility") + + assert first._model_values == ["model"] + assert second._model_values == [] + assert first._utility_values == ["utility"] + assert second._utility_values == [] + assert first._label_copy == "first" + assert second._label_copy == "second" + assert first._reader is input + assert second._reader is input + assert first.model_dump() == {"label": "first"} From 7119922871f39dd8e8f2cf61be7423c9cf0e4ea8 Mon Sep 17 00:00:00 2001 From: Gas Town Mayor Date: Fri, 28 Aug 2026 15:31:01 -0300 Subject: [PATCH 3/5] fix: simplify typed pydantic facades --- src/flext_core/_models/pydantic.py | 32 ++----------------- .../test_utilities_pydantic_coverage_100.py | 8 ++--- 2 files changed, 5 insertions(+), 35 deletions(-) diff --git a/src/flext_core/_models/pydantic.py b/src/flext_core/_models/pydantic.py index cf9dc516b..dddb1e5b2 100644 --- a/src/flext_core/_models/pydantic.py +++ b/src/flext_core/_models/pydantic.py @@ -18,7 +18,7 @@ from pathlib import Path from re import Pattern from types import EllipsisType -from typing import Literal, dataclass_transform, overload +from typing import Literal, dataclass_transform from pydantic import ( AfterValidator, @@ -82,30 +82,6 @@ def _field[DefaultT]( return field_factory(default, **kwargs) -@overload -def _private_attr[PrivateT]( - default: PrivateT | PydanticUndefinedType = PydanticUndefined, - *, - init: Literal[False] = False, -) -> PrivateT: ... - - -@overload -def _private_attr[PrivateT]( - *, - default_factory: Callable[[], PrivateT], - init: Literal[False] = False, -) -> PrivateT: ... - - -@overload -def _private_attr[PrivateT, ValidatedT]( - *, - default_factory: Callable[[dict[str, ValidatedT]], PrivateT], - init: Literal[False] = False, -) -> PrivateT: ... - - def _private_attr[PrivateT]( default: PrivateT | PydanticUndefinedType = PydanticUndefined, *, @@ -114,11 +90,7 @@ def _private_attr[PrivateT]( ) -> PrivateT: """Typed FLEXT facade for ``pydantic.PrivateAttr``.""" private_attr_factory: Callable[..., PrivateT] = PydanticPrivateAttr - return private_attr_factory( - default, - default_factory=default_factory, - init=init, - ) + return private_attr_factory(default, default_factory=default_factory, init=init) class FlextModelsPydantic: diff --git a/tests/unit/test_utilities_pydantic_coverage_100.py b/tests/unit/test_utilities_pydantic_coverage_100.py index e80936bd8..cf737ded2 100644 --- a/tests/unit/test_utilities_pydantic_coverage_100.py +++ b/tests/unit/test_utilities_pydantic_coverage_100.py @@ -83,7 +83,7 @@ def test_public_facade_supports_dynamic_models_and_json_roundtrip(self) -> None: assert payload_jsonable == payload_dict def test_validate_call_rejects_invalid_argument_values(self) -> None: - @u.validate_call() + @u.validate_call def double_positive(value: t.PositiveInt) -> int: doubled: int = value * 2 return doubled @@ -101,7 +101,7 @@ def test_public_facade_resolves_runtime_bootstrap_options_from_json(self) -> Non }) ) - @u.validate_call() + @u.validate_call def build_runtime_options( options: m.RuntimeBootstrapOptions, override_subproject: str, @@ -130,9 +130,7 @@ def build_runtime_options( ] assert resolved.settings_overrides == {"dry_run": True} - def test_private_attr_factories_preserve_pydantic_instance_semantics( - self, - ) -> None: + def test_private_attr_factories_preserve_pydantic_instance_semantics(self) -> None: first = _PrivateAttrContract(label="first") second = _PrivateAttrContract(label="second") From 751199e3ba5d6bb99cccf6db847161d9e20d6eb3 Mon Sep 17 00:00:00 2001 From: Gas Town Mayor Date: Fri, 28 Aug 2026 15:32:34 -0300 Subject: [PATCH 4/5] fix: type immutable mapping defaults --- .../_base_parts/flextmodelsbase_part_02.py | 2 +- .../flextmodelscontextscope_part_01.py | 2 +- .../flextmodelscontextscope_part_02.py | 25 ++++++++++++++++--- src/flext_core/_models/_context/_export.py | 4 +-- src/flext_core/_models/cqrs.py | 6 ++--- src/flext_core/_models/errors.py | 4 +-- 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/flext_core/_models/_base_parts/flextmodelsbase_part_02.py b/src/flext_core/_models/_base_parts/flextmodelsbase_part_02.py index 1d51ae28e..6bce83c7d 100644 --- a/src/flext_core/_models/_base_parts/flextmodelsbase_part_02.py +++ b/src/flext_core/_models/_base_parts/flextmodelsbase_part_02.py @@ -105,7 +105,7 @@ class Metadata(mp.BaseModel): title="Attributes", examples=[{"source": "api", "priority": "high"}], ), - ] = mp.Field(default_factory=lambda: MappingProxyType({})) + ] = mp.Field(default_factory=lambda: MappingProxyType(dict[str, t.JsonValue]())) metadata_value: Annotated[ t.Scalar | None, mp.Field(default=None, description="Scalar metadata value."), diff --git a/src/flext_core/_models/_context/__scope_parts/flextmodelscontextscope_part_01.py b/src/flext_core/_models/_context/__scope_parts/flextmodelscontextscope_part_01.py index 3cf4b040e..100571760 100644 --- a/src/flext_core/_models/_context/__scope_parts/flextmodelscontextscope_part_01.py +++ b/src/flext_core/_models/_context/__scope_parts/flextmodelscontextscope_part_01.py @@ -65,7 +65,7 @@ class ContextStatistics(FlextModelsBase.ArbitraryTypesModel): mp.Field( description="Additional metric counters and timing values grouped by metric key." ), - ] = mp.Field(default_factory=lambda: MappingProxyType({})) + ] = mp.Field(default_factory=lambda: MappingProxyType(dict[str, t.JsonValue]())) __all__: list[str] = ["FlextModelsContextScope"] diff --git a/src/flext_core/_models/_context/__scope_parts/flextmodelscontextscope_part_02.py b/src/flext_core/_models/_context/__scope_parts/flextmodelscontextscope_part_02.py index 55d8591a4..ab6d15ff1 100644 --- a/src/flext_core/_models/_context/__scope_parts/flextmodelscontextscope_part_02.py +++ b/src/flext_core/_models/_context/__scope_parts/flextmodelscontextscope_part_02.py @@ -34,10 +34,16 @@ class ContextRuntimeState(FlextModelsBase.ArbitraryTypesModel): hooks: Annotated[ t.ContextHookMap, mp.Field( - default_factory=lambda: MappingProxyType({}), + default_factory=lambda: MappingProxyType( + dict[str, t.SequenceOf[t.ContextHookCallable]]() + ), description="Lifecycle hooks keyed by event name", ), - ] = mp.Field(default_factory=lambda: MappingProxyType({})) + ] = mp.Field( + default_factory=lambda: MappingProxyType( + dict[str, t.SequenceOf[t.ContextHookCallable]]() + ) + ) statistics: Annotated[ FlextModelsContextScopePart01.ContextStatistics, mp.Field( @@ -63,10 +69,21 @@ class ContextRuntimeState(FlextModelsBase.ArbitraryTypesModel): str, contextvars.ContextVar[FlextModelsContainers.ConfigMap | None] ], mp.Field( - default_factory=lambda: MappingProxyType({}), + default_factory=lambda: MappingProxyType( + dict[ + str, + contextvars.ContextVar[FlextModelsContainers.ConfigMap | None], + ]() + ), description="ContextVar registry keyed by scope name", ), - ] = mp.Field(default_factory=lambda: MappingProxyType({})) + ] = mp.Field( + default_factory=lambda: MappingProxyType( + dict[ + str, contextvars.ContextVar[FlextModelsContainers.ConfigMap | None] + ]() + ) + ) @classmethod def create_default( diff --git a/src/flext_core/_models/_context/_export.py b/src/flext_core/_models/_context/_export.py index 29ed4d359..5023d254a 100644 --- a/src/flext_core/_models/_context/_export.py +++ b/src/flext_core/_models/_context/_export.py @@ -30,7 +30,7 @@ class ContextExport( data: Annotated[ t.MappingKV[str, t.JsonPayload], Field( - default_factory=lambda: MappingProxyType({}), + default_factory=lambda: MappingProxyType(dict[str, t.JsonPayload]()), description="All context data from all scopes", ), ] @@ -52,7 +52,7 @@ class ContextExport( ) ), Field( - default_factory=lambda: MappingProxyType({}), + default_factory=lambda: MappingProxyType(dict[str, t.JsonValue]()), description="Usage statistics (operation counts, timing info)", ), ] diff --git a/src/flext_core/_models/cqrs.py b/src/flext_core/_models/cqrs.py index 3e18dba97..5ccb4249d 100644 --- a/src/flext_core/_models/cqrs.py +++ b/src/flext_core/_models/cqrs.py @@ -130,7 +130,7 @@ class Query(m.ArbitraryTypesModel): title="Query Filters", examples=[{"status": "active", "tenant": "acme"}], ), - ] = Field(default_factory=lambda: MappingProxyType({})) + ] = Field(default_factory=lambda: MappingProxyType(dict[str, t.Scalar]())) pagination: Annotated[ _CqrsPagination, Field( @@ -238,11 +238,11 @@ class Event(m.ArbitraryTypesModel): ] = Field(default_factory=lambda: _u().generate_prefixed_id("evt")) data: Annotated[ t.MappingKV[str, t.Scalar], Field(description="Event payload data") - ] = Field(default_factory=lambda: MappingProxyType({})) + ] = Field(default_factory=lambda: MappingProxyType(dict[str, t.Scalar]())) metadata: Annotated[ t.MappingKV[str, t.Scalar], Field(description="Event metadata (timestamps, correlation IDs, etc.)"), - ] = Field(default_factory=lambda: MappingProxyType({})) + ] = Field(default_factory=lambda: MappingProxyType(dict[str, t.Scalar]())) type FlextMessage = t.MessageUnion[Command, Query, Event] diff --git a/src/flext_core/_models/errors.py b/src/flext_core/_models/errors.py index 37400dd3f..5d67d55d4 100644 --- a/src/flext_core/_models/errors.py +++ b/src/flext_core/_models/errors.py @@ -28,7 +28,7 @@ class ExceptionMetricsSnapshot(m.StrictModel): exception_counts: Annotated[ t.IntMapping, mp.Field(description="Per-exception occurrence totals keyed by type name."), - ] = mp.Field(default_factory=lambda: MappingProxyType({})) + ] = mp.Field(default_factory=lambda: MappingProxyType(dict[str, int]())) exception_counts_summary: Annotated[ str, mp.Field(description="Human-readable summary for logs and diagnostics."), @@ -61,7 +61,7 @@ class ExceptionMetricsState(m.StrictModel): exception_counts: Annotated[ t.IntMapping, mp.Field(description="Recorded counts keyed by exception type name."), - ] = mp.Field(default_factory=lambda: MappingProxyType({})) + ] = mp.Field(default_factory=lambda: MappingProxyType(dict[str, int]())) @up.computed_field @property From 187453f2a108d1573d60fc3efc9a7ce4bd8f2809 Mon Sep 17 00:00:00 2001 From: Gas Town Mayor Date: Fri, 28 Aug 2026 15:36:13 -0300 Subject: [PATCH 5/5] fix: type production callback contracts --- examples/ex_01_flext_result_helpers.py | 4 +-- .../_handlers_parts/flexthandlers_part_07.py | 14 ++++++-- .../_lazy_parts/flextlazy_part_02.py | 8 +++-- .../parser_targets_part_01.py | 32 ++++++++----------- src/flext_core/_utilities/config.py | 3 +- src/flext_core/_utilities/mapper.py | 15 ++++----- 6 files changed, 41 insertions(+), 35 deletions(-) diff --git a/examples/ex_01_flext_result_helpers.py b/examples/ex_01_flext_result_helpers.py index 5432e19ec..b11191ad3 100644 --- a/examples/ex_01_flext_result_helpers.py +++ b/examples/ex_01_flext_result_helpers.py @@ -86,10 +86,10 @@ def side_effects_and_folds(self) -> None: self.audit_check("map_or.success_default", ok_value.map_or(0)) self.audit_check("map_or.failure_default", fail_value.map_or(0)) self.audit_check( - "map_or.success_func", ok_value.map_or("none", lambda n: f"n={n}") + "map_or.success_func", ok_value.map_or("none", "n={}".format) ) self.audit_check( - "map_or.failure_func", fail_value.map_or("none", lambda n: f"n={n}") + "map_or.failure_func", fail_value.map_or("none", "n={}".format) ) self.audit_check( "fold.success", diff --git a/src/flext_core/_handlers_parts/flexthandlers_part_07.py b/src/flext_core/_handlers_parts/flexthandlers_part_07.py index 213997533..98f064da8 100644 --- a/src/flext_core/_handlers_parts/flexthandlers_part_07.py +++ b/src/flext_core/_handlers_parts/flexthandlers_part_07.py @@ -80,7 +80,11 @@ def scan_class( for name in dir(target_class) if hasattr(method := getattr(target_class, name, None), c.HANDLER_ATTR) ] - return sorted(handlers, key=lambda x: x[1].priority, reverse=True) + + def priority(item: tuple[str, p.DecoratorConfig]) -> int: + return item[1].priority + + return sorted(handlers, key=priority, reverse=True) @staticmethod def scan_module( @@ -135,7 +139,13 @@ def narrowed_func( setattr(narrowed_func, c.HANDLER_ATTR, settings) handlers.append((name, narrowed_func, settings)) - return sorted(handlers, key=lambda x: (-x[2].priority, x[0])) + + def priority_and_name( + item: tuple[str, Callable[..., t.Scalar | None], p.DecoratorConfig], + ) -> tuple[int, str]: + return -item[2].priority, item[0] + + return sorted(handlers, key=priority_and_name) __all__: list[str] = ["FlextHandlers"] diff --git a/src/flext_core/_lazy_parts/flextlazy_part_02.py b/src/flext_core/_lazy_parts/flextlazy_part_02.py index 2f57bc3c3..7baf1a1a1 100644 --- a/src/flext_core/_lazy_parts/flextlazy_part_02.py +++ b/src/flext_core/_lazy_parts/flextlazy_part_02.py @@ -3,6 +3,7 @@ from __future__ import annotations import sys +from functools import partial from types import ModuleType from typing import TYPE_CHECKING, cast @@ -207,8 +208,11 @@ def install( names = tuple(dict.fromkeys((*normalized, *all_exports))) module_globals["_LAZY_IMPORTS"] = normalized - module_globals["__getattr__"] = lambda name: self.get( - name, normalized, module_globals, module_name + module_globals["__getattr__"] = partial( + self.get, + lazy_imports=normalized, + module_globals=module_globals, + module_name=module_name, ) module_globals["__dir__"] = lambda: list(names) if publish_all: diff --git a/src/flext_core/_utilities/_parser_targets_parts/parser_targets_part_01.py b/src/flext_core/_utilities/_parser_targets_parts/parser_targets_part_01.py index 659b64ec1..b02c04da1 100644 --- a/src/flext_core/_utilities/_parser_targets_parts/parser_targets_part_01.py +++ b/src/flext_core/_utilities/_parser_targets_parts/parser_targets_part_01.py @@ -49,25 +49,19 @@ def _parse_try_direct[T]( if isinstance(value, target): return value target_name = target.__name__ if hasattr(target, "__name__") else "type" - parsed_direct: T = ( - FlextUtilitiesModel - .validate_value(target, value) - .fold( - lambda error: FlextUtilitiesParserTargets._parse_with_default( - default, - default_factory, - c.ERR_PARSER_CANNOT_PARSE_TO_TARGET.format( - field_prefix=fp, - source_type=value.__class__.__name__, - target_name=target_name, - error=error, - ), - ), - lambda validated: validated, - ) - .unwrap() - ) - return parsed_direct + validation = FlextUtilitiesModel.validate_value(target, value) + if validation.success: + return validation.value + return FlextUtilitiesParserTargets._parse_with_default( + default, + default_factory, + c.ERR_PARSER_CANNOT_PARSE_TO_TARGET.format( + field_prefix=fp, + source_type=value.__class__.__name__, + target_name=target_name, + error=validation.error, + ), + ).unwrap() @staticmethod @r.safe diff --git a/src/flext_core/_utilities/config.py b/src/flext_core/_utilities/config.py index 445e1674f..f85d5b2e5 100644 --- a/src/flext_core/_utilities/config.py +++ b/src/flext_core/_utilities/config.py @@ -14,6 +14,7 @@ import re import tomllib +from functools import partial from pathlib import Path from typing import TYPE_CHECKING, ClassVar @@ -51,7 +52,7 @@ def _expand_str(value: str, env: Mapping[str, str]) -> str: current = value for _ in range(c.CONFIG_EXPAND_MAX_PASSES): expanded = FlextUtilitiesConfig._EXPAND_PATTERN.sub( - lambda match: FlextUtilitiesConfig._expand_one(match, env), current + partial(FlextUtilitiesConfig._expand_one, env=env), current ) if expanded == current: return expanded diff --git a/src/flext_core/_utilities/mapper.py b/src/flext_core/_utilities/mapper.py index ed95faa59..b22f146ec 100644 --- a/src/flext_core/_utilities/mapper.py +++ b/src/flext_core/_utilities/mapper.py @@ -12,7 +12,6 @@ from flext_core import m, r, t from flext_core._models.pydantic import FlextModelsPydantic -from flext_core._utilities.collection import FlextUtilitiesCollection from flext_core._utilities.guards_type_core import FlextUtilitiesGuardsTypeCore from flext_core._utilities.mapper_extract import FlextUtilitiesMapperExtract from flext_core.runtime import FlextRuntime @@ -129,15 +128,13 @@ def _pipeline() -> t.JsonDict: if exclude_keys: step = {k: v for k, v in step.items() if k not in exclude_keys} if strip_none: - step = dict( - FlextUtilitiesCollection.filter(step, lambda v: v is not None) - ) + step = {k: v for k, v in step.items() if v is not None} if strip_empty: - step = dict( - FlextUtilitiesCollection.filter( - step, lambda v: not FlextUtilitiesGuardsTypeCore.empty_value(v) - ) - ) + step = { + k: v + for k, v in step.items() + if not FlextUtilitiesGuardsTypeCore.empty_value(v) + } return step transform_result: p.Result[t.JsonMapping] = r[