datacompy.cli package

Submodules

datacompy.cli.backends module

Backend strategies for the DataComPy CLI.

Each CLIBackend owns the three things that vary between backends: opening a session (Spark and Snowflake only), turning a --left or --right reference into something the comparison accepts, and calling the right *Compare constructor.

The constructor keyword arguments themselves are not written out here. They are derived from datacompy.cli.parser.OPTIONS by compare_kwargs(), so the parser and the constructor call site cannot drift apart.

Imports of pyspark and snowflake.snowpark are deferred into methods, following the same pattern as datacompy/__init__.py.

class datacompy.cli.backends.CLIBackend

Bases: ABC

Strategy describing how the CLI drives one comparison backend.

build(namespace: Namespace, session: Any, left: Any, right: Any) → BaseCompare

Construct the *Compare instance.

class_name: ClassVar[str]

Name of the *Compare class within module.

property compare_cls: Callable[[...], BaseCompare]

Import and return the backend’s *Compare class.

Typed as a callable rather than type[BaseCompare] because the constructor signatures differ between backends, which is exactly what compare_kwargs() and the drift guard in tests/cli exist to reconcile.

Raises:

MissingExtraError – When the backend needs an optional dependency that is not installed.

extra: ClassVar[str | None] = None

Optional dependency extra required by this backend, if any.

abstractmethod load(session: Any, ref: str, namespace: Namespace) → Any

Turn ref into whatever the backend’s comparison accepts.

module: ClassVar[str]

Module holding the *Compare class, imported on demand.

name: ClassVar[str]

Value accepted by --backend.

open_session(namespace: Namespace, stack: ExitStack) → Any

Return a session for this backend, or None when none is needed.

Implementations register teardown on stack so the session is closed on both the success and the failure path.

class datacompy.cli.backends.PandasBackend

Bases: CLIBackend

Compare two files in memory with pandas.

class_name: ClassVar[str] = 'PandasCompare'

Name of the *Compare class within module.

load(session: Any, ref: str, namespace: Namespace) → DataFrame

Read ref into a pandas.DataFrame.

module: ClassVar[str] = 'datacompy.pandas'

Module holding the *Compare class, imported on demand.

name: ClassVar[str] = 'pandas'

Value accepted by --backend.

class datacompy.cli.backends.PolarsBackend

Bases: CLIBackend

Compare two files in memory with polars.

class_name: ClassVar[str] = 'PolarsCompare'

Name of the *Compare class within module.

load(session: Any, ref: str, namespace: Namespace) → DataFrame

Read ref into a polars.DataFrame.

module: ClassVar[str] = 'datacompy.polars'

Module holding the *Compare class, imported on demand.

name: ClassVar[str] = 'polars'

Value accepted by --backend.

class datacompy.cli.backends.SnowflakeBackend

Bases: CLIBackend

Compare two Snowflake tables in place.

--left and --right are always table references for this backend, either db.schema.table or schema.table. Local files are read with --backend pandas or --backend polars instead.

build(namespace: Namespace, session: Any, left: Any, right: Any) → BaseCompare

Construct a SnowflakeCompare.

class_name: ClassVar[str] = 'SnowflakeCompare'

Name of the *Compare class within module.

extra: ClassVar[str | None] = 'snowflake'

Optional dependency extra required by this backend, if any.

load(session: Any, ref: str, namespace: Namespace) → str

Validate ref and return it as a fully qualified table name.

Raises:

BadArgsError – When ref is not a two or three part dotted identifier, or when a two part reference cannot be qualified because the session has no current database.

module: ClassVar[str] = 'datacompy.snowflake'

Module holding the *Compare class, imported on demand.

name: ClassVar[str] = 'snowflake'

Value accepted by --backend.

open_session(namespace: Namespace, stack: ExitStack) → Any

Return a Snowpark session built from --snowflake-config or the environment.

class datacompy.cli.backends.SparkBackend

Bases: CLIBackend

Compare two files with Spark SQL.

build(namespace: Namespace, session: Any, left: Any, right: Any) → BaseCompare

Construct a SparkSQLCompare.

class_name: ClassVar[str] = 'SparkSQLCompare'

