etlplus 0.9.0__py3-none-any.whl → 0.9.2__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.
- etlplus/README.md +37 -0
- etlplus/__init__.py +1 -26
- etlplus/api/README.md +51 -3
- etlplus/api/__init__.py +10 -0
- etlplus/api/config.py +39 -28
- etlplus/api/endpoint_client.py +3 -3
- etlplus/api/enums.py +51 -0
- etlplus/api/pagination/client.py +1 -1
- etlplus/api/rate_limiting/config.py +13 -1
- etlplus/api/rate_limiting/rate_limiter.py +8 -11
- etlplus/api/request_manager.py +11 -6
- etlplus/api/transport.py +14 -2
- etlplus/api/types.py +96 -6
- etlplus/{run_helpers.py → api/utils.py} +209 -153
- etlplus/cli/README.md +40 -0
- etlplus/cli/commands.py +94 -61
- etlplus/cli/constants.py +1 -1
- etlplus/cli/handlers.py +40 -12
- etlplus/cli/io.py +2 -2
- etlplus/cli/main.py +1 -1
- etlplus/cli/state.py +4 -7
- etlplus/database/README.md +48 -0
- etlplus/database/ddl.py +1 -1
- etlplus/database/engine.py +19 -3
- etlplus/database/orm.py +2 -0
- etlplus/database/schema.py +1 -1
- etlplus/enums.py +1 -107
- etlplus/file/README.md +105 -0
- etlplus/file/__init__.py +25 -0
- etlplus/file/_imports.py +141 -0
- etlplus/file/_io.py +160 -0
- etlplus/file/accdb.py +78 -0
- etlplus/file/arrow.py +78 -0
- etlplus/file/avro.py +176 -0
- etlplus/file/bson.py +77 -0
- etlplus/file/cbor.py +78 -0
- etlplus/file/cfg.py +79 -0
- etlplus/file/conf.py +80 -0
- etlplus/file/core.py +322 -0
- etlplus/file/csv.py +79 -0
- etlplus/file/dat.py +78 -0
- etlplus/file/dta.py +77 -0
- etlplus/file/duckdb.py +78 -0
- etlplus/file/enums.py +343 -0
- etlplus/file/feather.py +111 -0
- etlplus/file/fwf.py +77 -0
- etlplus/file/gz.py +123 -0
- etlplus/file/hbs.py +78 -0
- etlplus/file/hdf5.py +78 -0
- etlplus/file/ini.py +79 -0
- etlplus/file/ion.py +78 -0
- etlplus/file/jinja2.py +78 -0
- etlplus/file/json.py +98 -0
- etlplus/file/log.py +78 -0
- etlplus/file/mat.py +78 -0
- etlplus/file/mdb.py +78 -0
- etlplus/file/msgpack.py +78 -0
- etlplus/file/mustache.py +78 -0
- etlplus/file/nc.py +78 -0
- etlplus/file/ndjson.py +108 -0
- etlplus/file/numbers.py +75 -0
- etlplus/file/ods.py +79 -0
- etlplus/file/orc.py +111 -0
- etlplus/file/parquet.py +113 -0
- etlplus/file/pb.py +78 -0
- etlplus/file/pbf.py +77 -0
- etlplus/file/properties.py +78 -0
- etlplus/file/proto.py +77 -0
- etlplus/file/psv.py +79 -0
- etlplus/file/rda.py +78 -0
- etlplus/file/rds.py +78 -0
- etlplus/file/sas7bdat.py +78 -0
- etlplus/file/sav.py +77 -0
- etlplus/file/sqlite.py +78 -0
- etlplus/file/stub.py +84 -0
- etlplus/file/sylk.py +77 -0
- etlplus/file/tab.py +81 -0
- etlplus/file/toml.py +78 -0
- etlplus/file/tsv.py +80 -0
- etlplus/file/txt.py +102 -0
- etlplus/file/vm.py +78 -0
- etlplus/file/wks.py +77 -0
- etlplus/file/xls.py +88 -0
- etlplus/file/xlsm.py +79 -0
- etlplus/file/xlsx.py +99 -0
- etlplus/file/xml.py +185 -0
- etlplus/file/xpt.py +78 -0
- etlplus/file/yaml.py +95 -0
- etlplus/file/zip.py +175 -0
- etlplus/file/zsav.py +77 -0
- etlplus/ops/README.md +50 -0
- etlplus/ops/__init__.py +61 -0
- etlplus/{extract.py → ops/extract.py} +81 -99
- etlplus/{load.py → ops/load.py} +78 -101
- etlplus/{run.py → ops/run.py} +159 -127
- etlplus/{transform.py → ops/transform.py} +75 -68
- etlplus/{validation → ops}/utils.py +53 -17
- etlplus/{validate.py → ops/validate.py} +22 -12
- etlplus/templates/README.md +46 -0
- etlplus/types.py +5 -4
- etlplus/utils.py +136 -2
- etlplus/workflow/README.md +52 -0
- etlplus/{config → workflow}/__init__.py +10 -23
- etlplus/{config → workflow}/connector.py +58 -44
- etlplus/workflow/dag.py +105 -0
- etlplus/{config → workflow}/jobs.py +105 -32
- etlplus/{config → workflow}/pipeline.py +59 -51
- etlplus/{config → workflow}/profile.py +8 -5
- etlplus/workflow/types.py +115 -0
- {etlplus-0.9.0.dist-info → etlplus-0.9.2.dist-info}/METADATA +210 -17
- etlplus-0.9.2.dist-info/RECORD +134 -0
- {etlplus-0.9.0.dist-info → etlplus-0.9.2.dist-info}/WHEEL +1 -1
- etlplus/config/types.py +0 -204
- etlplus/config/utils.py +0 -120
- etlplus/file.py +0 -657
- etlplus/validation/__init__.py +0 -44
- etlplus-0.9.0.dist-info/RECORD +0 -65
- {etlplus-0.9.0.dist-info → etlplus-0.9.2.dist-info}/entry_points.txt +0 -0
- {etlplus-0.9.0.dist-info → etlplus-0.9.2.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.9.0.dist-info → etlplus-0.9.2.dist-info}/top_level.txt +0 -0
etlplus/config/types.py
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
:mod:`etlplus.config.types` module.
|
|
3
|
-
|
|
4
|
-
Type aliases and editor-only TypedDicts for :mod:`etlplus.config`.
|
|
5
|
-
|
|
6
|
-
These types improve IDE autocomplete and static analysis while the runtime
|
|
7
|
-
parsers remain permissive.
|
|
8
|
-
|
|
9
|
-
Notes
|
|
10
|
-
-----
|
|
11
|
-
- TypedDicts in this module are intentionally ``total=False`` and are not
|
|
12
|
-
enforced at runtime.
|
|
13
|
-
- ``*.from_obj`` constructors accept ``Mapping[str, Any]`` and perform
|
|
14
|
-
tolerant parsing and light casting. This keeps the runtime permissive while
|
|
15
|
-
improving autocomplete and static analysis for contributors.
|
|
16
|
-
|
|
17
|
-
Examples
|
|
18
|
-
--------
|
|
19
|
-
>>> from etlplus.config import Connector
|
|
20
|
-
>>> src: Connector = {
|
|
21
|
-
>>> "type": "file",
|
|
22
|
-
>>> "path": "/data/input.csv",
|
|
23
|
-
>>> }
|
|
24
|
-
>>> tgt: Connector = {
|
|
25
|
-
>>> "type": "database",
|
|
26
|
-
>>> "connection_string": "postgresql://user:pass@localhost/db",
|
|
27
|
-
>>> }
|
|
28
|
-
>>> from etlplus.api import RetryPolicy
|
|
29
|
-
>>> rp: RetryPolicy = {"max_attempts": 3, "backoff": 0.5}
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
from __future__ import annotations
|
|
33
|
-
|
|
34
|
-
from collections.abc import Mapping
|
|
35
|
-
from typing import Any
|
|
36
|
-
from typing import Literal
|
|
37
|
-
from typing import TypedDict
|
|
38
|
-
|
|
39
|
-
from ..api import PaginationConfigMap
|
|
40
|
-
from ..api import RateLimitConfigMap
|
|
41
|
-
from ..types import StrAnyMap
|
|
42
|
-
|
|
43
|
-
# SECTION: EXPORTS ========================================================= #
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
__all__ = [
|
|
47
|
-
# Type aliases
|
|
48
|
-
'ConnectorType',
|
|
49
|
-
# 'PaginationType',
|
|
50
|
-
# TypedDicts
|
|
51
|
-
'ApiProfileDefaultsMap',
|
|
52
|
-
'ApiProfileConfigMap',
|
|
53
|
-
'ApiConfigMap',
|
|
54
|
-
'EndpointMap',
|
|
55
|
-
'ConnectorApiConfigMap',
|
|
56
|
-
'ConnectorDbConfigMap',
|
|
57
|
-
'ConnectorFileConfigMap',
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# SECTION: TYPE ALIASES ===================================================== #
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
# Literal type for supported connector kinds
|
|
65
|
-
type ConnectorType = Literal['api', 'database', 'file']
|
|
66
|
-
|
|
67
|
-
# Literal type for supported pagination kinds
|
|
68
|
-
# type PaginationType = Literal['page', 'offset', 'cursor']
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# SECTION: TYPED DICTS ====================================================== #
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class ApiConfigMap(TypedDict, total=False):
|
|
75
|
-
"""
|
|
76
|
-
Top-level API config shape parsed by ApiConfig.from_obj.
|
|
77
|
-
|
|
78
|
-
Either provide a 'base_url' with optional 'headers' and 'endpoints', or
|
|
79
|
-
provide 'profiles' with at least one profile having a 'base_url'.
|
|
80
|
-
|
|
81
|
-
See Also
|
|
82
|
-
--------
|
|
83
|
-
- etlplus.config.api.ApiConfig.from_obj: parses this mapping
|
|
84
|
-
"""
|
|
85
|
-
|
|
86
|
-
base_url: str
|
|
87
|
-
headers: StrAnyMap
|
|
88
|
-
endpoints: Mapping[str, EndpointMap | str]
|
|
89
|
-
profiles: Mapping[str, ApiProfileConfigMap]
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class ApiProfileConfigMap(TypedDict, total=False):
|
|
93
|
-
"""
|
|
94
|
-
Shape accepted for a profile entry under ApiConfigMap.profiles.
|
|
95
|
-
|
|
96
|
-
Notes
|
|
97
|
-
-----
|
|
98
|
-
`base_url` is required at runtime when profiles are provided.
|
|
99
|
-
|
|
100
|
-
See Also
|
|
101
|
-
--------
|
|
102
|
-
- etlplus.config.api.ApiProfileConfig.from_obj: parses this mapping
|
|
103
|
-
"""
|
|
104
|
-
|
|
105
|
-
base_url: str
|
|
106
|
-
headers: StrAnyMap
|
|
107
|
-
base_path: str
|
|
108
|
-
auth: StrAnyMap
|
|
109
|
-
defaults: ApiProfileDefaultsMap
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
class ApiProfileDefaultsMap(TypedDict, total=False):
|
|
113
|
-
"""
|
|
114
|
-
Defaults block available under a profile (all keys optional).
|
|
115
|
-
|
|
116
|
-
Notes
|
|
117
|
-
-----
|
|
118
|
-
Runtime expects header values to be str; typing remains permissive.
|
|
119
|
-
|
|
120
|
-
See Also
|
|
121
|
-
--------
|
|
122
|
-
- etlplus.config.api.ApiProfileConfig.from_obj: consumes this block
|
|
123
|
-
- etlplus.config.pagination.PaginationConfig.from_obj: parses pagination
|
|
124
|
-
- etlplus.api.rate_limiting.RateLimitConfig.from_obj: parses rate_limit
|
|
125
|
-
"""
|
|
126
|
-
|
|
127
|
-
headers: StrAnyMap
|
|
128
|
-
pagination: PaginationConfigMap | StrAnyMap
|
|
129
|
-
rate_limit: RateLimitConfigMap | StrAnyMap
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
class ConnectorApiConfigMap(TypedDict, total=False):
|
|
133
|
-
"""
|
|
134
|
-
Shape accepted by ConnectorApi.from_obj (all keys optional).
|
|
135
|
-
|
|
136
|
-
See Also
|
|
137
|
-
--------
|
|
138
|
-
- etlplus.config.connector.ConnectorApi.from_obj
|
|
139
|
-
"""
|
|
140
|
-
|
|
141
|
-
name: str
|
|
142
|
-
type: ConnectorType
|
|
143
|
-
url: str
|
|
144
|
-
method: str
|
|
145
|
-
headers: StrAnyMap
|
|
146
|
-
query_params: StrAnyMap
|
|
147
|
-
pagination: PaginationConfigMap
|
|
148
|
-
rate_limit: RateLimitConfigMap
|
|
149
|
-
api: str
|
|
150
|
-
endpoint: str
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
class ConnectorDbConfigMap(TypedDict, total=False):
|
|
154
|
-
"""
|
|
155
|
-
Shape accepted by ConnectorDb.from_obj (all keys optional).
|
|
156
|
-
|
|
157
|
-
See Also
|
|
158
|
-
--------
|
|
159
|
-
- etlplus.config.connector.ConnectorDb.from_obj
|
|
160
|
-
"""
|
|
161
|
-
|
|
162
|
-
name: str
|
|
163
|
-
type: ConnectorType
|
|
164
|
-
connection_string: str
|
|
165
|
-
query: str
|
|
166
|
-
table: str
|
|
167
|
-
mode: str
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
class ConnectorFileConfigMap(TypedDict, total=False):
|
|
171
|
-
"""
|
|
172
|
-
Shape accepted by ConnectorFile.from_obj (all keys optional).
|
|
173
|
-
|
|
174
|
-
See Also
|
|
175
|
-
--------
|
|
176
|
-
- etlplus.config.connector.ConnectorFile.from_obj
|
|
177
|
-
"""
|
|
178
|
-
|
|
179
|
-
name: str
|
|
180
|
-
type: ConnectorType
|
|
181
|
-
format: str
|
|
182
|
-
path: str
|
|
183
|
-
options: StrAnyMap
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
class EndpointMap(TypedDict, total=False):
|
|
187
|
-
"""
|
|
188
|
-
Shape accepted by EndpointConfig.from_obj.
|
|
189
|
-
|
|
190
|
-
One of 'path' or 'url' should be provided.
|
|
191
|
-
|
|
192
|
-
See Also
|
|
193
|
-
--------
|
|
194
|
-
- etlplus.config.api.EndpointConfig.from_obj: parses this mapping
|
|
195
|
-
"""
|
|
196
|
-
|
|
197
|
-
path: str
|
|
198
|
-
url: str
|
|
199
|
-
method: str
|
|
200
|
-
path_params: StrAnyMap
|
|
201
|
-
query_params: StrAnyMap
|
|
202
|
-
body: Any
|
|
203
|
-
pagination: PaginationConfigMap
|
|
204
|
-
rate_limit: RateLimitConfigMap
|
etlplus/config/utils.py
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
:mod:`etlplus.config.utils` module.
|
|
3
|
-
|
|
4
|
-
A module defining utility helpers for ETL pipeline configuration.
|
|
5
|
-
|
|
6
|
-
Notes
|
|
7
|
-
-----
|
|
8
|
-
- Inputs to parsers favor ``Mapping[str, Any]`` to remain permissive and
|
|
9
|
-
avoid unnecessary copies; normalization returns concrete types.
|
|
10
|
-
- Substitution is shallow for strings and recursive for containers.
|
|
11
|
-
- Numeric coercion helpers are intentionally forgiving: invalid values
|
|
12
|
-
become ``None`` rather than raising.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
from collections.abc import Iterable
|
|
18
|
-
from collections.abc import Mapping
|
|
19
|
-
from typing import Any
|
|
20
|
-
|
|
21
|
-
from ..types import StrAnyMap
|
|
22
|
-
|
|
23
|
-
# SECTION: EXPORTS ========================================================== #
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
__all__ = [
|
|
27
|
-
# Functions
|
|
28
|
-
'deep_substitute',
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# SECTION: FUNCTIONS ======================================================== #
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def deep_substitute(
|
|
36
|
-
value: Any,
|
|
37
|
-
vars_map: StrAnyMap | None,
|
|
38
|
-
env_map: Mapping[str, str] | None,
|
|
39
|
-
) -> Any:
|
|
40
|
-
"""
|
|
41
|
-
Recursively substitute ``${VAR}`` tokens in nested structures.
|
|
42
|
-
|
|
43
|
-
Only strings are substituted; other types are returned as-is.
|
|
44
|
-
|
|
45
|
-
Parameters
|
|
46
|
-
----------
|
|
47
|
-
value : Any
|
|
48
|
-
The value to perform substitutions on.
|
|
49
|
-
vars_map : StrAnyMap | None
|
|
50
|
-
Mapping of variable names to replacement values (lower precedence).
|
|
51
|
-
env_map : Mapping[str, str] | None
|
|
52
|
-
Mapping of environment variables overriding ``vars_map`` values (higher
|
|
53
|
-
precedence).
|
|
54
|
-
|
|
55
|
-
Returns
|
|
56
|
-
-------
|
|
57
|
-
Any
|
|
58
|
-
New structure with substitutions applied where tokens were found.
|
|
59
|
-
"""
|
|
60
|
-
substitutions = _prepare_substitutions(vars_map, env_map)
|
|
61
|
-
|
|
62
|
-
def _apply(node: Any) -> Any:
|
|
63
|
-
match node:
|
|
64
|
-
case str():
|
|
65
|
-
return _replace_tokens(node, substitutions)
|
|
66
|
-
case Mapping():
|
|
67
|
-
return {k: _apply(v) for k, v in node.items()}
|
|
68
|
-
case list() | tuple() as seq:
|
|
69
|
-
apply = [_apply(item) for item in seq]
|
|
70
|
-
return apply if isinstance(seq, list) else tuple(apply)
|
|
71
|
-
case set():
|
|
72
|
-
return {_apply(item) for item in node}
|
|
73
|
-
case frozenset():
|
|
74
|
-
return frozenset(_apply(item) for item in node)
|
|
75
|
-
case _:
|
|
76
|
-
return node
|
|
77
|
-
|
|
78
|
-
return _apply(value)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
# SECTION: INTERNAL FUNCTIONS ============================================== #
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _prepare_substitutions(
|
|
85
|
-
vars_map: StrAnyMap | None,
|
|
86
|
-
env_map: Mapping[str, Any] | None,
|
|
87
|
-
) -> tuple[tuple[str, Any], ...]:
|
|
88
|
-
"""Merge variable and environment maps into an ordered substitutions list.
|
|
89
|
-
|
|
90
|
-
Parameters
|
|
91
|
-
----------
|
|
92
|
-
vars_map : StrAnyMap | None
|
|
93
|
-
Mapping of variable names to replacement values (lower precedence).
|
|
94
|
-
env_map : Mapping[str, Any] | None
|
|
95
|
-
Environment-backed values that override entries from ``vars_map``.
|
|
96
|
-
|
|
97
|
-
Returns
|
|
98
|
-
-------
|
|
99
|
-
tuple[tuple[str, Any], ...]
|
|
100
|
-
Immutable sequence of ``(name, value)`` pairs suitable for token
|
|
101
|
-
replacement.
|
|
102
|
-
"""
|
|
103
|
-
if not vars_map and not env_map:
|
|
104
|
-
return ()
|
|
105
|
-
merged: dict[str, Any] = {**(vars_map or {}), **(env_map or {})}
|
|
106
|
-
return tuple(merged.items())
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def _replace_tokens(
|
|
110
|
-
text: str,
|
|
111
|
-
substitutions: Iterable[tuple[str, Any]],
|
|
112
|
-
) -> str:
|
|
113
|
-
if not substitutions:
|
|
114
|
-
return text
|
|
115
|
-
out = text
|
|
116
|
-
for name, replacement in substitutions:
|
|
117
|
-
token = f'${{{name}}}'
|
|
118
|
-
if token in out:
|
|
119
|
-
out = out.replace(token, str(replacement))
|
|
120
|
-
return out
|