etlplus 0.14.3__py3-none-any.whl → 0.15.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 CHANGED
@@ -1,4 +1,4 @@
1
- # etlplus package
1
+ # `etlplus` Package
2
2
 
3
3
  The `etlplus` package provides a unified Python API and CLI for ETL operations: extraction,
4
4
  validation, transformation, and loading of data from files, APIs, and databases.
etlplus/api/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # etlplus.api subpackage
1
+ # `etlplus.api` Subpackage
2
2
 
3
3
  Documentation for the `etlplus.api` subpackage: a lightweight HTTP client and helpers for paginated
4
4
  REST endpoints.
@@ -12,7 +12,7 @@ REST endpoints.
12
12
 
13
13
  Back to project overview: see the top-level [README](../../README.md).
14
14
 
15
- - [etlplus.api subpackage](#etlplusapi-subpackage)
15
+ - [`etlplus.api` Subpackage](#etlplusapi-subpackage)
16
16
  - [Installation](#installation)
17
17
  - [Quickstart](#quickstart)
18
18
  - [Overriding Rate Limits Per Call](#overriding-rate-limits-per-call)
etlplus/api/utils.py CHANGED
@@ -892,4 +892,8 @@ def resolve_request(
892
892
  'Session object must supply a callable '
893
893
  f'"{http_method.value}" method',
894
894
  )
895
- return request_callable, request_timeout, http_method
895
+ typed_request_callable = cast(
896
+ Callable[..., requests.Response],
897
+ request_callable,
898
+ )
899
+ return typed_request_callable, request_timeout, http_method
etlplus/cli/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # etlplus.cli subpackage
1
+ # `etlplus.cli` Subpackage
2
2
 
3
3
  Documentation for the `etlplus.cli` subpackage: command-line interface for ETLPlus workflows.
4
4
 
@@ -9,7 +9,7 @@ Documentation for the `etlplus.cli` subpackage: command-line interface for ETLPl
9
9
 
10
10
  Back to project overview: see the top-level [README](../../README.md).
11
11
 
12
- - [etlplus.cli subpackage](#etlpluscli-subpackage)
12
+ - [`etlplus.cli` Subpackage](#etlpluscli-subpackage)
13
13
  - [Available Commands](#available-commands)
14
14
  - [Command Options](#command-options)
15
15
  - [Example: Running a Pipeline](#example-running-a-pipeline)
etlplus/cli/handlers.py CHANGED
@@ -14,8 +14,6 @@ from typing import Any
14
14
  from typing import Literal
15
15
  from typing import cast
16
16
 
17
- from ..config import PipelineConfig
18
- from ..config import load_pipeline_config
19
17
  from ..database import load_table_spec
20
18
  from ..database import render_tables
21
19
  from ..file import File
@@ -28,6 +26,8 @@ from ..ops import validate
28
26
  from ..ops.validate import FieldRules
29
27
  from ..types import JSONData
30
28
  from ..types import TemplateKey
29
+ from ..workflow import PipelineConfig
30
+ from ..workflow import load_pipeline_config
31
31
  from . import io as cli_io
32
32
 
33
33
  # SECTION: EXPORTS ========================================================== #
etlplus/config/README.md CHANGED
@@ -1,52 +1,50 @@
1
- # etlplus.config subpackage
1
+ # `etlplus.config` Subpackage
2
2
 
3
- Documentation for the `etlplus.config` subpackage: configuration helpers for connectors, pipelines,
4
- jobs, and profiles.
3
+ Documentation for the `etlplus.config` subpackage: type definitions and config shape helpers for
4
+ ETLPlus.
5
5
 
6
- - Provides classes and utilities for managing ETL pipeline configuration
7
- - Supports YAML/JSON config loading and validation
8
- - Includes helpers for connectors, jobs, pipelines, and profiles
9
- - Exposes type definitions for config schemas
6
+ - Exposes TypedDict-based config schemas for API profiles and endpoints
7
+ - Provides exported type aliases for API configuration maps
8
+ - Designed for Python 3.13 typing and editor assistance (runtime parsing lives elsewhere)
10
9
 
11
10
  Back to project overview: see the top-level [README](../../README.md).
12
11
 
13
- - [etlplus.config subpackage](#etlplusconfig-subpackage)
14
- - [Supported Configuration Types](#supported-configuration-types)
15
- - [Loading and Validating Configs](#loading-and-validating-configs)
16
- - [Example: Loading a Pipeline Config](#example-loading-a-pipeline-config)
12
+ - [`etlplus.config` Subpackage](#etlplusconfig-subpackage)
13
+ - [Modules](#modules)
14
+ - [Exported Types](#exported-types)
15
+ - [Example: Typing an API Config](#example-typing-an-api-config)
17
16
  - [See Also](#see-also)
18
17
 
19
- ## Supported Configuration Types
18
+ ## Modules
20
19
 
21
- - **Connector**: Connection details for databases, files, or APIs
22
- - **Job**: ETL job definitions and scheduling
23
- - **Pipeline**: End-to-end pipeline configuration
24
- - **Profile**: User or environment-specific settings
20
+ - `etlplus.config.__init__`: package exports and high-level package notes
21
+ - `etlplus.config.types`: TypedDict-based config schemas
25
22
 
26
- ## Loading and Validating Configs
23
+ ## Exported Types
27
24
 
28
- Use the provided classes to load and validate configuration files:
25
+ - `ApiConfigMap`: top-level API config shape
26
+ - `ApiProfileConfigMap`: per-profile API config shape
27
+ - `ApiProfileDefaultsMap`: defaults block within a profile
28
+ - `EndpointMap`: endpoint config shape
29
29
 
30
- ```python
31
- from etlplus.config import PipelineConfig
32
-
33
- cfg = PipelineConfig.from_yaml("pipeline.yml")
34
- ```
35
-
36
- - Supports YAML and JSON formats
37
- - Validates against expected schema
38
-
39
- ## Example: Loading a Pipeline Config
30
+ ## Example: Typing an API Config
40
31
 
41
32
  ```python
42
- from etlplus.config import PipelineConfig
43
-
44
- pipeline = PipelineConfig.from_yaml("configs/pipeline.yml")
45
- print(pipeline)
33
+ from etlplus.config import ApiConfigMap
34
+
35
+ api_cfg: ApiConfigMap = {
36
+ "base_url": "https://example.test",
37
+ "headers": {"Authorization": "Bearer token"},
38
+ "endpoints": {
39
+ "users": {
40
+ "path": "/users",
41
+ "method": "GET",
42
+ },
43
+ },
44
+ }
46
45
  ```
47
46
 
48
47
  ## See Also
49
48
 
50
49
  - Top-level CLI and library usage in the main [README](../../README.md)
51
50
  - Config type definitions in [types.py](types.py)
52
- - Config utilities in [utils.py](utils.py)
@@ -16,41 +16,18 @@ Notes
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- from .connector import Connector
20
- from .connector import ConnectorApi
21
- from .connector import ConnectorDb
22
- from .connector import ConnectorFile
23
- from .connector import parse_connector
24
- from .jobs import ExtractRef
25
- from .jobs import JobConfig
26
- from .jobs import LoadRef
27
- from .jobs import TransformRef
28
- from .jobs import ValidationRef
29
- from .pipeline import PipelineConfig
30
- from .pipeline import load_pipeline_config
31
- from .profile import ProfileConfig
32
- from .types import ConnectorType
19
+ from .types import ApiConfigMap
20
+ from .types import ApiProfileConfigMap
21
+ from .types import ApiProfileDefaultsMap
22
+ from .types import EndpointMap
33
23
 
34
24
  # SECTION: EXPORTS ========================================================== #
35
25
 
36
26
 
37
27
  __all__ = [
38
- # Connectors
39
- 'Connector',
40
- 'ConnectorType',
41
- 'ConnectorApi',
42
- 'ConnectorDb',
43
- 'ConnectorFile',
44
- 'parse_connector',
45
- # Jobs / Refs
46
- 'ExtractRef',
47
- 'JobConfig',
48
- 'LoadRef',
49
- 'TransformRef',
50
- 'ValidationRef',
51
- # Pipeline
52
- 'PipelineConfig',
53
- 'load_pipeline_config',
54
- # Profile
55
- 'ProfileConfig',
28
+ # Typed Dicts
29
+ 'ApiConfigMap',
30
+ 'ApiProfileConfigMap',
31
+ 'ApiProfileDefaultsMap',
32
+ 'EndpointMap',
56
33
  ]
etlplus/config/types.py CHANGED
@@ -33,7 +33,6 @@ from __future__ import annotations
33
33
 
34
34
  from collections.abc import Mapping
35
35
  from typing import Any
36
- from typing import Literal
37
36
  from typing import TypedDict
38
37
 
39
38
  from ..api import PaginationConfigMap
@@ -44,26 +43,17 @@ from ..types import StrAnyMap
44
43
 
45
44
 
46
45
  __all__ = [
47
- # Type aliases
48
- 'ConnectorType',
49
- # 'PaginationType',
50
46
  # TypedDicts
51
47
  'ApiProfileDefaultsMap',
52
48
  'ApiProfileConfigMap',
53
49
  'ApiConfigMap',
54
50
  'EndpointMap',
55
- 'ConnectorApiConfigMap',
56
- 'ConnectorDbConfigMap',
57
- 'ConnectorFileConfigMap',
58
51
  ]
59
52
 
60
53
 
61
54
  # SECTION: TYPE ALIASES ===================================================== #
62
55
 
63
56
 
64
- # Literal type for supported connector kinds
65
- type ConnectorType = Literal['api', 'database', 'file']
66
-
67
57
  # Literal type for supported pagination kinds
68
58
  # type PaginationType = Literal['page', 'offset', 'cursor']
69
59
 
@@ -129,60 +119,6 @@ class ApiProfileDefaultsMap(TypedDict, total=False):
129
119
  rate_limit: RateLimitConfigMap | StrAnyMap
130
120
 
131
121
 
132
- class ConnectorApiConfigMap(TypedDict, total=False):
133
- """
134
- Shape accepted by ConnectorApi.from_obj (all keys optional).
135
-
136
- See Also
137
- --------
138
- - etlplus.config.connector.ConnectorApi.from_obj
139
- """
140
-
141
- name: str
142
- type: ConnectorType
143
- url: str
144
- method: str
145
- headers: StrAnyMap
146
- query_params: StrAnyMap
147
- pagination: PaginationConfigMap
148
- rate_limit: RateLimitConfigMap
149
- api: str
150
- endpoint: str
151
-
152
-
153
- class ConnectorDbConfigMap(TypedDict, total=False):
154
- """
155
- Shape accepted by ConnectorDb.from_obj (all keys optional).
156
-
157
- See Also
158
- --------
159
- - etlplus.config.connector.ConnectorDb.from_obj
160
- """
161
-
162
- name: str
163
- type: ConnectorType
164
- connection_string: str
165
- query: str
166
- table: str
167
- mode: str
168
-
169
-
170
- class ConnectorFileConfigMap(TypedDict, total=False):
171
- """
172
- Shape accepted by ConnectorFile.from_obj (all keys optional).
173
-
174
- See Also
175
- --------
176
- - etlplus.config.connector.ConnectorFile.from_obj
177
- """
178
-
179
- name: str
180
- type: ConnectorType
181
- format: str
182
- path: str
183
- options: StrAnyMap
184
-
185
-
186
122
  class EndpointMap(TypedDict, total=False):
187
123
  """
188
124
  Shape accepted by EndpointConfig.from_obj.
@@ -1,4 +1,4 @@
1
- # etlplus.database subpackage
1
+ # `etlplus.database` Subpackage
2
2
 
3
3
  Documentation for the `etlplus.database` subpackage: database engine, schema, and ORM helpers.
4
4
 
@@ -9,7 +9,7 @@ Documentation for the `etlplus.database` subpackage: database engine, schema, an
9
9
 
10
10
  Back to project overview: see the top-level [README](../../README.md).
11
11
 
12
- - [etlplus.database subpackage](#etlplusdatabase-subpackage)
12
+ - [`etlplus.database` Subpackage](#etlplusdatabase-subpackage)
13
13
  - [Database Engine and Connections](#database-engine-and-connections)
14
14
  - [Schema and DDL Helpers](#schema-and-ddl-helpers)
15
15
  - [ORM Utilities](#orm-utilities)
etlplus/file/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # etlplus.file subpackage
1
+ # `etlplus.file` Subpackage
2
2
 
3
3
  Documentation for the `etlplus.file` subpackage: unified file format support and helpers for reading
4
4
  and writing data files.
@@ -11,7 +11,7 @@ and writing data files.
11
11
 
12
12
  Back to project overview: see the top-level [README](../../README.md).
13
13
 
14
- - [etlplus.file subpackage](#etlplusfile-subpackage)
14
+ - [`etlplus.file` Subpackage](#etlplusfile-subpackage)
15
15
  - [Supported File Formats](#supported-file-formats)
16
16
  - [Inferring File Format and Compression](#inferring-file-format-and-compression)
17
17
  - [Reading and Writing Files](#reading-and-writing-files)
etlplus/ops/run.py CHANGED
@@ -20,7 +20,6 @@ from ..api import RequestOptions
20
20
  from ..api import compose_api_request_env
21
21
  from ..api import compose_api_target_env
22
22
  from ..api import paginate_with_client
23
- from ..config import load_pipeline_config
24
23
  from ..enums import DataConnectorType
25
24
  from ..file import FileFormat
26
25
  from ..types import JSONData
@@ -29,6 +28,7 @@ from ..types import PipelineConfig
29
28
  from ..types import StrPath
30
29
  from ..types import Timeout
31
30
  from ..utils import print_json
31
+ from ..workflow import load_pipeline_config
32
32
  from .extract import extract
33
33
  from .load import load
34
34
  from .transform import transform
@@ -162,9 +162,12 @@ def run(
162
162
  # can monkeypatch this class on etlplus.ops.run.
163
163
  ClientClass = EndpointClient # noqa: N806
164
164
  client = ClientClass(
165
- base_url=cast(str, env['base_url']),
165
+ base_url=cast(str, env.get('base_url')),
166
166
  base_path=cast(str | None, env.get('base_path')),
167
- endpoints=cast(dict[str, str], env['endpoints_map']),
167
+ endpoints=cast(
168
+ dict[str, str],
169
+ env.get('endpoints_map', {}),
170
+ ),
168
171
  retry=env.get('retry'),
169
172
  retry_network_errors=bool(
170
173
  env.get('retry_network_errors', False),
@@ -173,7 +176,7 @@ def run(
173
176
  )
174
177
  data = paginate_with_client(
175
178
  client,
176
- cast(str, env['endpoint_key']),
179
+ cast(str, env.get('endpoint_key')),
177
180
  env.get('params'),
178
181
  env.get('headers'),
179
182
  env.get('timeout'),
@@ -276,12 +279,14 @@ def run(
276
279
  if not url_t:
277
280
  raise ValueError('API target missing "url"')
278
281
  kwargs_t: dict[str, Any] = {}
279
- if env_t.get('headers'):
280
- kwargs_t['headers'] = cast(dict[str, str], env_t['headers'])
282
+ headers = env_t.get('headers')
283
+ if headers:
284
+ kwargs_t['headers'] = cast(dict[str, str], headers)
281
285
  if env_t.get('timeout') is not None:
282
- kwargs_t['timeout'] = env_t['timeout']
283
- if env_t.get('session') is not None:
284
- kwargs_t['session'] = env_t['session']
286
+ kwargs_t['timeout'] = env_t.get('timeout')
287
+ session = env_t.get('session')
288
+ if session is not None:
289
+ kwargs_t['session'] = session
285
290
  result = load(
286
291
  data,
287
292
  'api',
@@ -1,4 +1,4 @@
1
- # etlplus.templates subpackage
1
+ # `etlplus.templates` Subpackage
2
2
 
3
3
  Documentation for the `etlplus.templates` subpackage: SQL and DDL template helpers.
4
4
 
@@ -8,7 +8,7 @@ Documentation for the `etlplus.templates` subpackage: SQL and DDL template helpe
8
8
 
9
9
  Back to project overview: see the top-level [README](../../README.md).
10
10
 
11
- - [etlplus.templates subpackage](#etlpustemplates-subpackage)
11
+ - [`etlplus.templates` Subpackage](#etlplus-templates-subpackage)
12
12
  - [Available Templates](#available-templates)
13
13
  - [Rendering Templates](#rendering-templates)
14
14
  - [Example: Rendering a DDL Template](#example-rendering-a-ddl-template)
@@ -0,0 +1,52 @@
1
+ # `etlplus.workflow` Subpackage
2
+
3
+ Documentation for the `etlplus.workflow` subpackage: configuration helpers for connectors,
4
+ pipelines, jobs, and profiles.
5
+
6
+ - Provides classes and utilities for managing ETL pipeline configuration
7
+ - Supports YAML/JSON config loading and validation
8
+ - Includes helpers for connectors, jobs, pipelines, and profiles
9
+ - Exposes type definitions for config schemas
10
+
11
+ Back to project overview: see the top-level [README](../../README.md).
12
+
13
+ - [`etlplus.workflow` Subpackage](#etlplusworkflow-subpackage)
14
+ - [Supported Configuration Types](#supported-configuration-types)
15
+ - [Loading and Validating Configs](#loading-and-validating-configs)
16
+ - [Example: Loading a Pipeline Config](#example-loading-a-pipeline-config)
17
+ - [See Also](#see-also)
18
+
19
+ ## Supported Configuration Types
20
+
21
+ - **Connector**: Connection details for databases, files, or APIs
22
+ - **Job**: ETL job definitions and scheduling
23
+ - **Pipeline**: End-to-end pipeline configuration
24
+ - **Profile**: User or environment-specific settings
25
+
26
+ ## Loading and Validating Configs
27
+
28
+ Use the provided classes to load and validate configuration files:
29
+
30
+ ```python
31
+ from etlplus.workflow import PipelineConfig
32
+
33
+ cfg = PipelineConfig.from_yaml("pipeline.yml")
34
+ ```
35
+
36
+ - Supports YAML and JSON formats
37
+ - Validates against expected schema
38
+
39
+ ## Example: Loading a Pipeline Config
40
+
41
+ ```python
42
+ from etlplus.workflow import PipelineConfig
43
+
44
+ pipeline = PipelineConfig.from_yaml("configs/pipeline.yml")
45
+ print(pipeline)
46
+ ```
47
+
48
+ ## See Also
49
+
50
+ - Top-level CLI and library usage in the main [README](../../README.md)
51
+ - Config type definitions in [types.py](types.py)
52
+ - Config utilities in [utils.py](utils.py)
@@ -0,0 +1,43 @@
1
+ """
2
+ :mod:`etlplus.workflow` package.
3
+
4
+ Job workflow helpers.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from .connector import Connector
10
+ from .connector import ConnectorApi
11
+ from .connector import ConnectorDb
12
+ from .connector import ConnectorFile
13
+ from .connector import parse_connector
14
+ from .dag import topological_sort_jobs
15
+ from .jobs import ExtractRef
16
+ from .jobs import JobConfig
17
+ from .jobs import LoadRef
18
+ from .jobs import TransformRef
19
+ from .jobs import ValidationRef
20
+ from .pipeline import PipelineConfig
21
+ from .pipeline import load_pipeline_config
22
+
23
+ # SECTION: EXPORTS ========================================================== #
24
+
25
+
26
+ __all__ = [
27
+ # Data Classes
28
+ 'ConnectorApi',
29
+ 'ConnectorDb',
30
+ 'ConnectorFile',
31
+ 'ExtractRef',
32
+ 'JobConfig',
33
+ 'LoadRef',
34
+ 'PipelineConfig',
35
+ 'TransformRef',
36
+ 'ValidationRef',
37
+ # Functions
38
+ 'load_pipeline_config',
39
+ 'parse_connector',
40
+ 'topological_sort_jobs',
41
+ # Type Aliases
42
+ 'Connector',
43
+ ]
@@ -1,5 +1,5 @@
1
1
  """
2
- :mod:`etlplus.config.connector` module.
2
+ :mod:`etlplus.workflow.connector` module.
3
3
 
4
4
  A module defining configuration types for data source/target connectors in ETL
5
5
  pipelines. A "connector" is any I/O endpoint:
@@ -11,18 +11,19 @@ pipelines. A "connector" is any I/O endpoint:
11
11
 
12
12
  Examples
13
13
  --------
14
- - Use ``ConnectorApi``/``ConnectorFile``/``ConnectorDb`` when you want the
15
- concrete dataclasses.
16
- - Use the ``Connector`` union for typing a value that can be any connector.
17
- - Use ``parse_connector(obj)`` to construct a connector instance from a generic
18
- mapping that includes a ``type`` key.
14
+ - Use :class:`ConnectorApi`/:class:`ConnectorFile`/:class:`ConnectorDb` when
15
+ you want the concrete dataclasses.
16
+ - Use the :class:`Connector` union for typing a value that can be any
17
+ connector.
18
+ - Use :func:`parse_connector(obj)` to construct a connector instance from a
19
+ generic mapping that includes a *type* key.
19
20
 
20
21
  Notes
21
22
  -----
22
23
  - TypedDict shapes are editor hints; runtime parsing remains permissive
23
- (from_obj accepts Mapping[str, Any]).
24
+ (from_obj accepts Mapping[str, Any]).
24
25
  - TypedDicts referenced in :mod:`etlplus.config.types` remain editor hints.
25
- Runtime parsing stays permissive and tolerant.
26
+ Runtime parsing stays permissive and tolerant.
26
27
 
27
28
  See Also
28
29
  --------
@@ -59,7 +60,7 @@ if TYPE_CHECKING: # Editor-only typing hints to avoid runtime imports
59
60
 
60
61
 
61
62
  __all__ = [
62
- # Classes
63
+ # Data Classes
63
64
  'ConnectorApi',
64
65
  'ConnectorDb',
65
66
  'ConnectorFile',
@@ -83,12 +84,12 @@ class ConnectorApi:
83
84
  name : str
84
85
  Unique connector name.
85
86
  type : ConnectorType
86
- Connector kind literal, always ``"api"``.
87
+ Connector kind literal, always ``'api'``.
87
88
  url : str | None
88
89
  Direct absolute URL (when not using ``service``/``endpoint`` refs).
89
90
  method : str | None
90
91
  Optional HTTP method; typically omitted for sources (defaults to
91
- GET) and used for targets (e.g., ``"post"``).
92
+ GET) and used for targets (e.g., ``'post'``).
92
93
  headers : dict[str, str]
93
94
  Additional request headers.
94
95
  query_params : dict[str, Any]
@@ -111,7 +112,7 @@ class ConnectorApi:
111
112
 
112
113
  # Direct form
113
114
  url: str | None = None
114
- # Optional HTTP method; typically omitted for sources (defaults to GET
115
+ # Optional HTTP method; typically omitted for sources (defaults to GET)
115
116
  # at runtime) and used for targets (e.g., 'post', 'put').
116
117
  method: str | None = None
117
118
  headers: dict[str, str] = field(default_factory=dict)
@@ -185,7 +186,7 @@ class ConnectorDb:
185
186
  name : str
186
187
  Unique connector name.
187
188
  type : ConnectorType
188
- Connector kind literal, always ``"database"``.
189
+ Connector kind literal, always ``'database'``.
189
190
  connection_string : str | None
190
191
  Connection string/DSN for the database.
191
192
  query : str | None
@@ -193,7 +194,7 @@ class ConnectorDb:
193
194
  table : str | None
194
195
  Target/source table name (optional).
195
196
  mode : str | None
196
- Load mode hint (e.g., ``"append"``, ``"replace"``) — future use.
197
+ Load mode hint (e.g., ``'append'``, ``'replace'``) — future use.
197
198
  """
198
199
 
199
200
  # -- Attributes -- #
@@ -262,9 +263,9 @@ class ConnectorFile:
262
263
  name : str
263
264
  Unique connector name.
264
265
  type : ConnectorType
265
- Connector kind literal, always ``"file"``.
266
+ Connector kind literal, always ``'file'``.
266
267
  format : str | None
267
- File format (e.g., ``"json"``, ``"csv"``).
268
+ File format (e.g., ``'json'``, ``'csv'``).
268
269
  path : str | None
269
270
  File path or URI.
270
271
  options : dict[str, Any]
@@ -0,0 +1,105 @@
1
+ """
2
+ :mod:`etlplus.workflow.dag` module.
3
+
4
+ Lightweight directed acyclic graph (DAG) helpers for ordering jobs based on
5
+ :attr:`depends_on`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections import deque
11
+ from dataclasses import dataclass
12
+
13
+ from .jobs import JobConfig
14
+
15
+ # SECTION: EXPORTS ========================================================== #
16
+
17
+
18
+ __all__ = [
19
+ # Errors
20
+ 'DagError',
21
+ # Functions
22
+ 'topological_sort_jobs',
23
+ ]
24
+
25
+
26
+ # SECTION: ERRORS =========================================================== #
27
+
28
+
29
+ @dataclass(slots=True)
30
+ class DagError(ValueError):
31
+ """
32
+ Raised when the job dependency graph is invalid.
33
+
34
+ Attributes
35
+ ----------
36
+ message : str
37
+ Error message.
38
+ """
39
+
40
+ # -- Attributes -- #
41
+
42
+ message: str
43
+
44
+ # -- Magic Methods (Object Representation) -- #
45
+
46
+ def __str__(self) -> str:
47
+ return self.message
48
+
49
+
50
+ # SECTION: FUNCTIONS ======================================================== #
51
+
52
+
53
+ def topological_sort_jobs(
54
+ jobs: list[JobConfig],
55
+ ) -> list[JobConfig]:
56
+ """
57
+ Return jobs in topological order based on :attr:`depends_on`.
58
+
59
+ Parameters
60
+ ----------
61
+ jobs : list[JobConfig]
62
+ List of job configurations to sort.
63
+
64
+ Returns
65
+ -------
66
+ list[JobConfig]
67
+ Jobs sorted in topological order.
68
+
69
+ Raises
70
+ ------
71
+ DagError
72
+ If a dependency is missing, self-referential, or when a cycle is
73
+ detected.
74
+ """
75
+ index = {job.name: job for job in jobs}
76
+ edges: dict[str, set[str]] = {name: set() for name in index}
77
+ indegree: dict[str, int] = {name: 0 for name in index}
78
+
79
+ for job in jobs:
80
+ for dep in job.depends_on:
81
+ if dep not in index:
82
+ raise DagError(
83
+ f'Unknown dependency "{dep}" in job "{job.name}"',
84
+ )
85
+ if dep == job.name:
86
+ raise DagError(f'Job "{job.name}" depends on itself')
87
+ if job.name not in edges[dep]:
88
+ edges[dep].add(job.name)
89
+ indegree[job.name] += 1
90
+
91
+ queue = deque(sorted(name for name, deg in indegree.items() if deg == 0))
92
+ ordered: list[str] = []
93
+
94
+ while queue:
95
+ name = queue.popleft()
96
+ ordered.append(name)
97
+ for child in sorted(edges[name]):
98
+ indegree[child] -= 1
99
+ if indegree[child] == 0:
100
+ queue.append(child)
101
+
102
+ if len(ordered) != len(jobs):
103
+ raise DagError('Dependency cycle detected')
104
+
105
+ return [index[name] for name in ordered]
@@ -1,12 +1,12 @@
1
1
  """
2
- :mod:`etlplus.config.jobs` module.
2
+ :mod:`etlplus.workflow.jobs` module.
3
3
 
4
4
  Data classes modeling job orchestration references (extract, validate,
5
5
  transform, load).
6
6
 
7
7
  Notes
8
8
  -----
9
- - Lightweight references used inside ``PipelineConfig`` to avoid storing
9
+ - Lightweight references used inside :class:`PipelineConfig` to avoid storing
10
10
  large nested structures.
11
11
  - All attributes are simple and optional where appropriate, keeping parsing
12
12
  tolerant.
@@ -26,6 +26,7 @@ from ..utils import maybe_mapping
26
26
 
27
27
 
28
28
  __all__ = [
29
+ # Data Classes
29
30
  'ExtractRef',
30
31
  'JobConfig',
31
32
  'LoadRef',
@@ -62,12 +63,13 @@ class ExtractRef:
62
63
  cls,
63
64
  obj: Any,
64
65
  ) -> Self | None:
65
- """Parse a mapping into an :class:`ExtractRef` instance.
66
+ """
67
+ Parse a mapping into an :class:`ExtractRef` instance.
66
68
 
67
69
  Parameters
68
70
  ----------
69
71
  obj : Any
70
- Mapping with ``source`` and optional ``options``.
72
+ Mapping with :attr:`source` and optional :attr:`options`.
71
73
 
72
74
  Returns
73
75
  -------
@@ -126,7 +128,8 @@ class JobConfig:
126
128
  cls,
127
129
  obj: Any,
128
130
  ) -> Self | None:
129
- """Parse a mapping into a :class:`JobConfig` instance.
131
+ """
132
+ Parse a mapping into a :class:`JobConfig` instance.
130
133
 
131
134
  Parameters
132
135
  ----------
@@ -194,12 +197,13 @@ class LoadRef:
194
197
  cls,
195
198
  obj: Any,
196
199
  ) -> Self | None:
197
- """Parse a mapping into a :class:`LoadRef` instance.
200
+ """
201
+ Parse a mapping into a :class:`LoadRef` instance.
198
202
 
199
203
  Parameters
200
204
  ----------
201
205
  obj : Any
202
- Mapping with ``target`` and optional ``overrides``.
206
+ Mapping with :attr:`target` and optional :attr:`overrides`.
203
207
 
204
208
  Returns
205
209
  -------
@@ -240,12 +244,13 @@ class TransformRef:
240
244
  cls,
241
245
  obj: Any,
242
246
  ) -> Self | None:
243
- """Parse a mapping into a :class:`TransformRef` instance.
247
+ """
248
+ Parse a mapping into a :class:`TransformRef` instance.
244
249
 
245
250
  Parameters
246
251
  ----------
247
252
  obj : Any
248
- Mapping with ``pipeline``.
253
+ Mapping with :attr:`pipeline`.
249
254
 
250
255
  Returns
251
256
  -------
@@ -290,12 +295,13 @@ class ValidationRef:
290
295
  cls,
291
296
  obj: Any,
292
297
  ) -> Self | None:
293
- """Parse a mapping into a :class:`ValidationRef` instance.
298
+ """
299
+ Parse a mapping into a :class:`ValidationRef` instance.
294
300
 
295
301
  Parameters
296
302
  ----------
297
303
  obj : Any
298
- Mapping with ``ruleset`` plus optional metadata.
304
+ Mapping with :attr:`ruleset` plus optional metadata.
299
305
 
300
306
  Returns
301
307
  -------
@@ -1,5 +1,5 @@
1
1
  """
2
- :mod:`etlplus.config.pipeline` module.
2
+ :mod:`etlplus.workflow.pipeline` module.
3
3
 
4
4
  Pipeline configuration model and helpers for job orchestration.
5
5
 
@@ -38,7 +38,15 @@ from .utils import deep_substitute
38
38
  # SECTION: EXPORTS ========================================================== #
39
39
 
40
40
 
41
- __all__ = ['PipelineConfig', 'load_pipeline_config']
41
+ __all__ = [
42
+ # Data Classes
43
+ 'PipelineConfig',
44
+ # Functions
45
+ 'load_pipeline_config',
46
+ ]
47
+
48
+
49
+ # SECTION: INTERNAL FUNCTIONS =============================================== #
42
50
 
43
51
 
44
52
  def _build_jobs(
@@ -156,7 +164,7 @@ def load_pipeline_config(
156
164
  return PipelineConfig.from_yaml(path, substitute=substitute, env=env)
157
165
 
158
166
 
159
- # SECTION: CLASSES ========================================================== #
167
+ # SECTION: DATA CLASSES ===================================================== #
160
168
 
161
169
 
162
170
  @dataclass(kw_only=True, slots=True)
@@ -1,5 +1,5 @@
1
1
  """
2
- :mod:`etlplus.config.profile` module.
2
+ :mod:`etlplus.workflow.profile` module.
3
3
 
4
4
  Profile model for pipeline-level defaults and environment.
5
5
 
@@ -22,10 +22,13 @@ from ..utils import cast_str_dict
22
22
  # SECTION: EXPORTS ========================================================== #
23
23
 
24
24
 
25
- __all__ = ['ProfileConfig']
25
+ __all__ = [
26
+ # Data Classes
27
+ 'ProfileConfig',
28
+ ]
26
29
 
27
30
 
28
- # SECTION: CLASSES ========================================================== #
31
+ # SECTION: DATA CLASSES ===================================================== #
29
32
 
30
33
 
31
34
  @dataclass(kw_only=True, slots=True)
@@ -53,7 +56,7 @@ class ProfileConfig:
53
56
  cls,
54
57
  obj: StrAnyMap | None,
55
58
  ) -> Self:
56
- """Parse a mapping into a ``ProfileConfig`` instance.
59
+ """Parse a mapping into a :class:`ProfileConfig` instance.
57
60
 
58
61
  Parameters
59
62
  ----------
@@ -64,7 +67,7 @@ class ProfileConfig:
64
67
  -------
65
68
  Self
66
69
  Parsed profile configuration; non-mapping input yields a default
67
- instance. All ``env`` values are coerced to strings.
70
+ instance. All :attr:`env` values are coerced to strings.
68
71
  """
69
72
  if not isinstance(obj, Mapping):
70
73
  return cls()
@@ -0,0 +1,115 @@
1
+ """
2
+ :mod:`etlplus.workflow.types` module.
3
+
4
+ Type aliases and editor-only :class:`TypedDict`s for :mod:`etlplus.config`.
5
+
6
+ These types improve IDE autocomplete and static analysis while the runtime
7
+ parsers remain permissive.
8
+
9
+ Notes
10
+ -----
11
+ - TypedDicts in this module are intentionally ``total=False`` and are not
12
+ enforced at runtime.
13
+ - :meth:`*.from_obj` constructors accept :class:`Mapping[str, Any]` and perform
14
+ tolerant parsing and light casting. This keeps the runtime permissive while
15
+ improving autocomplete and static analysis for contributors.
16
+
17
+ Examples
18
+ --------
19
+ >>> from etlplus.workflow import Connector
20
+ >>> src: Connector = {
21
+ >>> "type": "file",
22
+ >>> "path": "/data/input.csv",
23
+ >>> }
24
+ >>> tgt: Connector = {
25
+ >>> "type": "database",
26
+ >>> "connection_string": "postgresql://user:pass@localhost/db",
27
+ >>> }
28
+ >>> from etlplus.api import RetryPolicy
29
+ >>> rp: RetryPolicy = {"max_attempts": 3, "backoff": 0.5}
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ from typing import Literal
35
+ from typing import TypedDict
36
+
37
+ from ..api import PaginationConfigMap
38
+ from ..api import RateLimitConfigMap
39
+ from ..types import StrAnyMap
40
+
41
+ # SECTION: EXPORTS ========================================================= #
42
+
43
+
44
+ __all__ = [
45
+ # Type Aliases
46
+ 'ConnectorType',
47
+ # Typed Dicts
48
+ 'ConnectorApiConfigMap',
49
+ 'ConnectorDbConfigMap',
50
+ 'ConnectorFileConfigMap',
51
+ ]
52
+
53
+
54
+ # SECTION: TYPE ALIASES ===================================================== #
55
+
56
+
57
+ # Literal type for supported connector kinds
58
+ type ConnectorType = Literal['api', 'database', 'file']
59
+
60
+
61
+ # SECTION: TYPED DICTS ====================================================== #
62
+
63
+
64
+ class ConnectorApiConfigMap(TypedDict, total=False):
65
+ """
66
+ Shape accepted by :meth:`ConnectorApi.from_obj` (all keys optional).
67
+
68
+ See Also
69
+ --------
70
+ - :meth:`etlplus.workflow.connector.ConnectorApi.from_obj`
71
+ """
72
+
73
+ name: str
74
+ type: ConnectorType
75
+ url: str
76
+ method: str
77
+ headers: StrAnyMap
78
+ query_params: StrAnyMap
79
+ pagination: PaginationConfigMap
80
+ rate_limit: RateLimitConfigMap
81
+ api: str
82
+ endpoint: str
83
+
84
+
85
+ class ConnectorDbConfigMap(TypedDict, total=False):
86
+ """
87
+ Shape accepted by :meth:`ConnectorDb.from_obj` (all keys optional).
88
+
89
+ See Also
90
+ --------
91
+ - :meth:`etlplus.workflow.connector.ConnectorDb.from_obj`
92
+ """
93
+
94
+ name: str
95
+ type: ConnectorType
96
+ connection_string: str
97
+ query: str
98
+ table: str
99
+ mode: str
100
+
101
+
102
+ class ConnectorFileConfigMap(TypedDict, total=False):
103
+ """
104
+ Shape accepted by :meth:`ConnectorFile.from_obj` (all keys optional).
105
+
106
+ See Also
107
+ --------
108
+ - :meth:`etlplus.workflow.connector.ConnectorFile.from_obj`
109
+ """
110
+
111
+ name: str
112
+ type: ConnectorType
113
+ format: str
114
+ path: str
115
+ options: StrAnyMap
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlplus
3
- Version: 0.14.3
3
+ Version: 0.15.0
4
4
  Summary: A Swiss Army knife for simple ETL operations
5
5
  Home-page: https://github.com/Dagitali/ETLPlus
6
6
  Author: ETLPlus Team
@@ -1,4 +1,4 @@
1
- etlplus/README.md,sha256=HwDt6eh6p4422FOJhrpUBhfPGC82QzpsJNecs3zEtUU,1459
1
+ etlplus/README.md,sha256=NwnsMajz2_afAXgYDnTIwVU6uBoOKQn8f6UJHUY-Xe8,1461
2
2
  etlplus/__init__.py,sha256=mgTP4PJmRmsEjTCAizzzdtzAmhuHtarmPzphzdjvLgM,277
3
3
  etlplus/__main__.py,sha256=btoROneNiigyfBU7BSzPKZ1R9gzBMpxcpsbPwmuHwTM,479
4
4
  etlplus/__version__.py,sha256=1E0GMK_yUWCMQFKxXjTvyMwofi0qT2k4CDNiHWiymWE,327
@@ -8,7 +8,7 @@ etlplus/mixins.py,sha256=ifGpHwWv7U00yqGf-kN93vJax2IiK4jaGtTsPsO3Oak,1350
8
8
  etlplus/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  etlplus/types.py,sha256=LhyKU67CW7CcLEshE_1OIvEqWr-QVMJVYByf1221miY,6161
10
10
  etlplus/utils.py,sha256=BMLTWAvCJj3zLEcffBgURYnu0UGhhXsfH2WWpAt7fV8,13363
11
- etlplus/api/README.md,sha256=aGTbcL-EqaiMTS7GToibmeTIzjX-viP2OtUDfIJnZEo,7913
11
+ etlplus/api/README.md,sha256=Glxz9Y5RagqJomCPc8q3Nagm-23Owj9aAd8fM0H-Ph8,7917
12
12
  etlplus/api/__init__.py,sha256=PK2lQv1FbsE7ZZS_ejevFZQSuOUHGApBc22YfHAzMqA,4615
13
13
  etlplus/api/auth.py,sha256=GOO5on-LoMS1GXTAhtK9rFcfpjbBcNeA6NE5UZwIq0g,12158
14
14
  etlplus/api/config.py,sha256=Xi1_FUaogQxAAx7LUTGwYr5vEB_SIFvjU_xgMGF2Fsc,17913
@@ -19,7 +19,7 @@ etlplus/api/request_manager.py,sha256=fhzPV5x7DqpKqoLvfDR8GKhBX_QBMtvZsRXxVnQQEl
19
19
  etlplus/api/retry_manager.py,sha256=0GDhJVyIlb1Ww35JUWlYoa8QYUPjKLBtxQeZj3TdLbY,11306
20
20
  etlplus/api/transport.py,sha256=DVWzWFZLfQQXaTJ60w-SzdXRxiOE8llPprpUB5CtSyg,9410
21
21
  etlplus/api/types.py,sha256=ijHgREN5akEFF3th2yNdWbhJhXvAxh_24qQgg_sMB4w,4653
22
- etlplus/api/utils.py,sha256=I-19UPmBuOTLJs3fuCkCPbYUA4B3bXHmnRWSACP3n28,26134
22
+ etlplus/api/utils.py,sha256=U39OXcrPiLD9m0Y03pACXIIOyuuDPO6RRKuTcwEayBE,26249
23
23
  etlplus/api/pagination/__init__.py,sha256=a4UX2J0AG8RMvmHt_CCofUm5vSmFo6GAfkb8XnSXypM,1395
24
24
  etlplus/api/pagination/client.py,sha256=yMEpWqRxTCD4zRc9OYtEyUtShpGH5atiHFEAt95v2FE,5394
25
25
  etlplus/api/pagination/config.py,sha256=3dXDJ-nMbO9Zk6i344n4roBFbUlHsa294D1_plPmm6E,13579
@@ -27,32 +27,27 @@ etlplus/api/pagination/paginator.py,sha256=wtdY_er4yfjx5yTUQJ1gPq-IuWmpLAHeG5buB
27
27
  etlplus/api/rate_limiting/__init__.py,sha256=ZySB1dZettEDnWvI1EHf_TZ9L08M_kKsNR-Y_lbU6kI,1070
28
28
  etlplus/api/rate_limiting/config.py,sha256=eB-dSOG-c5awYeEpnDsQJ5pO8BJtEpGYxOunBetWiB0,9775
29
29
  etlplus/api/rate_limiting/rate_limiter.py,sha256=heAasm1KLeBFmoJKPz6W-eQbNuahMgG1E9-z8zGEUro,7035
30
- etlplus/cli/README.md,sha256=rl9VYNH5MluV0rh-eP7TbxJZ5BTMEIaksxhl_JXpYio,1233
30
+ etlplus/cli/README.md,sha256=8H_G2d3HteYIU6ReX9K9DM485QjWDT5vHMQbGD_vv20,1237
31
31
  etlplus/cli/__init__.py,sha256=J97-Rv931IL1_b4AXnB7Fbbd7HKnHBpx18NQfC_kE6c,299
32
32
  etlplus/cli/commands.py,sha256=g8_m3A8HEMyTRu2HctNiRoi2gtB5oSZCUEcyq-PIXos,24669
33
33
  etlplus/cli/constants.py,sha256=E6Uy4WauLa_0zkzxqImXh-bb1gKdb9sBZQVc8QOzr2Q,1943
34
- etlplus/cli/handlers.py,sha256=gbW1jH348QmX65g4gffxrtMt2obFIw8X0dEKnwXQKPQ,18170
34
+ etlplus/cli/handlers.py,sha256=mILI386pf2RzfI7WJfXJ13huIGwsqFfcYx4sNIf1aWU,18174
35
35
  etlplus/cli/io.py,sha256=EFaBPYaBOyOllfMQWXgTjy-MPiGfNejicpD7ROrPyAE,7840
36
36
  etlplus/cli/main.py,sha256=IgeqxypixfwLHR-QcpgVMQ7vMZ865bXOh2oO9v-BWeM,5234
37
37
  etlplus/cli/options.py,sha256=vfXT3YLh7wG1iC-aTdSg6ItMC8l6n0Lozmy53XjqLbA,1199
38
38
  etlplus/cli/state.py,sha256=Pfd8ru0wYIN7eGp1_A0tioqs1LiCDZCuJ6AnjZb6yYQ,8027
39
39
  etlplus/cli/types.py,sha256=tclhKVJXDqHzlTQBYKARfqMgDOcuBJ-Zej2pvFy96WM,652
40
- etlplus/config/README.md,sha256=ot6oFZxTz4x83mj1_FrQ13dO0z2QkRFDnkCkx7NPsSs,1636
41
- etlplus/config/__init__.py,sha256=VZWzOg7d2YR9NT6UwKTv44yf2FRUMjTHynkm1Dl5Qzo,1486
42
- etlplus/config/connector.py,sha256=0-TIwevHbKRHVmucvyGpPd-3tB1dKHB-dj0yJ6kq5eY,9809
43
- etlplus/config/jobs.py,sha256=oa2rNwacy2b_1HP_iFDLarGnn812ZVP2k5cHt4eqBIg,7843
44
- etlplus/config/pipeline.py,sha256=m4Jh0ctFcKrIx6zR7LEC0sYY5wq0o8NqOruWPlz6qmA,9494
45
- etlplus/config/profile.py,sha256=Ss2zedQGjkaGSpvBLTD4SZaWViMJ7TJPLB8Q2_BTpPg,1898
46
- etlplus/config/types.py,sha256=a0epJ3z16HQ5bY3Ctf8s_cQPa3f0HHcwdOcjCP2xoG4,4954
47
- etlplus/config/utils.py,sha256=4SUHMkt5bKBhMhiJm-DrnmE2Q4TfOgdNCKz8PJDS27o,3443
48
- etlplus/database/README.md,sha256=nUQVwLwGzmpA5_u4oYJYkPIhIuJAIdWsoeRgr4joAIE,1431
40
+ etlplus/config/README.md,sha256=CBeUW6IBfS8_U1iW4LzMQiAshcaTr2hgMDW3bos3BUY,1467
41
+ etlplus/config/__init__.py,sha256=rjUC-OUH_dXqG11B5r0hlJuss1J_nSinkvoygfuqId8,921
42
+ etlplus/config/types.py,sha256=3EyibjzqjK3GQu0fsvbmkfHERDtubEmtOU_lXsgXQuQ,3603
43
+ etlplus/database/README.md,sha256=3Af5BEGLkBmMoGOLtS1GQuj4wKPh_CwSp5NEPMf2uaY,1435
49
44
  etlplus/database/__init__.py,sha256=AKJsDl2RHuRGPS-eXgNJeh4aSncJP5Y0yLApBF6i7i8,1052
50
45
  etlplus/database/ddl.py,sha256=0dEM9SJMMabkhI_h-Fc0j9a1Sl5lSyZdI0bIeBVGm10,7913
51
46
  etlplus/database/engine.py,sha256=PUxXLvLPyc-KaxuW7fXe12wYci7EvUp-Ad1H3bGhUog,4058
52
47
  etlplus/database/orm.py,sha256=gCSqH-CjQz6tV9133-VqgiwokK5ylun0BwXaIWfImAo,10008
53
48
  etlplus/database/schema.py,sha256=813C0Dd3WE53KTYot4dgjAxctgKXLXx-8_Rk_4r2e28,7022
54
49
  etlplus/database/types.py,sha256=_pkQyC14TzAlgyeIqZG4F5LWYknZbHw3TW68Auk7Ya0,795
55
- etlplus/file/README.md,sha256=avWnyeKfs3uP3qa-DVBJ6t05jS2oFUPeQ3xf1Ph0eC0,3626
50
+ etlplus/file/README.md,sha256=ivU8svVs1fktQiW5ozvh1N-IOSLCAQ3oM9bW8DUFwIw,3630
56
51
  etlplus/file/__init__.py,sha256=X03bosSM-uSd6dh3ur0un6_ozFRw2Tm4PE6kVUjtXK8,475
57
52
  etlplus/file/_imports.py,sha256=9e8CWjyNIRcmiULEPuwtnJELUOXd4EvVv_vDnDYiB9c,3121
58
53
  etlplus/file/_io.py,sha256=GXTcvjfXQX4rSdyu1JNhFmqQJlirDqd8dEGCN3dHvNg,2968
@@ -119,17 +114,26 @@ etlplus/ops/README.md,sha256=8omi7DYZhelc26JKk8Cm8QR8I3OGwziysPj1ivx41iQ,1380
119
114
  etlplus/ops/__init__.py,sha256=NIIr2f-AZj5B0piBt6gjv46Yn0SzGYxEe6BPoopRh38,1702
120
115
  etlplus/ops/extract.py,sha256=OJozX25qTjME7m9aTdVPJScT3GHHgopGM8uHo_rHm1Y,5783
121
116
  etlplus/ops/load.py,sha256=RgEKnVygRN2cPDpzihU8UsIhd4eVoj0cPe0jBuNC0u4,8328
122
- etlplus/ops/run.py,sha256=4ACM-bHoiBDbUdF9DnEJYFsIvKjZobW0nXTbVHCg4Xo,13393
117
+ etlplus/ops/run.py,sha256=JOPQxrciRUQ67FJhUcZ-pW1aiYGZUdxHLwhzWLNCaDo,13528
123
118
  etlplus/ops/transform.py,sha256=1P43WYUaw872JDU86FhbbbkP8xnBWpgEPn2Q-z4ywls,25421
124
119
  etlplus/ops/utils.py,sha256=tbV-s3NjIGO-GfdyJFxPYIYihPibNo-4TbdRq9vWVWs,11480
125
120
  etlplus/ops/validate.py,sha256=gLa5zcwhxGbaIhH-OqTwDsQAgAYZSajLVGwwyeT2OZk,13257
126
- etlplus/templates/README.md,sha256=kHSZ8FWcrlrcWz0hBIbho-k1Bi-PL-DQ7g02o-g70c8,1355
121
+ etlplus/templates/README.md,sha256=IfPXlj1TGVA-uFWosHJhE2rabFW-znxOlOMazO9Z5cE,1361
127
122
  etlplus/templates/__init__.py,sha256=tsniN7XJYs3NwYxJ6c2HD5upHP3CDkLx-bQCMt97UOM,106
128
123
  etlplus/templates/ddl.sql.j2,sha256=s8fMWvcb4eaJVXkifuib1aQPljtZ8buuyB_uA-ZdU3Q,4734
129
124
  etlplus/templates/view.sql.j2,sha256=Iy8DHfhq5yyvrUKDxqp_aHIEXY4Tm6j4wT7YDEFWAhk,2180
130
- etlplus-0.14.3.dist-info/licenses/LICENSE,sha256=MuNO63i6kWmgnV2pbP2SLqP54mk1BGmu7CmbtxMmT-U,1069
131
- etlplus-0.14.3.dist-info/METADATA,sha256=0I15VHV30nTVM3CMhDZZEImeC6LV_9jtF9Fnbi3rSWg,28115
132
- etlplus-0.14.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
133
- etlplus-0.14.3.dist-info/entry_points.txt,sha256=6w-2-jzuPa55spzK34h-UKh2JTEShh38adFRONNP9QE,45
134
- etlplus-0.14.3.dist-info/top_level.txt,sha256=aWWF-udn_sLGuHTM6W6MLh99ArS9ROkUWO8Mi8y1_2U,8
135
- etlplus-0.14.3.dist-info/RECORD,,
125
+ etlplus/workflow/README.md,sha256=D1oloiJCOHiqpqgv3m3qpRSIUOMIQcWtIsOPv7KkNI0,1652
126
+ etlplus/workflow/__init__.py,sha256=LxI9VGlDBUc9ADoK8JNn3oVsGeaz1Uhjonn4Y5KIJdM,967
127
+ etlplus/workflow/connector.py,sha256=9b4KF2uNKaJtsQadwT7Id8OujfJM8GLJHqI-n4kFsGA,9851
128
+ etlplus/workflow/dag.py,sha256=kp31dORgk0GHbct_bipU5hu_0elwBtwLsXGjMWuhFHI,2503
129
+ etlplus/workflow/jobs.py,sha256=yFNj4S7ORMtpYSA0nCAUgb4E5msW7dyvtQVPBb-CAK8,7938
130
+ etlplus/workflow/pipeline.py,sha256=DL6XsX47gGPiURH8Qj1Ixm0RctI3taqo3e6Asg8fpto,9624
131
+ etlplus/workflow/profile.py,sha256=dZ6P50k_ZqXnrbgrbODUqgVkymbchcEqfZR-ExjTd3M,1935
132
+ etlplus/workflow/types.py,sha256=kVdOMT8qwnWlD7C4qWv-uF7r6-Kk-RlMZmoY8tnIo7A,2769
133
+ etlplus/workflow/utils.py,sha256=4SUHMkt5bKBhMhiJm-DrnmE2Q4TfOgdNCKz8PJDS27o,3443
134
+ etlplus-0.15.0.dist-info/licenses/LICENSE,sha256=MuNO63i6kWmgnV2pbP2SLqP54mk1BGmu7CmbtxMmT-U,1069
135
+ etlplus-0.15.0.dist-info/METADATA,sha256=YdRPU6L9SNAYpfB6fypG3-KewetI_QoyDlE2WnS2wPQ,28115
136
+ etlplus-0.15.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
137
+ etlplus-0.15.0.dist-info/entry_points.txt,sha256=6w-2-jzuPa55spzK34h-UKh2JTEShh38adFRONNP9QE,45
138
+ etlplus-0.15.0.dist-info/top_level.txt,sha256=aWWF-udn_sLGuHTM6W6MLh99ArS9ROkUWO8Mi8y1_2U,8
139
+ etlplus-0.15.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
File without changes