From 602a320af846081ad8cd4f267f29eacdeb00fb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=AF=E5=9F=BA=E9=AD=81?= <1412414664@qq.com> Date: Wed, 24 Jun 2026 17:50:06 +0800 Subject: [PATCH] fix: guard source resource lookup before init --- dlt/extract/source.py | 11 ++++++++--- tests/extract/test_sources.py | 10 ++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/dlt/extract/source.py b/dlt/extract/source.py index 48a1aa9f89..cc14caf3dd 100644 --- a/dlt/extract/source.py +++ b/dlt/extract/source.py @@ -611,7 +611,9 @@ def __iter__(self) -> Generator[TDataItem, None, None]: Container().injectable_context(source_context), Container().injectable_context(schema_context), ): - pipe_iterator: ManagedPipeIterator = ManagedPipeIterator.from_pipes(self._resources.selected_pipes) # type: ignore + pipe_iterator: ManagedPipeIterator = ManagedPipeIterator.from_pipes( + self._resources.selected_pipes + ) # type: ignore pipe_iterator.set_context([section_context, state_context, schema_context, source_context]) _iter = map(lambda item: item.item, pipe_iterator) return flatten_list_or_items(_iter) @@ -629,10 +631,13 @@ def __getattr__(self, resource_name: str) -> DltResource: # do not intercept dunder attributes (needed for copy/pickle/deepcopy) if resource_name.startswith("__") and resource_name.endswith("__"): raise AttributeError(resource_name) + resources = self.__dict__.get("_resources") + if resources is None: + raise AttributeError(resource_name) try: - return self._resources[resource_name] + return resources[resource_name] except KeyError: - all_resources = ", ".join(self._resources.keys()) + all_resources = ", ".join(resources.keys()) raise AttributeError( f"Resource `{resource_name}` not found in source `{self.name}`. Available" f" resources: {all_resources}" diff --git a/tests/extract/test_sources.py b/tests/extract/test_sources.py index 3de084747b..36a5caa322 100644 --- a/tests/extract/test_sources.py +++ b/tests/extract/test_sources.py @@ -539,6 +539,16 @@ def test_source(no_resources): assert list(s.with_resources()) == [] +def test_source_getattr_without_resources_raises_attribute_error() -> None: + source = DltSource.__new__(DltSource) + + with pytest.raises(AttributeError, match="anything"): + source.anything + + with pytest.raises(AttributeError, match="_resources"): + source._resources + + def test_clone_source() -> None: @dlt.source def test_source(no_resources):