From 67b87e55e687937fb5fc22f3fbf4b4e7f26d42f6 Mon Sep 17 00:00:00 2001 From: Yuren0312 <168316868+Yuren0312@users.noreply.github.com> Date: Sun, 18 Oct 2026 14:43:56 +0800 Subject: [PATCH] Coerce path-derived values to declared types in objects.all() Fixes #147. --- datafiles/manager.py | 28 ++++++++++++++++++++++++++-- tests/test_orm_usage.py | 1 - 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/datafiles/manager.py b/datafiles/manager.py index 8d22a7c..6ca7cba 100644 --- a/datafiles/manager.py +++ b/datafiles/manager.py @@ -8,13 +8,14 @@ from functools import reduce from glob import iglob from pathlib import Path -from typing import TYPE_CHECKING, Iterator, Optional +from typing import TYPE_CHECKING, Any, Iterator, Optional import log from parse import parse from ruamel.yaml.error import MarkedYAMLError from . import hooks, model +from .converters import map_type if TYPE_CHECKING: from .model import Model @@ -150,7 +151,30 @@ def all(self, *, _exclude: str = "") -> Iterator[Model]: continue names = [name.removeprefix("self.") for name in result.named] - yield self.get(**dict(zip(names, values))) + yield self.get(**dict(zip(names, self._coerce_values(names, values)))) + + def _coerce_values(self, names: list[str], values: list[str]) -> list[Any]: + """Coerce path-derived string values to their declared field types. + + ``all()`` extracts placeholder values (e.g. the primary key) from the + filename as strings. Unlike the other attributes, these are not loaded + from the file contents, so they never pass through the field's + converter. Convert them here so ``Model.objects.all()`` yields instances + whose path fields match their declared types (see issue #147). + """ + fields = { + field.name: field + for field in dataclasses.fields(self.model) + if field.init + } + coerced = [] + for name, value in zip(names, values): + field = fields.get(name) + if field is not None: + converter = map_type(field.type, name=name) # type: ignore + value = converter.to_python_value(value) + coerced.append(value) + return coerced def filter(self, *, _exclude: str = "", **query): for item in self.all(_exclude=_exclude): diff --git a/tests/test_orm_usage.py b/tests/test_orm_usage.py index 29db78e..b9a27b5 100644 --- a/tests/test_orm_usage.py +++ b/tests/test_orm_usage.py @@ -74,7 +74,6 @@ class Bar: expect(bar.nested.value) == 2 -@pytest.mark.xfail(reason="https://github.com/jacebrowning/datafiles/issues/147") def test_values_are_filled_from_disk(expect): InventoryItem.objects.get_or_create(42, "Things", 0.99)