etlplus 0.3.25__py3-none-any.whl → 0.4.1__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/__main__.py +1 -2
- etlplus/cli/__init__.py +15 -0
- etlplus/cli/app.py +1000 -0
- etlplus/cli/handlers.py +686 -0
- etlplus/cli/main.py +404 -0
- {etlplus-0.3.25.dist-info → etlplus-0.4.1.dist-info}/METADATA +2 -1
- {etlplus-0.3.25.dist-info → etlplus-0.4.1.dist-info}/RECORD +11 -8
- etlplus/cli.py +0 -868
- {etlplus-0.3.25.dist-info → etlplus-0.4.1.dist-info}/WHEEL +0 -0
- {etlplus-0.3.25.dist-info → etlplus-0.4.1.dist-info}/entry_points.txt +0 -0
- {etlplus-0.3.25.dist-info → etlplus-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.3.25.dist-info → etlplus-0.4.1.dist-info}/top_level.txt +0 -0
etlplus/cli/main.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""
|
|
2
|
+
:mod:`etlplus.cli.main` module.
|
|
3
|
+
|
|
4
|
+
Entry point helpers for the Typer-powered ``etlplus`` CLI.
|
|
5
|
+
|
|
6
|
+
This module exposes :func:`main` for the console script as well as
|
|
7
|
+
:func:`create_parser` for callers that still need an ``argparse`` parser.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import sys
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
from typing import Literal
|
|
16
|
+
|
|
17
|
+
import typer
|
|
18
|
+
|
|
19
|
+
from .. import __version__
|
|
20
|
+
from ..enums import DataConnectorType
|
|
21
|
+
from ..enums import FileFormat
|
|
22
|
+
from ..utils import json_type
|
|
23
|
+
from .app import PROJECT_URL
|
|
24
|
+
from .app import app
|
|
25
|
+
from .handlers import FORMAT_ENV_KEY
|
|
26
|
+
from .handlers import cmd_extract
|
|
27
|
+
from .handlers import cmd_list
|
|
28
|
+
from .handlers import cmd_load
|
|
29
|
+
from .handlers import cmd_pipeline
|
|
30
|
+
from .handlers import cmd_run
|
|
31
|
+
from .handlers import cmd_transform
|
|
32
|
+
from .handlers import cmd_validate
|
|
33
|
+
|
|
34
|
+
# SECTION: EXPORTS ========================================================== #
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
# Functions
|
|
39
|
+
'create_parser',
|
|
40
|
+
'main',
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# SECTION: TYPE ALIASES ===================================================== #
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
type FormatContext = Literal['source', 'target']
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# SECTION: INTERNAL CLASSES ================================================= #
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class _FormatAction(argparse.Action):
|
|
54
|
+
"""Argparse action that records when ``--format`` is provided."""
|
|
55
|
+
|
|
56
|
+
def __call__(
|
|
57
|
+
self,
|
|
58
|
+
parser: argparse.ArgumentParser,
|
|
59
|
+
namespace: argparse.Namespace,
|
|
60
|
+
values: str | Sequence[object] | None,
|
|
61
|
+
option_string: str | None = None,
|
|
62
|
+
) -> None: # pragma: no cover
|
|
63
|
+
setattr(namespace, self.dest, values)
|
|
64
|
+
namespace._format_explicit = True
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# SECTION: INTERNAL FUNCTIONS =============================================== #
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _add_format_options(
|
|
71
|
+
parser: argparse.ArgumentParser,
|
|
72
|
+
*,
|
|
73
|
+
context: FormatContext,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Attach shared ``--format`` options to extract/load parsers."""
|
|
76
|
+
|
|
77
|
+
parser.set_defaults(_format_explicit=False)
|
|
78
|
+
parser.add_argument(
|
|
79
|
+
'--strict-format',
|
|
80
|
+
action='store_true',
|
|
81
|
+
help=(
|
|
82
|
+
'Treat providing --format for file '
|
|
83
|
+
f'{context}s as an error (overrides environment behavior)'
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
parser.add_argument(
|
|
87
|
+
'--format',
|
|
88
|
+
choices=list(FileFormat.choices()),
|
|
89
|
+
default='json',
|
|
90
|
+
action=_FormatAction,
|
|
91
|
+
help=(
|
|
92
|
+
f'Format of the {context} when not a file. For file {context}s '
|
|
93
|
+
'this option is ignored and the format is inferred from the '
|
|
94
|
+
'filename extension.'
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _cli_description() -> str:
|
|
100
|
+
return '\n'.join(
|
|
101
|
+
[
|
|
102
|
+
'ETLPlus - A Swiss Army knife for simple ETL operations.',
|
|
103
|
+
'',
|
|
104
|
+
' Provide a subcommand and options. Examples:',
|
|
105
|
+
'',
|
|
106
|
+
' etlplus extract file in.csv -o out.json',
|
|
107
|
+
' etlplus validate in.json --rules \'{"required": ["id"]}\'',
|
|
108
|
+
(
|
|
109
|
+
' etlplus transform in.json --operations '
|
|
110
|
+
'\'{"select": ["id"]}\''
|
|
111
|
+
),
|
|
112
|
+
' etlplus load in.json file out.json',
|
|
113
|
+
'',
|
|
114
|
+
' Enforce error if --format is provided for files. Examples:',
|
|
115
|
+
'',
|
|
116
|
+
' etlplus extract file in.csv --format csv --strict-format',
|
|
117
|
+
(
|
|
118
|
+
' etlplus load in.json file out.csv --format csv '
|
|
119
|
+
'--strict-format'
|
|
120
|
+
),
|
|
121
|
+
],
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _cli_epilog(format_env_key: str) -> str:
|
|
126
|
+
return '\n'.join(
|
|
127
|
+
[
|
|
128
|
+
'Environment:',
|
|
129
|
+
(
|
|
130
|
+
f' {format_env_key} controls behavior when '
|
|
131
|
+
'--format is provided for files.'
|
|
132
|
+
),
|
|
133
|
+
' Values:',
|
|
134
|
+
' - error|fail|strict: treat as error',
|
|
135
|
+
' - warn (default): print a warning',
|
|
136
|
+
' - ignore|silent: no message',
|
|
137
|
+
'',
|
|
138
|
+
'Note:',
|
|
139
|
+
' --strict-format overrides the environment behavior.',
|
|
140
|
+
],
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# SECTION: FUNCTIONS ======================================================== #
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
148
|
+
"""
|
|
149
|
+
Return the legacy :mod:`argparse` parser wired to current handlers.
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
argparse.ArgumentParser
|
|
154
|
+
Parser compatible with historical ``etlplus`` entry points.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
parser = argparse.ArgumentParser(
|
|
158
|
+
prog='etlplus',
|
|
159
|
+
description=_cli_description(),
|
|
160
|
+
epilog=_cli_epilog(FORMAT_ENV_KEY),
|
|
161
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
parser.add_argument(
|
|
165
|
+
'-V',
|
|
166
|
+
'--version',
|
|
167
|
+
action='version',
|
|
168
|
+
version=f'%(prog)s {__version__}',
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
subparsers = parser.add_subparsers(
|
|
172
|
+
dest='command',
|
|
173
|
+
help='Available commands',
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
extract_parser = subparsers.add_parser(
|
|
177
|
+
'extract',
|
|
178
|
+
help='Extract data from sources (files, databases, REST APIs)',
|
|
179
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
180
|
+
)
|
|
181
|
+
extract_parser.add_argument(
|
|
182
|
+
'source_type',
|
|
183
|
+
choices=list(DataConnectorType.choices()),
|
|
184
|
+
help='Type of source to extract from',
|
|
185
|
+
)
|
|
186
|
+
extract_parser.add_argument(
|
|
187
|
+
'source',
|
|
188
|
+
help=(
|
|
189
|
+
'Source location (file path, database connection string, '
|
|
190
|
+
'or API URL)'
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
extract_parser.add_argument(
|
|
194
|
+
'-o',
|
|
195
|
+
'--output',
|
|
196
|
+
help='Output file to save extracted data (JSON format)',
|
|
197
|
+
)
|
|
198
|
+
_add_format_options(extract_parser, context='source')
|
|
199
|
+
extract_parser.set_defaults(func=cmd_extract)
|
|
200
|
+
|
|
201
|
+
validate_parser = subparsers.add_parser(
|
|
202
|
+
'validate',
|
|
203
|
+
help='Validate data from sources',
|
|
204
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
205
|
+
)
|
|
206
|
+
validate_parser.add_argument(
|
|
207
|
+
'source',
|
|
208
|
+
help='Data source to validate (file path or JSON string)',
|
|
209
|
+
)
|
|
210
|
+
validate_parser.add_argument(
|
|
211
|
+
'--rules',
|
|
212
|
+
type=json_type,
|
|
213
|
+
default={},
|
|
214
|
+
help='Validation rules as JSON string',
|
|
215
|
+
)
|
|
216
|
+
validate_parser.set_defaults(func=cmd_validate)
|
|
217
|
+
|
|
218
|
+
transform_parser = subparsers.add_parser(
|
|
219
|
+
'transform',
|
|
220
|
+
help='Transform data',
|
|
221
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
222
|
+
)
|
|
223
|
+
transform_parser.add_argument(
|
|
224
|
+
'source',
|
|
225
|
+
help='Data source to transform (file path or JSON string)',
|
|
226
|
+
)
|
|
227
|
+
transform_parser.add_argument(
|
|
228
|
+
'--operations',
|
|
229
|
+
type=json_type,
|
|
230
|
+
default={},
|
|
231
|
+
help='Transformation operations as JSON string',
|
|
232
|
+
)
|
|
233
|
+
transform_parser.add_argument(
|
|
234
|
+
'-o',
|
|
235
|
+
'--output',
|
|
236
|
+
help='Output file to save transformed data',
|
|
237
|
+
)
|
|
238
|
+
transform_parser.set_defaults(func=cmd_transform)
|
|
239
|
+
|
|
240
|
+
load_parser = subparsers.add_parser(
|
|
241
|
+
'load',
|
|
242
|
+
help='Load data to targets (files, databases, REST APIs)',
|
|
243
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
244
|
+
)
|
|
245
|
+
load_parser.add_argument(
|
|
246
|
+
'source',
|
|
247
|
+
help='Data source to load (file path or JSON string)',
|
|
248
|
+
)
|
|
249
|
+
load_parser.add_argument(
|
|
250
|
+
'target_type',
|
|
251
|
+
choices=list(DataConnectorType.choices()),
|
|
252
|
+
help='Type of target to load to',
|
|
253
|
+
)
|
|
254
|
+
load_parser.add_argument(
|
|
255
|
+
'target',
|
|
256
|
+
help=(
|
|
257
|
+
'Target location (file path, database connection string, '
|
|
258
|
+
'or API URL)'
|
|
259
|
+
),
|
|
260
|
+
)
|
|
261
|
+
_add_format_options(load_parser, context='target')
|
|
262
|
+
load_parser.set_defaults(func=cmd_load)
|
|
263
|
+
|
|
264
|
+
pipe_parser = subparsers.add_parser(
|
|
265
|
+
'pipeline',
|
|
266
|
+
help=(
|
|
267
|
+
'Inspect or run pipeline YAML (see '
|
|
268
|
+
f'{PROJECT_URL}/blob/main/docs/pipeline-guide.md)'
|
|
269
|
+
),
|
|
270
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
271
|
+
)
|
|
272
|
+
pipe_parser.add_argument(
|
|
273
|
+
'--config',
|
|
274
|
+
required=True,
|
|
275
|
+
help='Path to pipeline YAML configuration file',
|
|
276
|
+
)
|
|
277
|
+
pipe_parser.add_argument(
|
|
278
|
+
'--list',
|
|
279
|
+
action='store_true',
|
|
280
|
+
help='List available job names and exit',
|
|
281
|
+
)
|
|
282
|
+
pipe_parser.add_argument(
|
|
283
|
+
'--run',
|
|
284
|
+
metavar='JOB',
|
|
285
|
+
help='Run a specific job by name',
|
|
286
|
+
)
|
|
287
|
+
pipe_parser.set_defaults(func=cmd_pipeline)
|
|
288
|
+
|
|
289
|
+
list_parser = subparsers.add_parser(
|
|
290
|
+
'list',
|
|
291
|
+
help='List ETL pipeline metadata',
|
|
292
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
293
|
+
)
|
|
294
|
+
list_parser.add_argument(
|
|
295
|
+
'--config',
|
|
296
|
+
required=True,
|
|
297
|
+
help='Path to pipeline YAML configuration file',
|
|
298
|
+
)
|
|
299
|
+
list_parser.add_argument(
|
|
300
|
+
'--pipelines',
|
|
301
|
+
action='store_true',
|
|
302
|
+
help='List ETL pipelines',
|
|
303
|
+
)
|
|
304
|
+
list_parser.add_argument(
|
|
305
|
+
'--sources',
|
|
306
|
+
action='store_true',
|
|
307
|
+
help='List data sources',
|
|
308
|
+
)
|
|
309
|
+
list_parser.add_argument(
|
|
310
|
+
'--targets',
|
|
311
|
+
action='store_true',
|
|
312
|
+
help='List data targets',
|
|
313
|
+
)
|
|
314
|
+
list_parser.add_argument(
|
|
315
|
+
'--transforms',
|
|
316
|
+
action='store_true',
|
|
317
|
+
help='List data transforms',
|
|
318
|
+
)
|
|
319
|
+
list_parser.set_defaults(func=cmd_list)
|
|
320
|
+
|
|
321
|
+
run_parser = subparsers.add_parser(
|
|
322
|
+
'run',
|
|
323
|
+
help=(
|
|
324
|
+
'Run an ETL pipeline '
|
|
325
|
+
f'(see {PROJECT_URL}/blob/main/docs/run-module.md)'
|
|
326
|
+
),
|
|
327
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
328
|
+
)
|
|
329
|
+
run_parser.add_argument(
|
|
330
|
+
'--config',
|
|
331
|
+
required=True,
|
|
332
|
+
help='Path to pipeline YAML configuration file',
|
|
333
|
+
)
|
|
334
|
+
run_parser.add_argument(
|
|
335
|
+
'-j',
|
|
336
|
+
'--job',
|
|
337
|
+
help='Name of the job to run',
|
|
338
|
+
)
|
|
339
|
+
run_parser.add_argument(
|
|
340
|
+
'-p',
|
|
341
|
+
'--pipeline',
|
|
342
|
+
help='Name of the pipeline to run',
|
|
343
|
+
)
|
|
344
|
+
run_parser.set_defaults(func=cmd_run)
|
|
345
|
+
|
|
346
|
+
return parser
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def main(
|
|
350
|
+
argv: list[str] | None = None,
|
|
351
|
+
) -> int:
|
|
352
|
+
"""
|
|
353
|
+
Run the Typer-powered CLI and normalize exit codes.
|
|
354
|
+
|
|
355
|
+
Parameters
|
|
356
|
+
----------
|
|
357
|
+
argv : list[str] | None, optional
|
|
358
|
+
Sequence of command-line arguments excluding the program name. When
|
|
359
|
+
``None``, defaults to ``sys.argv[1:]``.
|
|
360
|
+
|
|
361
|
+
Returns
|
|
362
|
+
-------
|
|
363
|
+
int
|
|
364
|
+
A conventional POSIX exit code: zero on success, non-zero on error.
|
|
365
|
+
|
|
366
|
+
Raises
|
|
367
|
+
------
|
|
368
|
+
SystemExit
|
|
369
|
+
Re-raises SystemExit exceptions to preserve exit codes.
|
|
370
|
+
|
|
371
|
+
Notes
|
|
372
|
+
-----
|
|
373
|
+
This function uses Typer (Click) for parsing/dispatch, but preserves the
|
|
374
|
+
existing `cmd_*` handlers by adapting parsed arguments into an
|
|
375
|
+
:class:`argparse.Namespace`.
|
|
376
|
+
"""
|
|
377
|
+
resolved_argv = sys.argv[1:] if argv is None else list(argv)
|
|
378
|
+
command = typer.main.get_command(app)
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
result = command.main(
|
|
382
|
+
args=resolved_argv,
|
|
383
|
+
prog_name='etlplus',
|
|
384
|
+
standalone_mode=False,
|
|
385
|
+
)
|
|
386
|
+
return int(result or 0)
|
|
387
|
+
|
|
388
|
+
except typer.Exit as exc:
|
|
389
|
+
return int(exc.exit_code)
|
|
390
|
+
|
|
391
|
+
except typer.Abort:
|
|
392
|
+
return 1
|
|
393
|
+
|
|
394
|
+
except KeyboardInterrupt: # pragma: no cover - interactive path
|
|
395
|
+
# Conventional exit code for SIGINT
|
|
396
|
+
return 130
|
|
397
|
+
|
|
398
|
+
except SystemExit as e:
|
|
399
|
+
print(f'Error: {e}', file=sys.stderr)
|
|
400
|
+
raise e
|
|
401
|
+
|
|
402
|
+
except (OSError, TypeError, ValueError) as e:
|
|
403
|
+
print(f'Error: {e}', file=sys.stderr)
|
|
404
|
+
return 1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: etlplus
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
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
|
|
@@ -22,6 +22,7 @@ Requires-Dist: pyodbc>=5.3.0
|
|
|
22
22
|
Requires-Dist: python-dotenv>=1.2.1
|
|
23
23
|
Requires-Dist: pandas>=2.3.3
|
|
24
24
|
Requires-Dist: requests>=2.32.5
|
|
25
|
+
Requires-Dist: typer>=0.21.0
|
|
25
26
|
Provides-Extra: dev
|
|
26
27
|
Requires-Dist: black>=25.9.0; extra == "dev"
|
|
27
28
|
Requires-Dist: build>=1.2.2; extra == "dev"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
etlplus/__init__.py,sha256=M2gScnyir6WOMAh_EuoQIiAzdcTls0_5hbd_Q6of8I0,1021
|
|
2
|
-
etlplus/__main__.py,sha256=
|
|
2
|
+
etlplus/__main__.py,sha256=btoROneNiigyfBU7BSzPKZ1R9gzBMpxcpsbPwmuHwTM,479
|
|
3
3
|
etlplus/__version__.py,sha256=1E0GMK_yUWCMQFKxXjTvyMwofi0qT2k4CDNiHWiymWE,327
|
|
4
|
-
etlplus/cli.py,sha256=dmMW5dLbFiDRGne97qDsqr3YuU30g_Ekl_vo7bgDLig,21752
|
|
5
4
|
etlplus/enums.py,sha256=V_j18Ud2BCXpFsBk2pZGrvCVrvAMJ7uja1z9fppFGso,10175
|
|
6
5
|
etlplus/extract.py,sha256=f44JdHhNTACxgn44USx05paKTwq7LQY-V4wANCW9hVM,6173
|
|
7
6
|
etlplus/file.py,sha256=RxIAsGDN4f_vNA2B5-ct88JNd_ISAyYbooIRE5DstS8,17972
|
|
@@ -31,6 +30,10 @@ etlplus/api/pagination/paginator.py,sha256=wtdY_er4yfjx5yTUQJ1gPq-IuWmpLAHeG5buB
|
|
|
31
30
|
etlplus/api/rate_limiting/__init__.py,sha256=ZySB1dZettEDnWvI1EHf_TZ9L08M_kKsNR-Y_lbU6kI,1070
|
|
32
31
|
etlplus/api/rate_limiting/config.py,sha256=2b4wIynblN-1EyMqI4aXa71SljzSjXYh5N1Nngr3jOg,9406
|
|
33
32
|
etlplus/api/rate_limiting/rate_limiter.py,sha256=Uxozqd_Ej5Lsj-M-mLT2WexChgWh7x35_YP10yqYPQA,7159
|
|
33
|
+
etlplus/cli/__init__.py,sha256=J97-Rv931IL1_b4AXnB7Fbbd7HKnHBpx18NQfC_kE6c,299
|
|
34
|
+
etlplus/cli/app.py,sha256=xNsre2GDErbQv3Cwd3hwRHpkkoh-ZYIbBLrnKrFs0VI,25268
|
|
35
|
+
etlplus/cli/handlers.py,sha256=QlZTTg9R493J2R4z81yRS1Zt3XEGeiqmszGT_GT2c5A,16059
|
|
36
|
+
etlplus/cli/main.py,sha256=nofNgGqfStGdyNLV6O8k8XguRNhMIm92EhuO68nGPOE,11148
|
|
34
37
|
etlplus/config/__init__.py,sha256=VZWzOg7d2YR9NT6UwKTv44yf2FRUMjTHynkm1Dl5Qzo,1486
|
|
35
38
|
etlplus/config/connector.py,sha256=0-TIwevHbKRHVmucvyGpPd-3tB1dKHB-dj0yJ6kq5eY,9809
|
|
36
39
|
etlplus/config/jobs.py,sha256=hmzRCqt0OvCEZZR4ONKrd3lvSv0OmayjLc4yOBk3ug8,7399
|
|
@@ -40,9 +43,9 @@ etlplus/config/types.py,sha256=a0epJ3z16HQ5bY3Ctf8s_cQPa3f0HHcwdOcjCP2xoG4,4954
|
|
|
40
43
|
etlplus/config/utils.py,sha256=4SUHMkt5bKBhMhiJm-DrnmE2Q4TfOgdNCKz8PJDS27o,3443
|
|
41
44
|
etlplus/validation/__init__.py,sha256=Pe5Xg1_EA4uiNZGYu5WTF3j7odjmyxnAJ8rcioaplSQ,1254
|
|
42
45
|
etlplus/validation/utils.py,sha256=Mtqg449VIke0ziy_wd2r6yrwJzQkA1iulZC87FzXMjo,10201
|
|
43
|
-
etlplus-0.
|
|
44
|
-
etlplus-0.
|
|
45
|
-
etlplus-0.
|
|
46
|
-
etlplus-0.
|
|
47
|
-
etlplus-0.
|
|
48
|
-
etlplus-0.
|
|
46
|
+
etlplus-0.4.1.dist-info/licenses/LICENSE,sha256=MuNO63i6kWmgnV2pbP2SLqP54mk1BGmu7CmbtxMmT-U,1069
|
|
47
|
+
etlplus-0.4.1.dist-info/METADATA,sha256=PGl9CCUvlxyqz_zNfG_ibDQ5iOxib70Vas1xeWURgA4,16758
|
|
48
|
+
etlplus-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
49
|
+
etlplus-0.4.1.dist-info/entry_points.txt,sha256=6w-2-jzuPa55spzK34h-UKh2JTEShh38adFRONNP9QE,45
|
|
50
|
+
etlplus-0.4.1.dist-info/top_level.txt,sha256=aWWF-udn_sLGuHTM6W6MLh99ArS9ROkUWO8Mi8y1_2U,8
|
|
51
|
+
etlplus-0.4.1.dist-info/RECORD,,
|