Name of the *Compare class within module.

extra: ClassVar[str | None] = 'spark'

Optional dependency extra required by this backend, if any.

load(session: Any, ref: str, namespace: Namespace) → Any

Read ref into a PySpark DataFrame.

module: ClassVar[str] = 'datacompy.spark'

Module holding the *Compare class, imported on demand.

name: ClassVar[str] = 'spark'

Value accepted by --backend.

open_session(namespace: Namespace, stack: ExitStack) → Any

Return a pyspark.sql.SparkSession, stopping it only if we made it.

A SparkSession is process wide, so getOrCreate returns the caller’s existing session when there is one. datacompy.cli.main() is a public function that can be called in process from a notebook or an Airflow task, and stopping a session the CLI did not create would kill the caller’s SparkContext along with it. Teardown is therefore registered only when this call is the one that created the session.

For the same reason --spark-app-name has no effect when a session already exists: an application name cannot be changed once the SparkContext is running.

The log level defaults to ERROR so PySpark’s INFO and WARN chatter stays out of the CLI’s own output. Override it with DATACOMPY_SPARK_LOG_LEVEL.

datacompy.cli.backends.compare_kwargs(namespace: Namespace, backend: str) → dict[str, Any]

Build the *Compare constructor keyword arguments for backend.

Walks datacompy.cli.parser.OPTIONS, keeping the rows that name a constructor keyword and that apply to this backend. Values that resolve to None are omitted so the library default applies.

datacompy.cli.backends.infer_delimiter(ref: str, override: str | None) → str

Return the field delimiter for ref.

Parameters:
  • ref (str) – File path or URI.

  • override (str, optional) – Explicit --csv-delimiter value. Returned as is when provided, and applied to both inputs. This is also how a comma is forced for a comma delimited file that happens to be named .tsv, because --input-format selects the reader and says nothing about the delimiter.

Returns:

The delimiter implied by the extension, or a comma for anything else.

Return type:

str

datacompy.cli.backends.infer_format(ref: str, override: str | None) → str

Return the input format for ref.

Parameters:
  • ref (str) – File path or URI.

  • override (str, optional) – Explicit --input-format value. Returned as is when provided.

Returns:

One of "csv", "parquet", or "json".

Return type:

str

Raises:

BadArgsError – When the extension is unrecognised and no override was given.

datacompy.cli.backends.suspect_delimiter(ref: str, namespace: Namespace, frame: Any) → str | None

Return a warning when frame looks like it was read with the wrong delimiter.

A CSV input read with the wrong delimiter collapses every row into one column whose name is the entire header line. Nothing fails at that point: the symptom surfaces later as a missing join column, or not at all under --on-index, where two mangled strings are compared instead. This is the last place where the reference, the resolved format and the resolved delimiter are all still in hand, so the guess is made here.

Parameters:
  • ref (str) – The --left or --right reference frame was read from.

  • namespace (argparse.Namespace) – Parsed arguments, for the format and delimiter overrides.

  • frame (Any) – The loaded DataFrame. Only columns is read, which every file backend exposes without touching the data.

Returns:

A warning message, or None when the parse looks fine. Only a single column CSV whose one column name repeats a delimiter other than the one used is reported, because that is what a collapsed multi-column header looks like. A genuine single column file, a name that merely contains one such delimiter (for example a;b), and a plain typo in --on all stay quiet.

Return type:

str or None

datacompy.cli.compare module

The datacompy compare subcommand.

datacompy.cli.compare.run_compare(namespace: Namespace) → int

Run a comparison and return the process exit code.

Parameters:

namespace (argparse.Namespace) – Parsed arguments from the compare subparser.

Returns:

0 when the datasets match or stay within --max-unequal-rows, and 1 when they differ. Problems with the arguments, the input files, or the backend raise a CLIError, which datacompy.cli.main() turns into exit code 2.

Return type:

int

datacompy.cli.compare.validate_arguments(namespace: Namespace) → None

Reject argument combinations that argparse cannot express.

Per value rules (delimiter length, non negative counts, tolerance syntax) are enforced by the type= callables in datacompy.cli.parser. This function covers the rules that need to look at more than one argument at a time.

