# Configuration

## Decorator Parameters

These are the parameters for the decorator {meth}`pytest_csv_params.decorator.csv_params`.

### Overview

| Parameter        | Type                     | Description                                                                                                                            | Example                                                                                        |
|------------------|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|
| `data_file`      | `str`                    | The CSV file to use, relative or absolute path                                                                                         | `"/var/testdata/test1.csv"`                                                                    |
| `base_dir`       | `str` (optional)         | Directory to look up relative CSV files (see `data_file`); overrides the command line argument                                         | `join(dirname(__file__), "assets")`                                                            |
| `id_col`         | `str` (optional)         | Column name of the CSV that contains test case IDs                                                                                     | `"ID#"`                                                                                        |
| `dialect`        | `csv.Dialect` (optional) | CSV Dialect definition (see [Python CSV Documentation](https://docs.python.org/3/library/csv.html#dialects-and-formatting-parameters)) | `csv.excel_tab`                                                                                |
| `data_casts`     | `dict` (optional)        | Cast Methods for the CSV Data (see "Data Casting" below)                                                                               | `{ "a": int, "b": float }`                                                                     |
| `header_renames` | `dict` (optional)        | Replace headers from the CSV file, so that they can be used as parameters for the test function (since 0.3.0)                          | `{ "Annual Amount of Bananas": "banana_count", "Cherry export price": "cherry_export_price" }` | 

### Detailed Description

#### `data_file`

This points to the CSV file to load for this test. You can use relative or absolute paths. If you use a relative path
and a `base_dir`, the `base_dir` is prepended to the `data_file`.

````{admonition} Hint
It's a good idea to put your CSV data files in a `test-assets` folder on the same level than your `test_something.py`
file.

Example Layout:

```text
tests/
  +- test-assets/
  |    +- case1.csv
  |    +- case2.csv
  +- test_case1.py
  +- test_case2.py
```

Now use this for `data_file` and `base_dir` (in one of the `test_caseX.py`):

```python
from os.path import dirname, join
from pytest_csv_params.decorator import csv_params

@csv_params(data_file="case1.csv", base_dir=join(dirname(__file__), "test-assets"))
def test_case1():
    ...
```
````

#### `base_dir`

This is an optional parameter. Set it to the directory where the CSV file from the `data_file` parameter should be
looked up. If not `None` (which is the default value), the value will be prepended to the `data_file` value, as long as
`data_file` is not an absolute path.

See `--csv-params-base-dir` command line argument below also.

```{warning}
Setting `base_dir` to something that is not `None` overrides anything that is set by the `--csv-params-base-dir`
command line argument.
```

#### `id_col`

Name the column that contains the test case IDs. If `None` (which is the default value), no test case IDs will be
generated. In this case, pytest will create its own IDs based on the parameters for the test. The column name does not
need to be valid variable/argument name.

Example:

```text
"Test Case ID#", "val_a", "val_b"
"test-12 / 4",   "1234",  "4321"
"test-13 / 7",   "3210",  "0123"
"test-14 / 9",   "5432",  "2345"
```

The test case ID is in the column "Test Case ID#". You'd configure it like this:

```python
from os.path import dirname, join
from pytest_csv_params.decorator import csv_params

@csv_params(data_file=join(dirname(__file__), "test-assets", "case1.csv"), id_col="Test Case ID#")
def test_case1(param_1: str, param_2: str) -> None:
    ...
```

#### `dialect`

Set the CSV dialect, it must be of the type {class}`csv.Dialect`. A dialect defines how a CSV file looks like.

The default dialect is {class}`pytest_csv_params.dialect.CsvParamsDefaultDialect`.

A dialect consists of the following settings:

| Setting                               | Default value in {class}`~pytest_csv_params.dialect.CsvParamsDefaultDialect` |
|---------------------------------------|------------------------------------------------------------------------------|
| {attr}`~csv.Dialect.delimiter`        | `","`                                                                        |
| {attr}`~csv.Dialect.doublequote`      | `True`                                                                       |
| {attr}`~csv.Dialect.escapechar`       | `None`                                                                       |
| {attr}`~csv.Dialect.lineterminator`   | `"\r\n"`                                                                     |
| {attr}`~csv.Dialect.quotechar`        | `'"'`                                                                        |
| {attr}`~csv.Dialect.quoting`          | {data}`csv.QUOTE_ALL`                                                        |
| {attr}`~csv.Dialect.skipinitialspace` | `True`                                                                       |
| {attr}`~csv.Dialect.strict`           | `True`                                                                       |

See [Usage Examples](examples) to learn how to create your own decorator that would always use your own specific CSV
file dialect.

```{note}
Regardless of the format parameters you are defining, all values from the CSV file are read as `str`. You may need to
convert them into other types. This is where `data_casts` are for.
```

#### `data_casts`

This dictionary allows you to setup methods to convert the string values from the CSV files into types or formats
required for test execution.

```{admonition} Rule of thumb
1. You can use any method that accepts a single `str` parameter. It can return anything you need.
2. If you need to test your test code, you should prefer conversion methods over conversion lambdas. 
```

Example:

```text
"Test Case ID#", "val_a", "val_b", "val_c", "val_d", "val_e"
"test-12 / 4",   "2.022",  "152",  "1 x 3", "abcd",  "flox"
"test-13 / 7",   "3.125",  "300",  "2 x 4", "defg",  "trox"
"test-14 / 9",   "4.145",  "150",  "3x6x9", "hijk",  "bank"
```

- The values of column "Test Case ID#" do not need any conversion. The column will serve as `id_col`.
- The values of column "val_a" should be converted into `float`. Since `float` is also a method, it can be used 
  directly.
- The values of column "val_b" should be converted into `int`. Since `int` is also a method, it can be used directly.
- The values of column "val_c" must be converted a bit more complex. We'll use a `lambda` for that.
- The values of column "val_d" don't need to be converted. They are `str`.
- The values of column "val_e" will be converted with a helper method (`convert_val_e`).

Implementation of this example:

```python
from typing import List, Optional, Tuple
from pytest_csv_params.decorator import csv_params

def convert_val_e(value: str) -> Tuple[bool, Optional[str]]:
    str_val = None
    bool_val = value.endswith("ox")
    if bool_val:
        str_val = value[:2]
    return bool_val, str_val

@csv_params(
    data_file="test1.csv",
    id_col="Test Case ID#",
    data_casts={
        "val_a": float,
        "val_b": int,
        "val_c": lambda x: list(map(lambda y: y.strip(), x.split("x"))),
        "val_e": convert_val_e,
    },
)
def test_something(val_a: float, val_b: int, val_c: List[int], val_d: str, val_e: Tuple[bool, Optional[str]]) -> None:
    ...
```

```{note}
In this example, the columns were named as valid argument/parameter names. So there's no need for `header_renames` here. 
```

#### `header_renames`

This dictionary allows to rename the column headers into valid argument names for your test methods. The plugin will try
to rename invalid header names by replacing invalid chars with underscores, but this might not result in well-formed and
readable names.

Example:

```text
"Test Case ID#",  "Flux Compensator Setting", "Power Level"
"101 / 885 / 31", "1-1-2-1-2-7-5-3-4-9/7",    "100 %"
"109 / 995 / 21", "3-2-2-2-6-4-2-2-1-2/8",    "15 %"
"658 / 555 / 54", "3-2-3-4-5-6-7-3-2-3/2",    "25 %"
```

Configuration of the decorator:

```python
from pytest_csv_params.decorator import csv_params

@csv_params(
    data_file="test.csv",
    id_col="Test Case ID#",
    header_renames={
        "Flux Compensator Setting": "flux_setting",
        "Power Level": "power_level",
    },
)
def test_something_else(fux_setting: str, power_level: str) -> None:
    ...
```

```{warning}
`data_casts` dictionary keys must match the renamed column names!
```

## Command Line Arguments

These are the command line arguments for the pytest run.

### Overview

| Argument                | Required      | Description                                                          | Example                                      |
|-------------------------|---------------|----------------------------------------------------------------------|----------------------------------------------|
| `--csv-params-base-dir` | no (optional) | Define a base dir for all relative-path CSV data files (since 0.1.0) | `pytest --csv-params-base-dir /var/testdata` |

### Detailed Description

#### `--csv-params-base-dir`

This is a convenience command line argument. It allows you to set a base directory for all your CSV parametrized test
cases. If you use relative `data_file`s, this can be automatically prepended. You can still override this setting per
test by using the `base_dir` configuration.

## How a CSV file is found

```text
+-----------------------------------+             /-----------------------------------\
| data_dir is absolute path?        | --- yes --- | use this path                     |
+-----------------------------------+             \-----------------------------------/
      |
      no
      |
+-----------------------------------+             /-----------------------------------\
| is a base_dir set on the test?    | --- yes --- | prepend base_dir to data_file     |
+-----------------------------------+             \-----------------------------------/
      |
      no
      |
+-----------------------------------+             /-----------------------------------\
| is command line argument given?   | --- yes --- | prepend arg value to data_file    |
+-----------------------------------+             \-----------------------------------/
      |
      no
      |
/-----------------------------------\
| use data_file as relative path    |
\-----------------------------------/
```
