# Creating custom fields

Custom fields can be created by extending from one of the field classes in headfake.field. Headfake uses the [attrs](https://www.attrs.org) package  to provide a simpler way of initialising and handling class properties, although this does make it a little more difficult to sub-class field classes if the generated data is similar to that generated by other field types.

To deal with this Headfake provides a `DerivedField` class which acts as a decorator around another field (created through the `_internal_field` method).

In some cases, what you want to achieve may be better achieved by using a `Transformer` class instead, for example processing the generated value in some way.


## An example derived custom field
Below is a simple derived custom checkbox field which uses an internal OptionValueField to generate a 0 or 1 value. The user only provides a single 'yes_probability' to use the field.

An example checkbox field could look like this.
```python

@attr.s(kw_only=True)
class YesNoUnsureField(DerivedField):
    yes_probability = attr.ib() #probability that Y is generated
	unsure_probability = attr.ib()

    def _internal_field(self):
        ynu_probs = {
        	2:self.unsure_probability,
            1:self.yes_probability,
            0:1-self.yes_probability-self.unsure_probability,

        }

        return OptionValueField(probabilities=ynu_probs)

..

ynu_field = YesNoUnsureField(yes_probability=0.4, unsure_probablity=0.05)

```

## An example non-derived field
When generated data is completely different from any existing functionality then you should extend `headfake.field.Field` and create appropriate attributes. Then over-ride the `_next_value` function to create the value.

For example, this field will continuously rotate through a list of supplied characters with a different one one each row.

```python

@attr.s(kw_only=True)
class RotatingCharacterField(Field):
    characters = attr.ib()
    _curr_char_pos = attr.ib(default=0)

    def _next_value(self, row):
        char = self.characters[self._curr_char_pos]
        self._curr_char_pos+=1
        if self._curr_char_pos>len(self.characters):
            self.curr_char_pos = 0

        return char

```

## Using custom fields in YAML templates
This is as simple as entering the classname in the 'class' property in the YAML file along with the additional parameters. For example to use the RotatingCharacterField:

```yaml
my_character:
    class: mypackage.RotatingCharacterField
    characters: ABCDEFGH
```

Providing your package is within the Python library path, it should be found and used to generate the field values.


## Adding tests for a custom field
It is recommended that a test file is setup for new custom fields, particularly if the logic is complex.
To aid this, a field class can be created standalone the `next_value` method can be tested.

Below is an example using pytest to test the logic for the RotatingCharacterField:

```python
import pytest

def test_characters_are_rotated():
    rc_field = RotatingCharacterField(characters=["ABC"])
    assert rc_field.next_value(row={}) == "A"
    assert rc_field.next_value(row={}) == "B"
    assert rc_field.next_value(row={}) == "C"
    assert rc_field.next_value(row={}) == "A"
```

It can be difficult to handle randomly generated fields so you can either replace functions used internally with ones which you can control or you can set the random seed to a known value. For example in the CheckboxField.

```python
def test_CheckboxField_returns_expected_value(monkeypatch):
    HeadFake.set_seed(10)

    assert ov.next_value(row) == 1
    assert ov.next_value(row) == 1
    assert ov.next_value(row) == 0
    assert ov.next_value(row) == 1
```