Must run before datacompy.cli.parser.fill_defaults(), which is what makes was_given() distinguish an explicitly passed flag from an absent one.

Raises:

BadArgsError – On any invalid combination.

datacompy.cli.compare.within_threshold(namespace: Namespace, report_data: ReportData) → bool

Return True when the comparison counts as a pass.

Without --max-unequal-rows this reproduces matches(). With it, the datasets pass while the number of differing rows stays at or below the threshold, and while the columns line up unless --ignore-extra-columns was given.

Both branches read report_data, which build_report_data has already computed. Calling matches() here instead would recount straight from the DataFrames, and on Spark and Snowflake each of those counts is a distributed action, so the default invocation would scan the data a second time after the report had already been rendered.

datacompy.cli.errors module

Exception hierarchy for the DataComPy command line interface.

Every exception defined here carries an exit_code so that datacompy.cli.main() can catch CLIError at a single site and translate it into a friendly message plus the right process exit code.

exception datacompy.cli.errors.BadArgsError

Bases: CLIError

Raised when arguments are individually valid but invalid in combination.

exception datacompy.cli.errors.CLIError

Bases: Exception

Base class for expected CLI failures. Always maps to exit code 2.

exit_code: int = 2
exception datacompy.cli.errors.LoadError

Bases: CLIError

Raised when a dataset cannot be read.

exception datacompy.cli.errors.MissingExtraError

Bases: CLIError

Raised when a backend needs an optional dependency that is not installed.

exception datacompy.cli.errors.OutputError

Bases: CLIError

Raised when the report cannot be written to the requested destination.

datacompy.cli.output module

Report rendering and delivery for the DataComPy CLI.

Rendering and destination are independent: --report-format chooses between text, JSON, and HTML, and --output chooses between stdout and a file. All three renderings come from datacompy.report.ReportData, so the CLI adds no templating of its own.

datacompy.cli.output.emit(report_data: ReportData, report_format: str, output: Path | None, *, quiet: bool) → None

Write the report to stdout, to output, or to both.

quiet suppresses stdout only. A file requested with --output is always written, since asking for a file is an explicit request for it.

Raises:

OutputError – When the destination file cannot be written.

datacompy.cli.output.print_error(message: str) → None

Write message to stderr with a datacompy: prefix.

datacompy.cli.output.print_warning(message: str) → None

Write message to stderr with a datacompy: warning: prefix.

Warnings are diagnostics rather than output, so they ignore --quiet, which suppresses the report itself.

datacompy.cli.output.render(report_data: ReportData, report_format: str) → str

Render report_data in the requested format.

Parameters:
  • report_data (datacompy.report.ReportData) – Structured comparison result from compare.build_report_data().

  • report_format ({"text", "json", "html"}) – Rendering to produce.

Returns:

The rendered report.

Return type:

str

datacompy.cli.parser module

Declarative argument specification for the DataComPy CLI.

The module holds a single source of truth, OPTIONS, describing every option the compare subcommand accepts. Each Opt records both how argparse should register the flag and which *Compare constructor keyword it maps to, so build_parser() and datacompy.cli.backends.compare_kwargs() are generated from the same data rather than maintained as two parallel hand-written lists.

Adding a library keyword argument to the CLI is therefore a single new row, and tests/cli/test_parser.py checks every Opt.kwarg against the real constructor signature so the two can never silently drift apart.

datacompy.cli.parser.GROUP_INPUT = 'input'

Argument group headings, in the order they appear in --help.

