Skip to content

Commit

Permalink
Add configuration schemas (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszsmolinski authored Dec 28, 2023
1 parent fe85fb4 commit 6a195bf
Show file tree
Hide file tree
Showing 9 changed files with 463 additions and 1 deletion.
Empty file.
Empty file.
96 changes: 96 additions & 0 deletions midi_app_controller/models/_tests/test_binds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import pytest
from pydantic import ValidationError

from ..binds import ButtonBind, KnobBind, Binds


@pytest.fixture
def binds_data() -> dict:
return {
"name": "TestBinds",
"description": "Test description",
"app_name": "TestApp",
"controller_name": "TestController",
"button_binds": [{"button_id": 0, "action_name": "Action1"}],
"knob_binds": [
{
"knob_id": 127,
"action_name_increase": "incr",
"action_name_decrease": "decr",
}
],
}


def test_valid_binds(binds_data):
binds = Binds(**binds_data)

assert binds.dict() == binds_data


@pytest.mark.parametrize(
"button_binds, knob_binds",
[
(
[
{"button_id": 1, "action_name": "Action1"},
{"button_id": 1, "action_name": "Action2"},
],
[
{
"knob_id": 2,
"action_name_increase": "incr",
"action_name_decrease": "decr",
}
],
),
(
[{"button_id": 1, "action_name": "Action1"}],
[
{
"knob_id": 2,
"action_name_increase": "incr",
"action_name_decrease": "decr",
},
{
"knob_id": 2,
"action_name_increase": "incr",
"action_name_decrease": "decr",
},
],
),
(
[{"button_id": 1, "action_name": "Action1"}],
[
{
"knob_id": 1,
"action_name_increase": "incr",
"action_name_decrease": "decr",
}
],
),
],
)
def test_binds_duplicate_id(binds_data, button_binds, knob_binds):
binds_data["button_binds"] = button_binds
binds_data["knob_binds"] = knob_binds
with pytest.raises(ValidationError):
Binds(**binds_data)


@pytest.mark.parametrize("id", [-1, 128])
def test_button_id_out_of_range(id):
button_data = {"button_id": id, "action_name": "Action1"}
with pytest.raises(ValidationError):
ButtonBind(**button_data)


@pytest.mark.parametrize("id", [-1, 128])
def test_knob_id_out_of_range(id):
knob_data = {
"knob_id": id,
"action_name_increase": "a",
"action_name_decrease": "b",
}
with pytest.raises(ValidationError):
KnobBind(**knob_data)
105 changes: 105 additions & 0 deletions midi_app_controller/models/_tests/test_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import pytest
from pydantic import ValidationError

from ..controller import Controller, ControllerElement


@pytest.fixture
def controller_data() -> dict:
return {
"name": "TestController",
"button_value_off": 11,
"button_value_on": 100,
"knob_value_min": 33,
"knob_value_max": 55,
"buttons": [{"id": 1, "name": "Button1"}, {"id": 2, "name": "Button2"}],
"knobs": [{"id": 3, "name": "Knob1"}, {"id": 4, "name": "Knob2"}],
}


def test_valid_controller(controller_data):
controller = Controller(**controller_data)

assert controller.dict() == controller_data


@pytest.mark.parametrize(
"buttons, knobs",
[
(
[{"id": 1, "name": "Button1"}, {"id": 1, "name": "Button2"}],
[{"id": 2, "name": "Knob1"}],
),
([{"id": 1, "name": "Button1"}], [{"id": 1, "name": "Knob2"}]),
(
[{"id": 2, "name": "Button1"}],
[{"id": 1, "name": "Knob1"}, {"id": 1, "name": "Knob2"}],
),
],
)
def test_controller_duplicate_element_id(controller_data, buttons, knobs):
controller_data["buttons"] = buttons
controller_data["knobs"] = knobs
with pytest.raises(ValidationError):
Controller(**controller_data)


@pytest.mark.parametrize(
"buttons, knobs",
[
(
[{"id": 1, "name": "duplicate_name"}, {"id": 2, "name": "duplicate_name"}],
[{"id": 3, "name": "Knob1"}],
),
(
[{"id": 1, "name": "Button1"}],
[{"id": 2, "name": "duplicate_name"}, {"id": 3, "name": "duplicate_name"}],
),
],
)
def test_controller_duplicate_element_name(controller_data, buttons, knobs):
controller_data["buttons"] = buttons
controller_data["knobs"] = knobs
with pytest.raises(ValidationError):
Controller(**controller_data)


@pytest.mark.parametrize(
"button_value_off, button_value_on, knob_value_min, knob_value_max",
[
(-1, 127, 0, 127),
(0, 128, 0, 127),
(0, 127, -1, 127),
(0, 127, 0, 128),
],
)
def test_controller_values_range(
controller_data, button_value_off, button_value_on, knob_value_min, knob_value_max
):
controller_data["button_value_off"] = button_value_off
controller_data["button_value_on"] = button_value_on
controller_data["knob_value_min"] = knob_value_min
controller_data["knob_value_max"] = knob_value_max
with pytest.raises(ValidationError):
Controller(**controller_data)


def test_knob_min_value_greater_than_max_value(controller_data):
controller_data["knob_value_min"] = 100
controller_data["knob_value_max"] = 50
with pytest.raises(ValidationError):
Controller(**controller_data)


def test_button_on_and_off_values_equal(controller_data):
controller_data["button_value_off"] = 100
controller_data["button_value_on"] = 100
with pytest.raises(ValidationError):
Controller(**controller_data)


@pytest.mark.parametrize("id", [-1, 128])
def test_controller_element_id_out_of_range(id):
element_data = {"id": id, "name": "Name"}
with pytest.raises(ValidationError):
ControllerElement(**element_data)
17 changes: 17 additions & 0 deletions midi_app_controller/models/_tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pytest

from ..utils import find_duplicate


@pytest.mark.parametrize(
"values, result",
[
([1, 2, 3], None),
(["a", "b"], None),
([1, 2, 3, 1, 4], 1),
([1, 2, 1, 2], 1),
([], None),
],
)
def test_find_duplicate(values, result):
assert find_duplicate(values) == result
80 changes: 80 additions & 0 deletions midi_app_controller/models/binds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from typing import List, Optional
from pydantic import BaseModel, Field, root_validator

from .utils import YamlBaseModel, find_duplicate


class ButtonBind(BaseModel):
"""
Information about an action bound to a button.
Attributes
----------
button_id : int
The id of the button. Should be in the range [0, 127].
action_name : int
A name of the action to be executed when the button is pressed.
"""

button_id: int = Field(ge=0, le=127)
action_name: str


class KnobBind(BaseModel):
"""
Information about actions bound to a knob.
Attributes
----------
knob_id : int
The id of the knob. Should be in the range [0, 127].
action_name_increase : str
A name of the action to be executed when the knob's value increases.
action_name_decrease : str
A name of the action to be executed when the knob's value decreases.
"""

knob_id: int = Field(ge=0, le=127)
action_name_increase: str
action_name_decrease: str


class Binds(YamlBaseModel):
"""
User's binds for specific app and controller.
Attributes
----------
name : str
The name of the binds set.
description : Optional[str]
Additional information that the user may provide.
app_name : str
For which app are the binds intended.
controller_name : str
For which controller are the binds intended.
button_binds : List[ButtonBind]
A list of bound buttons.
knob_binds : List[KnobBind]
A list of bound knobs.
"""

name: str
description: Optional[str]
app_name: str
controller_name: str
button_binds: List[ButtonBind]
knob_binds: List[KnobBind]

@root_validator
@classmethod
def check_duplicate_ids(cls, values):
"""Ensures that every element has different id."""
button_ids = list(map(lambda x: x.button_id, values.get("button_binds")))
knob_ids = list(map(lambda x: x.knob_id, values.get("knob_binds")))

duplicate = find_duplicate(button_ids + knob_ids)
if duplicate is not None:
raise ValueError(f"id={duplicate} was bound to multiple actions")

return values
Loading

0 comments on commit 6a195bf

Please # to comment.