Skip to content

qc.csv

CsvTestSuite

CsvTestSuite(data_stream: Csv)

Bases: Suite

Test suite to check if CSV files generated are well formatted.

Provides tests to validate that CSV files conform to expected formatting standards and contain valid data.

Attributes:

Name Type Description
data_stream

The CSV data stream to test.

Examples:

from contraqctor.contract.csv import Csv, CsvParams
from contraqctor.qc.csv import CsvTestSuite
from contraqctor.qc.base import Runner

# Create and load a CSV data stream
params = CsvParams(path="data/measurements.csv")
csv_stream = Csv("measurements", reader_params=params).load()

# Create and run the test suite
suite = CsvTestSuite(csv_stream)
runner = Runner().add_suite(suite)
results = runner.run_all_with_progress()

Initialize the CSV test suite.

Parameters:

Name Type Description Default
data_stream Csv

The CSV data stream to test.

required
Source code in src/contraqctor/qc/csv.py
33
34
35
36
37
38
39
def __init__(self, data_stream: Csv):
    """Initialize the CSV test suite.

    Args:
        data_stream: The CSV data stream to test.
    """
    self.data_stream = data_stream

description property

description: Optional[str]

Get the description of the test suite from its docstring.

Returns:

Type Description
Optional[str]

Optional[str]: The docstring of the class, or None if not available.

name property

name: str

Get the name of the test suite.

Returns:

Name Type Description
str str

The name of the test suite class.

test_is_instance_of_pandas_dataframe

test_is_instance_of_pandas_dataframe()

Check if the data stream is a pandas DataFrame.

Source code in src/contraqctor/qc/csv.py
41
42
43
44
45
46
47
48
49
def test_is_instance_of_pandas_dataframe(self):
    """
    Check if the data stream is a pandas DataFrame.
    """
    if not self.data_stream.has_data:
        return self.fail_test(None, "Data stream does not have loaded data")
    if not isinstance(self.data_stream.data, pd.DataFrame):
        return self.fail_test(None, "Data stream is not a pandas DataFrame")
    return self.pass_test(None, "Data stream is a pandas DataFrame")

test_is_not_empty

test_is_not_empty()

Check if the DataFrame is not empty.

Source code in src/contraqctor/qc/csv.py
51
52
53
54
55
56
57
58
def test_is_not_empty(self):
    """
    Check if the DataFrame is not empty.
    """
    df = self.data_stream.data
    if df.empty:
        return self.fail_test(None, "Data stream is empty")
    return self.pass_test(None, "Data stream is not empty")

test_infer_missing_headers

test_infer_missing_headers()

Infer if the DataFrame was loaded from a CSV without headers.

Source code in src/contraqctor/qc/csv.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def test_infer_missing_headers(self):
    """
    Infer if the DataFrame was loaded from a CSV without headers.
    """
    if not self.data_stream.reader_params.strict_header:
        return self.skip_test("CSV was loaded with strict_header=False")

    df = self.data_stream.data
    if df.empty or len(df.columns) == 0:
        return self.fail_test(None, "Data stream is empty or has no columns")

    # Check if column names are default integer indexes (0, 1, 2...)
    if all(isinstance(col, int) or (isinstance(col, str) and col.isdigit()) for col in df.columns):
        return self.fail_test(None, "Data stream has non-integer column names")

    return self.pass_test(None, "DataFramed was likely loaded from a CSV with headers")

get_tests

get_tests() -> Generator[ITest, None, None]

Find all methods starting with 'test'.

Yields:

Name Type Description
ITest ITest

Test methods found in the suite.

Source code in src/contraqctor/qc/base.py
350
351
352
353
354
355
356
357
358
def get_tests(self) -> t.Generator[ITest, None, None]:
    """Find all methods starting with 'test'.

    Yields:
        ITest: Test methods found in the suite.
    """
    for name, method in inspect.getmembers(self, predicate=inspect.ismethod):
        if name.startswith("test"):
            yield method

pass_test

pass_test() -> Result
pass_test(result: Any) -> Result
pass_test(result: Any, message: str) -> Result
pass_test(result: Any, *, context: Any) -> Result
pass_test(
    result: Any, message: str, *, context: Any
) -> Result
pass_test(
    result: Any = None,
    message: Optional[str] = None,
    *,
    context: Optional[Any] = None,
) -> Result

Create a passing test result.

Parameters:

Name Type Description Default
result Any

The value to include in the test result.

None
message Optional[str]

Optional message describing why the test passed.

None
context Optional[Any]

Optional contextual data for the test result.

None

Returns:

Name Type Description
Result Result

A Result object with PASSED status.

Source code in src/contraqctor/qc/base.py
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
def pass_test(
    self, result: t.Any = None, message: t.Optional[str] = None, *, context: t.Optional[t.Any] = None
) -> Result:
    """Create a passing test result.

    Args:
        result: The value to include in the test result.
        message: Optional message describing why the test passed.
        context: Optional contextual data for the test result.

    Returns:
        Result: A Result object with PASSED status.
    """
    calling_func_name, description = self._get_caller_info()

    return Result(
        status=Status.PASSED,
        result=result,
        test_name=calling_func_name,
        suite_name=self.name,
        message=message,
        context=context,
        description=description,
    )