datacompy.cli.parser.OPTIONS_BY_FLAG: dict[str, Opt] = {'--abs-tol': Opt(flags=('--abs-tol',), help='Absolute tolerance for numeric comparisons (default 0). Accepts a single number applied to every column, or repeated COLUMN=VALUE pairs for per column tolerances.', group='comparison', kwarg='abs_tol', backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=functools.partial(<function _combine_tolerances>, flag='--abs-tol'), default=None, options={'action': 'append', 'type': <function tolerance>, 'metavar': 'N|COL=N'}), '--backend': Opt(flags=('--backend',), help='Comparison backend. Polars is fast and the default. Use pandas for index based joins, spark for distributed data, or snowflake to compare tables in place.', group='backend', kwarg=None, backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default='polars', options={'choices': ['pandas', 'polars', 'snowflake', 'spark']}), '--cache-intermediates': Opt(flags=('--cache-intermediates',), help='Cache intermediate DataFrames (default: enabled). Pass --no-cache-intermediates on Databricks Serverless and other environments that do not support caching.', group='backend specific', kwarg='cache_intermediates', backends=frozenset({'spark'}), resolve=None, default=True, options={'action': <class 'argparse.BooleanOptionalAction'>}), '--cast-column-names-lower': Opt(flags=('--cast-column-names-lower',), help='Cast column names to lowercase before comparing (default: enabled). Not applicable to snowflake, which normalises identifiers to uppercase.', group='comparison', kwarg='cast_column_names_lower', backends=frozenset({'polars', 'spark', 'pandas'}), resolve=None, default=True, options={'action': <class 'argparse.BooleanOptionalAction'>}), '--column-count': Opt(flags=('--column-count',), help='Maximum number of columns to show in unique row samples (default 10).', group='report', kwarg=None, backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default=10, options={'type': <function non_negative_int>, 'metavar': 'N'}), '--csv-delimiter': Opt(flags=('--csv-delimiter',), help="Field delimiter for CSV input. Inferred from each file extension, a tab for .tsv and a comma otherwise. This flag overrides inference for both inputs: use '\\t' for a tab separated file with an unusual extension, or ',' to force a comma for a comma separated file named .tsv.", group='input', kwarg=None, backends=frozenset({'polars', 'spark', 'pandas'}), resolve=None, default=None, options={'type': <function single_char>, 'metavar': 'CHAR'}), '--df1-name': Opt(flags=('--df1-name',), help='Label for the left dataset in the report (default: derived from --left).', group='naming', kwarg='df1_name', backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=functools.partial(<function _resolve_dataset_name>, side='left'), default=None, options={'metavar': 'NAME'}), '--df2-name': Opt(flags=('--df2-name',), help='Label for the right dataset in the report (default: derived from --right).', group='naming', kwarg='df2_name', backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=functools.partial(<function _resolve_dataset_name>, side='right'), default=None, options={'metavar': 'NAME'}), '--ignore-case': Opt(flags=('--ignore-case',), help='Ignore case in string columns.', group='comparison', kwarg='ignore_case', backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default=False, options={'action': 'store_true'}), '--ignore-extra-columns': Opt(flags=('--ignore-extra-columns',), help='Treat the datasets as matching even when one side has columns the other does not.', group='comparison', kwarg=None, backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default=False, options={'action': 'store_true'}), '--ignore-spaces': Opt(flags=('--ignore-spaces',), help='Ignore leading and trailing whitespace in string columns.', group='comparison', kwarg='ignore_spaces', backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default=False, options={'action': 'store_true'}), '--ignore-unique-rows': Opt(flags=('--ignore-unique-rows',), help='With --max-unequal-rows, exclude rows that exist in only one dataset from the difference count.', group='report', kwarg=None, backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default=False, options={'action': 'store_true'}), '--input-format': Opt(flags=('--input-format',), help='Force the input file format for both datasets. Omit to infer it from each file extension, which also handles mixed format inputs.', group='input', kwarg=None, backends=frozenset({'polars', 'spark', 'pandas'}), resolve=None, default=None, options={'choices': ['csv', 'parquet', 'json']}), '--left': Opt(flags=('--left',), help='Path, URI, or Snowflake table reference for the left dataset.', group='input', kwarg=None, backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default=None, options={'required': True, 'metavar': 'REF'}), '--max-unequal-rows': Opt(flags=('--max-unequal-rows',), help='Exit 0 when the number of differing rows is at most N, and 1 otherwise. Counts value mismatches plus rows present in only one dataset; pass --ignore-unique-rows to count value mismatches only.', group='report', kwarg=None, backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default=None, options={'type': <function non_negative_int>, 'metavar': 'N'}), '--on': Opt(flags=('--on',), help='Join column. Accepts a comma separated list (--on id,date) or repeated flags (--on id --on date). Use the repeated form for column names that contain a comma.', group='join keys', kwarg='join_columns', backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=<function _flatten_join_columns>, default=None, options={'action': 'append', 'type': <function join_column_group>, 'metavar': 'COL[,COL...]'}), '--on-index': Opt(flags=('--on-index',), help='Join on the DataFrame index instead of columns. Pandas backend only.', group='join keys', kwarg='on_index', backends=frozenset({'pandas'}), resolve=None, default=False, options={'action': 'store_true'}), '--output': Opt(flags=('--output',), help='Write the report to this file instead of stdout. Parent directories are created as needed.', group='output', kwarg=None, backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default=None, options={'type': <class 'pathlib.Path'>, 'metavar': 'PATH'}), '--quiet': Opt(flags=('--quiet',), help='Do not print the report to stdout. A file named by --output is still written. The exit code still reflects the result.', group='output', kwarg=None, backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default=False, options={'action': 'store_true'}), '--rel-tol': Opt(flags=('--rel-tol',), help='Relative tolerance for numeric comparisons (default 0). Accepts a single number or repeated COLUMN=VALUE pairs.', group='comparison', kwarg='rel_tol', backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=functools.partial(<function _combine_tolerances>, flag='--rel-tol'), default=None, options={'action': 'append', 'type': <function tolerance>, 'metavar': 'N|COL=N'}), '--report-format': Opt(flags=('--report-format',), help='Report rendering (default: text).', group='output', kwarg=None, backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default='text', options={'choices': ['text', 'json', 'html']}), '--right': Opt(flags=('--right',), help='Path, URI, or Snowflake table reference for the right dataset.', group='input', kwarg=None, backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default=None, options={'required': True, 'metavar': 'REF'}), '--sample-count': Opt(flags=('--sample-count',), help='Maximum number of sample mismatch rows to show per column (default 10).', group='report', kwarg=None, backends=frozenset({'polars', 'snowflake', 'spark', 'pandas'}), resolve=None, default=10, options={'type': <function non_negative_int>, 'metavar': 'N'}), '--snowflake-config': Opt(flags=('--snowflake-config',), help='Path to a JSON file of Snowflake connection parameters. When omitted the session is built from SNOWFLAKE_ACCOUNT plus one of SNOWFLAKE_TOKEN (OAuth), SNOWFLAKE_AUTHENTICATOR, or SNOWFLAKE_PASSWORD. SNOWFLAKE_USER is required for everything except OAuth, and SNOWFLAKE_ROLE, SNOWFLAKE_WAREHOUSE, SNOWFLAKE_DATABASE, and SNOWFLAKE_SCHEMA are optional.', group='backend specific', kwarg=None, backends=frozenset({'snowflake'}), resolve=None, default=None, options={'type': <class 'pathlib.Path'>, 'metavar': 'PATH'}), '--spark-app-name': Opt(flags=('--spark-app-name',), help='Spark application name.', group='backend specific', kwarg=None, backends=frozenset({'spark'}), resolve=None, default='datacompy-cli', options={'metavar': 'NAME'})}

