etlplus 0.15.0__py3-none-any.whl → 0.16.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. etlplus/README.md +25 -3
  2. etlplus/__init__.py +2 -0
  3. etlplus/api/README.md +31 -0
  4. etlplus/api/__init__.py +14 -14
  5. etlplus/api/auth.py +10 -7
  6. etlplus/api/config.py +8 -13
  7. etlplus/api/endpoint_client.py +20 -20
  8. etlplus/api/errors.py +4 -4
  9. etlplus/api/pagination/__init__.py +6 -6
  10. etlplus/api/pagination/config.py +12 -10
  11. etlplus/api/pagination/paginator.py +6 -7
  12. etlplus/api/rate_limiting/__init__.py +2 -2
  13. etlplus/api/rate_limiting/config.py +14 -14
  14. etlplus/api/rate_limiting/rate_limiter.py +3 -3
  15. etlplus/api/request_manager.py +4 -4
  16. etlplus/api/retry_manager.py +8 -8
  17. etlplus/api/transport.py +11 -11
  18. etlplus/api/types.py +131 -11
  19. etlplus/api/utils.py +50 -50
  20. etlplus/cli/commands.py +93 -60
  21. etlplus/cli/constants.py +1 -1
  22. etlplus/cli/handlers.py +43 -26
  23. etlplus/cli/io.py +2 -2
  24. etlplus/cli/main.py +2 -2
  25. etlplus/cli/state.py +4 -7
  26. etlplus/{workflow/pipeline.py → config.py} +62 -99
  27. etlplus/connector/__init__.py +43 -0
  28. etlplus/connector/api.py +161 -0
  29. etlplus/connector/connector.py +26 -0
  30. etlplus/connector/core.py +132 -0
  31. etlplus/connector/database.py +122 -0
  32. etlplus/connector/enums.py +52 -0
  33. etlplus/connector/file.py +120 -0
  34. etlplus/connector/types.py +40 -0
  35. etlplus/connector/utils.py +122 -0
  36. etlplus/database/ddl.py +2 -2
  37. etlplus/database/engine.py +19 -3
  38. etlplus/database/orm.py +2 -0
  39. etlplus/enums.py +36 -200
  40. etlplus/file/_imports.py +1 -0
  41. etlplus/file/_io.py +52 -4
  42. etlplus/file/accdb.py +3 -2
  43. etlplus/file/arrow.py +3 -2
  44. etlplus/file/avro.py +3 -2
  45. etlplus/file/bson.py +3 -2
  46. etlplus/file/cbor.py +3 -2
  47. etlplus/file/cfg.py +3 -2
  48. etlplus/file/conf.py +3 -2
  49. etlplus/file/core.py +11 -8
  50. etlplus/file/csv.py +3 -2
  51. etlplus/file/dat.py +3 -2
  52. etlplus/file/dta.py +3 -2
  53. etlplus/file/duckdb.py +3 -2
  54. etlplus/file/enums.py +1 -1
  55. etlplus/file/feather.py +3 -2
  56. etlplus/file/fwf.py +3 -2
  57. etlplus/file/gz.py +3 -2
  58. etlplus/file/hbs.py +3 -2
  59. etlplus/file/hdf5.py +3 -2
  60. etlplus/file/ini.py +3 -2
  61. etlplus/file/ion.py +3 -2
  62. etlplus/file/jinja2.py +3 -2
  63. etlplus/file/json.py +5 -16
  64. etlplus/file/log.py +3 -2
  65. etlplus/file/mat.py +3 -2
  66. etlplus/file/mdb.py +3 -2
  67. etlplus/file/msgpack.py +3 -2
  68. etlplus/file/mustache.py +3 -2
  69. etlplus/file/nc.py +3 -2
  70. etlplus/file/ndjson.py +3 -2
  71. etlplus/file/numbers.py +3 -2
  72. etlplus/file/ods.py +3 -2
  73. etlplus/file/orc.py +3 -2
  74. etlplus/file/parquet.py +3 -2
  75. etlplus/file/pb.py +3 -2
  76. etlplus/file/pbf.py +3 -2
  77. etlplus/file/properties.py +3 -2
  78. etlplus/file/proto.py +3 -2
  79. etlplus/file/psv.py +3 -2
  80. etlplus/file/rda.py +3 -2
  81. etlplus/file/rds.py +3 -2
  82. etlplus/file/sas7bdat.py +3 -2
  83. etlplus/file/sav.py +3 -2
  84. etlplus/file/sqlite.py +3 -2
  85. etlplus/file/stub.py +1 -0
  86. etlplus/file/sylk.py +3 -2
  87. etlplus/file/tab.py +3 -2
  88. etlplus/file/toml.py +3 -2
  89. etlplus/file/tsv.py +3 -2
  90. etlplus/file/txt.py +4 -3
  91. etlplus/file/vm.py +3 -2
  92. etlplus/file/wks.py +3 -2
  93. etlplus/file/xls.py +3 -2
  94. etlplus/file/xlsm.py +3 -2
  95. etlplus/file/xlsx.py +3 -2
  96. etlplus/file/xml.py +9 -3
  97. etlplus/file/xpt.py +3 -2
  98. etlplus/file/yaml.py +5 -16
  99. etlplus/file/zip.py +3 -2
  100. etlplus/file/zsav.py +3 -2
  101. etlplus/ops/__init__.py +1 -0
  102. etlplus/ops/enums.py +173 -0
  103. etlplus/ops/extract.py +222 -23
  104. etlplus/ops/load.py +155 -36
  105. etlplus/ops/run.py +92 -107
  106. etlplus/ops/transform.py +48 -29
  107. etlplus/ops/types.py +147 -0
  108. etlplus/ops/utils.py +11 -40
  109. etlplus/ops/validate.py +16 -16
  110. etlplus/types.py +6 -102
  111. etlplus/utils.py +163 -29
  112. etlplus/workflow/README.md +0 -24
  113. etlplus/workflow/__init__.py +2 -15
  114. etlplus/workflow/dag.py +23 -1
  115. etlplus/workflow/jobs.py +83 -39
  116. etlplus/workflow/profile.py +4 -2
  117. {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/METADATA +4 -4
  118. etlplus-0.16.6.dist-info/RECORD +143 -0
  119. {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/WHEEL +1 -1
  120. etlplus/config/README.md +0 -50
  121. etlplus/config/__init__.py +0 -33
  122. etlplus/config/types.py +0 -140
  123. etlplus/dag.py +0 -103
  124. etlplus/workflow/connector.py +0 -373
  125. etlplus/workflow/types.py +0 -115
  126. etlplus/workflow/utils.py +0 -120
  127. etlplus-0.15.0.dist-info/RECORD +0 -139
  128. {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/entry_points.txt +0 -0
  129. {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/licenses/LICENSE +0 -0
  130. {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,26 @@
1
+ """
2
+ :mod:`etlplus.connector.connector` module.
3
+
4
+ Compatibility re-exports for connector configuration classes.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from .api import ConnectorApi
10
+ from .database import ConnectorDb
11
+ from .file import ConnectorFile
12
+
13
+ # SECTION: EXPORTS ========================================================== #
14
+
15
+
16
+ __all__ = [
17
+ # Type aliases
18
+ 'Connector',
19
+ ]
20
+
21
+
22
+ # SECTION: TYPED ALIASES ==================================================== #
23
+
24
+
25
+ # Type alias representing any supported connector
26
+ type Connector = ConnectorApi | ConnectorDb | ConnectorFile
@@ -0,0 +1,132 @@
1
+ """
2
+ :mod:`etlplus.connector.core` module.
3
+
4
+ Protocols and base classes for connector implementations.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from abc import ABC
10
+ from abc import abstractmethod
11
+ from dataclasses import dataclass
12
+ from typing import Protocol
13
+ from typing import Self
14
+ from typing import runtime_checkable
15
+
16
+ from ..types import StrAnyMap
17
+ from .types import ConnectorType
18
+
19
+ # SECTION: EXPORTS ========================================================== #
20
+
21
+
22
+ __all__ = [
23
+ 'ConnectorBase',
24
+ 'ConnectorProtocol',
25
+ ]
26
+
27
+
28
+ # SECTION: PROTOCOLS ======================================================== #
29
+
30
+
31
+ @runtime_checkable
32
+ class ConnectorProtocol(Protocol):
33
+ """
34
+ Structural contract for connector implementations.
35
+
36
+ Attributes
37
+ ----------
38
+ name : str
39
+ Unique connector name.
40
+ type : ConnectorType
41
+ Connector kind.
42
+ """
43
+
44
+ # -- Attributes -- #
45
+
46
+ name: str
47
+ type: ConnectorType
48
+
49
+ # -- Class Methods -- #
50
+
51
+ @classmethod
52
+ def from_obj(cls, obj: StrAnyMap) -> Self:
53
+ """
54
+ Parse a mapping into a connector instance.
55
+
56
+ Parameters
57
+ ----------
58
+ obj : StrAnyMap
59
+ Mapping with at least ``name``.
60
+
61
+ Returns
62
+ -------
63
+ Self
64
+ Parsed connector instance.
65
+ """
66
+ ...
67
+
68
+ # -- Internal Static Methods -- #
69
+
70
+ @staticmethod
71
+ def _require_name(obj: StrAnyMap, *, kind: str) -> str:
72
+ """
73
+ Extract and validate the ``name`` field from connector mappings.
74
+
75
+ Parameters
76
+ ----------
77
+ obj : StrAnyMap
78
+ Connector mapping with a ``name`` entry.
79
+ kind : str
80
+ Connector kind used in the error message.
81
+
82
+ Returns
83
+ -------
84
+ str
85
+ Valid connector name.
86
+
87
+ Raises
88
+ ------
89
+ TypeError
90
+ If ``name`` is missing or not a string.
91
+ """
92
+ name = obj.get('name')
93
+ if not isinstance(name, str):
94
+ raise TypeError(f'Connector{kind} requires a "name" (str)')
95
+ return name
96
+
97
+
98
+ # SECTION: ABSTRACT BASE DATA CLASSES ======================================= #
99
+
100
+
101
+ @dataclass(kw_only=True, slots=True)
102
+ class ConnectorBase(ABC, ConnectorProtocol):
103
+ """
104
+ Abstract base class for connector implementations.
105
+
106
+ Attributes
107
+ ----------
108
+ name : str
109
+ Unique connector name.
110
+ type : ConnectorType
111
+ Connector kind.
112
+ """
113
+
114
+ name: str
115
+ type: ConnectorType
116
+
117
+ @classmethod
118
+ @abstractmethod
119
+ def from_obj(cls, obj: StrAnyMap) -> Self:
120
+ """
121
+ Parse a mapping into a connector instance.
122
+
123
+ Parameters
124
+ ----------
125
+ obj : StrAnyMap
126
+ Mapping with at least ``name``.
127
+
128
+ Returns
129
+ -------
130
+ Self
131
+ Parsed connector instance.
132
+ """
@@ -0,0 +1,122 @@
1
+ """
2
+ :mod:`etlplus.connector.database` module.
3
+
4
+ Database connector configuration dataclass.
5
+
6
+ Notes
7
+ -----
8
+ - TypedDicts in this module are intentionally ``total=False`` and are not
9
+ enforced at runtime.
10
+ - :meth:`*.from_obj` constructors accept :class:`Mapping[str, Any]` and perform
11
+ tolerant parsing and light casting. This keeps the runtime permissive while
12
+ improving autocomplete and static analysis for contributors.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass
18
+ from typing import Self
19
+ from typing import TypedDict
20
+ from typing import overload
21
+
22
+ from ..types import StrAnyMap
23
+ from .core import ConnectorBase
24
+ from .enums import DataConnectorType
25
+ from .types import ConnectorType
26
+
27
+ # SECTION: EXPORTS ========================================================== #
28
+
29
+
30
+ __all__ = [
31
+ 'ConnectorDb',
32
+ 'ConnectorDbConfigDict',
33
+ ]
34
+
35
+
36
+ # SECTION: TYPED DICTS ====================================================== #
37
+
38
+
39
+ class ConnectorDbConfigDict(TypedDict, total=False):
40
+ """
41
+ Shape accepted by :meth:`ConnectorDb.from_obj` (all keys optional).
42
+
43
+ See Also
44
+ --------
45
+ - :meth:`etlplus.connector.database.ConnectorDb.from_obj`
46
+ """
47
+
48
+ name: str
49
+ type: ConnectorType
50
+ connection_string: str
51
+ query: str
52
+ table: str
53
+ mode: str
54
+
55
+
56
+ # SECTION: DATA CLASSES ===================================================== #
57
+
58
+
59
+ @dataclass(kw_only=True, slots=True)
60
+ class ConnectorDb(ConnectorBase):
61
+ """
62
+ Configuration for a database-based data connector.
63
+
64
+ Attributes
65
+ ----------
66
+ type : ConnectorType
67
+ Connector kind, always ``'database'``.
68
+ connection_string : str | None
69
+ Connection string/DSN for the database.
70
+ query : str | None
71
+ Query to execute for extraction (optional).
72
+ table : str | None
73
+ Target/source table name (optional).
74
+ mode : str | None
75
+ Load mode hint (e.g., ``'append'``, ``'replace'``) - future use.
76
+ """
77
+
78
+ # -- Attributes -- #
79
+
80
+ type: ConnectorType = DataConnectorType.DATABASE
81
+ connection_string: str | None = None
82
+ query: str | None = None
83
+ table: str | None = None
84
+ mode: str | None = None # append|replace|upsert (future)
85
+
86
+ # -- Class Methods -- #
87
+
88
+ @classmethod
89
+ @overload
90
+ def from_obj(cls, obj: ConnectorDbConfigDict) -> Self: ...
91
+
92
+ @classmethod
93
+ @overload
94
+ def from_obj(cls, obj: StrAnyMap) -> Self: ...
95
+
96
+ @classmethod
97
+ def from_obj(
98
+ cls,
99
+ obj: StrAnyMap,
100
+ ) -> Self:
101
+ """
102
+ Parse a mapping into a ``ConnectorDb`` instance.
103
+
104
+ Parameters
105
+ ----------
106
+ obj : StrAnyMap
107
+ Mapping with at least ``name``.
108
+
109
+ Returns
110
+ -------
111
+ Self
112
+ Parsed connector instance.
113
+ """
114
+ name = cls._require_name(obj, kind='Db')
115
+
116
+ return cls(
117
+ name=name,
118
+ connection_string=obj.get('connection_string'),
119
+ query=obj.get('query'),
120
+ table=obj.get('table'),
121
+ mode=obj.get('mode'),
122
+ )
@@ -0,0 +1,52 @@
1
+ """
2
+ :mod:`etlplus.connector.enums` module.
3
+
4
+ Connector enums and helpers.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from ..enums import CoercibleStrEnum
10
+ from ..types import StrStrMap
11
+
12
+ # SECTION: EXPORTS ========================================================= #
13
+
14
+
15
+ __all__ = [
16
+ # Enums
17
+ 'DataConnectorType',
18
+ ]
19
+
20
+
21
+ # SECTION: ENUMS ============================================================ #
22
+
23
+
24
+ class DataConnectorType(CoercibleStrEnum):
25
+ """Supported data connector types."""
26
+
27
+ # -- Constants -- #
28
+
29
+ API = 'api'
30
+ DATABASE = 'database'
31
+ FILE = 'file'
32
+
33
+ # -- Class Methods -- #
34
+
35
+ @classmethod
36
+ def aliases(cls) -> StrStrMap:
37
+ """
38
+ Return a mapping of common aliases for each enum member.
39
+
40
+ Returns
41
+ -------
42
+ StrStrMap
43
+ A mapping of alias names to their corresponding enum member names.
44
+ """
45
+ return {
46
+ 'http': 'api',
47
+ 'https': 'api',
48
+ 'rest': 'api',
49
+ 'db': 'database',
50
+ 'filesystem': 'file',
51
+ 'fs': 'file',
52
+ }
@@ -0,0 +1,120 @@
1
+ """
2
+ :mod:`etlplus.connector.file` module.
3
+
4
+ File connector configuration dataclass.
5
+
6
+ Notes
7
+ -----
8
+ - TypedDicts in this module are intentionally ``total=False`` and are not
9
+ enforced at runtime.
10
+ - :meth:`*.from_obj` constructors accept :class:`Mapping[str, Any]` and perform
11
+ tolerant parsing and light casting. This keeps the runtime permissive while
12
+ improving autocomplete and static analysis for contributors.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass
18
+ from dataclasses import field
19
+ from typing import Any
20
+ from typing import Self
21
+ from typing import TypedDict
22
+ from typing import overload
23
+
24
+ from ..types import StrAnyMap
25
+ from ..utils import coerce_dict
26
+ from .core import ConnectorBase
27
+ from .enums import DataConnectorType
28
+ from .types import ConnectorType
29
+
30
+ # SECTION: EXPORTS ========================================================== #
31
+
32
+
33
+ __all__ = [
34
+ 'ConnectorFile',
35
+ 'ConnectorFileConfigDict',
36
+ ]
37
+
38
+
39
+ # SECTION: TYPED DICTS ====================================================== #
40
+
41
+
42
+ class ConnectorFileConfigDict(TypedDict, total=False):
43
+ """
44
+ Shape accepted by :meth:`ConnectorFile.from_obj` (all keys optional).
45
+
46
+ See Also
47
+ --------
48
+ - :meth:`etlplus.connector.file.ConnectorFile.from_obj`
49
+ """
50
+
51
+ name: str
52
+ type: ConnectorType
53
+ format: str
54
+ path: str
55
+ options: StrAnyMap
56
+
57
+
58
+ # SECTION: DATA CLASSES ===================================================== #
59
+
60
+
61
+ @dataclass(kw_only=True, slots=True)
62
+ class ConnectorFile(ConnectorBase):
63
+ """
64
+ Configuration for a file-based data connector.
65
+
66
+ Attributes
67
+ ----------
68
+ type : ConnectorType
69
+ Connector kind, always ``'file'``.
70
+ format : str | None
71
+ File format (e.g., ``'json'``, ``'csv'``).
72
+ path : str | None
73
+ File path or URI.
74
+ options : dict[str, Any]
75
+ Reader/writer format options.
76
+ """
77
+
78
+ # -- Attributes -- #
79
+
80
+ type: ConnectorType = DataConnectorType.FILE
81
+ format: str | None = None
82
+ path: str | None = None
83
+ options: dict[str, Any] = field(default_factory=dict)
84
+
85
+ # -- Class Methods -- #
86
+
87
+ @classmethod
88
+ @overload
89
+ def from_obj(cls, obj: ConnectorFileConfigDict) -> Self: ...
90
+
91
+ @classmethod
92
+ @overload
93
+ def from_obj(cls, obj: StrAnyMap) -> Self: ...
94
+
95
+ @classmethod
96
+ def from_obj(
97
+ cls,
98
+ obj: StrAnyMap,
99
+ ) -> Self:
100
+ """
101
+ Parse a mapping into a ``ConnectorFile`` instance.
102
+
103
+ Parameters
104
+ ----------
105
+ obj : StrAnyMap
106
+ Mapping with at least ``name``.
107
+
108
+ Returns
109
+ -------
110
+ Self
111
+ Parsed connector instance.
112
+ """
113
+ name = cls._require_name(obj, kind='File')
114
+
115
+ return cls(
116
+ name=name,
117
+ format=obj.get('format'),
118
+ path=obj.get('path'),
119
+ options=coerce_dict(obj.get('options')),
120
+ )
@@ -0,0 +1,40 @@
1
+ """
2
+ :mod:`etlplus.connector.types` module.
3
+
4
+ Connector type aliases for :mod:`etlplus.connector`.
5
+
6
+ Examples
7
+ --------
8
+ >>> from etlplus.connector import Connector
9
+ >>> src: Connector = {
10
+ >>> "type": "file",
11
+ >>> "path": "/data/input.csv",
12
+ >>> }
13
+ >>> tgt: Connector = {
14
+ >>> "type": "database",
15
+ >>> "connection_string": "postgresql://user:pass@localhost/db",
16
+ >>> }
17
+ >>> from etlplus.api import RetryPolicyDict
18
+ >>> rp: RetryPolicyDict = {"max_attempts": 3, "backoff": 0.5}
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Literal
24
+
25
+ from .enums import DataConnectorType
26
+
27
+ # SECTION: EXPORTS ========================================================= #
28
+
29
+
30
+ __all__ = [
31
+ # Type Aliases
32
+ 'ConnectorType',
33
+ ]
34
+
35
+
36
+ # SECTION: TYPE ALIASES ===================================================== #
37
+
38
+
39
+ # Literal type for supported connector kinds (strings or enum members)
40
+ type ConnectorType = DataConnectorType | Literal['api', 'database', 'file']
@@ -0,0 +1,122 @@
1
+ """
2
+ :mod:`etlplus.connector.utils` module.
3
+
4
+ Shared connector parsing helpers.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Mapping
10
+ from typing import Any
11
+
12
+ from .api import ConnectorApi
13
+ from .connector import Connector
14
+ from .database import ConnectorDb
15
+ from .enums import DataConnectorType
16
+ from .file import ConnectorFile
17
+
18
+ # SECTION: EXPORTS ========================================================== #
19
+
20
+
21
+ __all__ = [
22
+ # Functions
23
+ 'parse_connector',
24
+ ]
25
+
26
+
27
+ # SECTION: INTERNAL FUNCTIONS =============================================== #
28
+
29
+
30
+ def _coerce_connector_type(
31
+ obj: Mapping[str, Any],
32
+ ) -> DataConnectorType:
33
+ """
34
+ Normalize and validate the connector ``type`` field.
35
+
36
+ Parameters
37
+ ----------
38
+ obj : Mapping[str, Any]
39
+ Mapping with a ``type`` entry.
40
+
41
+ Returns
42
+ -------
43
+ DataConnectorType
44
+ Normalized connector type enum.
45
+
46
+ Raises
47
+ ------
48
+ TypeError
49
+ If ``type`` is missing or unsupported.
50
+ """
51
+ if 'type' not in obj:
52
+ raise TypeError('Connector requires a "type"')
53
+ try:
54
+ return DataConnectorType.coerce(obj.get('type'))
55
+ except ValueError as exc:
56
+ allowed = ', '.join(DataConnectorType.choices())
57
+ raise TypeError(
58
+ f'Unsupported connector type: {obj.get("type")!r}. '
59
+ f'Expected one of {allowed}.',
60
+ ) from exc
61
+
62
+
63
+ def _load_connector(
64
+ kind: DataConnectorType,
65
+ ) -> type[Connector]:
66
+ """
67
+ Resolve the connector class for the requested kind.
68
+
69
+ Parameters
70
+ ----------
71
+ kind : DataConnectorType
72
+ Connector kind enum.
73
+
74
+ Returns
75
+ -------
76
+ type[Connector]
77
+ Connector class corresponding to *kind*.
78
+ """
79
+ match kind:
80
+ case DataConnectorType.API:
81
+ return ConnectorApi
82
+ case DataConnectorType.DATABASE:
83
+ return ConnectorDb
84
+ case DataConnectorType.FILE:
85
+ return ConnectorFile
86
+
87
+
88
+ # SECTION: FUNCTIONS ======================================================== #
89
+
90
+
91
+ def parse_connector(
92
+ obj: Mapping[str, Any],
93
+ ) -> Connector:
94
+ """
95
+ Dispatch to a concrete connector constructor based on ``type``.
96
+
97
+ Parameters
98
+ ----------
99
+ obj : Mapping[str, Any]
100
+ Mapping with at least ``name`` and ``type``.
101
+
102
+ Returns
103
+ -------
104
+ Connector
105
+ Concrete connector instance.
106
+
107
+ Raises
108
+ ------
109
+ TypeError
110
+ If the mapping is invalid or the connector type is unsupported.
111
+
112
+ Notes
113
+ -----
114
+ Delegates to the tolerant ``from_obj`` constructors for each connector
115
+ kind. Connector types are normalized via
116
+ :class:`etlplus.connector.enums.DataConnectorType`, so common aliases
117
+ (e.g., ``'db'`` or ``'http'``) are accepted.
118
+ """
119
+ if not isinstance(obj, Mapping):
120
+ raise TypeError('Connector configuration must be a mapping.')
121
+ connector_cls = _load_connector(_coerce_connector_type(obj))
122
+ return connector_cls.from_obj(obj)
etlplus/database/ddl.py CHANGED
@@ -233,7 +233,7 @@ def render_table_sql(
233
233
  template : TemplateKey | None, optional
234
234
  Template key to use (default: 'ddl').
235
235
  template_path : str | None, optional
236
- Path to a custom template file (overrides ``template``).
236
+ Path to a custom template file (overrides *template*).
237
237
 
238
238
  Returns
239
239
  -------
@@ -264,7 +264,7 @@ def render_tables(
264
264
  template : TemplateKey | None, optional
265
265
  Template key to use (default: 'ddl').
266
266
  template_path : str | None, optional
267
- Path to a custom template file (overrides ``template``).
267
+ Path to a custom template file (overrides *template*).
268
268
 
269
269
  Returns
270
270
  -------
@@ -87,7 +87,7 @@ def load_database_url_from_config(
87
87
  Extract a database URL/DSN from a YAML/JSON config file.
88
88
 
89
89
  The loader is schema-tolerant: it looks for a top-level "databases" map
90
- and then for a named entry (``name``). Each entry may contain either a
90
+ and then for a named entry (*name*). Each entry may contain either a
91
91
  ``connection_string``/``url``/``dsn`` or a nested ``default`` block with
92
92
  those fields.
93
93
 
@@ -136,9 +136,25 @@ def load_database_url_from_config(
136
136
  return url
137
137
 
138
138
 
139
- def make_engine(url: str | None = None, **engine_kwargs: Any) -> Engine:
140
- """Create a SQLAlchemy Engine, defaulting to env config if no URL given."""
139
+ def make_engine(
140
+ url: str | None = None,
141
+ **engine_kwargs: Any,
142
+ ) -> Engine:
143
+ """
144
+ Create a SQLAlchemy Engine, defaulting to env config if no URL given.
145
+
146
+ Parameters
147
+ ----------
148
+ url : str | None, optional
149
+ Database URL/DSN string. When omitted, ``DATABASE_URL`` is used.
150
+ **engine_kwargs : Any
151
+ Extra keyword arguments forwarded to ``create_engine``.
141
152
 
153
+ Returns
154
+ -------
155
+ Engine
156
+ Configured SQLAlchemy engine instance.
157
+ """
142
158
  resolved_url = url or DATABASE_URL
143
159
  return create_engine(resolved_url, pool_pre_ping=True, **engine_kwargs)
144
160
 
etlplus/database/orm.py CHANGED
@@ -201,12 +201,14 @@ def build_models(
201
201
  ) -> ModelRegistry:
202
202
  """
203
203
  Build SQLAlchemy ORM models from table specifications.
204
+
204
205
  Parameters
205
206
  ----------
206
207
  specs : list[TableSpec]
207
208
  List of table specifications.
208
209
  base : type[DeclarativeBase], optional
209
210
  Base class for the ORM models (default: :class:`Base`).
211
+
210
212
  Returns
211
213
  -------
212
214
  ModelRegistry