etlplus 0.15.0__py3-none-any.whl → 0.16.0__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 +3 -3
- etlplus/api/README.md +31 -0
- etlplus/api/auth.py +1 -1
- etlplus/api/config.py +5 -10
- etlplus/api/endpoint_client.py +4 -4
- etlplus/api/pagination/config.py +1 -1
- etlplus/api/pagination/paginator.py +6 -7
- etlplus/api/rate_limiting/config.py +4 -4
- etlplus/api/rate_limiting/rate_limiter.py +1 -1
- etlplus/api/retry_manager.py +2 -2
- etlplus/api/transport.py +1 -1
- etlplus/api/types.py +99 -0
- etlplus/api/utils.py +1 -1
- etlplus/cli/commands.py +75 -42
- etlplus/cli/constants.py +1 -1
- etlplus/cli/handlers.py +31 -13
- etlplus/cli/io.py +2 -2
- etlplus/cli/main.py +2 -2
- etlplus/cli/state.py +4 -7
- etlplus/connector/__init__.py +43 -0
- etlplus/connector/api.py +161 -0
- etlplus/connector/connector.py +26 -0
- etlplus/connector/core.py +132 -0
- etlplus/connector/database.py +122 -0
- etlplus/connector/enums.py +52 -0
- etlplus/connector/file.py +120 -0
- etlplus/connector/types.py +40 -0
- etlplus/connector/utils.py +122 -0
- etlplus/database/ddl.py +2 -2
- etlplus/database/engine.py +19 -3
- etlplus/database/orm.py +2 -0
- etlplus/enums.py +1 -33
- etlplus/file/_imports.py +1 -0
- etlplus/file/_io.py +52 -4
- etlplus/file/accdb.py +3 -2
- etlplus/file/arrow.py +3 -2
- etlplus/file/avro.py +3 -2
- etlplus/file/bson.py +3 -2
- etlplus/file/cbor.py +3 -2
- etlplus/file/cfg.py +3 -2
- etlplus/file/conf.py +3 -2
- etlplus/file/core.py +11 -8
- etlplus/file/csv.py +3 -2
- etlplus/file/dat.py +3 -2
- etlplus/file/dta.py +3 -2
- etlplus/file/duckdb.py +3 -2
- etlplus/file/enums.py +1 -1
- etlplus/file/feather.py +3 -2
- etlplus/file/fwf.py +3 -2
- etlplus/file/gz.py +3 -2
- etlplus/file/hbs.py +3 -2
- etlplus/file/hdf5.py +3 -2
- etlplus/file/ini.py +3 -2
- etlplus/file/ion.py +3 -2
- etlplus/file/jinja2.py +3 -2
- etlplus/file/json.py +5 -16
- etlplus/file/log.py +3 -2
- etlplus/file/mat.py +3 -2
- etlplus/file/mdb.py +3 -2
- etlplus/file/msgpack.py +3 -2
- etlplus/file/mustache.py +3 -2
- etlplus/file/nc.py +3 -2
- etlplus/file/ndjson.py +3 -2
- etlplus/file/numbers.py +3 -2
- etlplus/file/ods.py +3 -2
- etlplus/file/orc.py +3 -2
- etlplus/file/parquet.py +3 -2
- etlplus/file/pb.py +3 -2
- etlplus/file/pbf.py +3 -2
- etlplus/file/properties.py +3 -2
- etlplus/file/proto.py +3 -2
- etlplus/file/psv.py +3 -2
- etlplus/file/rda.py +3 -2
- etlplus/file/rds.py +3 -2
- etlplus/file/sas7bdat.py +3 -2
- etlplus/file/sav.py +3 -2
- etlplus/file/sqlite.py +3 -2
- etlplus/file/stub.py +1 -0
- etlplus/file/sylk.py +3 -2
- etlplus/file/tab.py +3 -2
- etlplus/file/toml.py +3 -2
- etlplus/file/tsv.py +3 -2
- etlplus/file/txt.py +4 -3
- etlplus/file/vm.py +3 -2
- etlplus/file/wks.py +3 -2
- etlplus/file/xls.py +3 -2
- etlplus/file/xlsm.py +3 -2
- etlplus/file/xlsx.py +3 -2
- etlplus/file/xml.py +9 -3
- etlplus/file/xpt.py +3 -2
- etlplus/file/yaml.py +5 -16
- etlplus/file/zip.py +3 -2
- etlplus/file/zsav.py +3 -2
- etlplus/ops/extract.py +13 -1
- etlplus/ops/load.py +15 -2
- etlplus/ops/run.py +4 -4
- etlplus/ops/transform.py +2 -2
- etlplus/ops/utils.py +6 -35
- etlplus/ops/validate.py +3 -3
- etlplus/types.py +3 -2
- etlplus/utils.py +163 -29
- etlplus/workflow/__init__.py +0 -11
- etlplus/workflow/jobs.py +84 -27
- etlplus/workflow/pipeline.py +48 -48
- {etlplus-0.15.0.dist-info → etlplus-0.16.0.dist-info}/METADATA +4 -4
- etlplus-0.16.0.dist-info/RECORD +141 -0
- {etlplus-0.15.0.dist-info → etlplus-0.16.0.dist-info}/WHEEL +1 -1
- etlplus/config/README.md +0 -50
- etlplus/config/__init__.py +0 -33
- etlplus/config/types.py +0 -140
- etlplus/dag.py +0 -103
- etlplus/workflow/connector.py +0 -373
- etlplus/workflow/types.py +0 -115
- etlplus/workflow/utils.py +0 -120
- etlplus-0.15.0.dist-info/RECORD +0 -139
- {etlplus-0.15.0.dist-info → etlplus-0.16.0.dist-info}/entry_points.txt +0 -0
- {etlplus-0.15.0.dist-info → etlplus-0.16.0.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.15.0.dist-info → etlplus-0.16.0.dist-info}/top_level.txt +0 -0
etlplus/README.md
CHANGED
|
@@ -13,12 +13,12 @@ Back to project overview: see the top-level [README](../README.md).
|
|
|
13
13
|
|
|
14
14
|
- [etlplus.api](api/README.md): Lightweight HTTP client and paginated REST helpers
|
|
15
15
|
- [etlplus.file](file/README.md): Unified file format support and helpers
|
|
16
|
-
- [etlplus.
|
|
17
|
-
profiles
|
|
18
|
-
- [etlplus.cli](cli/README.md): Command-line interface for ETLPlus workflows
|
|
16
|
+
- [etlplus.cli](cli/README.md): Command-line interface definitions for `etlplus`
|
|
19
17
|
- [etlplus.database](database/README.md): Database engine, schema, and ORM helpers
|
|
20
18
|
- [etlplus.templates](templates/README.md): SQL and DDL template helpers
|
|
21
19
|
- [etlplus.validation](validation/README.md): Data validation utilities and helpers
|
|
20
|
+
- [etlplus.workflow](etlplus/workflow/README.md): Helpers for data connectors, pipelines, jobs, and
|
|
21
|
+
profiles
|
|
22
22
|
|
|
23
23
|
## Quickstart
|
|
24
24
|
|
etlplus/api/README.md
CHANGED
|
@@ -22,6 +22,7 @@ Back to project overview: see the top-level [README](../../README.md).
|
|
|
22
22
|
- [Authentication](#authentication)
|
|
23
23
|
- [Errors and Rate Limiting](#errors-and-rate-limiting)
|
|
24
24
|
- [Types and Transport](#types-and-transport)
|
|
25
|
+
- [Config Schemas](#config-schemas)
|
|
25
26
|
- [Supporting Modules](#supporting-modules)
|
|
26
27
|
- [Minimal Contract](#minimal-contract)
|
|
27
28
|
- [See also](#see-also)
|
|
@@ -225,6 +226,36 @@ providers can fall back to their own defaults. If you already possess a static t
|
|
|
225
226
|
`etlplus/api/request_manager.py` wraps `requests` sessions plus retry orchestration. Advanced
|
|
226
227
|
users may consult those modules to adapt behavior.
|
|
227
228
|
|
|
229
|
+
## Config Schemas
|
|
230
|
+
|
|
231
|
+
`etlplus.api.types` defines TypedDict-based configuration shapes for API profiles and endpoints.
|
|
232
|
+
Runtime parsing remains permissive in `etlplus.api.config`, but these types improve IDE
|
|
233
|
+
autocomplete and static analysis.
|
|
234
|
+
|
|
235
|
+
Exported types:
|
|
236
|
+
|
|
237
|
+
- `ApiConfigMap`: top-level API config shape
|
|
238
|
+
- `ApiProfileConfigMap`: per-profile API config shape
|
|
239
|
+
- `ApiProfileDefaultsMap`: defaults block within a profile
|
|
240
|
+
- `EndpointMap`: endpoint config shape
|
|
241
|
+
|
|
242
|
+
Example:
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from etlplus.api import ApiConfigMap
|
|
246
|
+
|
|
247
|
+
api_cfg: ApiConfigMap = {
|
|
248
|
+
"base_url": "https://example.test",
|
|
249
|
+
"headers": {"Authorization": "Bearer token"},
|
|
250
|
+
"endpoints": {
|
|
251
|
+
"users": {
|
|
252
|
+
"path": "/users",
|
|
253
|
+
"method": "GET",
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
228
259
|
## Supporting Modules
|
|
229
260
|
|
|
230
261
|
- `etlplus.api.types` collects friendly aliases such as `Headers`, `Params`, `Url`, and
|
etlplus/api/auth.py
CHANGED
etlplus/api/config.py
CHANGED
|
@@ -3,11 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
Configuration dataclasses for REST API services, profiles, and endpoints.
|
|
5
5
|
|
|
6
|
-
These models used to live under :mod:`etlplus.config`, but they belong in the
|
|
7
|
-
API layer because they compose runtime types such as
|
|
8
|
-
:class:`etlplus.api.EndpointClient`, :class:`etlplus.api.PaginationConfig`, and
|
|
9
|
-
:class:`etlplus.api.RateLimitConfig`.
|
|
10
|
-
|
|
11
6
|
Notes
|
|
12
7
|
-----
|
|
13
8
|
- TypedDict references remain editor hints only; :meth:`from_obj` accepts
|
|
@@ -41,9 +36,9 @@ from .pagination import PaginationConfig
|
|
|
41
36
|
from .rate_limiting import RateLimitConfig
|
|
42
37
|
|
|
43
38
|
if TYPE_CHECKING:
|
|
44
|
-
from
|
|
45
|
-
from
|
|
46
|
-
from
|
|
39
|
+
from .types import ApiConfigMap
|
|
40
|
+
from .types import ApiProfileConfigMap
|
|
41
|
+
from .types import EndpointMap
|
|
47
42
|
|
|
48
43
|
|
|
49
44
|
# SECTION: EXPORTS ========================================================== #
|
|
@@ -73,7 +68,7 @@ def _effective_service_defaults(
|
|
|
73
68
|
fallback_headers: dict[str, str],
|
|
74
69
|
) -> tuple[str, dict[str, str]]:
|
|
75
70
|
"""
|
|
76
|
-
Return ``(base_url, headers)`` using
|
|
71
|
+
Return ``(base_url, headers)`` using *profiles* when present.
|
|
77
72
|
|
|
78
73
|
Parameters
|
|
79
74
|
----------
|
|
@@ -92,7 +87,7 @@ def _effective_service_defaults(
|
|
|
92
87
|
Raises
|
|
93
88
|
------
|
|
94
89
|
TypeError
|
|
95
|
-
If no profiles are defined and
|
|
90
|
+
If no profiles are defined and *fallback_base* is not a string.
|
|
96
91
|
"""
|
|
97
92
|
if profiles:
|
|
98
93
|
name = 'default' if 'default' in profiles else next(iter(profiles))
|
etlplus/api/endpoint_client.py
CHANGED
|
@@ -759,8 +759,8 @@ class EndpointClient:
|
|
|
759
759
|
Raises
|
|
760
760
|
------
|
|
761
761
|
KeyError
|
|
762
|
-
If
|
|
763
|
-
|
|
762
|
+
If *endpoint_key* is unknown or a required placeholder in the path
|
|
763
|
+
has no corresponding entry in *path_parameters*.
|
|
764
764
|
ValueError
|
|
765
765
|
If the path template is invalid.
|
|
766
766
|
|
|
@@ -836,7 +836,7 @@ class EndpointClient:
|
|
|
836
836
|
"""
|
|
837
837
|
Sleep for the specified seconds if positive.
|
|
838
838
|
|
|
839
|
-
The optional
|
|
839
|
+
The optional *sleeper* is useful for tests (e.g., pass
|
|
840
840
|
``lambda s: None``). Defaults to using time.sleep when not provided.
|
|
841
841
|
|
|
842
842
|
Parameters
|
|
@@ -870,7 +870,7 @@ class EndpointClient:
|
|
|
870
870
|
rate_limit : RateLimitConfigMap | None
|
|
871
871
|
Client-wide rate limit configuration.
|
|
872
872
|
overrides : RateLimitOverrides, optional
|
|
873
|
-
Per-call overrides that take precedence over
|
|
873
|
+
Per-call overrides that take precedence over *rate_limit*.
|
|
874
874
|
|
|
875
875
|
Returns
|
|
876
876
|
-------
|
etlplus/api/pagination/config.py
CHANGED
|
@@ -66,14 +66,14 @@ def _resolve_path(
|
|
|
66
66
|
path: str | None,
|
|
67
67
|
) -> Any:
|
|
68
68
|
"""
|
|
69
|
-
Resolve dotted
|
|
69
|
+
Resolve dotted *path* within *obj* or return ``_MISSING``.
|
|
70
70
|
|
|
71
71
|
Parameters
|
|
72
72
|
----------
|
|
73
73
|
obj : Any
|
|
74
74
|
JSON payload from an API response.
|
|
75
75
|
path : str | None
|
|
76
|
-
Dotted path to the target value within
|
|
76
|
+
Dotted path to the target value within *obj*.
|
|
77
77
|
|
|
78
78
|
Returns
|
|
79
79
|
-------
|
|
@@ -665,7 +665,7 @@ class Paginator:
|
|
|
665
665
|
records_path : str | None
|
|
666
666
|
Optional dotted path to the records within the payload.
|
|
667
667
|
fallback_path : str | None
|
|
668
|
-
Secondary dotted path consulted when
|
|
668
|
+
Secondary dotted path consulted when *records_path* resolves to
|
|
669
669
|
``None`` or an empty list.
|
|
670
670
|
|
|
671
671
|
Returns
|
|
@@ -675,9 +675,8 @@ class Paginator:
|
|
|
675
675
|
|
|
676
676
|
Notes
|
|
677
677
|
-----
|
|
678
|
-
Supports dotted path extraction via
|
|
679
|
-
|
|
680
|
-
``{"value": x}``.
|
|
678
|
+
Supports dotted path extraction via *records_path* and handles lists,
|
|
679
|
+
mappings, and scalars by coercing non-dict items into ``{"value": x}``.
|
|
681
680
|
"""
|
|
682
681
|
resolver = partial(_resolve_path, x)
|
|
683
682
|
data = resolver(records_path)
|
|
@@ -729,7 +728,7 @@ class Paginator:
|
|
|
729
728
|
Returns
|
|
730
729
|
-------
|
|
731
730
|
PaginationType | None
|
|
732
|
-
Detected pagination type, or
|
|
731
|
+
Detected pagination type, or *default* if not found.
|
|
733
732
|
"""
|
|
734
733
|
if not config:
|
|
735
734
|
return default
|
|
@@ -84,14 +84,14 @@ def _merge_rate_limit(
|
|
|
84
84
|
overrides: RateLimitOverrides = None,
|
|
85
85
|
) -> dict[str, Any]:
|
|
86
86
|
"""
|
|
87
|
-
Merge
|
|
87
|
+
Merge *rate_limit* and *overrides* honoring override precedence.
|
|
88
88
|
|
|
89
89
|
Parameters
|
|
90
90
|
----------
|
|
91
91
|
rate_limit : StrAnyMap | None
|
|
92
92
|
Base rate-limit configuration.
|
|
93
93
|
overrides : RateLimitOverrides, optional
|
|
94
|
-
Override configuration with precedence over
|
|
94
|
+
Override configuration with precedence over *rate_limit*.
|
|
95
95
|
|
|
96
96
|
Returns
|
|
97
97
|
-------
|
|
@@ -274,7 +274,7 @@ class RateLimitConfig(BoundsWarningsMixin):
|
|
|
274
274
|
rate_limit : StrAnyMap | RateLimitConfig | None, optional
|
|
275
275
|
Base rate-limit configuration to normalize.
|
|
276
276
|
overrides : RateLimitOverrides, optional
|
|
277
|
-
Override values that take precedence over
|
|
277
|
+
Override values that take precedence over *rate_limit*.
|
|
278
278
|
|
|
279
279
|
Returns
|
|
280
280
|
-------
|
|
@@ -330,7 +330,7 @@ class RateLimitConfig(BoundsWarningsMixin):
|
|
|
330
330
|
Returns
|
|
331
331
|
-------
|
|
332
332
|
Self | None
|
|
333
|
-
Parsed instance, or ``None`` if
|
|
333
|
+
Parsed instance, or ``None`` if *obj* isn't a mapping.
|
|
334
334
|
"""
|
|
335
335
|
if obj is None:
|
|
336
336
|
return None
|
|
@@ -235,7 +235,7 @@ class RateLimiter:
|
|
|
235
235
|
Base rate-limit configuration. May contain ``"sleep_seconds"`` or
|
|
236
236
|
``"max_per_sec"``.
|
|
237
237
|
overrides : RateLimitOverrides, optional
|
|
238
|
-
Optional overrides with the same keys as
|
|
238
|
+
Optional overrides with the same keys as *rate_limit*.
|
|
239
239
|
|
|
240
240
|
Returns
|
|
241
241
|
-------
|
etlplus/api/retry_manager.py
CHANGED
|
@@ -278,7 +278,7 @@ class RetryManager:
|
|
|
278
278
|
**kwargs: Any,
|
|
279
279
|
) -> JSONData:
|
|
280
280
|
"""
|
|
281
|
-
Execute
|
|
281
|
+
Execute *func* with exponential-backoff retries.
|
|
282
282
|
|
|
283
283
|
Parameters
|
|
284
284
|
----------
|
|
@@ -287,7 +287,7 @@ class RetryManager:
|
|
|
287
287
|
url : str
|
|
288
288
|
URL for the API request.
|
|
289
289
|
**kwargs : Any
|
|
290
|
-
Additional keyword arguments to pass to
|
|
290
|
+
Additional keyword arguments to pass to *func*
|
|
291
291
|
|
|
292
292
|
Returns
|
|
293
293
|
-------
|
etlplus/api/transport.py
CHANGED
|
@@ -309,7 +309,7 @@ def build_session_with_adapters(
|
|
|
309
309
|
adapters_cfg: Sequence[HTTPAdapterMountConfig],
|
|
310
310
|
) -> requests.Session:
|
|
311
311
|
"""
|
|
312
|
-
Mount adapters described by
|
|
312
|
+
Mount adapters described by *adapters_cfg* onto a new session.
|
|
313
313
|
|
|
314
314
|
Ignores invalid adapter configurations so that a usable session is always
|
|
315
315
|
returned.
|
etlplus/api/types.py
CHANGED
|
@@ -20,9 +20,11 @@ Examples
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
from collections.abc import Callable
|
|
23
|
+
from collections.abc import Mapping
|
|
23
24
|
from dataclasses import dataclass
|
|
24
25
|
from typing import Any
|
|
25
26
|
from typing import Self
|
|
27
|
+
from typing import TypedDict
|
|
26
28
|
from typing import cast
|
|
27
29
|
|
|
28
30
|
from ..types import JSONData
|
|
@@ -40,6 +42,11 @@ __all__ = [
|
|
|
40
42
|
'Headers',
|
|
41
43
|
'Params',
|
|
42
44
|
'Url',
|
|
45
|
+
# Typed Dicts
|
|
46
|
+
'ApiConfigMap',
|
|
47
|
+
'ApiProfileConfigMap',
|
|
48
|
+
'ApiProfileDefaultsMap',
|
|
49
|
+
'EndpointMap',
|
|
43
50
|
]
|
|
44
51
|
|
|
45
52
|
|
|
@@ -49,6 +56,98 @@ __all__ = [
|
|
|
49
56
|
_UNSET = object()
|
|
50
57
|
|
|
51
58
|
|
|
59
|
+
# SECTION: TYPED DICTS ====================================================== #
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ApiConfigMap(TypedDict, total=False):
|
|
63
|
+
"""
|
|
64
|
+
Top-level API config shape parsed by
|
|
65
|
+
:meth:`etlplus.api.config.ApiConfig.from_obj`.
|
|
66
|
+
|
|
67
|
+
Either provide a :attr:`base_url` with optional :attr:`headers` and
|
|
68
|
+
:attr:`endpoints`, or provide :attr:`profiles` with at least one profile
|
|
69
|
+
having a :attr:`base_url`.
|
|
70
|
+
|
|
71
|
+
See Also
|
|
72
|
+
--------
|
|
73
|
+
- :class:`etlplus.api.config.ApiConfig`
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
base_url: str
|
|
77
|
+
headers: StrAnyMap
|
|
78
|
+
endpoints: Mapping[str, EndpointMap | str]
|
|
79
|
+
profiles: Mapping[str, ApiProfileConfigMap]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ApiProfileConfigMap(TypedDict, total=False):
|
|
83
|
+
"""
|
|
84
|
+
Shape accepted for a profile entry under
|
|
85
|
+
:meth:`etlplus.api.config.ApiConfig.from_obj`.
|
|
86
|
+
|
|
87
|
+
Notes
|
|
88
|
+
-----
|
|
89
|
+
- :attr:`base_url` is required at runtime when :attr:`profiles` key/value
|
|
90
|
+
pairs are provided.
|
|
91
|
+
- :meth:`etlplus.api.config.ApiProfileConfig.from_obj` parses this mapping.
|
|
92
|
+
|
|
93
|
+
See Also
|
|
94
|
+
--------
|
|
95
|
+
- :class:`etlplus.api.config.ApiProfileConfig`
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
base_url: str
|
|
99
|
+
headers: StrAnyMap
|
|
100
|
+
base_path: str
|
|
101
|
+
auth: StrAnyMap
|
|
102
|
+
defaults: ApiProfileDefaultsMap
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ApiProfileDefaultsMap(TypedDict, total=False):
|
|
106
|
+
"""
|
|
107
|
+
Defaults block available under a profile (all keys optional).
|
|
108
|
+
|
|
109
|
+
Notes
|
|
110
|
+
-----
|
|
111
|
+
- Runtime expects header values to be ``str``; typing remains permissive.
|
|
112
|
+
- :meth:`etlplus.api.config.ApiProfileConfig.from_obj` consumes this block.
|
|
113
|
+
- :meth:`etlplus.api.pagination.PaginationConfig.from_obj` parses
|
|
114
|
+
:attr:`pagination`.
|
|
115
|
+
- :meth:`etlplus.api.rate_limiting.RateLimitConfig.from_obj` parses
|
|
116
|
+
:attr:`rate_limit`.
|
|
117
|
+
|
|
118
|
+
See Also
|
|
119
|
+
--------
|
|
120
|
+
- :class:`etlplus.api.config.ApiProfileConfig`
|
|
121
|
+
- :class:`etlplus.api.pagination.PaginationConfig`
|
|
122
|
+
- :class:`etlplus.api.rate_limiting.RateLimitConfig`
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
headers: StrAnyMap
|
|
126
|
+
pagination: StrAnyMap # PaginationConfigMap | StrAnyMap
|
|
127
|
+
rate_limit: StrAnyMap # RateLimitConfigMap | StrAnyMap
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class EndpointMap(TypedDict, total=False):
|
|
131
|
+
"""
|
|
132
|
+
Shape accepted by :meth:`etlplus.api.config.EndpointConfig.from_obj`.
|
|
133
|
+
|
|
134
|
+
One of :attr:`path` or :attr:`url` should be provided.
|
|
135
|
+
|
|
136
|
+
See Also
|
|
137
|
+
--------
|
|
138
|
+
- :class:`etlplus.api.config.EndpointConfig`
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
path: str
|
|
142
|
+
url: str
|
|
143
|
+
method: str
|
|
144
|
+
path_params: StrAnyMap
|
|
145
|
+
query_params: StrAnyMap
|
|
146
|
+
body: Any
|
|
147
|
+
pagination: StrAnyMap # PaginationConfigMap | StrAnyMap
|
|
148
|
+
rate_limit: StrAnyMap # RateLimitConfigMap | StrAnyMap
|
|
149
|
+
|
|
150
|
+
|
|
52
151
|
# SECTION: DATA CLASSES ===================================================== #
|
|
53
152
|
|
|
54
153
|
|
etlplus/api/utils.py
CHANGED
etlplus/cli/commands.py
CHANGED
|
@@ -61,6 +61,24 @@ __all__ = ['app']
|
|
|
61
61
|
|
|
62
62
|
# SECTION: TYPE ALIASES ==================================================== #
|
|
63
63
|
|
|
64
|
+
|
|
65
|
+
JobOption = Annotated[
|
|
66
|
+
str | None,
|
|
67
|
+
typer.Option(
|
|
68
|
+
'-j',
|
|
69
|
+
'--job',
|
|
70
|
+
help='Name of the job to run',
|
|
71
|
+
),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
JobsOption = Annotated[
|
|
75
|
+
bool,
|
|
76
|
+
typer.Option(
|
|
77
|
+
'--jobs',
|
|
78
|
+
help='List available job names and exit',
|
|
79
|
+
),
|
|
80
|
+
]
|
|
81
|
+
|
|
64
82
|
OperationsOption = Annotated[
|
|
65
83
|
str,
|
|
66
84
|
typer.Option(
|
|
@@ -89,6 +107,23 @@ PipelineConfigOption = Annotated[
|
|
|
89
107
|
),
|
|
90
108
|
]
|
|
91
109
|
|
|
110
|
+
PipelineOption = Annotated[
|
|
111
|
+
str | None,
|
|
112
|
+
typer.Option(
|
|
113
|
+
'-p',
|
|
114
|
+
'--pipeline',
|
|
115
|
+
help='Name of the pipeline to run',
|
|
116
|
+
),
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
PipelinesOption = Annotated[
|
|
120
|
+
bool,
|
|
121
|
+
typer.Option(
|
|
122
|
+
'--pipelines',
|
|
123
|
+
help='List ETL pipelines',
|
|
124
|
+
),
|
|
125
|
+
]
|
|
126
|
+
|
|
92
127
|
RenderConfigOption = Annotated[
|
|
93
128
|
str | None,
|
|
94
129
|
typer.Option(
|
|
@@ -193,6 +228,22 @@ SourceTypeOption = Annotated[
|
|
|
193
228
|
),
|
|
194
229
|
]
|
|
195
230
|
|
|
231
|
+
SourcesOption = Annotated[
|
|
232
|
+
bool,
|
|
233
|
+
typer.Option(
|
|
234
|
+
'--sources',
|
|
235
|
+
help='List data sources',
|
|
236
|
+
),
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
SummaryOption = Annotated[
|
|
240
|
+
bool,
|
|
241
|
+
typer.Option(
|
|
242
|
+
'--summary',
|
|
243
|
+
help='Show pipeline summary (name, version, sources, targets, jobs)',
|
|
244
|
+
),
|
|
245
|
+
]
|
|
246
|
+
|
|
196
247
|
TargetArg = Annotated[
|
|
197
248
|
str,
|
|
198
249
|
typer.Argument(
|
|
@@ -227,6 +278,22 @@ TargetTypeOption = Annotated[
|
|
|
227
278
|
),
|
|
228
279
|
]
|
|
229
280
|
|
|
281
|
+
TargetsOption = Annotated[
|
|
282
|
+
bool,
|
|
283
|
+
typer.Option(
|
|
284
|
+
'--targets',
|
|
285
|
+
help='List data targets',
|
|
286
|
+
),
|
|
287
|
+
]
|
|
288
|
+
|
|
289
|
+
TransformsOption = Annotated[
|
|
290
|
+
bool,
|
|
291
|
+
typer.Option(
|
|
292
|
+
'--transforms',
|
|
293
|
+
help='List data transforms',
|
|
294
|
+
),
|
|
295
|
+
]
|
|
296
|
+
|
|
230
297
|
|
|
231
298
|
# SECTION: INTERNAL FUNCTIONS =============================================== #
|
|
232
299
|
|
|
@@ -341,36 +408,12 @@ def _root(
|
|
|
341
408
|
def check_cmd(
|
|
342
409
|
ctx: typer.Context,
|
|
343
410
|
config: PipelineConfigOption,
|
|
344
|
-
jobs:
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
False,
|
|
351
|
-
'--pipelines',
|
|
352
|
-
help='List ETL pipelines',
|
|
353
|
-
),
|
|
354
|
-
sources: bool = typer.Option(
|
|
355
|
-
False,
|
|
356
|
-
'--sources',
|
|
357
|
-
help='List data sources',
|
|
358
|
-
),
|
|
359
|
-
summary: bool = typer.Option(
|
|
360
|
-
False,
|
|
361
|
-
'--summary',
|
|
362
|
-
help='Show pipeline summary (name, version, sources, targets, jobs)',
|
|
363
|
-
),
|
|
364
|
-
targets: bool = typer.Option(
|
|
365
|
-
False,
|
|
366
|
-
'--targets',
|
|
367
|
-
help='List data targets',
|
|
368
|
-
),
|
|
369
|
-
transforms: bool = typer.Option(
|
|
370
|
-
False,
|
|
371
|
-
'--transforms',
|
|
372
|
-
help='List data transforms',
|
|
373
|
-
),
|
|
411
|
+
jobs: JobsOption = False,
|
|
412
|
+
pipelines: PipelinesOption = False,
|
|
413
|
+
sources: SourcesOption = False,
|
|
414
|
+
summary: SummaryOption = False,
|
|
415
|
+
targets: TargetsOption = False,
|
|
416
|
+
transforms: TransformsOption = False,
|
|
374
417
|
) -> int:
|
|
375
418
|
"""
|
|
376
419
|
Inspect a pipeline configuration.
|
|
@@ -683,18 +726,8 @@ def render_cmd(
|
|
|
683
726
|
def run_cmd(
|
|
684
727
|
ctx: typer.Context,
|
|
685
728
|
config: PipelineConfigOption,
|
|
686
|
-
job:
|
|
687
|
-
|
|
688
|
-
'-j',
|
|
689
|
-
'--job',
|
|
690
|
-
help='Name of the job to run',
|
|
691
|
-
),
|
|
692
|
-
pipeline: str | None = typer.Option(
|
|
693
|
-
None,
|
|
694
|
-
'-p',
|
|
695
|
-
'--pipeline',
|
|
696
|
-
help='Name of the pipeline to run',
|
|
697
|
-
),
|
|
729
|
+
job: JobOption = None,
|
|
730
|
+
pipeline: PipelineOption = None,
|
|
698
731
|
) -> int:
|
|
699
732
|
"""
|
|
700
733
|
Execute an ETL job or pipeline from a YAML configuration.
|
etlplus/cli/constants.py
CHANGED
|
@@ -8,7 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
from typing import Final
|
|
10
10
|
|
|
11
|
-
from ..
|
|
11
|
+
from ..connector import DataConnectorType
|
|
12
12
|
from ..file import FileFormat
|
|
13
13
|
|
|
14
14
|
# SECTION: EXPORTS ========================================================== #
|
etlplus/cli/handlers.py
CHANGED
|
@@ -121,9 +121,12 @@ def _check_sections(
|
|
|
121
121
|
if targets:
|
|
122
122
|
sections['targets'] = [tgt.name for tgt in cfg.targets]
|
|
123
123
|
if transforms:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
if isinstance(cfg.transforms, Mapping):
|
|
125
|
+
sections['transforms'] = list(cfg.transforms)
|
|
126
|
+
else:
|
|
127
|
+
sections['transforms'] = [
|
|
128
|
+
getattr(trf, 'name', None) for trf in cfg.transforms
|
|
129
|
+
]
|
|
127
130
|
if not sections:
|
|
128
131
|
sections['jobs'] = _pipeline_summary(cfg)['jobs']
|
|
129
132
|
return sections
|
|
@@ -157,6 +160,29 @@ def _pipeline_summary(
|
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
|
|
163
|
+
def _write_file_payload(
|
|
164
|
+
payload: JSONData,
|
|
165
|
+
target: str,
|
|
166
|
+
*,
|
|
167
|
+
format_hint: str | None,
|
|
168
|
+
) -> None:
|
|
169
|
+
"""
|
|
170
|
+
Write a JSON-like payload to a file path using an optional format hint.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
payload : JSONData
|
|
175
|
+
The structured data to write.
|
|
176
|
+
target : str
|
|
177
|
+
File path to write to.
|
|
178
|
+
format_hint : str | None
|
|
179
|
+
Optional format hint for :class:`FileFormat`.
|
|
180
|
+
"""
|
|
181
|
+
file_path = Path(target)
|
|
182
|
+
file_format = FileFormat.coerce(format_hint) if format_hint else None
|
|
183
|
+
File(file_path, file_format=file_format).write(payload)
|
|
184
|
+
|
|
185
|
+
|
|
160
186
|
# SECTION: FUNCTIONS ======================================================== #
|
|
161
187
|
|
|
162
188
|
|
|
@@ -479,7 +505,7 @@ def run_handler(
|
|
|
479
505
|
Name of the job to run. If not provided, runs the entire pipeline.
|
|
480
506
|
Default is ``None``.
|
|
481
507
|
pipeline : str | None, optional
|
|
482
|
-
Alias for
|
|
508
|
+
Alias for *job*. Default is ``None``.
|
|
483
509
|
pretty : bool, optional
|
|
484
510
|
Whether to pretty-print output. Default is ``True``.
|
|
485
511
|
|
|
@@ -572,15 +598,7 @@ def transform_handler(
|
|
|
572
598
|
|
|
573
599
|
# TODO: Generalize to handle non-file targets.
|
|
574
600
|
if target and target != '-':
|
|
575
|
-
|
|
576
|
-
file_path = Path(target)
|
|
577
|
-
file_format = None
|
|
578
|
-
if target_format is not None:
|
|
579
|
-
try:
|
|
580
|
-
file_format = FileFormat(target_format)
|
|
581
|
-
except ValueError:
|
|
582
|
-
file_format = None # or handle error as appropriate
|
|
583
|
-
File(file_path, file_format=file_format).write(data)
|
|
601
|
+
_write_file_payload(data, target, format_hint=target_format)
|
|
584
602
|
print(f'Data transformed and saved to {target}')
|
|
585
603
|
return 0
|
|
586
604
|
|
etlplus/cli/io.py
CHANGED
|
@@ -71,7 +71,7 @@ def emit_or_write(
|
|
|
71
71
|
success_message: str,
|
|
72
72
|
) -> None:
|
|
73
73
|
"""
|
|
74
|
-
Emit JSON or persist to disk based on
|
|
74
|
+
Emit JSON or persist to disk based on *output_path*.
|
|
75
75
|
|
|
76
76
|
Parameters
|
|
77
77
|
----------
|
|
@@ -122,7 +122,7 @@ def materialize_file_payload(
|
|
|
122
122
|
format_explicit: bool,
|
|
123
123
|
) -> JSONData | object:
|
|
124
124
|
"""
|
|
125
|
-
Return structured payloads when
|
|
125
|
+
Return structured payloads when *source* references a file.
|
|
126
126
|
|
|
127
127
|
Parameters
|
|
128
128
|
----------
|
etlplus/cli/main.py
CHANGED
|
@@ -44,13 +44,13 @@ def _emit_context_help(
|
|
|
44
44
|
Returns
|
|
45
45
|
-------
|
|
46
46
|
bool
|
|
47
|
-
``True`` when help was emitted, ``False`` when
|
|
47
|
+
``True`` when help was emitted, ``False`` when *ctx* was ``None``.
|
|
48
48
|
"""
|
|
49
49
|
if ctx is None:
|
|
50
50
|
return False
|
|
51
51
|
|
|
52
52
|
with contextlib.redirect_stdout(sys.stderr):
|
|
53
|
-
ctx.get_help()
|
|
53
|
+
print(ctx.get_help())
|
|
54
54
|
return True
|
|
55
55
|
|
|
56
56
|
|