Lookup from primary flag to specification row, for targeted validation.

class datacompy.cli.parser.Opt(flags: tuple[str, ...], help: str, group: str, kwarg: str | None = None, backends: frozenset[str] = frozenset({'pandas', 'polars', 'snowflake', 'spark'}), resolve: ~collections.abc.Callable[[~typing.Any, ~argparse.Namespace], ~typing.Any] | None = None, default: ~typing.Any = None, options: dict[str, ~typing.Any] = <factory>)

Bases: object

One command line option and its mapping onto a *Compare keyword.

Variables:
  • flags (tuple of str) – Option strings passed to argparse.ArgumentParser.add_argument.

  • help (str) – Help text shown in --help.

  • group (str) – Argument group heading the option is listed under.

  • kwarg (str, optional) – Name of the *Compare constructor keyword this option supplies. None marks the option as CLI only (for example --output).

  • backends (frozenset of str) – Backends that accept this option. Passing the flag with any other backend is rejected by datacompy.cli.compare.validate_arguments().

  • resolve (callable, optional) – Post-processing applied to the raw parsed value before it is handed to the constructor. Receives (raw_value, namespace). Used where the parsed shape differs from the library shape, such as flattening repeated --on groups into a single list.

  • default (Any) – Value used when the flag is absent. Options are registered with argparse.SUPPRESS so that “left at the default” stays distinguishable from “explicitly passed”.

  • options (dict) – Extra keyword arguments forwarded to add_argument (action, type, choices, metavar, required).

