etlplus 0.10.1__tar.gz → 0.10.5__tar.gz
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-0.10.1/etlplus.egg-info → etlplus-0.10.5}/PKG-INFO +1 -1
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/enums.py +37 -3
- {etlplus-0.10.1 → etlplus-0.10.5/etlplus.egg-info}/PKG-INFO +1 -1
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus.egg-info/SOURCES.txt +1 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/cli/conftest.py +105 -4
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/cli/test_u_cli_handlers.py +90 -370
- etlplus-0.10.5/tests/unit/cli/test_u_cli_io.py +326 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/cli/test_u_cli_main.py +59 -25
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/cli/test_u_cli_state.py +45 -41
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/test_u_enums.py +17 -9
- {etlplus-0.10.1 → etlplus-0.10.5}/.coveragerc +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/.editorconfig +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/.gitattributes +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/.github/actions/python-bootstrap/action.yml +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/.github/workflows/ci.yml +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/.gitignore +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/.pre-commit-config.yaml +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/.ruff.toml +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/CODE_OF_CONDUCT.md +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/CONTRIBUTING.md +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/DEMO.md +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/LICENSE +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/MANIFEST.in +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/Makefile +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/README.md +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/REFERENCES.md +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/docs/README.md +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/docs/pipeline-guide.md +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/docs/snippets/installation_version.md +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/__init__.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/__main__.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/__version__.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/README.md +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/__init__.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/auth.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/config.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/endpoint_client.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/errors.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/pagination/__init__.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/pagination/client.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/pagination/config.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/pagination/paginator.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/rate_limiting/__init__.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/rate_limiting/config.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/rate_limiting/rate_limiter.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/request_manager.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/retry_manager.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/transport.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/api/types.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/cli/__init__.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/cli/commands.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/cli/constants.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/cli/handlers.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/cli/io.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/cli/main.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/cli/options.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/cli/state.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/cli/types.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/config/__init__.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/config/connector.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/config/jobs.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/config/pipeline.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/config/profile.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/config/types.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/config/utils.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/database/__init__.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/database/ddl.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/database/engine.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/database/orm.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/database/schema.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/database/types.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/extract.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/file.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/load.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/mixins.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/py.typed +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/run.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/run_helpers.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/templates/__init__.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/templates/ddl.sql.j2 +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/templates/view.sql.j2 +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/transform.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/types.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/utils.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/validate.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/validation/__init__.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus/validation/utils.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus.egg-info/dependency_links.txt +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus.egg-info/entry_points.txt +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus.egg-info/requires.txt +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/etlplus.egg-info/top_level.txt +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/examples/README.md +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/examples/configs/ddl_spec.yml +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/examples/configs/pipeline.yml +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/examples/data/sample.csv +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/examples/data/sample.json +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/examples/data/sample.xml +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/examples/data/sample.xsd +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/examples/data/sample.yaml +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/examples/quickstart_python.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/pyproject.toml +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/pytest.ini +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/setup.cfg +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/setup.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/__init__.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/conftest.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/integration/conftest.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/integration/test_i_cli.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/integration/test_i_examples_data_parity.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/integration/test_i_pagination_strategy.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/integration/test_i_pipeline_smoke.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/integration/test_i_pipeline_yaml_load.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/integration/test_i_run.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/integration/test_i_run_profile_pagination_defaults.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/integration/test_i_run_profile_rate_limit_defaults.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/conftest.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_auth.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_config.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_endpoint_client.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_mocks.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_pagination_client.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_pagination_config.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_paginator.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_rate_limit_config.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_rate_limiter.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_request_manager.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_retry_manager.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_transport.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/api/test_u_types.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/config/test_u_config_utils.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/config/test_u_connector.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/config/test_u_jobs.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/config/test_u_pipeline.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/conftest.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/database/test_u_database_ddl.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/database/test_u_database_engine.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/database/test_u_database_orm.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/database/test_u_database_schema.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/test_u_extract.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/test_u_file.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/test_u_load.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/test_u_main.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/test_u_mixins.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/test_u_run.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/test_u_run_helpers.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/test_u_transform.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/test_u_utils.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/test_u_validate.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/test_u_version.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tests/unit/validation/test_u_validation_utils.py +0 -0
- {etlplus-0.10.1 → etlplus-0.10.5}/tools/update_demo_snippets.py +0 -0
|
@@ -300,12 +300,16 @@ class FileFormat(CoercibleStrEnum):
|
|
|
300
300
|
'.yml': 'yaml',
|
|
301
301
|
# MIME types
|
|
302
302
|
'application/avro': 'avro',
|
|
303
|
+
'application/csv': 'csv',
|
|
303
304
|
'application/feather': 'feather',
|
|
304
305
|
'application/gzip': 'gz',
|
|
305
306
|
'application/json': 'json',
|
|
306
307
|
'application/jsonlines': 'ndjson',
|
|
307
308
|
'application/ndjson': 'ndjson',
|
|
308
309
|
'application/orc': 'orc',
|
|
310
|
+
'application/parquet': 'parquet',
|
|
311
|
+
'application/vnd.apache.avro': 'avro',
|
|
312
|
+
'application/vnd.apache.parquet': 'parquet',
|
|
309
313
|
'application/vnd.apache.arrow.file': 'feather',
|
|
310
314
|
'application/vnd.apache.orc': 'orc',
|
|
311
315
|
'application/vnd.ms-excel': 'xls',
|
|
@@ -314,13 +318,20 @@ class FileFormat(CoercibleStrEnum):
|
|
|
314
318
|
'officedocument.spreadsheetml.sheet'
|
|
315
319
|
): 'xlsx',
|
|
316
320
|
'application/x-avro': 'avro',
|
|
321
|
+
'application/x-csv': 'csv',
|
|
322
|
+
'application/x-feather': 'feather',
|
|
323
|
+
'application/x-orc': 'orc',
|
|
317
324
|
'application/x-ndjson': 'ndjson',
|
|
318
325
|
'application/x-parquet': 'parquet',
|
|
326
|
+
'application/x-yaml': 'yaml',
|
|
319
327
|
'application/xml': 'xml',
|
|
320
328
|
'application/zip': 'zip',
|
|
321
329
|
'text/csv': 'csv',
|
|
322
330
|
'text/plain': 'txt',
|
|
323
331
|
'text/tab-separated-values': 'tsv',
|
|
332
|
+
'text/tsv': 'tsv',
|
|
333
|
+
'text/xml': 'xml',
|
|
334
|
+
'text/yaml': 'yaml',
|
|
324
335
|
}
|
|
325
336
|
|
|
326
337
|
|
|
@@ -524,6 +535,7 @@ def coerce_http_method(
|
|
|
524
535
|
|
|
525
536
|
def infer_file_format_and_compression(
|
|
526
537
|
value: object,
|
|
538
|
+
filename: object | None = None,
|
|
527
539
|
) -> tuple[FileFormat | None, CompressionFormat | None]:
|
|
528
540
|
"""
|
|
529
541
|
Infer data format and compression from a filename, extension, or MIME type.
|
|
@@ -532,6 +544,9 @@ def infer_file_format_and_compression(
|
|
|
532
544
|
----------
|
|
533
545
|
value : object
|
|
534
546
|
A filename, extension, MIME type, or existing enum member.
|
|
547
|
+
filename : object | None, optional
|
|
548
|
+
A filename to consult for extension-based inference (e.g. when
|
|
549
|
+
``value`` is ``application/octet-stream``).
|
|
535
550
|
|
|
536
551
|
Returns
|
|
537
552
|
-------
|
|
@@ -552,10 +567,29 @@ def infer_file_format_and_compression(
|
|
|
552
567
|
normalized = text.casefold()
|
|
553
568
|
mime = normalized.split(';', 1)[0].strip()
|
|
554
569
|
|
|
570
|
+
is_octet_stream = mime == 'application/octet-stream'
|
|
555
571
|
compression = CompressionFormat.try_coerce(mime)
|
|
556
|
-
fmt = FileFormat.try_coerce(mime)
|
|
557
|
-
|
|
558
|
-
|
|
572
|
+
fmt = None if is_octet_stream else FileFormat.try_coerce(mime)
|
|
573
|
+
|
|
574
|
+
is_mime = mime.startswith(
|
|
575
|
+
(
|
|
576
|
+
'application/',
|
|
577
|
+
'text/',
|
|
578
|
+
'audio/',
|
|
579
|
+
'image/',
|
|
580
|
+
'video/',
|
|
581
|
+
'multipart/',
|
|
582
|
+
),
|
|
583
|
+
)
|
|
584
|
+
suffix_source: object | None = filename if filename is not None else text
|
|
585
|
+
if is_mime and filename is None:
|
|
586
|
+
suffix_source = None
|
|
587
|
+
|
|
588
|
+
suffixes = (
|
|
589
|
+
PurePath(str(suffix_source)).suffixes
|
|
590
|
+
if suffix_source is not None
|
|
591
|
+
else []
|
|
592
|
+
)
|
|
559
593
|
if suffixes:
|
|
560
594
|
normalized_suffixes = [suffix.casefold() for suffix in suffixes]
|
|
561
595
|
compression = (
|
|
@@ -134,6 +134,7 @@ tests/unit/api/test_u_transport.py
|
|
|
134
134
|
tests/unit/api/test_u_types.py
|
|
135
135
|
tests/unit/cli/conftest.py
|
|
136
136
|
tests/unit/cli/test_u_cli_handlers.py
|
|
137
|
+
tests/unit/cli/test_u_cli_io.py
|
|
137
138
|
tests/unit/cli/test_u_cli_main.py
|
|
138
139
|
tests/unit/cli/test_u_cli_state.py
|
|
139
140
|
tests/unit/config/test_u_config_utils.py
|
|
@@ -14,6 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
import json
|
|
15
15
|
import types
|
|
16
16
|
from collections.abc import Callable
|
|
17
|
+
from collections.abc import Mapping
|
|
17
18
|
from dataclasses import dataclass
|
|
18
19
|
from dataclasses import field
|
|
19
20
|
from pathlib import Path
|
|
@@ -33,6 +34,84 @@ from etlplus.config import PipelineConfig
|
|
|
33
34
|
|
|
34
35
|
CSV_TEXT: Final[str] = 'a,b\n1,2\n3,4\n'
|
|
35
36
|
|
|
37
|
+
type CaptureHandler = Callable[[object, str], dict[str, object]]
|
|
38
|
+
type CaptureIo = dict[str, list[tuple[tuple[object, ...], dict[str, object]]]]
|
|
39
|
+
type InvokeCli = Callable[..., Result]
|
|
40
|
+
type StubCommand = Callable[[Callable[..., object]], None]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def assert_emit_json(
|
|
44
|
+
calls: CaptureIo,
|
|
45
|
+
payload: object,
|
|
46
|
+
*,
|
|
47
|
+
pretty: bool,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Assert that :func:`emit_json` was called once with expected payload.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
calls : CaptureIo
|
|
55
|
+
Captured IO calls from the CLI fixtures.
|
|
56
|
+
payload : object
|
|
57
|
+
Expected JSON payload.
|
|
58
|
+
pretty : bool
|
|
59
|
+
Expected pretty-print flag.
|
|
60
|
+
"""
|
|
61
|
+
assert calls['emit_json'] == [((payload,), {'pretty': pretty})]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def assert_emit_or_write(
|
|
65
|
+
calls: CaptureIo,
|
|
66
|
+
payload: object,
|
|
67
|
+
target: object,
|
|
68
|
+
*,
|
|
69
|
+
pretty: bool,
|
|
70
|
+
) -> dict[str, object]:
|
|
71
|
+
"""
|
|
72
|
+
Assert that :func:`emit_or_write` was called once and return kwargs.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
calls : CaptureIo
|
|
77
|
+
Captured IO calls from the CLI fixtures.
|
|
78
|
+
payload : object
|
|
79
|
+
Expected payload argument.
|
|
80
|
+
target : object
|
|
81
|
+
Expected output path argument.
|
|
82
|
+
pretty : bool
|
|
83
|
+
Expected pretty-print flag.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
dict[str, object]
|
|
88
|
+
Captured keyword arguments for the call.
|
|
89
|
+
"""
|
|
90
|
+
assert len(calls['emit_or_write']) == 1
|
|
91
|
+
args, kwargs = calls['emit_or_write'][0]
|
|
92
|
+
assert args[0] == payload
|
|
93
|
+
assert args[1] == target
|
|
94
|
+
assert kwargs['pretty'] is pretty
|
|
95
|
+
return kwargs
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def assert_mapping_contains(
|
|
99
|
+
actual: Mapping[str, object],
|
|
100
|
+
expected: Mapping[str, object],
|
|
101
|
+
) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Assert that ``actual`` contains the ``expected`` key/value pairs.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
actual : Mapping[str, object]
|
|
108
|
+
Mapping returned by the handler capture fixture.
|
|
109
|
+
expected : Mapping[str, object]
|
|
110
|
+
Expected key/value pairs that must be present in ``actual``.
|
|
111
|
+
"""
|
|
112
|
+
for key, value in expected.items():
|
|
113
|
+
assert actual[key] == value
|
|
114
|
+
|
|
36
115
|
|
|
37
116
|
@dataclass(frozen=True, slots=True)
|
|
38
117
|
class DummyCfg:
|
|
@@ -60,8 +139,20 @@ class DummyCfg:
|
|
|
60
139
|
@pytest.fixture(name='capture_handler')
|
|
61
140
|
def capture_handler_fixture(
|
|
62
141
|
monkeypatch: pytest.MonkeyPatch,
|
|
63
|
-
) ->
|
|
64
|
-
"""
|
|
142
|
+
) -> CaptureHandler:
|
|
143
|
+
"""
|
|
144
|
+
Patch a handler function and capture the kwargs it receives.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
monkeypatch : pytest.MonkeyPatch
|
|
149
|
+
Pytest monkeypatch fixture.
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
CaptureHandler
|
|
154
|
+
Callable that records handler keyword arguments.
|
|
155
|
+
"""
|
|
65
156
|
|
|
66
157
|
def _capture(module: object, attr: str) -> dict[str, object]:
|
|
67
158
|
calls: dict[str, object] = {}
|
|
@@ -77,14 +168,24 @@ def capture_handler_fixture(
|
|
|
77
168
|
|
|
78
169
|
|
|
79
170
|
@pytest.fixture(name='capture_io')
|
|
80
|
-
def capture_io_fixture(monkeypatch: pytest.MonkeyPatch):
|
|
171
|
+
def capture_io_fixture(monkeypatch: pytest.MonkeyPatch) -> CaptureIo:
|
|
81
172
|
"""
|
|
82
173
|
Patch handler functions and capture CLI output.
|
|
83
174
|
Returns a dict with lists of call args for each function.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
monkeypatch : pytest.MonkeyPatch
|
|
179
|
+
Pytest monkeypatch fixture.
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
CaptureIo
|
|
184
|
+
Mapping of captured IO call arguments by function name.
|
|
84
185
|
"""
|
|
85
186
|
import etlplus.cli.io as _io
|
|
86
187
|
|
|
87
|
-
calls:
|
|
188
|
+
calls: CaptureIo = {
|
|
88
189
|
'emit_or_write': [],
|
|
89
190
|
'emit_json': [],
|
|
90
191
|
'print_json': [],
|