warn_test

warn_test() -> Result
warn_test(result: Any) -> Result
warn_test(result: Any, message: str) -> Result
warn_test(result: Any, *, context: Any) -> Result
warn_test(
    result: Any, message: str, *, context: Any
) -> Result
warn_test(
    result: Any = None,
    message: Optional[str] = None,
    *,
    context: Optional[Any] = None,
) -> Result

Create a warning test result.

Creates a result with WARNING status, or FAILED if warnings are elevated.

Parameters:

Name Type Description Default
result Any

The value to include in the test result.

None
message Optional[str]

Optional message describing the warning.

None
context Optional[Any]

Optional contextual data for the test result.

None

Returns:

Name Type Description
Result Result

A Result object with WARNING or FAILED status.

Source code in src/contraqctor/qc/base.py
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
def warn_test(
    self, result: t.Any = None, message: t.Optional[str] = None, *, context: t.Optional[t.Any] = None
) -> Result:
    """Create a warning test result.

    Creates a result with WARNING status, or FAILED if warnings are elevated.

    Args:
        result: The value to include in the test result.
        message: Optional message describing the warning.
        context: Optional contextual data for the test result.

    Returns:
        Result: A Result object with WARNING or FAILED status.
    """
    calling_func_name, description = self._get_caller_info()

    return Result(
        status=Status.WARNING if not _elevate_warning.get() else Status.FAILED,
        result=result,
        test_name=calling_func_name,
        suite_name=self.name,
        message=message,
        context=context,
        description=description,
    )

fail_test

fail_test() -> Result
fail_test(result: Any) -> Result
fail_test(result: Any, message: str) -> Result
fail_test(
    result: Any, message: str, *, context: Any
) -> Result
fail_test(
    result: Optional[Any] = None,
    message: Optional[str] = None,
    *,
    context: Optional[Any] = None,
) -> Result

Create a failing test result.

Parameters:

Name Type Description Default
result Optional[Any]

The value to include in the test result.

None
message Optional[str]

Optional message describing why the test failed.

None
context Optional[Any]

Optional contextual data for the test result.

None

Returns:

Name Type Description
Result Result

A Result object with FAILED status.

Source code in src/contraqctor/qc/base.py
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
def fail_test(
    self, result: t.Optional[t.Any] = None, message: t.Optional[str] = None, *, context: t.Optional[t.Any] = None
) -> Result:
    """Create a failing test result.

    Args:
        result: The value to include in the test result.
        message: Optional message describing why the test failed.
        context: Optional contextual data for the test result.

    Returns:
        Result: A Result object with FAILED status.
    """
    calling_func_name, description = self._get_caller_info()

    return Result(
        status=Status.FAILED,
        result=result,
        test_name=calling_func_name,
        suite_name=self.name,
        message=message,
        context=context,
        description=description,
    )

skip_test

skip_test() -> Result
skip_test(message: str) -> Result
skip_test(message: str, *, context: Any) -> Result
skip_test(
    message: Optional[str] = None,
    *,
    context: Optional[Any] = None,
) -> Result

Create a skipped test result.

Creates a result with SKIPPED status, or FAILED if skips are elevated.

Parameters:

Name Type Description Default
message Optional[str]

Optional message explaining why the test was skipped.

None
context Optional[Any]

Optional contextual data for the test result.

None

Returns:

Name Type Description
Result Result

A Result object with SKIPPED or FAILED status.

Source code in src/contraqctor/qc/base.py
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
def skip_test(self, message: t.Optional[str] = None, *, context: t.Optional[t.Any] = None) -> Result:
    """Create a skipped test result.

    Creates a result with SKIPPED status, or FAILED if skips are elevated.

    Args:
        message: Optional message explaining why the test was skipped.
        context: Optional contextual data for the test result.

    Returns:
        Result: A Result object with SKIPPED or FAILED status.
    """
    calling_func_name, description = self._get_caller_info()
    return Result(
        status=Status.SKIPPED if not _elevate_skippable.get() else Status.FAILED,
        result=None,
        test_name=calling_func_name,
        suite_name=self.name,
        message=message,
        context=context,
        description=description,
    )

setup_suite

setup_suite() -> None

Run once before any test method in the suite.

Mimics :meth:unittest.TestCase.setUpClass. Override this to perform expensive or failure-prone preparation — e.g. loading a data stream's .data — a single time for the whole suite, rather than in __init__ (which is not protected by exception handling) or in :meth:setup (which re-runs before every test).

The :class:Runner invokes this inside an exception-handling block. If it raises, every test in the suite is reported as an error instead of being run, and :meth:teardown_suite is not called. Suite construction and test discovery are unaffected.

