etlplus 0.16.3__py3-none-any.whl → 0.16.5__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 +22 -0
- etlplus/__init__.py +2 -0
- etlplus/api/__init__.py +14 -14
- etlplus/api/auth.py +9 -6
- etlplus/api/config.py +6 -6
- etlplus/api/endpoint_client.py +16 -16
- etlplus/api/errors.py +4 -4
- etlplus/api/pagination/__init__.py +6 -6
- etlplus/api/pagination/config.py +11 -9
- etlplus/api/rate_limiting/__init__.py +2 -2
- etlplus/api/rate_limiting/config.py +10 -10
- etlplus/api/rate_limiting/rate_limiter.py +2 -2
- etlplus/api/request_manager.py +4 -4
- etlplus/api/retry_manager.py +6 -6
- etlplus/api/transport.py +10 -10
- etlplus/api/types.py +15 -15
- etlplus/api/utils.py +49 -49
- etlplus/cli/commands.py +22 -22
- etlplus/cli/handlers.py +12 -13
- etlplus/{workflow/pipeline.py → config.py} +17 -37
- etlplus/connector/__init__.py +6 -6
- etlplus/connector/api.py +7 -7
- etlplus/connector/database.py +3 -3
- etlplus/connector/file.py +3 -3
- etlplus/connector/types.py +2 -2
- etlplus/ops/extract.py +2 -2
- etlplus/ops/run.py +2 -2
- etlplus/ops/utils.py +5 -5
- etlplus/ops/validate.py +13 -13
- etlplus/types.py +2 -1
- etlplus/workflow/README.md +0 -24
- etlplus/workflow/__init__.py +0 -4
- etlplus/workflow/jobs.py +0 -2
- {etlplus-0.16.3.dist-info → etlplus-0.16.5.dist-info}/METADATA +1 -1
- {etlplus-0.16.3.dist-info → etlplus-0.16.5.dist-info}/RECORD +39 -39
- {etlplus-0.16.3.dist-info → etlplus-0.16.5.dist-info}/WHEEL +0 -0
- {etlplus-0.16.3.dist-info → etlplus-0.16.5.dist-info}/entry_points.txt +0 -0
- {etlplus-0.16.3.dist-info → etlplus-0.16.5.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.16.3.dist-info → etlplus-0.16.5.dist-info}/top_level.txt +0 -0
etlplus/api/transport.py
CHANGED
|
@@ -41,8 +41,8 @@ from ..utils import to_positive_int
|
|
|
41
41
|
|
|
42
42
|
__all__ = [
|
|
43
43
|
# Classes
|
|
44
|
-
'
|
|
45
|
-
'
|
|
44
|
+
'HTTPAdapterMountConfigDict',
|
|
45
|
+
'HTTPAdapterRetryConfigDict',
|
|
46
46
|
# Functions
|
|
47
47
|
'build_http_adapter',
|
|
48
48
|
'build_session_with_adapters',
|
|
@@ -52,7 +52,7 @@ __all__ = [
|
|
|
52
52
|
# SECTION: TYPED DICTS ====================================================== #
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
class
|
|
55
|
+
class HTTPAdapterRetryConfigDict(TypedDict, total=False):
|
|
56
56
|
"""
|
|
57
57
|
Retry configuration for urllib3 ``Retry``.
|
|
58
58
|
|
|
@@ -89,7 +89,7 @@ class HTTPAdapterRetryConfig(TypedDict, total=False):
|
|
|
89
89
|
|
|
90
90
|
Examples
|
|
91
91
|
--------
|
|
92
|
-
>>> retry_cfg:
|
|
92
|
+
>>> retry_cfg: HTTPAdapterRetryConfigDict = {
|
|
93
93
|
... 'total': 5,
|
|
94
94
|
... 'backoff_factor': 0.5,
|
|
95
95
|
... 'status_forcelist': [429, 503],
|
|
@@ -111,7 +111,7 @@ class HTTPAdapterRetryConfig(TypedDict, total=False):
|
|
|
111
111
|
respect_retry_after_header: bool
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
class
|
|
114
|
+
class HTTPAdapterMountConfigDict(TypedDict, total=False):
|
|
115
115
|
"""
|
|
116
116
|
Configuration mapping for mounting an ``HTTPAdapter`` on a ``Session``.
|
|
117
117
|
|
|
@@ -132,13 +132,13 @@ class HTTPAdapterMountConfig(TypedDict, total=False):
|
|
|
132
132
|
pool_block : bool
|
|
133
133
|
Whether the pool should block for connections instead of creating new
|
|
134
134
|
ones.
|
|
135
|
-
max_retries : int |
|
|
135
|
+
max_retries : int | HTTPAdapterRetryConfigDict
|
|
136
136
|
Retry configuration passed to ``HTTPAdapter`` (int) or converted to
|
|
137
137
|
``Retry``.
|
|
138
138
|
|
|
139
139
|
Examples
|
|
140
140
|
--------
|
|
141
|
-
>>> adapter_cfg:
|
|
141
|
+
>>> adapter_cfg: HTTPAdapterMountConfigDict = {
|
|
142
142
|
... 'prefix': 'https://',
|
|
143
143
|
... 'pool_connections': 10,
|
|
144
144
|
... 'pool_maxsize': 10,
|
|
@@ -156,7 +156,7 @@ class HTTPAdapterMountConfig(TypedDict, total=False):
|
|
|
156
156
|
pool_connections: int
|
|
157
157
|
pool_maxsize: int
|
|
158
158
|
pool_block: bool
|
|
159
|
-
max_retries: int |
|
|
159
|
+
max_retries: int | HTTPAdapterRetryConfigDict
|
|
160
160
|
|
|
161
161
|
|
|
162
162
|
# SECTION: INTERNAL FUNCTIONS ============================================== #
|
|
@@ -306,7 +306,7 @@ def build_http_adapter(
|
|
|
306
306
|
|
|
307
307
|
|
|
308
308
|
def build_session_with_adapters(
|
|
309
|
-
adapters_cfg: Sequence[
|
|
309
|
+
adapters_cfg: Sequence[HTTPAdapterMountConfigDict],
|
|
310
310
|
) -> requests.Session:
|
|
311
311
|
"""
|
|
312
312
|
Mount adapters described by *adapters_cfg* onto a new session.
|
|
@@ -316,7 +316,7 @@ def build_session_with_adapters(
|
|
|
316
316
|
|
|
317
317
|
Parameters
|
|
318
318
|
----------
|
|
319
|
-
adapters_cfg : Sequence[
|
|
319
|
+
adapters_cfg : Sequence[HTTPAdapterMountConfigDict]
|
|
320
320
|
Configuration mappings describing the adapter prefix, pooling
|
|
321
321
|
values, and retry policy for each mounted adapter.
|
|
322
322
|
|
etlplus/api/types.py
CHANGED
|
@@ -43,10 +43,10 @@ __all__ = [
|
|
|
43
43
|
'Params',
|
|
44
44
|
'Url',
|
|
45
45
|
# Typed Dicts
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
46
|
+
'ApiConfigDict',
|
|
47
|
+
'ApiProfileConfigDict',
|
|
48
|
+
'ApiProfileDefaultsDict',
|
|
49
|
+
'EndpointConfigDict',
|
|
50
50
|
]
|
|
51
51
|
|
|
52
52
|
|
|
@@ -83,7 +83,7 @@ def _to_dict(
|
|
|
83
83
|
# SECTION: TYPED DICTS ====================================================== #
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
class
|
|
86
|
+
class ApiConfigDict(TypedDict, total=False):
|
|
87
87
|
"""
|
|
88
88
|
Top-level API config shape parsed by
|
|
89
89
|
:meth:`etlplus.api.config.ApiConfig.from_obj`.
|
|
@@ -99,11 +99,11 @@ class ApiConfigMap(TypedDict, total=False):
|
|
|
99
99
|
|
|
100
100
|
base_url: str
|
|
101
101
|
headers: StrAnyMap
|
|
102
|
-
endpoints: Mapping[str,
|
|
103
|
-
profiles: Mapping[str,
|
|
102
|
+
endpoints: Mapping[str, EndpointConfigDict | str]
|
|
103
|
+
profiles: Mapping[str, ApiProfileConfigDict]
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
class
|
|
106
|
+
class ApiProfileConfigDict(TypedDict, total=False):
|
|
107
107
|
"""
|
|
108
108
|
Shape accepted for a profile entry under
|
|
109
109
|
:meth:`etlplus.api.config.ApiConfig.from_obj`.
|
|
@@ -123,10 +123,10 @@ class ApiProfileConfigMap(TypedDict, total=False):
|
|
|
123
123
|
headers: StrAnyMap
|
|
124
124
|
base_path: str
|
|
125
125
|
auth: StrAnyMap
|
|
126
|
-
defaults:
|
|
126
|
+
defaults: ApiProfileDefaultsDict
|
|
127
127
|
|
|
128
128
|
|
|
129
|
-
class
|
|
129
|
+
class ApiProfileDefaultsDict(TypedDict, total=False):
|
|
130
130
|
"""
|
|
131
131
|
Defaults block available under a profile (all keys optional).
|
|
132
132
|
|
|
@@ -147,11 +147,11 @@ class ApiProfileDefaultsMap(TypedDict, total=False):
|
|
|
147
147
|
"""
|
|
148
148
|
|
|
149
149
|
headers: StrAnyMap
|
|
150
|
-
pagination: StrAnyMap #
|
|
151
|
-
rate_limit: StrAnyMap #
|
|
150
|
+
pagination: StrAnyMap # PaginationConfigDict | StrAnyMap
|
|
151
|
+
rate_limit: StrAnyMap # RateLimitConfigDict | StrAnyMap
|
|
152
152
|
|
|
153
153
|
|
|
154
|
-
class
|
|
154
|
+
class EndpointConfigDict(TypedDict, total=False):
|
|
155
155
|
"""
|
|
156
156
|
Shape accepted by :meth:`etlplus.api.config.EndpointConfig.from_obj`.
|
|
157
157
|
|
|
@@ -168,8 +168,8 @@ class EndpointMap(TypedDict, total=False):
|
|
|
168
168
|
path_params: StrAnyMap
|
|
169
169
|
query_params: StrAnyMap
|
|
170
170
|
body: Any
|
|
171
|
-
pagination: StrAnyMap #
|
|
172
|
-
rate_limit: StrAnyMap #
|
|
171
|
+
pagination: StrAnyMap # PaginationConfigDict | StrAnyMap
|
|
172
|
+
rate_limit: StrAnyMap # RateLimitConfigDict | StrAnyMap
|
|
173
173
|
|
|
174
174
|
|
|
175
175
|
# SECTION: DATA CLASSES ===================================================== #
|
etlplus/api/utils.py
CHANGED
|
@@ -22,11 +22,11 @@ from .config import EndpointConfig
|
|
|
22
22
|
from .endpoint_client import EndpointClient
|
|
23
23
|
from .enums import HttpMethod
|
|
24
24
|
from .pagination import PaginationConfig
|
|
25
|
-
from .pagination import
|
|
25
|
+
from .pagination import PaginationConfigDict
|
|
26
26
|
from .rate_limiting import RateLimitConfig
|
|
27
|
-
from .rate_limiting import
|
|
27
|
+
from .rate_limiting import RateLimitConfigDict
|
|
28
28
|
from .rate_limiting import RateLimiter
|
|
29
|
-
from .retry_manager import
|
|
29
|
+
from .retry_manager import RetryPolicyDict
|
|
30
30
|
from .types import Headers
|
|
31
31
|
from .types import Params
|
|
32
32
|
from .types import Url
|
|
@@ -53,16 +53,16 @@ __all__ = [
|
|
|
53
53
|
'paginate_with_client',
|
|
54
54
|
'resolve_request',
|
|
55
55
|
# Typed Dicts
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
'
|
|
56
|
+
'ApiRequestEnvDict',
|
|
57
|
+
'ApiTargetEnvDict',
|
|
58
|
+
'SessionConfigDict',
|
|
59
59
|
]
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
# SECTION: TYPED DICTS ====================================================== #
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
class
|
|
65
|
+
class BaseApiHttpEnvDict(TypedDict, total=False):
|
|
66
66
|
"""
|
|
67
67
|
Common HTTP request environment for API interactions.
|
|
68
68
|
|
|
@@ -78,7 +78,7 @@ class BaseApiHttpEnv(TypedDict, total=False):
|
|
|
78
78
|
session: requests.Session | None
|
|
79
79
|
|
|
80
80
|
|
|
81
|
-
class
|
|
81
|
+
class ApiRequestEnvDict(BaseApiHttpEnvDict, total=False):
|
|
82
82
|
"""
|
|
83
83
|
Composed HTTP request environment configuration for REST API sources.
|
|
84
84
|
|
|
@@ -96,15 +96,15 @@ class ApiRequestEnv(BaseApiHttpEnv, total=False):
|
|
|
96
96
|
|
|
97
97
|
# Request
|
|
98
98
|
params: dict[str, Any]
|
|
99
|
-
pagination:
|
|
99
|
+
pagination: PaginationConfigDict | None
|
|
100
100
|
sleep_seconds: float
|
|
101
101
|
|
|
102
102
|
# Reliability
|
|
103
|
-
retry:
|
|
103
|
+
retry: RetryPolicyDict | None
|
|
104
104
|
retry_network_errors: bool
|
|
105
105
|
|
|
106
106
|
|
|
107
|
-
class
|
|
107
|
+
class ApiTargetEnvDict(BaseApiHttpEnvDict, total=False):
|
|
108
108
|
"""
|
|
109
109
|
Composed HTTP request environment configuration for REST API targets.
|
|
110
110
|
|
|
@@ -126,7 +126,7 @@ class ApiTargetEnv(BaseApiHttpEnv, total=False):
|
|
|
126
126
|
method: str | None
|
|
127
127
|
|
|
128
128
|
|
|
129
|
-
class
|
|
129
|
+
class SessionConfigDict(TypedDict, total=False):
|
|
130
130
|
"""
|
|
131
131
|
Minimal session configuration schema accepted by the
|
|
132
132
|
:class:`requests.Session` runner.
|
|
@@ -148,14 +148,14 @@ class SessionConfig(TypedDict, total=False):
|
|
|
148
148
|
|
|
149
149
|
|
|
150
150
|
def _build_session_optional(
|
|
151
|
-
cfg:
|
|
151
|
+
cfg: SessionConfigDict | None,
|
|
152
152
|
) -> requests.Session | None:
|
|
153
153
|
"""
|
|
154
154
|
Return a configured session when *cfg* is a mapping.
|
|
155
155
|
|
|
156
156
|
Parameters
|
|
157
157
|
----------
|
|
158
|
-
cfg :
|
|
158
|
+
cfg : SessionConfigDict | None
|
|
159
159
|
Session configuration mapping.
|
|
160
160
|
|
|
161
161
|
Returns
|
|
@@ -164,7 +164,7 @@ def _build_session_optional(
|
|
|
164
164
|
Configured session or ``None``.
|
|
165
165
|
"""
|
|
166
166
|
if isinstance(cfg, Mapping):
|
|
167
|
-
return build_session(cast(
|
|
167
|
+
return build_session(cast(SessionConfigDict, cfg))
|
|
168
168
|
return None
|
|
169
169
|
|
|
170
170
|
|
|
@@ -233,9 +233,9 @@ def _inherit_http_from_api_endpoint(
|
|
|
233
233
|
ep: EndpointConfig,
|
|
234
234
|
url: Url | None,
|
|
235
235
|
headers: dict[str, str],
|
|
236
|
-
session_cfg:
|
|
236
|
+
session_cfg: SessionConfigDict | None,
|
|
237
237
|
force_url: bool = False,
|
|
238
|
-
) -> tuple[Url | None, dict[str, str],
|
|
238
|
+
) -> tuple[Url | None, dict[str, str], SessionConfigDict | None]:
|
|
239
239
|
"""
|
|
240
240
|
Return HTTP settings inherited from API + endpoint definitions.
|
|
241
241
|
|
|
@@ -249,14 +249,14 @@ def _inherit_http_from_api_endpoint(
|
|
|
249
249
|
Existing URL to use when not forcing endpoint URL.
|
|
250
250
|
headers : dict[str, str]
|
|
251
251
|
Existing headers to augment.
|
|
252
|
-
session_cfg :
|
|
252
|
+
session_cfg : SessionConfigDict | None
|
|
253
253
|
Existing session configuration to augment.
|
|
254
254
|
force_url : bool, optional
|
|
255
255
|
Whether to always use the endpoint URL.
|
|
256
256
|
|
|
257
257
|
Returns
|
|
258
258
|
-------
|
|
259
|
-
tuple[Url | None, dict[str, str],
|
|
259
|
+
tuple[Url | None, dict[str, str], SessionConfigDict | None]
|
|
260
260
|
Resolved URL, headers, and session configuration.
|
|
261
261
|
"""
|
|
262
262
|
if force_url or not url:
|
|
@@ -269,8 +269,8 @@ def _inherit_http_from_api_endpoint(
|
|
|
269
269
|
def _merge_session_cfg_three(
|
|
270
270
|
api_cfg: ApiConfig,
|
|
271
271
|
ep: EndpointConfig,
|
|
272
|
-
source_session_cfg:
|
|
273
|
-
) ->
|
|
272
|
+
source_session_cfg: SessionConfigDict | None,
|
|
273
|
+
) -> SessionConfigDict | None:
|
|
274
274
|
"""
|
|
275
275
|
Merge session configurations from API, endpoint, and source.
|
|
276
276
|
|
|
@@ -280,12 +280,12 @@ def _merge_session_cfg_three(
|
|
|
280
280
|
API configuration.
|
|
281
281
|
ep : EndpointConfig
|
|
282
282
|
Endpoint configuration.
|
|
283
|
-
source_session_cfg :
|
|
283
|
+
source_session_cfg : SessionConfigDict | None
|
|
284
284
|
Source session configuration.
|
|
285
285
|
|
|
286
286
|
Returns
|
|
287
287
|
-------
|
|
288
|
-
|
|
288
|
+
SessionConfigDict | None
|
|
289
289
|
Merged session configuration.
|
|
290
290
|
"""
|
|
291
291
|
api_sess = getattr(api_cfg, 'session', None)
|
|
@@ -297,7 +297,7 @@ def _merge_session_cfg_three(
|
|
|
297
297
|
merged.update(ep_sess)
|
|
298
298
|
if isinstance(source_session_cfg, Mapping):
|
|
299
299
|
merged.update(source_session_cfg)
|
|
300
|
-
return cast(
|
|
300
|
+
return cast(SessionConfigDict | None, (merged or None))
|
|
301
301
|
|
|
302
302
|
|
|
303
303
|
def _update_mapping(
|
|
@@ -361,7 +361,7 @@ def compose_api_request_env(
|
|
|
361
361
|
cfg: Any,
|
|
362
362
|
source_obj: Any,
|
|
363
363
|
ex_opts: Mapping[str, Any] | None,
|
|
364
|
-
) ->
|
|
364
|
+
) -> ApiRequestEnvDict:
|
|
365
365
|
"""
|
|
366
366
|
Compose the API request environment.
|
|
367
367
|
|
|
@@ -376,7 +376,7 @@ def compose_api_request_env(
|
|
|
376
376
|
|
|
377
377
|
Returns
|
|
378
378
|
-------
|
|
379
|
-
|
|
379
|
+
ApiRequestEnvDict
|
|
380
380
|
The composed API request environment.
|
|
381
381
|
"""
|
|
382
382
|
ex_opts = ex_opts or {}
|
|
@@ -393,13 +393,13 @@ def compose_api_request_env(
|
|
|
393
393
|
headers: dict[str, str] = cast(dict[str, str], coerce_dict(source_headers))
|
|
394
394
|
pagination = getattr(source_obj, 'pagination', None)
|
|
395
395
|
rate_limit = getattr(source_obj, 'rate_limit', None)
|
|
396
|
-
retry:
|
|
397
|
-
|
|
396
|
+
retry: RetryPolicyDict | None = cast(
|
|
397
|
+
RetryPolicyDict | None,
|
|
398
398
|
getattr(source_obj, 'retry', None),
|
|
399
399
|
)
|
|
400
400
|
retry_network_errors = getattr(source_obj, 'retry_network_errors', None)
|
|
401
401
|
session_cfg = cast(
|
|
402
|
-
|
|
402
|
+
SessionConfigDict | None,
|
|
403
403
|
getattr(source_obj, 'session', None),
|
|
404
404
|
)
|
|
405
405
|
api_name = getattr(source_obj, 'api', None)
|
|
@@ -435,7 +435,7 @@ def compose_api_request_env(
|
|
|
435
435
|
api_cfg.effective_rate_limit_defaults(),
|
|
436
436
|
)
|
|
437
437
|
retry = cast(
|
|
438
|
-
|
|
438
|
+
RetryPolicyDict | None,
|
|
439
439
|
_coalesce(
|
|
440
440
|
retry,
|
|
441
441
|
getattr(ep, 'retry', None),
|
|
@@ -465,8 +465,8 @@ def compose_api_request_env(
|
|
|
465
465
|
timeout: Timeout = ex_opts.get('timeout')
|
|
466
466
|
pag_ov = ex_opts.get('pagination', {})
|
|
467
467
|
rl_ov = ex_opts.get('rate_limit', {})
|
|
468
|
-
rty_ov:
|
|
469
|
-
|
|
468
|
+
rty_ov: RetryPolicyDict | None = cast(
|
|
469
|
+
RetryPolicyDict | None,
|
|
470
470
|
(ex_opts.get('retry') if 'retry' in ex_opts else None),
|
|
471
471
|
)
|
|
472
472
|
rne_ov = (
|
|
@@ -474,7 +474,7 @@ def compose_api_request_env(
|
|
|
474
474
|
if 'retry_network_errors' in ex_opts
|
|
475
475
|
else None
|
|
476
476
|
)
|
|
477
|
-
sess_ov = cast(
|
|
477
|
+
sess_ov = cast(SessionConfigDict | None, ex_opts.get('session'))
|
|
478
478
|
sleep_s = compute_rl_sleep_seconds(rate_limit, rl_ov) or 0.0
|
|
479
479
|
if rty_ov is not None:
|
|
480
480
|
retry = rty_ov
|
|
@@ -485,8 +485,8 @@ def compose_api_request_env(
|
|
|
485
485
|
cast(Mapping[str, Any], session_cfg or {}),
|
|
486
486
|
)
|
|
487
487
|
base_cfg.update(sess_ov)
|
|
488
|
-
session_cfg = cast(
|
|
489
|
-
pag_cfg:
|
|
488
|
+
session_cfg = cast(SessionConfigDict, base_cfg)
|
|
489
|
+
pag_cfg: PaginationConfigDict | None = build_pagination_cfg(
|
|
490
490
|
pagination,
|
|
491
491
|
pag_ov,
|
|
492
492
|
)
|
|
@@ -513,7 +513,7 @@ def compose_api_target_env(
|
|
|
513
513
|
cfg: Any,
|
|
514
514
|
target_obj: Any,
|
|
515
515
|
overrides: Mapping[str, Any] | None,
|
|
516
|
-
) ->
|
|
516
|
+
) -> ApiTargetEnvDict:
|
|
517
517
|
"""
|
|
518
518
|
Compose the API target environment.
|
|
519
519
|
|
|
@@ -528,7 +528,7 @@ def compose_api_target_env(
|
|
|
528
528
|
|
|
529
529
|
Returns
|
|
530
530
|
-------
|
|
531
|
-
|
|
531
|
+
ApiTargetEnvDict
|
|
532
532
|
Composed API target environment.
|
|
533
533
|
"""
|
|
534
534
|
ov = overrides or {}
|
|
@@ -553,8 +553,8 @@ def compose_api_target_env(
|
|
|
553
553
|
timeout: Timeout = (
|
|
554
554
|
cast(Timeout, ov.get('timeout')) if 'timeout' in ov else None
|
|
555
555
|
)
|
|
556
|
-
sess_cfg:
|
|
557
|
-
|
|
556
|
+
sess_cfg: SessionConfigDict | None = cast(
|
|
557
|
+
SessionConfigDict | None,
|
|
558
558
|
ov.get('session'),
|
|
559
559
|
)
|
|
560
560
|
api_name = getattr(target_obj, 'api', None)
|
|
@@ -583,7 +583,7 @@ def compose_api_target_env(
|
|
|
583
583
|
def build_pagination_cfg(
|
|
584
584
|
pagination: PaginationConfig | None,
|
|
585
585
|
overrides: Mapping[str, Any] | None,
|
|
586
|
-
) ->
|
|
586
|
+
) -> PaginationConfigDict | None:
|
|
587
587
|
"""
|
|
588
588
|
Build pagination configuration.
|
|
589
589
|
|
|
@@ -596,7 +596,7 @@ def build_pagination_cfg(
|
|
|
596
596
|
|
|
597
597
|
Returns
|
|
598
598
|
-------
|
|
599
|
-
|
|
599
|
+
PaginationConfigDict | None
|
|
600
600
|
Pagination configuration.
|
|
601
601
|
"""
|
|
602
602
|
ptype: str | None = None
|
|
@@ -683,7 +683,7 @@ def build_pagination_cfg(
|
|
|
683
683
|
case _:
|
|
684
684
|
pass
|
|
685
685
|
|
|
686
|
-
return cast(
|
|
686
|
+
return cast(PaginationConfigDict, cfg)
|
|
687
687
|
|
|
688
688
|
|
|
689
689
|
def paginate_with_client(
|
|
@@ -692,7 +692,7 @@ def paginate_with_client(
|
|
|
692
692
|
params: Params | None,
|
|
693
693
|
headers: Headers | None,
|
|
694
694
|
timeout: Timeout,
|
|
695
|
-
pagination:
|
|
695
|
+
pagination: PaginationConfigDict | None,
|
|
696
696
|
sleep_seconds: float | None,
|
|
697
697
|
) -> Any:
|
|
698
698
|
"""
|
|
@@ -710,7 +710,7 @@ def paginate_with_client(
|
|
|
710
710
|
Headers to include in the API request.
|
|
711
711
|
timeout : Timeout
|
|
712
712
|
Timeout configuration for the API request.
|
|
713
|
-
pagination :
|
|
713
|
+
pagination : PaginationConfigDict | None
|
|
714
714
|
Pagination configuration for the API request.
|
|
715
715
|
sleep_seconds : float | None
|
|
716
716
|
Sleep duration between API requests.
|
|
@@ -771,9 +771,9 @@ def compute_rl_sleep_seconds(
|
|
|
771
771
|
else:
|
|
772
772
|
rl_map = cast(Mapping[str, Any] | None, rate_limit)
|
|
773
773
|
|
|
774
|
-
rl_mapping = cast(
|
|
774
|
+
rl_mapping = cast(RateLimitConfigDict | None, rl_map)
|
|
775
775
|
|
|
776
|
-
typed_override:
|
|
776
|
+
typed_override: RateLimitConfigDict | None = None
|
|
777
777
|
if overrides:
|
|
778
778
|
filtered: dict[str, float | None] = {}
|
|
779
779
|
if 'sleep_seconds' in overrides:
|
|
@@ -787,7 +787,7 @@ def compute_rl_sleep_seconds(
|
|
|
787
787
|
overrides.get('max_per_sec'),
|
|
788
788
|
)
|
|
789
789
|
if filtered:
|
|
790
|
-
typed_override = cast(
|
|
790
|
+
typed_override = cast(RateLimitConfigDict, filtered)
|
|
791
791
|
|
|
792
792
|
return RateLimiter.resolve_sleep_seconds(
|
|
793
793
|
rate_limit=rl_mapping,
|
|
@@ -796,14 +796,14 @@ def compute_rl_sleep_seconds(
|
|
|
796
796
|
|
|
797
797
|
|
|
798
798
|
def build_session(
|
|
799
|
-
cfg:
|
|
799
|
+
cfg: SessionConfigDict | None,
|
|
800
800
|
) -> requests.Session:
|
|
801
801
|
"""
|
|
802
802
|
Build a requests.Session object with the given configuration.
|
|
803
803
|
|
|
804
804
|
Parameters
|
|
805
805
|
----------
|
|
806
|
-
cfg :
|
|
806
|
+
cfg : SessionConfigDict | None
|
|
807
807
|
Session configuration.
|
|
808
808
|
|
|
809
809
|
Returns
|
etlplus/cli/commands.py
CHANGED
|
@@ -62,6 +62,16 @@ __all__ = ['app']
|
|
|
62
62
|
# SECTION: TYPE ALIASES ==================================================== #
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
ConfigOption = Annotated[
|
|
66
|
+
str,
|
|
67
|
+
typer.Option(
|
|
68
|
+
...,
|
|
69
|
+
'--config',
|
|
70
|
+
metavar='PATH',
|
|
71
|
+
help='Path to YAML-formatted configuration file.',
|
|
72
|
+
),
|
|
73
|
+
]
|
|
74
|
+
|
|
65
75
|
JobOption = Annotated[
|
|
66
76
|
str | None,
|
|
67
77
|
typer.Option(
|
|
@@ -97,16 +107,6 @@ OutputOption = Annotated[
|
|
|
97
107
|
),
|
|
98
108
|
]
|
|
99
109
|
|
|
100
|
-
PipelineConfigOption = Annotated[
|
|
101
|
-
str,
|
|
102
|
-
typer.Option(
|
|
103
|
-
...,
|
|
104
|
-
'--config',
|
|
105
|
-
metavar='PATH',
|
|
106
|
-
help='Path to pipeline YAML configuration file.',
|
|
107
|
-
),
|
|
108
|
-
]
|
|
109
|
-
|
|
110
110
|
PipelineOption = Annotated[
|
|
111
111
|
str | None,
|
|
112
112
|
typer.Option(
|
|
@@ -407,7 +407,7 @@ def _root(
|
|
|
407
407
|
@app.command('check')
|
|
408
408
|
def check_cmd(
|
|
409
409
|
ctx: typer.Context,
|
|
410
|
-
config:
|
|
410
|
+
config: ConfigOption,
|
|
411
411
|
jobs: JobsOption = False,
|
|
412
412
|
pipelines: PipelinesOption = False,
|
|
413
413
|
sources: SourcesOption = False,
|
|
@@ -422,20 +422,20 @@ def check_cmd(
|
|
|
422
422
|
----------
|
|
423
423
|
ctx : typer.Context
|
|
424
424
|
The Typer context.
|
|
425
|
-
config :
|
|
425
|
+
config : ConfigOption
|
|
426
426
|
Path to pipeline YAML configuration file.
|
|
427
|
-
jobs :
|
|
427
|
+
jobs : JobsOption, optional
|
|
428
428
|
List available job names and exit. Default is ``False``.
|
|
429
|
-
pipelines :
|
|
429
|
+
pipelines : PipelinesOption, optional
|
|
430
430
|
List ETL pipelines. Default is ``False``.
|
|
431
|
-
sources :
|
|
431
|
+
sources : SourcesOption, optional
|
|
432
432
|
List data sources. Default is ``False``.
|
|
433
|
-
summary :
|
|
433
|
+
summary : SummaryOption, optional
|
|
434
434
|
Show pipeline summary (name, version, sources, targets, jobs). Default
|
|
435
435
|
is ``False``.
|
|
436
|
-
targets :
|
|
436
|
+
targets : TargetsOption, optional
|
|
437
437
|
List data targets. Default is ``False``.
|
|
438
|
-
transforms :
|
|
438
|
+
transforms : TransformsOption, optional
|
|
439
439
|
List data transforms. Default is ``False``.
|
|
440
440
|
|
|
441
441
|
Returns
|
|
@@ -725,7 +725,7 @@ def render_cmd(
|
|
|
725
725
|
@app.command('run')
|
|
726
726
|
def run_cmd(
|
|
727
727
|
ctx: typer.Context,
|
|
728
|
-
config:
|
|
728
|
+
config: ConfigOption,
|
|
729
729
|
job: JobOption = None,
|
|
730
730
|
pipeline: PipelineOption = None,
|
|
731
731
|
) -> int:
|
|
@@ -736,11 +736,11 @@ def run_cmd(
|
|
|
736
736
|
----------
|
|
737
737
|
ctx : typer.Context
|
|
738
738
|
The Typer context.
|
|
739
|
-
config :
|
|
739
|
+
config : ConfigOption
|
|
740
740
|
Path to pipeline YAML configuration file.
|
|
741
|
-
job :
|
|
741
|
+
job : JobOption, optional
|
|
742
742
|
Name of the job to run. Default is ``None``.
|
|
743
|
-
pipeline :
|
|
743
|
+
pipeline : PipelineOption, optional
|
|
744
744
|
Name of the pipeline to run. Default is ``None``.
|
|
745
745
|
|
|
746
746
|
Returns
|
etlplus/cli/handlers.py
CHANGED
|
@@ -14,6 +14,7 @@ from typing import Any
|
|
|
14
14
|
from typing import Literal
|
|
15
15
|
from typing import cast
|
|
16
16
|
|
|
17
|
+
from .. import Config
|
|
17
18
|
from ..database import load_table_spec
|
|
18
19
|
from ..database import render_tables
|
|
19
20
|
from ..file import File
|
|
@@ -23,11 +24,9 @@ from ..ops import load
|
|
|
23
24
|
from ..ops import run
|
|
24
25
|
from ..ops import transform
|
|
25
26
|
from ..ops import validate
|
|
26
|
-
from ..ops.validate import
|
|
27
|
+
from ..ops.validate import FieldRulesDict
|
|
27
28
|
from ..types import JSONData
|
|
28
29
|
from ..types import TemplateKey
|
|
29
|
-
from ..workflow import PipelineConfig
|
|
30
|
-
from ..workflow import load_pipeline_config
|
|
31
30
|
from . import io as cli_io
|
|
32
31
|
|
|
33
32
|
# SECTION: EXPORTS ========================================================== #
|
|
@@ -73,14 +72,14 @@ def _collect_table_specs(
|
|
|
73
72
|
specs.append(dict(load_table_spec(Path(spec_path))))
|
|
74
73
|
|
|
75
74
|
if config_path:
|
|
76
|
-
cfg =
|
|
75
|
+
cfg = Config.from_yaml(config_path, substitute=True)
|
|
77
76
|
specs.extend(getattr(cfg, 'table_schemas', []))
|
|
78
77
|
|
|
79
78
|
return specs
|
|
80
79
|
|
|
81
80
|
|
|
82
81
|
def _check_sections(
|
|
83
|
-
cfg:
|
|
82
|
+
cfg: Config,
|
|
84
83
|
*,
|
|
85
84
|
jobs: bool,
|
|
86
85
|
pipelines: bool,
|
|
@@ -93,7 +92,7 @@ def _check_sections(
|
|
|
93
92
|
|
|
94
93
|
Parameters
|
|
95
94
|
----------
|
|
96
|
-
cfg :
|
|
95
|
+
cfg : Config
|
|
97
96
|
The loaded pipeline configuration.
|
|
98
97
|
jobs : bool
|
|
99
98
|
Whether to include job metadata.
|
|
@@ -133,14 +132,14 @@ def _check_sections(
|
|
|
133
132
|
|
|
134
133
|
|
|
135
134
|
def _pipeline_summary(
|
|
136
|
-
cfg:
|
|
135
|
+
cfg: Config,
|
|
137
136
|
) -> dict[str, Any]:
|
|
138
137
|
"""
|
|
139
138
|
Return a human-friendly snapshot of a pipeline config.
|
|
140
139
|
|
|
141
140
|
Parameters
|
|
142
141
|
----------
|
|
143
|
-
cfg :
|
|
142
|
+
cfg : Config
|
|
144
143
|
The loaded pipeline configuration.
|
|
145
144
|
|
|
146
145
|
Returns
|
|
@@ -229,7 +228,7 @@ def check_handler(
|
|
|
229
228
|
Zero on success.
|
|
230
229
|
|
|
231
230
|
"""
|
|
232
|
-
cfg =
|
|
231
|
+
cfg = Config.from_yaml(config, substitute=substitute)
|
|
233
232
|
if summary:
|
|
234
233
|
cli_io.emit_json(_pipeline_summary(cfg), pretty=True)
|
|
235
234
|
return 0
|
|
@@ -514,7 +513,7 @@ def run_handler(
|
|
|
514
513
|
int
|
|
515
514
|
Zero on success.
|
|
516
515
|
"""
|
|
517
|
-
cfg =
|
|
516
|
+
cfg = Config.from_yaml(config, substitute=True)
|
|
518
517
|
|
|
519
518
|
job_name = job or pipeline
|
|
520
519
|
if job_name:
|
|
@@ -662,7 +661,7 @@ def validate_handler(
|
|
|
662
661
|
if not isinstance(rules_payload, dict):
|
|
663
662
|
raise ValueError('rules must resolve to a mapping of field rules')
|
|
664
663
|
|
|
665
|
-
field_rules = cast(Mapping[str,
|
|
664
|
+
field_rules = cast(Mapping[str, FieldRulesDict], rules_payload)
|
|
666
665
|
result = validate(payload, field_rules)
|
|
667
666
|
|
|
668
667
|
if target and target != '-':
|
|
@@ -671,11 +670,11 @@ def validate_handler(
|
|
|
671
670
|
cli_io.write_json_output(
|
|
672
671
|
validated_data,
|
|
673
672
|
target,
|
|
674
|
-
success_message='
|
|
673
|
+
success_message='ValidationDict result saved to',
|
|
675
674
|
)
|
|
676
675
|
else:
|
|
677
676
|
print(
|
|
678
|
-
f'
|
|
677
|
+
f'ValidationDict failed, no data to save for {target}',
|
|
679
678
|
file=sys.stderr,
|
|
680
679
|
)
|
|
681
680
|
else:
|