Skip to content

Commit 628b90f

Browse files
committed
Code simplified
1 parent 22c62bd commit 628b90f

File tree

7 files changed

+111
-125
lines changed

7 files changed

+111
-125
lines changed

dlt/common/destination/client.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,6 @@ def from_normalized_mapping(
142142
)
143143

144144

145-
@configspec
146-
class DestinationTypeConfiguration(BaseConfiguration):
147-
destination_type: str = None
148-
149-
150145
@configspec
151146
class DestinationClientConfiguration(BaseConfiguration):
152147
destination_type: Annotated[str, NotResolved()] = dataclasses.field(

dlt/common/destination/exceptions.py

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Any, Iterable, List, Sequence, Optional
2+
import textwrap
23

34
from dlt.common.exceptions import DltException, TerminalException, TransientException
45
from dlt.common.reflection.exceptions import ReferenceImportError
@@ -15,20 +16,53 @@ def __init__(
1516
ref: str,
1617
qualified_refs: Sequence[str],
1718
traces: Sequence[ImportTrace],
18-
from_name: bool = False,
19+
destination_type: str = None,
20+
named_dest_attempted: bool = False,
1921
) -> None:
2022
self.ref = ref
2123
self.qualified_refs = qualified_refs
22-
self.from_name = from_name
24+
self.destination_type = destination_type
25+
self.named_dest_attempted = named_dest_attempted
2326
super().__init__(traces=traces)
2427

2528
def __str__(self) -> str:
29+
msg = ""
2630
if "." in self.ref:
27-
msg = f"Destination module `{self.ref}` is not registered."
31+
msg += f"Destination module `{self.ref}` is not registered."
2832
else:
29-
msg = (
30-
f"Destination{' type' if self.from_name else ''} `{self.ref}` is not one of the"
31-
f" standard dlt destination{' types' if self.from_name else 's'}."
33+
if self.named_dest_attempted:
34+
msg += (
35+
f"Destination '{self.ref}' was first attempted to be resolved as a named"
36+
" destination with a configured type. "
37+
)
38+
if self.destination_type:
39+
msg += (
40+
f"However, the configured destination type '{self.destination_type}' is not"
41+
" valid. Set a valid destination type. "
42+
)
43+
else:
44+
msg += (
45+
"However, no destination type was configured. "
46+
"If your destination is a named destination, "
47+
"set a valid destination type either as an environment variable:\n\n"
48+
)
49+
msg += textwrap.indent(
50+
f"DESTINATION__{self.ref.upper()}__DESTINATION_TYPE=duckdb\n", " "
51+
)
52+
msg += "\nor in your configuration files:\n\n"
53+
msg += textwrap.indent(
54+
f'[destination.{self.ref}]\ndestination_type="duckdb"\n\n', " "
55+
)
56+
57+
msg += (
58+
f"Since no{' valid' if self.destination_type else ''} destination type was"
59+
f" found, dlt also tried to resolve '{self.ref}' as a standard destination."
60+
" However, "
61+
)
62+
63+
msg += (
64+
f"{'d' if self.named_dest_attempted else 'D'}estination `{self.ref}` is not one of"
65+
" the standard dlt destinations."
3266
)
3367

3468
if len(self.qualified_refs) == 1 and self.qualified_refs[0] == self.ref:
@@ -43,31 +77,6 @@ def __str__(self) -> str:
4377
return msg
4478

4579

46-
class DestinationTypeResolutionException(DestinationException):
47-
def __init__(
48-
self,
49-
ref: str,
50-
type_resolution_error: Exception,
51-
named_dest_error: Optional[Exception],
52-
) -> None:
53-
self.ref = ref
54-
self.named_dest_error = named_dest_error
55-
self.type_resolution_error = type_resolution_error
56-
57-
msg = f"Failed to resolve destination '{ref}'"
58-
59-
if named_dest_error:
60-
msg += (
61-
". First tried to resolve as a named destination with destination type, "
62-
f"but failed: {named_dest_error}. "
63-
f"Then tried to resolve as destination type, but failed: {type_resolution_error}"
64-
)
65-
else:
66-
msg += f" as destination type: {type_resolution_error}"
67-
68-
super().__init__(msg)
69-
70-
7180
class InvalidDestinationReference(DestinationException):
7281
def __init__(self, refs: Any) -> None:
7382
self.refs = refs

dlt/common/destination/reference.py

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,17 @@
1616
from typing_extensions import TypeAlias
1717
import inspect
1818

19+
import dlt
1920
from dlt.common import logger
2021
from dlt.common.configuration.specs.base_configuration import BaseConfiguration
2122
from dlt.common.normalizers.naming import NamingConvention
2223
from dlt.common.configuration import resolve_configuration, known_sections
2324
from dlt.common.destination.capabilities import DestinationCapabilitiesContext
2425
from dlt.common.destination.exceptions import (
25-
DestinationTypeResolutionException,
2626
InvalidDestinationReference,
2727
UnknownDestinationModule,
2828
)
29-
from dlt.common.destination.client import (
30-
DestinationClientConfiguration,
31-
JobClientBase,
32-
DestinationTypeConfiguration,
33-
)
29+
from dlt.common.destination.client import DestinationClientConfiguration, JobClientBase
3430
from dlt.common.runtime.run_context import get_plugin_modules
3531
from dlt.common.schema.schema import Schema
3632
from dlt.common.typing import is_subclass
@@ -272,37 +268,40 @@ def from_reference(
272268
)
273269
return ref
274270

275-
# If destination_name is not provided and ref does not contain dots
276-
# try to resolve named destination with destination type
277-
named_dest_error = None
278-
if not destination_name and "." not in ref:
271+
# If destination name is provided or ref is a module ref
272+
# don't attempt to resolve as named destination
273+
if destination_name or "." in ref:
274+
return DestinationReference.from_reference(
275+
ref, credentials, destination_name, environment, **kwargs
276+
)
277+
278+
# First, try to resolve as a named destination with configured type
279+
destination_type: str = dlt.config.get(f"destination.{ref}.destination_type")
280+
if destination_type:
279281
try:
280282
return DestinationReference.from_name(
281-
credentials=credentials,
283+
destination_type=destination_type,
282284
destination_name=ref,
285+
credentials=credentials,
283286
environment=environment,
284287
**kwargs,
285288
)
286-
except Exception as e:
287-
named_dest_error = e
289+
except Exception:
290+
pass
288291

289-
# Fallback to shorthand type reference
292+
# Then, try to resolve as a shorthand destination ref
290293
try:
291-
dest_ref = DestinationReference.from_reference(
294+
return DestinationReference.from_reference(
292295
ref, credentials, destination_name, environment, **kwargs
293296
)
294-
return dest_ref
295-
except Exception as e:
296-
if named_dest_error is None:
297-
# Only direct type resolution was attempted, raise original error
298-
raise e
299-
else:
300-
# Both resolution methods failed, use comprehensive exception
301-
raise DestinationTypeResolutionException(
302-
ref=ref,
303-
type_resolution_error=e,
304-
named_dest_error=named_dest_error,
305-
)
297+
except UnknownDestinationModule as e:
298+
raise UnknownDestinationModule(
299+
ref=e.ref,
300+
qualified_refs=e.qualified_refs,
301+
traces=e.traces,
302+
destination_type=destination_type,
303+
named_dest_attempted=True,
304+
) from e.__cause__
306305

307306

308307
class DestinationReference:
@@ -415,31 +414,23 @@ def from_reference(
415414
@classmethod
416415
def from_name(
417416
cls,
417+
destination_type: str,
418418
destination_name: str,
419419
credentials: Optional[Any] = None,
420420
environment: Optional[str] = None,
421421
**kwargs: Any,
422422
) -> Optional[AnyDestination]:
423-
resolved_config = resolve_configuration(
424-
DestinationTypeConfiguration(),
425-
sections=(known_sections.DESTINATION, destination_name),
423+
"""Instantiate destination from a destination type and name.
424+
This method creates a named destination by using the destination type
425+
as a factory and instantiating it with the provided destination name.
426+
"""
427+
return DestinationReference.from_reference(
428+
ref=destination_type,
429+
credentials=credentials,
430+
destination_name=destination_name,
431+
environment=environment,
432+
**kwargs,
426433
)
427-
destination_type = resolved_config.destination_type
428-
try:
429-
return DestinationReference.from_reference(
430-
ref=destination_type,
431-
credentials=credentials,
432-
destination_name=destination_name,
433-
environment=environment,
434-
**kwargs,
435-
)
436-
except UnknownDestinationModule as e:
437-
raise UnknownDestinationModule(
438-
ref=e.ref,
439-
qualified_refs=e.qualified_refs,
440-
traces=e.traces,
441-
from_name=True,
442-
)
443434

444435
@staticmethod
445436
def normalize_type(destination_type: str) -> str:

tests/common/destination/test_reference.py

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@
88
from dlt.common.destination import Destination, DestinationReference
99
from dlt.common.destination.client import DestinationClientDwhConfiguration, WithStagingDataset
1010
from dlt.common.destination import DestinationCapabilitiesContext
11-
from dlt.common.destination.exceptions import (
12-
UnknownDestinationModule,
13-
DestinationTypeResolutionException,
14-
)
11+
from dlt.common.destination.exceptions import UnknownDestinationModule
1512
from dlt.common.schema import Schema
1613
from dlt.common.typing import is_subclass
1714
from dlt.common.normalizers.naming import sql_ci_v1, sql_cs_v1
@@ -28,10 +25,9 @@ def test_import_unknown_destination() -> None:
2825
assert unk_ex.value.qualified_refs == [
2926
"meltdb",
3027
"dlt.destinations.meltdb",
31-
"dlt.destinations.meltdb",
3228
]
3329
traces = unk_ex.value.traces
34-
assert len(traces) == 2 and traces[0].reason == "AttrNotFound"
30+
assert len(traces) == 1 and traces[0].reason == "AttrNotFound"
3531

3632
# custom module
3733
with pytest.raises(UnknownDestinationModule) as unk_ex:
@@ -331,43 +327,44 @@ def test_import_destination_type_config(
331327
if destination_type == "wrong_type":
332328
# Case 1: Fully qualified ref with dots
333329
# Skips named destination resolution and only attempts direct type resolution
334-
with pytest.raises(UnknownDestinationModule) as module_ex:
330+
with pytest.raises(UnknownDestinationModule) as py_exc:
335331
Destination.from_reference(ref=f"dlt.destinations.{destination_type}")
336-
assert "`dlt.destinations.wrong_type` is not registered." in str(module_ex.value)
332+
assert "`dlt.destinations.wrong_type` is not registered" in str(py_exc.value)
333+
assert not py_exc.value.named_dest_attempted
334+
assert not py_exc.value.destination_type
337335

338336
# Case 2: Explicit destination_name provided
339337
# Same as Case 1
340-
with pytest.raises(UnknownDestinationModule) as module_ex:
338+
with pytest.raises(UnknownDestinationModule) as py_exc:
341339
Destination.from_reference(ref=destination_type, destination_name="my_destination")
342-
assert "`wrong_type` is not one of the standard dlt destinations." in str(module_ex.value)
340+
assert "`wrong_type` is not one of the standard dlt destinations" in str(py_exc.value)
341+
assert not py_exc.value.named_dest_attempted
342+
assert not py_exc.value.destination_type
343343

344344
# Case 3: Named destination with invalid configured type
345345
# First tries named destination "my_destination" with configured type "wrong_type"
346346
# Then tries "my_destination" as destination type
347-
with pytest.raises(DestinationTypeResolutionException) as resolution_exc:
347+
with pytest.raises(UnknownDestinationModule) as py_exc:
348348
Destination.from_reference(ref="my_destination")
349-
assert resolution_exc.value.named_dest_error
350-
assert "`wrong_type` is not one of the standard dlt destination types." in str(
351-
resolution_exc.value
352-
)
353-
assert "`my_destination` is not one of the standard dlt destinations." in str(
354-
resolution_exc.value
349+
assert f"destination type '{destination_type}' is not valid" in str(py_exc.value)
350+
assert "dlt also tried to resolve 'my_destination' as a standard destination" in str(
351+
py_exc.value
355352
)
353+
assert py_exc.value.named_dest_attempted is True
354+
assert py_exc.value.destination_type == "wrong_type"
356355

357356
# Case 4: Named destination with missing type configuration
358357
# First tries named destination "my_destination" but no type configured (config error)
359358
# Then tries "my_destination" as direct destination type
360359
environment.clear()
361-
with pytest.raises(DestinationTypeResolutionException) as resolution_exc:
360+
with pytest.raises(UnknownDestinationModule) as py_exc:
362361
Destination.from_reference(ref="my_destination")
363-
assert resolution_exc.value.named_dest_error
364-
assert (
365-
"Missing 1 field(s) in configuration `DestinationTypeConfiguration`: `destination_type`"
366-
in str(resolution_exc.value)
367-
)
368-
assert "`my_destination` is not one of the standard dlt destinations." in str(
369-
resolution_exc.value
362+
assert "no destination type was configured" in str(py_exc.value)
363+
assert "dlt also tried to resolve 'my_destination' as a standard destination" in str(
364+
py_exc.value
370365
)
366+
assert py_exc.value.named_dest_attempted is True
367+
assert not py_exc.value.destination_type
371368

372369
else:
373370
dest = Destination.from_reference(ref="my_destination")

tests/common/runtime/test_run_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def test_run_context() -> None:
6565

6666
# check plugin modules
6767
# NOTE: first `dlt` - is the root module of current context, second is always present
68-
assert get_plugin_modules() == ["dlt", "dlt"]
68+
assert get_plugin_modules() == ["dlt"]
6969

7070

7171
def test_context_without_module() -> None:

tests/destinations/test_custom_destination.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,14 @@
1111
from dlt.common.schema import TTableSchema
1212
from dlt.common.data_writers.writers import TLoaderFileFormat
1313
from dlt.common.destination import Destination, DestinationReference
14-
from dlt.common.destination.exceptions import (
15-
DestinationTransientException,
16-
DestinationTypeResolutionException,
17-
)
14+
from dlt.common.destination.exceptions import DestinationTransientException
1815
from dlt.common.configuration.exceptions import ConfigFieldMissingException, ConfigurationValueError
1916
from dlt.common.configuration.specs import ConnectionStringCredentials
2017
from dlt.common.configuration.inject import get_fun_spec
2118
from dlt.common.configuration.specs import BaseConfiguration
2219

2320
from dlt.destinations.impl.destination.configuration import CustomDestinationClientConfiguration
24-
from dlt.destinations.impl.destination.factory import destination
21+
from dlt.destinations.impl.destination.factory import UnknownCustomDestinationCallable, destination
2522
from dlt.pipeline.exceptions import PipelineStepFailed
2623

2724
from tests.cases import table_update_and_row
@@ -293,7 +290,7 @@ def local_sink_func_no_params(items: TDataItems, table: TTableSchema) -> None:
293290
p.run([1, 2, 3], table_name="items")
294291

295292
# pass invalid string reference will fail on instantiation
296-
with pytest.raises(DestinationTypeResolutionException):
293+
with pytest.raises(UnknownCustomDestinationCallable):
297294
p = dlt.pipeline(
298295
"sink_test",
299296
destination=Destination.from_reference(

tests/load/pipeline/test_pipelines.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010
from dlt.common.pipeline import SupportsPipeline
1111
from dlt.common.destination import Destination
1212
from dlt.common.destination.client import WithStagingDataset
13-
from dlt.common.destination.exceptions import (
14-
UnknownDestinationModule,
15-
DestinationTypeResolutionException,
16-
)
13+
from dlt.common.destination.exceptions import UnknownDestinationModule
1714
from dlt.common.schema.schema import Schema
1815
from dlt.common.schema.typing import VERSION_TABLE_NAME, REPLACE_STRATEGIES, TLoaderReplaceStrategy
1916
from dlt.common.schema.utils import new_table
@@ -1149,14 +1146,14 @@ def test_data():
11491146
assert_load_info(info)
11501147

11511148
# test unconfigured destination name
1152-
with pytest.raises(DestinationTypeResolutionException) as py_exc:
1149+
with pytest.raises(UnknownDestinationModule) as py_exc:
11531150
dlt.pipeline(destination="another_custom_name")
1154-
assert py_exc.value.named_dest_error
1155-
assert (
1156-
"Missing 1 field(s) in configuration `DestinationTypeConfiguration`: `destination_type`"
1157-
in str(py_exc.value)
1158-
)
1151+
assert py_exc.value.named_dest_attempted is True
1152+
assert not py_exc.value.destination_type
1153+
assert "no destination type was configured" in str(py_exc.value)
11591154

11601155
# if destination contains dots, no fallbacks must happen
1161-
with pytest.raises(UnknownDestinationModule):
1156+
with pytest.raises(UnknownDestinationModule) as py_exc:
11621157
dlt.pipeline(destination="dlt.destinations.unknown")
1158+
assert not py_exc.value.named_dest_attempted
1159+
assert not py_exc.value.destination_type

0 commit comments

Comments
 (0)