Source code in src/contraqctor/qc/base.py
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
def setup_suite(self) -> None:
    """Run once before any test method in the suite.

    Mimics :meth:`unittest.TestCase.setUpClass`. Override this to perform
    expensive or failure-prone preparation — e.g. loading a data stream's
    ``.data`` — a single time for the whole suite, rather than in ``__init__``
    (which is not protected by exception handling) or in :meth:`setup` (which
    re-runs before every test).

    The :class:`Runner` invokes this inside an exception-handling block. If it
    raises, every test in the suite is reported as an error instead of being
    run, and :meth:`teardown_suite` is *not* called. Suite construction and
    test discovery are unaffected.
    """
    pass

teardown_suite

teardown_suite() -> None

Run once after all test methods in the suite have run.

Mimics :meth:unittest.TestCase.tearDownClass. Only invoked if :meth:setup_suite completed successfully.

Source code in src/contraqctor/qc/base.py
730
731
732
733
734
735
736
def teardown_suite(self) -> None:
    """Run once after all test methods in the suite have run.

    Mimics :meth:`unittest.TestCase.tearDownClass`. Only invoked if
    :meth:`setup_suite` completed successfully.
    """
    pass

setup

setup() -> None

Run before each test method.

Mimics :meth:unittest.TestCase.setUp. This method can be overridden by subclasses to implement setup logic that runs before each test. For work that should happen only once for the whole suite, override :meth:setup_suite instead.

Source code in src/contraqctor/qc/base.py
738
739
740
741
742
743
744
745
746
def setup(self) -> None:
    """Run before each test method.

    Mimics :meth:`unittest.TestCase.setUp`. This method can be overridden by
    subclasses to implement setup logic that runs before each test. For work
    that should happen only once for the whole suite, override
    :meth:`setup_suite` instead.
    """
    pass

teardown

teardown() -> None

Run after each test method.

Mimics :meth:unittest.TestCase.tearDown. This method can be overridden by subclasses to implement teardown logic that runs after each test. For work that should happen only once for the whole suite, override :meth:teardown_suite instead.

Source code in src/contraqctor/qc/base.py
748
749
750
751
752
753
754
755
756
def teardown(self) -> None:
    """Run after each test method.

    Mimics :meth:`unittest.TestCase.tearDown`. This method can be overridden
    by subclasses to implement teardown logic that runs after each test. For
    work that should happen only once for the whole suite, override
    :meth:`teardown_suite` instead.
    """
    pass

run_test

run_test(
    test_method: ITest,
) -> Generator[Result, None, None]

Run a single test method and yield its results.

Handles setup, test execution, result processing, and teardown.

Parameters:

Name Type Description Default
test_method ITest

The test method to run.

required

Yields:

Name Type Description
Result Result

Result objects produced by the test method.

Source code in src/contraqctor/qc/base.py
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
def run_test(self, test_method: ITest) -> t.Generator[Result, None, None]:
    """Run a single test method and yield its results.

    Handles setup, test execution, result processing, and teardown.

    Args:
        test_method: The test method to run.

    Yields:
        Result: Result objects produced by the test method.
    """
    test_name = test_method.__name__
    suite_name = self.name
    test_description = getattr(test_method, "__doc__", None)

    try:
        self.setup()
        result = test_method()
        if inspect.isgenerator(result):
            for sub_result in result:
                yield self._process_test_result(sub_result, test_method, test_name, test_description)
        else:
            yield self._process_test_result(result, test_method, test_name, test_description)
    except Exception as e:
        tb = traceback.format_exc()
        yield Result(
            status=Status.ERROR,
            result=None,
            test_name=test_name,
            suite_name=suite_name,
            description=test_description,
            message=f"Error during test execution: {str(e)}",
            exception=e,
            traceback=tb,
            test_reference=test_method,
            suite_reference=self,
        )
    finally:
        self.teardown()

run_all

run_all() -> Generator[Result, None, None]

Run all test methods in the suite.

Runs :meth:setup_suite once, then all test methods in sequence, then :meth:teardown_suite. If :meth:setup_suite raises, every test is yielded as an error result and no tests are run.

Yields:

Name Type Description
Result Result

Result objects produced by all test methods.

Source code in src/contraqctor/qc/base.py
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
def run_all(self) -> t.Generator[Result, None, None]:
    """Run all test methods in the suite.

    Runs :meth:`setup_suite` once, then all test methods in sequence, then
    :meth:`teardown_suite`. If :meth:`setup_suite` raises, every test is
    yielded as an error result and no tests are run.

    Yields:
        Result: Result objects produced by all test methods.
    """
    tests = list(self.get_tests())
    setup_failure = self._try_setup_suite()
    if setup_failure is not None:
        exception, tb = setup_failure
        for test in tests:
            yield self._suite_setup_error_result(test, exception, tb)
        return

    try:
        for test in tests:
            yield from self.run_test(test)
    finally:
        self.teardown_suite()