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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. etlplus/README.md +25 -3
  2. etlplus/__init__.py +2 -0
  3. etlplus/api/README.md +31 -0
  4. etlplus/api/__init__.py +14 -14
  5. etlplus/api/auth.py +10 -7
  6. etlplus/api/config.py +8 -13
  7. etlplus/api/endpoint_client.py +20 -20
  8. etlplus/api/errors.py +4 -4
  9. etlplus/api/pagination/__init__.py +6 -6
  10. etlplus/api/pagination/config.py +12 -10
  11. etlplus/api/pagination/paginator.py +6 -7
  12. etlplus/api/rate_limiting/__init__.py +2 -2
  13. etlplus/api/rate_limiting/config.py +14 -14
  14. etlplus/api/rate_limiting/rate_limiter.py +3 -3
  15. etlplus/api/request_manager.py +4 -4
  16. etlplus/api/retry_manager.py +8 -8
  17. etlplus/api/transport.py +11 -11
  18. etlplus/api/types.py +131 -11
  19. etlplus/api/utils.py +50 -50
  20. etlplus/cli/commands.py +93 -60
  21. etlplus/cli/constants.py +1 -1
  22. etlplus/cli/handlers.py +43 -26
  23. etlplus/cli/io.py +2 -2
  24. etlplus/cli/main.py +2 -2
  25. etlplus/cli/state.py +4 -7
  26. etlplus/{workflow/pipeline.py → config.py} +62 -99
  27. etlplus/connector/__init__.py +43 -0
  28. etlplus/connector/api.py +161 -0
  29. etlplus/connector/connector.py +26 -0
  30. etlplus/connector/core.py +132 -0
  31. etlplus/connector/database.py +122 -0
  32. etlplus/connector/enums.py +52 -0
  33. etlplus/connector/file.py +120 -0
  34. etlplus/connector/types.py +40 -0
  35. etlplus/connector/utils.py +122 -0
  36. etlplus/database/ddl.py +2 -2
  37. etlplus/database/engine.py +19 -3
  38. etlplus/database/orm.py +2 -0
  39. etlplus/enums.py +36 -200
  40. etlplus/file/_imports.py +1 -0
  41. etlplus/file/_io.py +52 -4
  42. etlplus/file/accdb.py +3 -2
  43. etlplus/file/arrow.py +3 -2
  44. etlplus/file/avro.py +3 -2
  45. etlplus/file/bson.py +3 -2
  46. etlplus/file/cbor.py +3 -2
  47. etlplus/file/cfg.py +3 -2
  48. etlplus/file/conf.py +3 -2
  49. etlplus/file/core.py +11 -8
  50. etlplus/file/csv.py +3 -2
  51. etlplus/file/dat.py +3 -2
  52. etlplus/file/dta.py +3 -2
  53. etlplus/file/duckdb.py +3 -2
  54. etlplus/file/enums.py +1 -1
  55. etlplus/file/feather.py +3 -2
  56. etlplus/file/fwf.py +3 -2
  57. etlplus/file/gz.py +3 -2
  58. etlplus/file/hbs.py +3 -2
  59. etlplus/file/hdf5.py +3 -2
  60. etlplus/file/ini.py +3 -2
  61. etlplus/file/ion.py +3 -2
  62. etlplus/file/jinja2.py +3 -2
  63. etlplus/file/json.py +5 -16
  64. etlplus/file/log.py +3 -2
  65. etlplus/file/mat.py +3 -2
  66. etlplus/file/mdb.py +3 -2
  67. etlplus/file/msgpack.py +3 -2
  68. etlplus/file/mustache.py +3 -2
  69. etlplus/file/nc.py +3 -2
  70. etlplus/file/ndjson.py +3 -2
  71. etlplus/file/numbers.py +3 -2
  72. etlplus/file/ods.py +3 -2
  73. etlplus/file/orc.py +3 -2
  74. etlplus/file/parquet.py +3 -2
  75. etlplus/file/pb.py +3 -2
  76. etlplus/file/pbf.py +3 -2
  77. etlplus/file/properties.py +3 -2
  78. etlplus/file/proto.py +3 -2
  79. etlplus/file/psv.py +3 -2
  80. etlplus/file/rda.py +3 -2
  81. etlplus/file/rds.py +3 -2
  82. etlplus/file/sas7bdat.py +3 -2
  83. etlplus/file/sav.py +3 -2
  84. etlplus/file/sqlite.py +3 -2
  85. etlplus/file/stub.py +1 -0
  86. etlplus/file/sylk.py +3 -2
  87. etlplus/file/tab.py +3 -2
  88. etlplus/file/toml.py +3 -2
  89. etlplus/file/tsv.py +3 -2
  90. etlplus/file/txt.py +4 -3
  91. etlplus/file/vm.py +3 -2
  92. etlplus/file/wks.py +3 -2
  93. etlplus/file/xls.py +3 -2
  94. etlplus/file/xlsm.py +3 -2
  95. etlplus/file/xlsx.py +3 -2
  96. etlplus/file/xml.py +9 -3
  97. etlplus/file/xpt.py +3 -2
  98. etlplus/file/yaml.py +5 -16
  99. etlplus/file/zip.py +3 -2
  100. etlplus/file/zsav.py +3 -2
  101. etlplus/ops/__init__.py +1 -0
  102. etlplus/ops/enums.py +173 -0
  103. etlplus/ops/extract.py +222 -23
  104. etlplus/ops/load.py +155 -36
  105. etlplus/ops/run.py +92 -107
  106. etlplus/ops/transform.py +48 -29
  107. etlplus/ops/types.py +147 -0
  108. etlplus/ops/utils.py +11 -40
  109. etlplus/ops/validate.py +16 -16
  110. etlplus/types.py +6 -102
  111. etlplus/utils.py +163 -29
  112. etlplus/workflow/README.md +0 -24
  113. etlplus/workflow/__init__.py +2 -15
  114. etlplus/workflow/dag.py +23 -1
  115. etlplus/workflow/jobs.py +83 -39
  116. etlplus/workflow/profile.py +4 -2
  117. {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/METADATA +4 -4
  118. etlplus-0.16.6.dist-info/RECORD +143 -0
  119. {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/WHEEL +1 -1
  120. etlplus/config/README.md +0 -50
  121. etlplus/config/__init__.py +0 -33
  122. etlplus/config/types.py +0 -140
  123. etlplus/dag.py +0 -103
  124. etlplus/workflow/connector.py +0 -373
  125. etlplus/workflow/types.py +0 -115
  126. etlplus/workflow/utils.py +0 -120
  127. etlplus-0.15.0.dist-info/RECORD +0 -139
  128. {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/entry_points.txt +0 -0
  129. {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/licenses/LICENSE +0 -0
  130. {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/top_level.txt +0 -0
@@ -41,7 +41,7 @@ __all__ = [
41
41
  # Type Aliases
42
42
  'RateLimitOverrides',
43
43
  # Typed Dicts
44
- 'RateLimitConfigMap',
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
- ) -> RateLimitConfigMap | None:
53
+ ) -> RateLimitConfigDict | None:
54
54
  """
55
- Normalize user inputs into a :class:`RateLimitConfigMap`.
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
- RateLimitConfigMap | None
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 ``rate_limit`` and ``overrides`` honoring override precedence.
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 ``rate_limit``.
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 RateLimitConfigMap(TypedDict, total=False):
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: RateLimitConfigMap = {'max_per_sec': 4}
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) -> RateLimitConfigMap:
200
+ def as_mapping(self) -> RateLimitConfigDict:
201
201
  """Return a normalized mapping consumable by rate-limit helpers."""
202
- cfg: RateLimitConfigMap = {}
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 ``rate_limit``.
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: RateLimitConfigMap,
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 ``obj`` isn't a mapping.
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 = RateLimitConfigMap | None
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 RateLimitConfigMap
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
- 'RateLimitConfigMap',
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 ``rate_limit``.
238
+ Optional overrides with the same keys as *rate_limit*.
239
239
 
240
240
  Returns
241
241
  -------
@@ -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 HTTPAdapterMountConfig
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[HTTPAdapterMountConfig] | None, optional
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[HTTPAdapterMountConfig] | None
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[HTTPAdapterMountConfig] | None = None
101
+ session_adapters: Sequence[HTTPAdapterMountConfigDict] | None = None
102
102
 
103
103
  def __post_init__(self) -> None:
104
104
  if self.session_adapters:
@@ -47,7 +47,7 @@ __all__ = [
47
47
  'RetryStrategy',
48
48
  'RetryManager',
49
49
  # Typed Dicts
50
- 'RetryPolicy',
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 RetryPolicy(TypedDict, total=False):
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 = RetryPolicy | None
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:`RetryPolicy`."""
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 : RetryPolicy
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: RetryPolicy
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 ``func`` with exponential-backoff retries.
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 ``func``
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
- 'HTTPAdapterMountConfig',
45
- 'HTTPAdapterRetryConfig',
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 HTTPAdapterRetryConfig(TypedDict, total=False):
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: HTTPAdapterRetryConfig = {
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 HTTPAdapterMountConfig(TypedDict, total=False):
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 | HTTPAdapterRetryConfig
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: HTTPAdapterMountConfig = {
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 | HTTPAdapterRetryConfig
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[HTTPAdapterMountConfig],
309
+ adapters_cfg: Sequence[HTTPAdapterMountConfigDict],
310
310
  ) -> requests.Session:
311
311
  """
312
- Mount adapters described by ``adapters_cfg`` onto a new session.
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[HTTPAdapterMountConfig]
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', dict(self.params))
203
+ object.__setattr__(self, 'params', _to_dict(self.params))
81
204
  if self.headers is not None:
82
- object.__setattr__(self, 'headers', dict(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
- RequestOptions
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 = cast(dict, 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 = cast(dict, 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: