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.
- etlplus/README.md +25 -3
- etlplus/__init__.py +2 -0
- etlplus/api/README.md +31 -0
- etlplus/api/__init__.py +14 -14
- etlplus/api/auth.py +10 -7
- etlplus/api/config.py +8 -13
- etlplus/api/endpoint_client.py +20 -20
- etlplus/api/errors.py +4 -4
- etlplus/api/pagination/__init__.py +6 -6
- etlplus/api/pagination/config.py +12 -10
- etlplus/api/pagination/paginator.py +6 -7
- etlplus/api/rate_limiting/__init__.py +2 -2
- etlplus/api/rate_limiting/config.py +14 -14
- etlplus/api/rate_limiting/rate_limiter.py +3 -3
- etlplus/api/request_manager.py +4 -4
- etlplus/api/retry_manager.py +8 -8
- etlplus/api/transport.py +11 -11
- etlplus/api/types.py +131 -11
- etlplus/api/utils.py +50 -50
- etlplus/cli/commands.py +93 -60
- etlplus/cli/constants.py +1 -1
- etlplus/cli/handlers.py +43 -26
- etlplus/cli/io.py +2 -2
- etlplus/cli/main.py +2 -2
- etlplus/cli/state.py +4 -7
- etlplus/{workflow/pipeline.py → config.py} +62 -99
- 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 +36 -200
- 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/__init__.py +1 -0
- etlplus/ops/enums.py +173 -0
- etlplus/ops/extract.py +222 -23
- etlplus/ops/load.py +155 -36
- etlplus/ops/run.py +92 -107
- etlplus/ops/transform.py +48 -29
- etlplus/ops/types.py +147 -0
- etlplus/ops/utils.py +11 -40
- etlplus/ops/validate.py +16 -16
- etlplus/types.py +6 -102
- etlplus/utils.py +163 -29
- etlplus/workflow/README.md +0 -24
- etlplus/workflow/__init__.py +2 -15
- etlplus/workflow/dag.py +23 -1
- etlplus/workflow/jobs.py +83 -39
- etlplus/workflow/profile.py +4 -2
- {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/METADATA +4 -4
- etlplus-0.16.6.dist-info/RECORD +143 -0
- {etlplus-0.15.0.dist-info → etlplus-0.16.6.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.6.dist-info}/entry_points.txt +0 -0
- {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/top_level.txt +0 -0
|
@@ -41,7 +41,7 @@ __all__ = [
|
|
|
41
41
|
# Type Aliases
|
|
42
42
|
'RateLimitOverrides',
|
|
43
43
|
# Typed Dicts
|
|
44
|
-
'
|
|
44
|
+
'RateLimitConfigDict',
|
|
45
45
|
]
|
|
46
46
|
|
|
47
47
|
|
|
@@ -50,9 +50,9 @@ __all__ = [
|
|
|
50
50
|
|
|
51
51
|
def _coerce_rate_limit_map(
|
|
52
52
|
rate_limit: StrAnyMap | RateLimitConfig | None,
|
|
53
|
-
) ->
|
|
53
|
+
) -> RateLimitConfigDict | None:
|
|
54
54
|
"""
|
|
55
|
-
Normalize user inputs into a :class:`
|
|
55
|
+
Normalize user inputs into a :class:`RateLimitConfigDict`.
|
|
56
56
|
|
|
57
57
|
This helper is the single entry point for converting loosely-typed
|
|
58
58
|
configuration into the canonical mapping consumed by downstream
|
|
@@ -65,7 +65,7 @@ def _coerce_rate_limit_map(
|
|
|
65
65
|
|
|
66
66
|
Returns
|
|
67
67
|
-------
|
|
68
|
-
|
|
68
|
+
RateLimitConfigDict | None
|
|
69
69
|
Normalized mapping, or ``None`` if input couldn't be parsed.
|
|
70
70
|
"""
|
|
71
71
|
if rate_limit is None:
|
|
@@ -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
|
-------
|
|
@@ -133,7 +133,7 @@ def _normalized_rate_values(
|
|
|
133
133
|
# SECTION: TYPED DICTS ====================================================== #
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
class
|
|
136
|
+
class RateLimitConfigDict(TypedDict, total=False):
|
|
137
137
|
"""
|
|
138
138
|
Configuration mapping for HTTP request rate limits.
|
|
139
139
|
|
|
@@ -149,7 +149,7 @@ class RateLimitConfigMap(TypedDict, total=False):
|
|
|
149
149
|
|
|
150
150
|
Examples
|
|
151
151
|
--------
|
|
152
|
-
>>> rl:
|
|
152
|
+
>>> rl: RateLimitConfigDict = {'max_per_sec': 4}
|
|
153
153
|
... # sleep ~= 0.25s between calls
|
|
154
154
|
"""
|
|
155
155
|
|
|
@@ -197,9 +197,9 @@ class RateLimitConfig(BoundsWarningsMixin):
|
|
|
197
197
|
|
|
198
198
|
# -- Instance Methods -- #
|
|
199
199
|
|
|
200
|
-
def as_mapping(self) ->
|
|
200
|
+
def as_mapping(self) -> RateLimitConfigDict:
|
|
201
201
|
"""Return a normalized mapping consumable by rate-limit helpers."""
|
|
202
|
-
cfg:
|
|
202
|
+
cfg: RateLimitConfigDict = {}
|
|
203
203
|
if (sleep := to_float(self.sleep_seconds)) is not None:
|
|
204
204
|
cfg['sleep_seconds'] = sleep
|
|
205
205
|
if (rate := to_float(self.max_per_sec)) is not None:
|
|
@@ -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
|
-------
|
|
@@ -309,7 +309,7 @@ class RateLimitConfig(BoundsWarningsMixin):
|
|
|
309
309
|
@overload
|
|
310
310
|
def from_obj(
|
|
311
311
|
cls,
|
|
312
|
-
obj:
|
|
312
|
+
obj: RateLimitConfigDict,
|
|
313
313
|
) -> Self: ...
|
|
314
314
|
|
|
315
315
|
@classmethod
|
|
@@ -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
|
|
@@ -352,4 +352,4 @@ class RateLimitConfig(BoundsWarningsMixin):
|
|
|
352
352
|
type RateLimitInput = StrAnyMap | RateLimitConfig | None
|
|
353
353
|
|
|
354
354
|
# Optional mapping of rate-limit fields to override values.
|
|
355
|
-
type RateLimitOverrides =
|
|
355
|
+
type RateLimitOverrides = RateLimitConfigDict | None
|
|
@@ -25,7 +25,7 @@ from typing import Self
|
|
|
25
25
|
from ...utils import to_float
|
|
26
26
|
from ...utils import to_positive_float
|
|
27
27
|
from .config import RateLimitConfig
|
|
28
|
-
from .config import
|
|
28
|
+
from .config import RateLimitConfigDict
|
|
29
29
|
from .config import RateLimitInput
|
|
30
30
|
from .config import RateLimitOverrides
|
|
31
31
|
|
|
@@ -38,7 +38,7 @@ __all__ = [
|
|
|
38
38
|
# Data Classes
|
|
39
39
|
'RateLimitConfig',
|
|
40
40
|
# Typed Dicts
|
|
41
|
-
'
|
|
41
|
+
'RateLimitConfigDict',
|
|
42
42
|
]
|
|
43
43
|
|
|
44
44
|
|
|
@@ -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/request_manager.py
CHANGED
|
@@ -28,7 +28,7 @@ from .errors import ApiAuthError
|
|
|
28
28
|
from .errors import ApiRequestError
|
|
29
29
|
from .retry_manager import RetryInput
|
|
30
30
|
from .retry_manager import RetryManager
|
|
31
|
-
from .transport import
|
|
31
|
+
from .transport import HTTPAdapterMountConfigDict
|
|
32
32
|
from .transport import build_session_with_adapters
|
|
33
33
|
|
|
34
34
|
# SECTION: TYPE ALIASES ==================================================== #
|
|
@@ -68,7 +68,7 @@ class RequestManager:
|
|
|
68
68
|
``None``.
|
|
69
69
|
retry_cap : float, optional
|
|
70
70
|
Maximum backoff cap in seconds. Default is 30.0.
|
|
71
|
-
session_adapters : Sequence[
|
|
71
|
+
session_adapters : Sequence[HTTPAdapterMountConfigDict] | None, optional
|
|
72
72
|
Adapter mount configurations used when lazily building a session via
|
|
73
73
|
:func:`etlplus.api.transport.build_session_with_adapters`.
|
|
74
74
|
|
|
@@ -86,7 +86,7 @@ class RequestManager:
|
|
|
86
86
|
Optional factory for creating sessions.
|
|
87
87
|
retry_cap : float
|
|
88
88
|
Maximum backoff cap in seconds for :class:`RetryManager` sleeps.
|
|
89
|
-
session_adapters : Sequence[
|
|
89
|
+
session_adapters : Sequence[HTTPAdapterMountConfigDict] | None
|
|
90
90
|
Adapter mount configurations used when lazily building a session.
|
|
91
91
|
"""
|
|
92
92
|
|
|
@@ -98,7 +98,7 @@ class RequestManager:
|
|
|
98
98
|
session: requests.Session | None = None
|
|
99
99
|
session_factory: Callable[[], requests.Session] | None = None
|
|
100
100
|
retry_cap: float = 30.0
|
|
101
|
-
session_adapters: Sequence[
|
|
101
|
+
session_adapters: Sequence[HTTPAdapterMountConfigDict] | None = None
|
|
102
102
|
|
|
103
103
|
def __post_init__(self) -> None:
|
|
104
104
|
if self.session_adapters:
|
etlplus/api/retry_manager.py
CHANGED
|
@@ -47,7 +47,7 @@ __all__ = [
|
|
|
47
47
|
'RetryStrategy',
|
|
48
48
|
'RetryManager',
|
|
49
49
|
# Typed Dicts
|
|
50
|
-
'
|
|
50
|
+
'RetryPolicyDict',
|
|
51
51
|
# Type Aliases
|
|
52
52
|
'RetryInput',
|
|
53
53
|
]
|
|
@@ -69,7 +69,7 @@ DEFAULT_RETRY_STATUS_CODES: Final[frozenset[int]] = frozenset(
|
|
|
69
69
|
# SECTION: TYPED DICTS ====================================================== #
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
class
|
|
72
|
+
class RetryPolicyDict(TypedDict, total=False):
|
|
73
73
|
"""
|
|
74
74
|
Optional retry policy for HTTP requests.
|
|
75
75
|
|
|
@@ -100,7 +100,7 @@ class RetryPolicy(TypedDict, total=False):
|
|
|
100
100
|
# SECTION: TYPE ALIASES ===================================================== #
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
type RetryInput =
|
|
103
|
+
type RetryInput = RetryPolicyDict | None
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
# SECTION: DATA CLASSES ===================================================== #
|
|
@@ -108,7 +108,7 @@ type RetryInput = RetryPolicy | None
|
|
|
108
108
|
|
|
109
109
|
@dataclass(frozen=True, slots=True)
|
|
110
110
|
class RetryStrategy:
|
|
111
|
-
"""Normalized retry settings derived from a :class:`
|
|
111
|
+
"""Normalized retry settings derived from a :class:`RetryPolicyDict`."""
|
|
112
112
|
|
|
113
113
|
# -- Attributes -- #
|
|
114
114
|
|
|
@@ -171,7 +171,7 @@ class RetryManager:
|
|
|
171
171
|
Default HTTP status codes considered retryable.
|
|
172
172
|
DEFAULT_CAP : ClassVar[float]
|
|
173
173
|
Default maximum sleep seconds for jittered backoff.
|
|
174
|
-
policy :
|
|
174
|
+
policy : RetryPolicyDict
|
|
175
175
|
Retry policy configuration.
|
|
176
176
|
retry_network_errors : bool
|
|
177
177
|
Whether to retry on network errors (timeouts, connection errors).
|
|
@@ -191,7 +191,7 @@ class RetryManager:
|
|
|
191
191
|
|
|
192
192
|
# -- Instance Attributes-- #
|
|
193
193
|
|
|
194
|
-
policy:
|
|
194
|
+
policy: RetryPolicyDict
|
|
195
195
|
retry_network_errors: bool = False
|
|
196
196
|
cap: float = DEFAULT_CAP
|
|
197
197
|
sleeper: Sleeper = time.sleep
|
|
@@ -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
|
@@ -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,17 +306,17 @@ 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
|
-
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.
|
|
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
|
@@ -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,13 +42,134 @@ __all__ = [
|
|
|
40
42
|
'Headers',
|
|
41
43
|
'Params',
|
|
42
44
|
'Url',
|
|
45
|
+
# Typed Dicts
|
|
46
|
+
'ApiConfigDict',
|
|
47
|
+
'ApiProfileConfigDict',
|
|
48
|
+
'ApiProfileDefaultsDict',
|
|
49
|
+
'EndpointConfigDict',
|
|
43
50
|
]
|
|
44
51
|
|
|
45
52
|
|
|
46
53
|
# SECTION: CONSTANTS ======================================================== #
|
|
47
54
|
|
|
48
55
|
|
|
49
|
-
_UNSET = object()
|
|
56
|
+
_UNSET: object = object()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# SECTION: INTERNAL FUNCTIONS =============================================== #
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _to_dict(
|
|
63
|
+
value: Mapping[str, Any] | object | None,
|
|
64
|
+
) -> dict[str, Any] | None:
|
|
65
|
+
"""
|
|
66
|
+
Return a defensive ``dict`` copy for mapping inputs.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
value : Mapping[str, Any] | object | None
|
|
71
|
+
Mapping to copy, or ``None``.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
dict[str, Any] | None
|
|
76
|
+
New ``dict`` instance or ``None`` when the input is ``None``.
|
|
77
|
+
"""
|
|
78
|
+
if value is None:
|
|
79
|
+
return None
|
|
80
|
+
return cast(dict[str, Any], value)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# SECTION: TYPED DICTS ====================================================== #
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ApiConfigDict(TypedDict, total=False):
|
|
87
|
+
"""
|
|
88
|
+
Top-level API config shape parsed by
|
|
89
|
+
:meth:`etlplus.api.config.ApiConfig.from_obj`.
|
|
90
|
+
|
|
91
|
+
Either provide a :attr:`base_url` with optional :attr:`headers` and
|
|
92
|
+
:attr:`endpoints`, or provide :attr:`profiles` with at least one profile
|
|
93
|
+
having a :attr:`base_url`.
|
|
94
|
+
|
|
95
|
+
See Also
|
|
96
|
+
--------
|
|
97
|
+
- :class:`etlplus.api.config.ApiConfig`
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
base_url: str
|
|
101
|
+
headers: StrAnyMap
|
|
102
|
+
endpoints: Mapping[str, EndpointConfigDict | str]
|
|
103
|
+
profiles: Mapping[str, ApiProfileConfigDict]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ApiProfileConfigDict(TypedDict, total=False):
|
|
107
|
+
"""
|
|
108
|
+
Shape accepted for a profile entry under
|
|
109
|
+
:meth:`etlplus.api.config.ApiConfig.from_obj`.
|
|
110
|
+
|
|
111
|
+
Notes
|
|
112
|
+
-----
|
|
113
|
+
- :attr:`base_url` is required at runtime when :attr:`profiles` key/value
|
|
114
|
+
pairs are provided.
|
|
115
|
+
- :meth:`etlplus.api.config.ApiProfileConfig.from_obj` parses this mapping.
|
|
116
|
+
|
|
117
|
+
See Also
|
|
118
|
+
--------
|
|
119
|
+
- :class:`etlplus.api.config.ApiProfileConfig`
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
base_url: str
|
|
123
|
+
headers: StrAnyMap
|
|
124
|
+
base_path: str
|
|
125
|
+
auth: StrAnyMap
|
|
126
|
+
defaults: ApiProfileDefaultsDict
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ApiProfileDefaultsDict(TypedDict, total=False):
|
|
130
|
+
"""
|
|
131
|
+
Defaults block available under a profile (all keys optional).
|
|
132
|
+
|
|
133
|
+
Notes
|
|
134
|
+
-----
|
|
135
|
+
- Runtime expects header values to be ``str``; typing remains permissive.
|
|
136
|
+
- :meth:`etlplus.api.config.ApiProfileConfig.from_obj` consumes this block.
|
|
137
|
+
- :meth:`etlplus.api.pagination.PaginationConfig.from_obj` parses
|
|
138
|
+
:attr:`pagination`.
|
|
139
|
+
- :meth:`etlplus.api.rate_limiting.RateLimitConfig.from_obj` parses
|
|
140
|
+
:attr:`rate_limit`.
|
|
141
|
+
|
|
142
|
+
See Also
|
|
143
|
+
--------
|
|
144
|
+
- :class:`etlplus.api.config.ApiProfileConfig`
|
|
145
|
+
- :class:`etlplus.api.pagination.PaginationConfig`
|
|
146
|
+
- :class:`etlplus.api.rate_limiting.RateLimitConfig`
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
headers: StrAnyMap
|
|
150
|
+
pagination: StrAnyMap # PaginationConfigDict | StrAnyMap
|
|
151
|
+
rate_limit: StrAnyMap # RateLimitConfigDict | StrAnyMap
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class EndpointConfigDict(TypedDict, total=False):
|
|
155
|
+
"""
|
|
156
|
+
Shape accepted by :meth:`etlplus.api.config.EndpointConfig.from_obj`.
|
|
157
|
+
|
|
158
|
+
One of :attr:`path` or :attr:`url` should be provided.
|
|
159
|
+
|
|
160
|
+
See Also
|
|
161
|
+
--------
|
|
162
|
+
- :class:`etlplus.api.config.EndpointConfig`
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
path: str
|
|
166
|
+
url: str
|
|
167
|
+
method: str
|
|
168
|
+
path_params: StrAnyMap
|
|
169
|
+
query_params: StrAnyMap
|
|
170
|
+
body: Any
|
|
171
|
+
pagination: StrAnyMap # PaginationConfigDict | StrAnyMap
|
|
172
|
+
rate_limit: StrAnyMap # RateLimitConfigDict | StrAnyMap
|
|
50
173
|
|
|
51
174
|
|
|
52
175
|
# SECTION: DATA CLASSES ===================================================== #
|
|
@@ -77,9 +200,9 @@ class RequestOptions:
|
|
|
77
200
|
|
|
78
201
|
def __post_init__(self) -> None:
|
|
79
202
|
if self.params is not None:
|
|
80
|
-
object.__setattr__(self, 'params',
|
|
203
|
+
object.__setattr__(self, 'params', _to_dict(self.params))
|
|
81
204
|
if self.headers is not None:
|
|
82
|
-
object.__setattr__(self, 'headers',
|
|
205
|
+
object.__setattr__(self, 'headers', _to_dict(self.headers))
|
|
83
206
|
|
|
84
207
|
# -- Instance Methods -- #
|
|
85
208
|
|
|
@@ -125,23 +248,20 @@ class RequestOptions:
|
|
|
125
248
|
|
|
126
249
|
Returns
|
|
127
250
|
-------
|
|
128
|
-
|
|
251
|
+
Self
|
|
129
252
|
New snapshot reflecting the provided overrides.
|
|
130
253
|
"""
|
|
131
254
|
if params is _UNSET:
|
|
132
255
|
next_params = self.params
|
|
133
|
-
elif params is None:
|
|
134
|
-
next_params = None
|
|
135
256
|
else:
|
|
136
|
-
next_params =
|
|
257
|
+
# next_params = _to_dict(params) if params is not None else None
|
|
258
|
+
next_params = _to_dict(params)
|
|
137
259
|
|
|
138
260
|
if headers is _UNSET:
|
|
139
261
|
next_headers = self.headers
|
|
140
|
-
elif headers is None:
|
|
141
|
-
next_headers = None
|
|
142
262
|
else:
|
|
143
|
-
next_headers =
|
|
144
|
-
|
|
263
|
+
# next_headers = _to_dict(headers) if headers is not None else None
|
|
264
|
+
next_headers = _to_dict(headers)
|
|
145
265
|
if timeout is _UNSET:
|
|
146
266
|
next_timeout = self.timeout
|
|
147
267
|
else:
|