backends: frozenset[str] = frozenset({'pandas', 'polars', 'snowflake', 'spark'})
default: Any = None
property dest: str

The argparse destination attribute, derived from the first flag.

flags: tuple[str, ...]
group: str
help: str
kwarg: str | None = None
options: dict[str, Any]
resolve: Callable[[Any, Namespace], Any] | None = None
resolved(namespace: Namespace) → Any

Return the value in the shape the library constructor expects.

value(namespace: Namespace) → Any

Return the parsed value, falling back to default.

was_given(namespace: Namespace) → bool

Return True when the user actually passed this flag.

datacompy.cli.parser.build_parser() → ArgumentParser

Build the top level datacompy argument parser.

datacompy.cli.parser.default_dataset_name(ref: str, backend: str) → str

Derive a report label from a file path or Snowflake table reference.

File paths use the stem, so sales_data.parquet becomes sales_data. Snowflake references use the final segment, so PROD.ANALYTICS.SALES becomes SALES rather than the misleading PROD.ANALYTICS that Path.stem would produce.

datacompy.cli.parser.fill_defaults(namespace: Namespace) → None

Populate namespace in place with the default for every absent option.

Options are registered with argparse.SUPPRESS so that an absent flag leaves no attribute behind, which is what makes Opt.was_given() meaningful. Call datacompy.cli.compare.validate_arguments() before this function, because afterwards every option looks as though it was explicitly passed.

datacompy.cli.parser.join_column_group(value: str) → list[str]

Split a single --on value on commas.

Combined with action="append" this makes --on id,date, --on id --on date and any mix of the two equivalent. Column names that genuinely contain a comma must use the repeated form.

datacompy.cli.parser.non_negative_int(value: str) → int

Accept a non negative integer.

datacompy.cli.parser.package_version() → str

Return the datacompy version.

Read from the package itself rather than from installed distribution metadata, because pyproject.toml derives the distribution version from datacompy.__version__ and an editable install can carry stale metadata.

datacompy.cli.parser.single_char(value: str) → str

Accept a one character delimiter, translating a literal \t to a tab.

datacompy.cli.parser.tolerance(value: str) → float | tuple[str, float]

Parse a tolerance as either a bare number or a COLUMN=VALUE pair.

A bare number applies to every numeric column. Repeated COLUMN=VALUE pairs are collected into the per column dictionary that datacompy.base.validate_tolerance_parameter() accepts.

Module contents

DataComPy command line interface.

Invoked as datacompy once installed, or as python -m datacompy.

Examples

Compare two CSV files with the polars backend, which is the default:

datacompy compare --left before.csv --right after.csv --on id

Emit a machine readable report for a CI pipeline and rely on the exit code:

datacompy compare --left a.parquet --right b.parquet --on id,date \\
    --report-format json --max-unequal-rows 0

Write an HTML report to a file:

datacompy compare --left a.csv --right b.csv --on id \\
    --report-format html --output report.html
datacompy.cli.COMMANDS: dict[str, Callable[[Namespace], int]] = {'compare': <function run_compare>}

Subcommand name to handler. Adding a command is additive.

datacompy.cli.build_parser() → ArgumentParser

Build the top level datacompy argument parser.

datacompy.cli.main(argv: Sequence[str] | None = None) → int

Parse argv, dispatch the subcommand, and return the exit code.

Parameters:

argv (sequence of str, optional) – Argument list. When None, argparse reads sys.argv.

Returns:

0 on a match, 1 on a mismatch, 2 on an expected error, and 130 on interrupt. Argparse exits with 2 itself on a parse failure, before this function returns.

Return type:

int